Moved BGP code and Router code into their own bundle.

The main goal of this is to allow routing code to be used by multiple
applications.

Changes include:
 * Created an onos-app-routing bundle and moved BGP code and routing code
   into it.
 * Created an onos-app-routing-api bundle as a common API bundle between
   onos-app-routing and onos-app-sdnip, to prevent circular dependencies.
 * Moved API classes into onos-app-routing-api bundle.
 * Made Router and BgpSessionManager into OSGi components. This is not quite
   clean, because there is still a chain of start() method calls from SdnIp
   through to BgpSessionManager to preserve startup order. This should be
   revisted so components can be started using activate()
 * Created BgpService and RoutingService APIs to glue different components
   together.
 * Many unit test changes. A lot of the previous unit tests spanned the
   Router and IntentSynchronizer classes, but this is not possible any more
   since these classes are in different bundles. I had to rewrite some of
   these tests so that each unit test class only tests one real class. A
   nice side-effect is that the tests are now simpler because each test
   tests less functionality.
 * Removed SdnIp test seeing as it doesn't run automatically, was already
   broken and has been largely superseded by other unit tests and the nightly
   functional tests.

Change-Id: I70ecf5391aa353e99e7cdcf7ed38a530c87571bb
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpUpdate.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpUpdate.java
new file mode 100644
index 0000000..e9abd9c
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpUpdate.java
@@ -0,0 +1,1695 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class for handling BGP UPDATE messages.
+ */
+final class BgpUpdate {
+    private static final Logger log = LoggerFactory.getLogger(BgpUpdate.class);
+
+    /**
+     * Default constructor.
+     * <p>
+     * The constructor is private to prevent creating an instance of
+     * this utility class.
+     */
+    private BgpUpdate() {
+    }
+
+    /**
+     * Processes BGP UPDATE message.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    static void processBgpUpdate(BgpSession bgpSession,
+                                 ChannelHandlerContext ctx,
+                                 ChannelBuffer message) {
+        DecodedBgpRoutes decodedBgpRoutes = new DecodedBgpRoutes();
+
+        int minLength =
+            BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+        if (message.readableBytes() < minLength) {
+            log.debug("BGP RX UPDATE Error from {}: " +
+                      "Message length {} too short. Must be at least {}",
+                      bgpSession.remoteInfo().address(),
+                      message.readableBytes(), minLength);
+            //
+            // ERROR: Bad Message Length
+            //
+            // Send NOTIFICATION and close the connection
+            ChannelBuffer txMessage =
+                BgpNotification.prepareBgpNotificationBadMessageLength(
+                message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeSession(ctx);
+            return;
+        }
+
+        log.debug("BGP RX UPDATE message from {}",
+                  bgpSession.remoteInfo().address());
+
+        //
+        // Parse the UPDATE message
+        //
+
+        //
+        // Parse the Withdrawn Routes
+        //
+        int withdrawnRoutesLength = message.readUnsignedShort();
+        if (withdrawnRoutesLength > message.readableBytes()) {
+            // ERROR: Malformed Attribute List
+            actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+            return;
+        }
+        Collection<Ip4Prefix> withdrawnPrefixes = null;
+        try {
+            withdrawnPrefixes = parsePackedIp4Prefixes(withdrawnRoutesLength,
+                                                       message);
+        } catch (BgpMessage.BgpParseException e) {
+            // ERROR: Invalid Network Field
+            log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ",
+                      bgpSession.remoteInfo().bgpId(), e);
+            actionsBgpUpdateInvalidNetworkField(bgpSession, ctx);
+            return;
+        }
+        for (Ip4Prefix prefix : withdrawnPrefixes) {
+            log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
+                      bgpSession.remoteInfo().address(), prefix);
+            BgpRouteEntry bgpRouteEntry = bgpSession.findBgpRoute(prefix);
+            if (bgpRouteEntry != null) {
+                decodedBgpRoutes.deletedUnicastRoutes4.put(prefix,
+                                                           bgpRouteEntry);
+            }
+        }
+
+        //
+        // Parse the Path Attributes
+        //
+        try {
+            parsePathAttributes(bgpSession, ctx, message, decodedBgpRoutes);
+        } catch (BgpMessage.BgpParseException e) {
+            log.debug("Exception parsing Path Attributes from BGP peer {}: ",
+                      bgpSession.remoteInfo().bgpId(), e);
+            // NOTE: The session was already closed, so nothing else to do
+            return;
+        }
+
+        //
+        // Update the BGP RIB-IN
+        //
+        for (Ip4Prefix ip4Prefix :
+                 decodedBgpRoutes.deletedUnicastRoutes4.keySet()) {
+            bgpSession.removeBgpRoute(ip4Prefix);
+        }
+        //
+        for (BgpRouteEntry bgpRouteEntry :
+                 decodedBgpRoutes.addedUnicastRoutes4.values()) {
+            bgpSession.addBgpRoute(bgpRouteEntry);
+        }
+        //
+        for (Ip6Prefix ip6Prefix :
+                 decodedBgpRoutes.deletedUnicastRoutes6.keySet()) {
+            bgpSession.removeBgpRoute(ip6Prefix);
+        }
+        //
+        for (BgpRouteEntry bgpRouteEntry :
+                 decodedBgpRoutes.addedUnicastRoutes6.values()) {
+            bgpSession.addBgpRoute(bgpRouteEntry);
+        }
+
+        //
+        // Push the updates to the BGP Merged RIB
+        //
+        BgpRouteSelector bgpRouteSelector =
+            bgpSession.getBgpSessionManager().getBgpRouteSelector();
+        bgpRouteSelector.routeUpdates(bgpSession,
+                                decodedBgpRoutes.addedUnicastRoutes4.values(),
+                                decodedBgpRoutes.deletedUnicastRoutes4.values());
+        bgpRouteSelector.routeUpdates(bgpSession,
+                                decodedBgpRoutes.addedUnicastRoutes6.values(),
+                                decodedBgpRoutes.deletedUnicastRoutes6.values());
+
+        // Start the Session Timeout timer
+        bgpSession.restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Parse BGP Path Attributes from the BGP UPDATE message.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param message the message to parse
+     * @param decodedBgpRoutes the container to store the decoded BGP Route
+     * Entries. It might already contain some route entries such as withdrawn
+     * IPv4 prefixes
+     * @throws BgpMessage.BgpParseException
+     */
+    // CHECKSTYLE IGNORE MethodLength FOR NEXT 300 LINES
+    private static void parsePathAttributes(
+                                        BgpSession bgpSession,
+                                        ChannelHandlerContext ctx,
+                                        ChannelBuffer message,
+                                        DecodedBgpRoutes decodedBgpRoutes)
+        throws BgpMessage.BgpParseException {
+
+        //
+        // Parsed values
+        //
+        Short origin = -1;                      // Mandatory
+        BgpRouteEntry.AsPath asPath = null;     // Mandatory
+        // Legacy NLRI (RFC 4271). Mandatory NEXT_HOP if legacy NLRI is used
+        MpNlri legacyNlri = new MpNlri(
+                BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4,
+                                       BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST);
+        long multiExitDisc =                    // Optional
+            BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+        Long localPref = null;                  // Mandatory
+        Long aggregatorAsNumber = null;         // Optional: unused
+        Ip4Address aggregatorIpAddress = null;  // Optional: unused
+        Collection<MpNlri> mpNlriReachList = new ArrayList<>();   // Optional
+        Collection<MpNlri> mpNlriUnreachList = new ArrayList<>(); // Optional
+
+        //
+        // Get and verify the Path Attributes Length
+        //
+        int pathAttributeLength = message.readUnsignedShort();
+        if (pathAttributeLength > message.readableBytes()) {
+            // ERROR: Malformed Attribute List
+            actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+            String errorMsg = "Malformed Attribute List";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+        if (pathAttributeLength == 0) {
+            return;
+        }
+
+        //
+        // Parse the Path Attributes
+        //
+        int pathAttributeEnd = message.readerIndex() + pathAttributeLength;
+        while (message.readerIndex() < pathAttributeEnd) {
+            int attrFlags = message.readUnsignedByte();
+            if (message.readerIndex() >= pathAttributeEnd) {
+                // ERROR: Malformed Attribute List
+                actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+                String errorMsg = "Malformed Attribute List";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+            int attrTypeCode = message.readUnsignedByte();
+
+            // The Attribute Flags
+            boolean optionalBit =       ((0x80 & attrFlags) != 0);
+            boolean transitiveBit =     ((0x40 & attrFlags) != 0);
+            boolean partialBit =        ((0x20 & attrFlags) != 0);
+            boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
+
+            // The Attribute Length
+            int attrLen = 0;
+            int attrLenOctets = 1;
+            if (extendedLengthBit) {
+                attrLenOctets = 2;
+            }
+            if (message.readerIndex() + attrLenOctets > pathAttributeEnd) {
+                // ERROR: Malformed Attribute List
+                actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+                String errorMsg = "Malformed Attribute List";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+            if (extendedLengthBit) {
+                attrLen = message.readUnsignedShort();
+            } else {
+                attrLen = message.readUnsignedByte();
+            }
+            if (message.readerIndex() + attrLen > pathAttributeEnd) {
+                // ERROR: Malformed Attribute List
+                actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+                String errorMsg = "Malformed Attribute List";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+
+            // Verify the Attribute Flags
+            verifyBgpUpdateAttributeFlags(bgpSession, ctx, attrTypeCode,
+                                          attrLen, attrFlags, message);
+
+            //
+            // Extract the Attribute Value based on the Attribute Type Code
+            //
+            switch (attrTypeCode) {
+
+            case BgpConstants.Update.Origin.TYPE:
+                // Attribute Type Code ORIGIN
+                origin = parseAttributeTypeOrigin(bgpSession, ctx,
+                                                  attrTypeCode, attrLen,
+                                                  attrFlags, message);
+                break;
+
+            case BgpConstants.Update.AsPath.TYPE:
+                // Attribute Type Code AS_PATH
+                asPath = parseAttributeTypeAsPath(bgpSession, ctx,
+                                                  attrTypeCode, attrLen,
+                                                  attrFlags, message);
+                break;
+
+            case BgpConstants.Update.NextHop.TYPE:
+                // Attribute Type Code NEXT_HOP
+                legacyNlri.nextHop4 =
+                    parseAttributeTypeNextHop(bgpSession, ctx,
+                                              attrTypeCode, attrLen,
+                                              attrFlags, message);
+                break;
+
+            case BgpConstants.Update.MultiExitDisc.TYPE:
+                // Attribute Type Code MULTI_EXIT_DISC
+                multiExitDisc =
+                    parseAttributeTypeMultiExitDisc(bgpSession, ctx,
+                                                    attrTypeCode, attrLen,
+                                                    attrFlags, message);
+                break;
+
+            case BgpConstants.Update.LocalPref.TYPE:
+                // Attribute Type Code LOCAL_PREF
+                localPref =
+                    parseAttributeTypeLocalPref(bgpSession, ctx,
+                                                attrTypeCode, attrLen,
+                                                attrFlags, message);
+                break;
+
+            case BgpConstants.Update.AtomicAggregate.TYPE:
+                // Attribute Type Code ATOMIC_AGGREGATE
+                parseAttributeTypeAtomicAggregate(bgpSession, ctx,
+                                                  attrTypeCode, attrLen,
+                                                  attrFlags, message);
+                // Nothing to do: this attribute is primarily informational
+                break;
+
+            case BgpConstants.Update.Aggregator.TYPE:
+                // Attribute Type Code AGGREGATOR
+                Pair<Long, Ip4Address> aggregator =
+                    parseAttributeTypeAggregator(bgpSession, ctx,
+                                                 attrTypeCode, attrLen,
+                                                 attrFlags, message);
+                aggregatorAsNumber = aggregator.getLeft();
+                aggregatorIpAddress = aggregator.getRight();
+                break;
+
+            case BgpConstants.Update.MpReachNlri.TYPE:
+                // Attribute Type Code MP_REACH_NLRI
+                MpNlri mpNlriReach =
+                    parseAttributeTypeMpReachNlri(bgpSession, ctx,
+                                                  attrTypeCode,
+                                                  attrLen,
+                                                  attrFlags, message);
+                if (mpNlriReach != null) {
+                    mpNlriReachList.add(mpNlriReach);
+                }
+                break;
+
+            case BgpConstants.Update.MpUnreachNlri.TYPE:
+                // Attribute Type Code MP_UNREACH_NLRI
+                MpNlri mpNlriUnreach =
+                    parseAttributeTypeMpUnreachNlri(bgpSession, ctx,
+                                                    attrTypeCode, attrLen,
+                                                    attrFlags, message);
+                if (mpNlriUnreach != null) {
+                    mpNlriUnreachList.add(mpNlriUnreach);
+                }
+                break;
+
+            default:
+                // NOTE: Parse any new Attribute Types if needed
+                if (!optionalBit) {
+                    // ERROR: Unrecognized Well-known Attribute
+                    actionsBgpUpdateUnrecognizedWellKnownAttribute(
+                        bgpSession, ctx, attrTypeCode, attrLen, attrFlags,
+                        message);
+                    String errorMsg = "Unrecognized Well-known Attribute: " +
+                        attrTypeCode;
+                    throw new BgpMessage.BgpParseException(errorMsg);
+                }
+
+                // Skip the data from the unrecognized attribute
+                log.debug("BGP RX UPDATE message from {}: " +
+                          "Unrecognized Attribute Type {}",
+                          bgpSession.remoteInfo().address(), attrTypeCode);
+                message.skipBytes(attrLen);
+                break;
+            }
+        }
+
+        //
+        // Parse the NLRI (Network Layer Reachability Information)
+        //
+        int nlriLength = message.readableBytes();
+        try {
+            Collection<Ip4Prefix> addedPrefixes4 =
+                parsePackedIp4Prefixes(nlriLength, message);
+            // Store it inside the legacy NLRI wrapper
+            legacyNlri.nlri4 = addedPrefixes4;
+        } catch (BgpMessage.BgpParseException e) {
+            // ERROR: Invalid Network Field
+            log.debug("Exception parsing NLRI from BGP peer {}: ",
+                      bgpSession.remoteInfo().bgpId(), e);
+            actionsBgpUpdateInvalidNetworkField(bgpSession, ctx);
+            // Rethrow the exception
+            throw e;
+        }
+
+        // Verify the Well-known Attributes
+        verifyBgpUpdateWellKnownAttributes(bgpSession, ctx, origin, asPath,
+                                           localPref, legacyNlri,
+                                           mpNlriReachList);
+
+        //
+        // Generate the deleted routes
+        //
+        for (MpNlri mpNlri : mpNlriUnreachList) {
+            BgpRouteEntry bgpRouteEntry;
+
+            // The deleted IPv4 routes
+            for (Ip4Prefix prefix : mpNlri.nlri4) {
+                bgpRouteEntry = bgpSession.findBgpRoute(prefix);
+                if (bgpRouteEntry != null) {
+                    decodedBgpRoutes.deletedUnicastRoutes4.put(prefix,
+                                                               bgpRouteEntry);
+                }
+            }
+
+            // The deleted IPv6 routes
+            for (Ip6Prefix prefix : mpNlri.nlri6) {
+                bgpRouteEntry = bgpSession.findBgpRoute(prefix);
+                if (bgpRouteEntry != null) {
+                    decodedBgpRoutes.deletedUnicastRoutes6.put(prefix,
+                                                               bgpRouteEntry);
+                }
+            }
+        }
+
+        //
+        // Generate the added routes
+        //
+        mpNlriReachList.add(legacyNlri);
+        for (MpNlri mpNlri : mpNlriReachList) {
+            BgpRouteEntry bgpRouteEntry;
+
+            // The added IPv4 routes
+            for (Ip4Prefix prefix : mpNlri.nlri4) {
+                bgpRouteEntry =
+                    new BgpRouteEntry(bgpSession, prefix, mpNlri.nextHop4,
+                                      origin.byteValue(), asPath, localPref);
+                bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+                if (bgpRouteEntry.hasAsPathLoop(bgpSession.localInfo().asNumber())) {
+                    log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+                              "nextHop {}: contains AS Path loop",
+                              bgpSession.remoteInfo().address(), prefix,
+                              mpNlri.nextHop4);
+                    continue;
+                } else {
+                    log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+                              bgpSession.remoteInfo().address(), prefix,
+                              mpNlri.nextHop4);
+                }
+                // Remove from the collection of deleted routes
+                decodedBgpRoutes.deletedUnicastRoutes4.remove(prefix);
+                decodedBgpRoutes.addedUnicastRoutes4.put(prefix,
+                                                         bgpRouteEntry);
+            }
+
+            // The added IPv6 routes
+            for (Ip6Prefix prefix : mpNlri.nlri6) {
+                bgpRouteEntry =
+                    new BgpRouteEntry(bgpSession, prefix, mpNlri.nextHop6,
+                                      origin.byteValue(), asPath, localPref);
+                bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+                if (bgpRouteEntry.hasAsPathLoop(bgpSession.localInfo().asNumber())) {
+                    log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+                              "nextHop {}: contains AS Path loop",
+                              bgpSession.remoteInfo().address(), prefix,
+                              mpNlri.nextHop6);
+                    continue;
+                } else {
+                    log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+                              bgpSession.remoteInfo().address(), prefix,
+                              mpNlri.nextHop6);
+                }
+                // Remove from the collection of deleted routes
+                decodedBgpRoutes.deletedUnicastRoutes6.remove(prefix);
+                decodedBgpRoutes.addedUnicastRoutes6.put(prefix,
+                                                         bgpRouteEntry);
+            }
+        }
+    }
+
+    /**
+     * Verifies BGP UPDATE Well-known Attributes.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param origin the ORIGIN well-known mandatory attribute
+     * @param asPath the AS_PATH well-known mandatory attribute
+     * @param localPref the LOCAL_PREF required attribute
+     * @param legacyNlri the legacy NLRI. Encapsulates the NEXT_HOP well-known
+     * mandatory attribute (mandatory if legacy NLRI is used).
+     * @param mpNlriReachList the Multiprotocol NLRI attributes
+     * @throws BgpMessage.BgpParseException
+     */
+    private static void verifyBgpUpdateWellKnownAttributes(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                Short origin,
+                                BgpRouteEntry.AsPath asPath,
+                                Long localPref,
+                                MpNlri legacyNlri,
+                                Collection<MpNlri> mpNlriReachList)
+        throws BgpMessage.BgpParseException {
+        boolean hasNlri = false;
+        boolean hasLegacyNlri = false;
+
+        //
+        // Convenience flags that are used to check for missing attributes.
+        //
+        // NOTE: The hasLegacyNlri flag is always set to true if the
+        // Multiprotocol Extensions are not enabled, even if the UPDATE
+        // message doesn't contain the legacy NLRI (per RFC 4271).
+        //
+        if (!bgpSession.mpExtensions()) {
+            hasNlri = true;
+            hasLegacyNlri = true;
+        } else {
+            if (!legacyNlri.nlri4.isEmpty()) {
+                hasNlri = true;
+                hasLegacyNlri = true;
+            }
+            if (!mpNlriReachList.isEmpty()) {
+                hasNlri = true;
+            }
+        }
+
+        //
+        // Check for Missing Well-known Attributes
+        //
+        if (hasNlri && ((origin == null) || (origin == -1))) {
+            // Missing Attribute Type Code ORIGIN
+            int type = BgpConstants.Update.Origin.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+            String errorMsg = "Missing Well-known Attribute: ORIGIN";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+        if (hasNlri && (asPath == null)) {
+            // Missing Attribute Type Code AS_PATH
+            int type = BgpConstants.Update.AsPath.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+            String errorMsg = "Missing Well-known Attribute: AS_PATH";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+        if (hasNlri && (localPref == null)) {
+            // Missing Attribute Type Code LOCAL_PREF
+            // NOTE: Required for iBGP
+            int type = BgpConstants.Update.LocalPref.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+            String errorMsg = "Missing Well-known Attribute: LOCAL_PREF";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+        if (hasLegacyNlri && (legacyNlri.nextHop4 == null)) {
+            // Missing Attribute Type Code NEXT_HOP
+            int type = BgpConstants.Update.NextHop.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+            String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+    }
+
+    /**
+     * Verifies the BGP UPDATE Attribute Flags.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @throws BgpMessage.BgpParseException
+     */
+    private static void verifyBgpUpdateAttributeFlags(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        //
+        // Assign the Attribute Type Name and the Well-known flag
+        //
+        String typeName = "UNKNOWN";
+        boolean isWellKnown = false;
+        switch (attrTypeCode) {
+        case BgpConstants.Update.Origin.TYPE:
+            isWellKnown = true;
+            typeName = "ORIGIN";
+            break;
+        case BgpConstants.Update.AsPath.TYPE:
+            isWellKnown = true;
+            typeName = "AS_PATH";
+            break;
+        case BgpConstants.Update.NextHop.TYPE:
+            isWellKnown = true;
+            typeName = "NEXT_HOP";
+            break;
+        case BgpConstants.Update.MultiExitDisc.TYPE:
+            isWellKnown = false;
+            typeName = "MULTI_EXIT_DISC";
+            break;
+        case BgpConstants.Update.LocalPref.TYPE:
+            isWellKnown = true;
+            typeName = "LOCAL_PREF";
+            break;
+        case BgpConstants.Update.AtomicAggregate.TYPE:
+            isWellKnown = true;
+            typeName = "ATOMIC_AGGREGATE";
+            break;
+        case BgpConstants.Update.Aggregator.TYPE:
+            isWellKnown = false;
+            typeName = "AGGREGATOR";
+            break;
+        case BgpConstants.Update.MpReachNlri.TYPE:
+            isWellKnown = false;
+            typeName = "MP_REACH_NLRI";
+            break;
+        case BgpConstants.Update.MpUnreachNlri.TYPE:
+            isWellKnown = false;
+            typeName = "MP_UNREACH_NLRI";
+            break;
+        default:
+            isWellKnown = false;
+            typeName = "UNKNOWN(" + attrTypeCode + ")";
+            break;
+        }
+
+        //
+        // Verify the Attribute Flags
+        //
+        boolean optionalBit =       ((0x80 & attrFlags) != 0);
+        boolean transitiveBit =     ((0x40 & attrFlags) != 0);
+        boolean partialBit =        ((0x20 & attrFlags) != 0);
+        if ((isWellKnown && optionalBit) ||
+            (isWellKnown && (!transitiveBit)) ||
+            (isWellKnown && partialBit) ||
+            (optionalBit && (!transitiveBit) && partialBit)) {
+            //
+            // ERROR: The Optional bit cannot be set for Well-known attributes
+            // ERROR: The Transtive bit MUST be 1 for well-known attributes
+            // ERROR: The Partial bit MUST be 0 for well-known attributes
+            // ERROR: The Partial bit MUST be 0 for optional non-transitive
+            //        attributes
+            //
+            actionsBgpUpdateAttributeFlagsError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Flags Error for " + typeName + ": " +
+                attrFlags;
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type ORIGIN.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed ORIGIN value
+     * @throws BgpMessage.BgpParseException
+     */
+    private static short parseAttributeTypeOrigin(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.Origin.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        short origin = message.readUnsignedByte();
+        switch (origin) {
+        case BgpConstants.Update.Origin.IGP:
+            // FALLTHROUGH
+        case BgpConstants.Update.Origin.EGP:
+            // FALLTHROUGH
+        case BgpConstants.Update.Origin.INCOMPLETE:
+            break;
+        default:
+            // ERROR: Invalid ORIGIN Attribute
+            message.resetReaderIndex();
+            actionsBgpUpdateInvalidOriginAttribute(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message,
+                origin);
+            String errorMsg = "Invalid ORIGIN Attribute: " + origin;
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        return origin;
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute AS Path.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed AS Path
+     * @throws BgpMessage.BgpParseException
+     */
+    private static BgpRouteEntry.AsPath parseAttributeTypeAsPath(
+                                                BgpSession bgpSession,
+                                                ChannelHandlerContext ctx,
+                                                int attrTypeCode,
+                                                int attrLen,
+                                                int attrFlags,
+                                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+
+        //
+        // Parse the message
+        //
+        while (attrLen > 0) {
+            if (attrLen < 2) {
+                // ERROR: Malformed AS_PATH
+                actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
+                String errorMsg = "Malformed AS Path";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+            // Get the Path Segment Type and Length (in number of ASes)
+            short pathSegmentType = message.readUnsignedByte();
+            short pathSegmentLength = message.readUnsignedByte();
+            attrLen -= 2;
+
+            // Verify the Path Segment Type
+            switch (pathSegmentType) {
+            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
+                //
+                // NOTE: The BGP Spec (RFC 4271) doesn't contain Error Subcode
+                // for "Invalid Path Segment Type", hence we return
+                // the error as "Malformed AS_PATH".
+                //
+                actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
+                String errorMsg =
+                    "Invalid AS Path Segment Type: " + pathSegmentType;
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+
+            // 4-octet AS number handling.
+            int asPathLen;
+            if (bgpSession.isAs4OctetCapable()) {
+                asPathLen = BgpConstants.Update.AsPath.AS_4OCTET_LENGTH;
+            } else {
+                asPathLen = BgpConstants.Update.AsPath.AS_LENGTH;
+            }
+
+            // Parse the AS numbers
+            if (asPathLen * pathSegmentLength > attrLen) {
+                // ERROR: Malformed AS_PATH
+                actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
+                String errorMsg = "Malformed AS Path";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+            attrLen -= (asPathLen * pathSegmentLength);
+            ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+            while (pathSegmentLength-- > 0) {
+                long asNumber;
+                if (asPathLen == BgpConstants.Update.AsPath.AS_4OCTET_LENGTH) {
+                    asNumber = message.readUnsignedInt();
+                } else {
+                    asNumber = message.readUnsignedShort();
+                }
+                segmentAsNumbers.add(asNumber);
+            }
+
+            BgpRouteEntry.PathSegment pathSegment =
+                new BgpRouteEntry.PathSegment((byte) pathSegmentType,
+                                              segmentAsNumbers);
+            pathSegments.add(pathSegment);
+        }
+
+        return new BgpRouteEntry.AsPath(pathSegments);
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type NEXT_HOP.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed NEXT_HOP value
+     * @throws BgpMessage.BgpParseException
+     */
+    private static Ip4Address parseAttributeTypeNextHop(
+                                        BgpSession bgpSession,
+                                        ChannelHandlerContext ctx,
+                                        int attrTypeCode,
+                                        int attrLen,
+                                        int attrFlags,
+                                        ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.NextHop.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        Ip4Address nextHopAddress =
+            Ip4Address.valueOf((int) message.readUnsignedInt());
+        //
+        // Check whether the NEXT_HOP IP address is semantically correct.
+        // As per RFC 4271, Section 6.3:
+        //
+        //  a) It MUST NOT be the IP address of the receiving speaker
+        //  b) In the case of an EBGP ....
+        //
+        // Here we check only (a), because (b) doesn't apply for us: all our
+        // peers are iBGP.
+        //
+        if (nextHopAddress.equals(bgpSession.localInfo().ip4Address())) {
+            // ERROR: Invalid NEXT_HOP Attribute
+            message.resetReaderIndex();
+            actionsBgpUpdateInvalidNextHopAttribute(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message,
+                nextHopAddress);
+            String errorMsg = "Invalid NEXT_HOP Attribute: " + nextHopAddress;
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        return nextHopAddress;
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type MULTI_EXIT_DISC.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed MULTI_EXIT_DISC value
+     * @throws BgpMessage.BgpParseException
+     */
+    private static long parseAttributeTypeMultiExitDisc(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        long multiExitDisc = message.readUnsignedInt();
+        return multiExitDisc;
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type LOCAL_PREF.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed LOCAL_PREF value
+     * @throws BgpMessage.BgpParseException
+     */
+    private static long parseAttributeTypeLocalPref(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.LocalPref.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        long localPref = message.readUnsignedInt();
+        return localPref;
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type ATOMIC_AGGREGATE.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @throws BgpMessage.BgpParseException
+     */
+    private static void parseAttributeTypeAtomicAggregate(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        // Nothing to do: this attribute is primarily informational
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type AGGREGATOR.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed AGGREGATOR value: a tuple of <AS-Number, IP-Address>
+     * @throws BgpMessage.BgpParseException
+     */
+    private static Pair<Long, Ip4Address> parseAttributeTypeAggregator(
+                                                BgpSession bgpSession,
+                                                ChannelHandlerContext ctx,
+                                                int attrTypeCode,
+                                                int attrLen,
+                                                int attrFlags,
+                                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.Aggregator.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        // The AGGREGATOR AS number
+        long aggregatorAsNumber = message.readUnsignedShort();
+        // The AGGREGATOR IP address
+        Ip4Address aggregatorIpAddress =
+            Ip4Address.valueOf((int) message.readUnsignedInt());
+
+        Pair<Long, Ip4Address> aggregator = Pair.of(aggregatorAsNumber,
+                                                    aggregatorIpAddress);
+        return aggregator;
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type MP_REACH_NLRI.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed MP_REACH_NLRI information if recognized, otherwise
+     * null
+     * @throws BgpMessage.BgpParseException
+     */
+    private static MpNlri parseAttributeTypeMpReachNlri(
+                                                BgpSession bgpSession,
+                                                ChannelHandlerContext ctx,
+                                                int attrTypeCode,
+                                                int attrLen,
+                                                int attrFlags,
+                                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+        int attributeEnd = message.readerIndex() + attrLen;
+
+        // Check the Attribute Length
+        if (attrLen < BgpConstants.Update.MpReachNlri.MIN_LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        int afi = message.readUnsignedShort();
+        int safi = message.readUnsignedByte();
+        int nextHopLen = message.readUnsignedByte();
+
+        //
+        // Verify the AFI/SAFI, and skip the attribute if not recognized.
+        // NOTE: Currently, we support only IPv4/IPv6 UNICAST
+        //
+        if (((afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4) &&
+             (afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6)) ||
+            (safi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST)) {
+            // Skip the attribute
+            message.resetReaderIndex();
+            message.skipBytes(attrLen);
+            return null;
+        }
+
+        //
+        // Verify the next-hop length
+        //
+        int expectedNextHopLen = 0;
+        switch (afi) {
+        case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4:
+            expectedNextHopLen = Ip4Address.BYTE_LENGTH;
+            break;
+        case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6:
+            expectedNextHopLen = Ip6Address.BYTE_LENGTH;
+            break;
+        default:
+            // UNREACHABLE
+            break;
+        }
+        if (nextHopLen != expectedNextHopLen) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Invalid next-hop network address length. " +
+                "Received " + nextHopLen + " expected " + expectedNextHopLen;
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+        // NOTE: We use "+ 1" to take into account the Reserved field (1 octet)
+        if (message.readerIndex() + nextHopLen + 1 >= attributeEnd) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Malformed next-hop network address";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        //
+        // Get the Next-hop address, skip the Reserved field, and get the NLRI
+        //
+        byte[] nextHopBuffer = new byte[nextHopLen];
+        message.readBytes(nextHopBuffer, 0, nextHopLen);
+        int reserved = message.readUnsignedByte();
+        MpNlri mpNlri = new MpNlri(afi, safi);
+        try {
+            switch (afi) {
+            case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4:
+                // The next-hop address
+                mpNlri.nextHop4 = Ip4Address.valueOf(nextHopBuffer);
+                // The NLRI
+                mpNlri.nlri4 = parsePackedIp4Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6:
+                // The next-hop address
+                mpNlri.nextHop6 = Ip6Address.valueOf(nextHopBuffer);
+                // The NLRI
+                mpNlri.nlri6 = parsePackedIp6Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            default:
+                // UNREACHABLE
+                break;
+            }
+        } catch (BgpMessage.BgpParseException e) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Malformed network layer reachability information";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        return mpNlri;
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type MP_UNREACH_NLRI.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed MP_UNREACH_NLRI information if recognized, otherwise
+     * null
+     * @throws BgpMessage.BgpParseException
+     */
+    private static MpNlri parseAttributeTypeMpUnreachNlri(
+                                                BgpSession bgpSession,
+                                                ChannelHandlerContext ctx,
+                                                int attrTypeCode,
+                                                int attrLen,
+                                                int attrFlags,
+                                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+        int attributeEnd = message.readerIndex() + attrLen;
+
+        // Check the Attribute Length
+        if (attrLen < BgpConstants.Update.MpUnreachNlri.MIN_LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        int afi = message.readUnsignedShort();
+        int safi = message.readUnsignedByte();
+
+        //
+        // Verify the AFI/SAFI, and skip the attribute if not recognized.
+        // NOTE: Currently, we support only IPv4/IPv6 UNICAST
+        //
+        if (((afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4) &&
+             (afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6)) ||
+            (safi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST)) {
+            // Skip the attribute
+            message.resetReaderIndex();
+            message.skipBytes(attrLen);
+            return null;
+        }
+
+        //
+        // Get the Withdrawn Routes
+        //
+        MpNlri mpNlri = new MpNlri(afi, safi);
+        try {
+            switch (afi) {
+            case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4:
+                // The Withdrawn Routes
+                mpNlri.nlri4 = parsePackedIp4Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6:
+                // The Withdrawn Routes
+                mpNlri.nlri6 = parsePackedIp6Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            default:
+                // UNREACHABLE
+                break;
+            }
+        } catch (BgpMessage.BgpParseException e) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Malformed withdrawn routes";
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+
+        return mpNlri;
+    }
+
+    /**
+     * Parses a message that contains encoded IPv4 network prefixes.
+     * <p>
+     * The IPv4 prefixes are encoded in the form:
+     * <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
+     * and Prefix is the IPv4 prefix (padded with trailing bits to the end
+     * of an octet).
+     *
+     * @param totalLength the total length of the data to parse
+     * @param message the message with data to parse
+     * @return a collection of parsed IPv4 network prefixes
+     * @throws BgpMessage.BgpParseException
+     */
+    private static Collection<Ip4Prefix> parsePackedIp4Prefixes(
+                                                int totalLength,
+                                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+        Collection<Ip4Prefix> result = new ArrayList<>();
+
+        if (totalLength == 0) {
+            return result;
+        }
+
+        // Parse the data
+        byte[] buffer = new byte[Ip4Address.BYTE_LENGTH];
+        int dataEnd = message.readerIndex() + totalLength;
+        while (message.readerIndex() < dataEnd) {
+            int prefixBitlen = message.readUnsignedByte();
+            int prefixBytelen = (prefixBitlen + 7) / 8;     // Round-up
+            if (message.readerIndex() + prefixBytelen > dataEnd) {
+                String errorMsg = "Malformed Network Prefixes";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+
+            message.readBytes(buffer, 0, prefixBytelen);
+            Ip4Prefix prefix = Ip4Prefix.valueOf(Ip4Address.valueOf(buffer),
+                                                 prefixBitlen);
+            result.add(prefix);
+        }
+
+        return result;
+    }
+
+    /**
+     * Parses a message that contains encoded IPv6 network prefixes.
+     * <p>
+     * The IPv6 prefixes are encoded in the form:
+     * <Length, Prefix> where Length is the length in bits of the IPv6 prefix,
+     * and Prefix is the IPv6 prefix (padded with trailing bits to the end
+     * of an octet).
+     *
+     * @param totalLength the total length of the data to parse
+     * @param message the message with data to parse
+     * @return a collection of parsed IPv6 network prefixes
+     * @throws BgpMessage.BgpParseException
+     */
+    private static Collection<Ip6Prefix> parsePackedIp6Prefixes(
+                                                int totalLength,
+                                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+        Collection<Ip6Prefix> result = new ArrayList<>();
+
+        if (totalLength == 0) {
+            return result;
+        }
+
+        // Parse the data
+        byte[] buffer = new byte[Ip6Address.BYTE_LENGTH];
+        int dataEnd = message.readerIndex() + totalLength;
+        while (message.readerIndex() < dataEnd) {
+            int prefixBitlen = message.readUnsignedByte();
+            int prefixBytelen = (prefixBitlen + 7) / 8;     // Round-up
+            if (message.readerIndex() + prefixBytelen > dataEnd) {
+                String errorMsg = "Malformed Network Prefixes";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+
+            message.readBytes(buffer, 0, prefixBytelen);
+            Ip6Prefix prefix = Ip6Prefix.valueOf(Ip6Address.valueOf(buffer),
+                                                 prefixBitlen);
+            result.add(prefix);
+        }
+
+        return result;
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Invalid Network Field Error: send NOTIFICATION and close the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     */
+    private static void actionsBgpUpdateInvalidNetworkField(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx) {
+        log.debug("BGP RX UPDATE Error from {}: Invalid Network Field",
+                  bgpSession.remoteInfo().address());
+
+        //
+        // ERROR: Invalid Network Field
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.INVALID_NETWORK_FIELD;
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   null);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Malformed Attribute List Error: send NOTIFICATION and close the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     */
+    private static void actionsBgpUpdateMalformedAttributeList(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx) {
+        log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List",
+                  bgpSession.remoteInfo().address());
+
+        //
+        // ERROR: Malformed Attribute List
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.MALFORMED_ATTRIBUTE_LIST;
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   null);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Missing Well-known Attribute Error: send NOTIFICATION and close the
+     * channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param missingAttrTypeCode the missing attribute type code
+     */
+    private static void actionsBgpUpdateMissingWellKnownAttribute(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int missingAttrTypeCode) {
+        log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}",
+                  bgpSession.remoteInfo().address(), missingAttrTypeCode);
+
+        //
+        // ERROR: Missing Well-known Attribute
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.MISSING_WELL_KNOWN_ATTRIBUTE;
+        ChannelBuffer data = ChannelBuffers.buffer(1);
+        data.writeByte(missingAttrTypeCode);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Invalid ORIGIN Attribute Error: send NOTIFICATION and close the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     * @param origin the ORIGIN attribute value
+     */
+    private static void actionsBgpUpdateInvalidOriginAttribute(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message,
+                                short origin) {
+        log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute",
+                  bgpSession.remoteInfo().address());
+
+        //
+        // ERROR: Invalid ORIGIN Attribute
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.INVALID_ORIGIN_ATTRIBUTE;
+        ChannelBuffer data =
+            prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+                                                    attrFlags, message);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Attribute Flags Error: send NOTIFICATION and close the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     */
+    private static void actionsBgpUpdateAttributeFlagsError(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error",
+                  bgpSession.remoteInfo().address());
+
+        //
+        // ERROR: Attribute Flags Error
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.ATTRIBUTE_FLAGS_ERROR;
+        ChannelBuffer data =
+            prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+                                                    attrFlags, message);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Invalid NEXT_HOP Attribute Error: send NOTIFICATION and close the
+     * channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     * @param nextHop the NEXT_HOP attribute value
+     */
+    private static void actionsBgpUpdateInvalidNextHopAttribute(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message,
+                                Ip4Address nextHop) {
+        log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}",
+                  bgpSession.remoteInfo().address(), nextHop);
+
+        //
+        // ERROR: Invalid NEXT_HOP Attribute
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.INVALID_NEXT_HOP_ATTRIBUTE;
+        ChannelBuffer data =
+            prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+                                                    attrFlags, message);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Unrecognized Well-known Attribute Error: send NOTIFICATION and close
+     * the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     */
+    private static void actionsBgpUpdateUnrecognizedWellKnownAttribute(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: " +
+                  "Unrecognized Well-known Attribute Error: {}",
+                  bgpSession.remoteInfo().address(), attrTypeCode);
+
+        //
+        // ERROR: Unrecognized Well-known Attribute
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode =
+            BgpConstants.Notifications.UpdateMessageError.UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE;
+        ChannelBuffer data =
+            prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+                                                    attrFlags, message);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Optional Attribute Error: send NOTIFICATION and close
+     * the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     */
+    private static void actionsBgpUpdateOptionalAttributeError(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: Optional Attribute Error: {}",
+                  bgpSession.remoteInfo().address(), attrTypeCode);
+
+        //
+        // ERROR: Optional Attribute Error
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode =
+            BgpConstants.Notifications.UpdateMessageError.OPTIONAL_ATTRIBUTE_ERROR;
+        ChannelBuffer data =
+            prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+                                                    attrFlags, message);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Attribute Length Error: send NOTIFICATION and close the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     */
+    private static void actionsBgpUpdateAttributeLengthError(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: Attribute Length Error",
+                  bgpSession.remoteInfo().address());
+
+        //
+        // ERROR: Attribute Length Error
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.ATTRIBUTE_LENGTH_ERROR;
+        ChannelBuffer data =
+            prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+                                                    attrFlags, message);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Malformed AS_PATH Error: send NOTIFICATION and close the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     */
+    private static void actionsBgpUpdateMalformedAsPath(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx) {
+        log.debug("BGP RX UPDATE Error from {}: Malformed AS Path",
+                  bgpSession.remoteInfo().address());
+
+        //
+        // ERROR: Malformed AS_PATH
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.UpdateMessageError.MALFORMED_AS_PATH;
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   null);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Prepares BGP UPDATE Notification data payload.
+     *
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     * @return the buffer with the data payload for the BGP UPDATE Notification
+     */
+    private static ChannelBuffer prepareBgpUpdateNotificationDataPayload(
+                                        int attrTypeCode,
+                                        int attrLen,
+                                        int attrFlags,
+                                        ChannelBuffer message) {
+        // Compute the attribute length field octets
+        boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
+        int attrLenOctets = 1;
+        if (extendedLengthBit) {
+            attrLenOctets = 2;
+        }
+        ChannelBuffer data =
+            ChannelBuffers.buffer(attrLen + attrLenOctets + 1);
+        data.writeByte(attrTypeCode);
+        if (extendedLengthBit) {
+            data.writeShort(attrLen);
+        } else {
+            data.writeByte(attrLen);
+        }
+        data.writeBytes(message, attrLen);
+        return data;
+    }
+
+    /**
+     * Helper class for storing Multiprotocol Network Layer Reachability
+     * information.
+     */
+    private static final class MpNlri {
+        private final int afi;
+        private final int safi;
+        private Ip4Address nextHop4;
+        private Ip6Address nextHop6;
+        private Collection<Ip4Prefix> nlri4 = new ArrayList<>();
+        private Collection<Ip6Prefix> nlri6 = new ArrayList<>();
+
+        /**
+         * Constructor.
+         *
+         * @param afi the Address Family Identifier
+         * @param safi the Subsequent Address Family Identifier
+         */
+        private MpNlri(int afi, int safi) {
+            this.afi = afi;
+            this.safi = safi;
+        }
+    }
+
+    /**
+     * Helper class for storing decoded BGP routing information.
+     */
+    private static final class DecodedBgpRoutes {
+        private final Map<Ip4Prefix, BgpRouteEntry> addedUnicastRoutes4 =
+            new HashMap<>();
+        private final Map<Ip6Prefix, BgpRouteEntry> addedUnicastRoutes6 =
+            new HashMap<>();
+        private final Map<Ip4Prefix, BgpRouteEntry> deletedUnicastRoutes4 =
+            new HashMap<>();
+        private final Map<Ip6Prefix, BgpRouteEntry> deletedUnicastRoutes6 =
+            new HashMap<>();
+    }
+}