Added support for parsing and handling BGP Confederation related AS Path
attributes.
Note: BGP Confedertions are not supported (yet).

Also, updated/simplified the MED comparison in the BGP Path Comparison
implementation.

Change-Id: Iabe01facffd2c6912f33f647841c1244d85282f3
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
index 596720c..33fe411 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
@@ -168,6 +168,12 @@
             /** BGP UPDATE AS_PATH Type: AS_SEQUENCE. */
             public static final int AS_SEQUENCE = 2;
 
+            /** BGP UPDATE AS_PATH Type: AS_CONFED_SEQUENCE. */
+            public static final int AS_CONFED_SEQUENCE = 3;
+
+            /** BGP UPDATE AS_PATH Type: AS_CONFED_SET. */
+            public static final int AS_CONFED_SET = 4;
+
             /**
              * Gets the BGP AS_PATH type as a string.
              *
@@ -184,6 +190,12 @@
                 case AS_SEQUENCE:
                     typeString = "AS_SEQUENCE";
                     break;
+                case AS_CONFED_SEQUENCE:
+                    typeString = "AS_CONFED_SEQUENCE";
+                    break;
+                case AS_CONFED_SET:
+                    typeString = "AS_CONFED_SET";
+                    break;
                 default:
                     break;
                 }
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
index 203e03c..4ca7da6 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
@@ -21,6 +21,7 @@
 import java.util.Objects;
 
 import org.onlab.onos.sdnip.RouteEntry;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Update;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
 
@@ -35,8 +36,7 @@
     private final byte origin;          // Route ORIGIN: IGP, EGP, INCOMPLETE
     private final AsPath asPath;        // The AS Path
     private final long localPref;       // The local preference for the route
-    private long multiExitDisc =
-        BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+    private long multiExitDisc = Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
 
     /**
      * Class constructor.
@@ -116,21 +116,33 @@
      * Tests whether the route is originated from the local AS.
      * <p>
      * The route is considered originated from the local AS if the AS Path
-     * is empty or if it begins with an AS_SET.
+     * is empty or if it begins with an AS_SET (after skipping
+     * AS_CONFED_SEQUENCE and AS_CONFED_SET).
      * </p>
      *
      * @return true if the route is originated from the local AS, otherwise
      * false
      */
     boolean isLocalRoute() {
-        if (asPath.getPathSegments().isEmpty()) {
+        PathSegment firstPathSegment = null;
+
+        // Find the first Path Segment by ignoring the AS_CONFED_* segments
+        for (PathSegment pathSegment : asPath.getPathSegments()) {
+            if ((pathSegment.getType() == Update.AsPath.AS_SET) ||
+                (pathSegment.getType() == Update.AsPath.AS_SEQUENCE)) {
+                firstPathSegment = pathSegment;
+                break;
+            }
+        }
+        if (firstPathSegment == null) {
+            return true;                // Local route: no path segments
+        }
+        // If the first path segment is AS_SET, the route is considered local
+        if (firstPathSegment.getType() == Update.AsPath.AS_SET) {
             return true;
         }
-        PathSegment firstPathSegment = asPath.getPathSegments().get(0);
-        if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
-            return true;
-        }
-        return false;
+
+        return false;                   // The route is not local
     }
 
     /**
@@ -143,10 +155,25 @@
      * @return the BGP Neighbor AS number the route was received from.
      */
     long getNeighborAs() {
+        PathSegment firstPathSegment = null;
+
         if (isLocalRoute()) {
             return BgpConstants.BGP_AS_0;
         }
-        PathSegment firstPathSegment = asPath.getPathSegments().get(0);
+
+        // Find the first Path Segment by ignoring the AS_CONFED_* segments
+        for (PathSegment pathSegment : asPath.getPathSegments()) {
+            if ((pathSegment.getType() == Update.AsPath.AS_SET) ||
+                (pathSegment.getType() == Update.AsPath.AS_SEQUENCE)) {
+                firstPathSegment = pathSegment;
+                break;
+            }
+        }
+        if (firstPathSegment == null) {
+            // NOTE: Shouldn't happen - should be captured by isLocalRoute()
+            return BgpConstants.BGP_AS_0;
+        }
+
         if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
             // TODO: Shouldn't happen. Should check during the parsing.
             return BgpConstants.BGP_AS_0;
@@ -211,18 +238,16 @@
 
         // Compare the MED if the neighbor AS is same: larger is better
         medLabel: {
-            boolean thisIsLocalRoute = isLocalRoute();
-            if (thisIsLocalRoute != other.isLocalRoute()) {
-                break medLabel;                 // AS number is different
+            if (isLocalRoute() || other.isLocalRoute()) {
+                // Compare MEDs for non-local routes only
+                break medLabel;
             }
-            if (!thisIsLocalRoute) {
-                long thisNeighborAs = getNeighborAs();
-                if (thisNeighborAs != other.getNeighborAs()) {
-                    break medLabel;             // AS number is different
-                }
-                if (thisNeighborAs == BgpConstants.BGP_AS_0) {
-                    break medLabel;             // Invalid AS number
-                }
+            long thisNeighborAs = getNeighborAs();
+            if (thisNeighborAs != other.getNeighborAs()) {
+                break medLabel;             // AS number is different
+            }
+            if (thisNeighborAs == BgpConstants.BGP_AS_0) {
+                break medLabel;             // Invalid AS number
             }
 
             // Compare the MED
@@ -253,13 +278,16 @@
      * A class to represent AS Path Segment.
      */
     public static class PathSegment {
-        private final byte type;        // Segment type: AS_SET, AS_SEQUENCE
+        // Segment type: AS_SET(1), AS_SEQUENCE(2), AS_CONFED_SEQUENCE(3),
+        // AS_CONFED_SET(4)
+        private final byte type;
         private final ArrayList<Long> segmentAsNumbers;   // Segment AS numbers
 
         /**
          * Constructor.
          *
-         * @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE
+         * @param type the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
+         * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4)
          * @param segmentAsNumbers the Segment AS numbers
          */
         PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
@@ -268,9 +296,11 @@
         }
 
         /**
-         * Gets the Path Segment Type: AS_SET, AS_SEQUENCE.
+         * Gets the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
+         * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4).
          *
-         * @return the Path Segment Type: AS_SET, AS_SEQUENCE
+         * @return the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
+         * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4)
          */
         public byte getType() {
             return type;
@@ -309,7 +339,7 @@
         @Override
         public String toString() {
             return MoreObjects.toStringHelper(getClass())
-                .add("type", BgpConstants.Update.AsPath.typeToString(type))
+                .add("type", Update.AsPath.typeToString(type))
                 .add("segmentAsNumbers", this.segmentAsNumbers)
                 .toString();
         }
@@ -333,15 +363,27 @@
              //
              // Precompute the AS Path Length:
              // - AS_SET counts as 1
+             // - AS_SEQUENCE counts how many AS numbers are included
+             // - AS_CONFED_SEQUENCE and AS_CONFED_SET are ignored
              //
              int pl = 0;
              for (PathSegment pathSegment : pathSegments) {
-                 if (pathSegment.getType() ==
-                     BgpConstants.Update.AsPath.AS_SET) {
-                     pl++;
-                     continue;
+                 switch (pathSegment.getType()) {
+                 case Update.AsPath.AS_SET:
+                     pl++;              // AS_SET counts as 1
+                     break;
+                 case Update.AsPath.AS_SEQUENCE:
+                     // Count each AS number
+                     pl += pathSegment.getSegmentAsNumbers().size();
+                     break;
+                 case Update.AsPath.AS_CONFED_SEQUENCE:
+                     break;             // Ignore
+                 case Update.AsPath.AS_CONFED_SET:
+                     break;             // Ignore
+                 default:
+                     // TODO: What to do if the Path Segment type is unknown?
+                     break;
                  }
-                 pl += pathSegment.getSegmentAsNumbers().size();
              }
              asPathLength = pl;
          }
@@ -444,7 +486,7 @@
             .add("prefix", prefix())
             .add("nextHop", nextHop())
             .add("bgpId", bgpSession.getRemoteBgpId())
-            .add("origin", BgpConstants.Update.Origin.typeToString(origin))
+            .add("origin", Update.Origin.typeToString(origin))
             .add("asPath", asPath)
             .add("localPref", localPref)
             .add("multiExitDisc", multiExitDisc)
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
index 98477ae..e0a710a 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
@@ -975,6 +975,10 @@
             case BgpConstants.Update.AsPath.AS_SET:
                 // FALLTHROUGH
             case BgpConstants.Update.AsPath.AS_SEQUENCE:
+                // FALLTHROUGH
+            case BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE:
+                // FALLTHROUGH
+            case BgpConstants.Update.AsPath.AS_CONFED_SET:
                 break;
             default:
                 // ERROR: Invalid Path Segment Type
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/AsPathTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/AsPathTest.java
index fddd895..a750431 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/AsPathTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/AsPathTest.java
@@ -28,30 +28,63 @@
  */
 public class AsPathTest {
     /**
+     * Generates Path Segments.
+     *
+     * @return the generated Path Segments
+     */
+    private ArrayList<BgpRouteEntry.PathSegment> generatePathSegments() {
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+        byte pathSegmentType;
+        ArrayList<Long> segmentAsNumbers;
+        BgpRouteEntry.PathSegment pathSegment;
+
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 1);
+        segmentAsNumbers.add((long) 2);
+        segmentAsNumbers.add((long) 3);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
+        //
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SET;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 4);
+        segmentAsNumbers.add((long) 5);
+        segmentAsNumbers.add((long) 6);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
+        //
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 7);
+        segmentAsNumbers.add((long) 8);
+        segmentAsNumbers.add((long) 9);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
+        //
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SET;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 10);
+        segmentAsNumbers.add((long) 11);
+        segmentAsNumbers.add((long) 12);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
+
+        return pathSegments;
+    }
+
+    /**
      * Generates an AS Path.
      *
      * @return a generated AS Path
      */
     private BgpRouteEntry.AsPath generateAsPath() {
-        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
-        byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
-        ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
-        segmentAsNumbers1.add((long) 1);
-        segmentAsNumbers1.add((long) 2);
-        segmentAsNumbers1.add((long) 3);
-        BgpRouteEntry.PathSegment pathSegment1 =
-            new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
-        pathSegments.add(pathSegment1);
-        //
-        byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
-        ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
-        segmentAsNumbers2.add((long) 4);
-        segmentAsNumbers2.add((long) 5);
-        segmentAsNumbers2.add((long) 6);
-        BgpRouteEntry.PathSegment pathSegment2 =
-            new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
-        pathSegments.add(pathSegment2);
-        //
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments =
+            generatePathSegments();
         BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
 
         return asPath;
@@ -65,9 +98,11 @@
         BgpRouteEntry.AsPath asPath = generateAsPath();
 
         String expectedString =
-            "AsPath{pathSegments=" +
-            "[PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
-            "PathSegment{type=AS_SET, segmentAsNumbers=[4, 5, 6]}]}";
+            "AsPath{pathSegments=[" +
+            "PathSegment{type=AS_CONFED_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+            "PathSegment{type=AS_CONFED_SET, segmentAsNumbers=[4, 5, 6]}, " +
+            "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[7, 8, 9]}, " +
+            "PathSegment{type=AS_SET, segmentAsNumbers=[10, 11, 12]}]}";
         assertThat(asPath.toString(), is(expectedString));
     }
 
@@ -86,24 +121,8 @@
     @Test
     public void testGetFields() {
         // Create the fields to compare against
-        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
-        byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
-        ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
-        segmentAsNumbers1.add((long) 1);
-        segmentAsNumbers1.add((long) 2);
-        segmentAsNumbers1.add((long) 3);
-        BgpRouteEntry.PathSegment pathSegment1 =
-            new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
-        pathSegments.add(pathSegment1);
-        //
-        byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
-        ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
-        segmentAsNumbers2.add((long) 4);
-        segmentAsNumbers2.add((long) 5);
-        segmentAsNumbers2.add((long) 6);
-        BgpRouteEntry.PathSegment pathSegment2 =
-            new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
-        pathSegments.add(pathSegment2);
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments =
+            generatePathSegments();
 
         // Generate the entry to test
         BgpRouteEntry.AsPath asPath = generateAsPath();
@@ -116,6 +135,11 @@
      */
     @Test
     public void testGetAsPathLength() {
+        //
+        // NOTE:
+        //  - AS_CONFED_SEQUENCE and AS_CONFED_SET are excluded
+        //  - AS_SET counts as a single hop
+        //
         BgpRouteEntry.AsPath asPath = generateAsPath();
         assertThat(asPath.getAsPathLength(), is(4));
 
@@ -145,23 +169,45 @@
 
         // Setup AS Path 2
         ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
-        byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
-        ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
-        segmentAsNumbers1.add((long) 1);
-        segmentAsNumbers1.add((long) 2);
-        segmentAsNumbers1.add((long) 3);
-        BgpRouteEntry.PathSegment pathSegment1 =
-            new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
-        pathSegments.add(pathSegment1);
+        byte pathSegmentType;
+        ArrayList<Long> segmentAsNumbers;
+        BgpRouteEntry.PathSegment pathSegment;
+
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 1);
+        segmentAsNumbers.add((long) 2);
+        segmentAsNumbers.add((long) 3);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
         //
-        byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
-        ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
-        segmentAsNumbers2.add((long) 4);
-        segmentAsNumbers2.add((long) 55);                       // Different
-        segmentAsNumbers2.add((long) 6);
-        BgpRouteEntry.PathSegment pathSegment2 =
-            new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
-        pathSegments.add(pathSegment2);
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SET;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 4);
+        segmentAsNumbers.add((long) 5);
+        segmentAsNumbers.add((long) 6);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
+        //
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 7);
+        segmentAsNumbers.add((long) 8);
+        segmentAsNumbers.add((long) 9);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
+        //
+        pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SET;
+        segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 10);
+        segmentAsNumbers.add((long) 111);                       // Different
+        segmentAsNumbers.add((long) 12);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
         //
         BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments);
 
@@ -176,9 +222,11 @@
         BgpRouteEntry.AsPath asPath = generateAsPath();
 
         String expectedString =
-            "AsPath{pathSegments=" +
-            "[PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
-            "PathSegment{type=AS_SET, segmentAsNumbers=[4, 5, 6]}]}";
+            "AsPath{pathSegments=[" +
+            "PathSegment{type=AS_CONFED_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+            "PathSegment{type=AS_CONFED_SET, segmentAsNumbers=[4, 5, 6]}, " +
+            "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[7, 8, 9]}, " +
+            "PathSegment{type=AS_SET, segmentAsNumbers=[10, 11, 12]}]}";
         assertThat(asPath.toString(), is(expectedString));
     }
 }