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/Router.java b/apps/routing/src/main/java/org/onosproject/routing/Router.java
new file mode 100644
index 0000000..494c190
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/Router.java
@@ -0,0 +1,523 @@
+/*
+ * 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;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.googlecode.concurrenttrees.common.KeyValuePair;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.routingapi.BgpService;
+import org.onosproject.routingapi.FibEntry;
+import org.onosproject.routingapi.FibListener;
+import org.onosproject.routingapi.FibUpdate;
+import org.onosproject.routingapi.RouteEntry;
+import org.onosproject.routingapi.RouteListener;
+import org.onosproject.routingapi.RouteUpdate;
+import org.onosproject.routingapi.RoutingService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * This class processes route updates and maintains a Routing Information Base
+ * (RIB). After route updates have been processed and next hops have been
+ * resolved, FIB updates are sent to any listening FIB components.
+ */
+@Component(immediate = true)
+@Service
+public class Router implements RoutingService {
+
+    private static final Logger log = LoggerFactory.getLogger(Router.class);
+
+    // Route entries are stored in a radix tree.
+    // The key in this tree is the binary string of prefix of the route.
+    private InvertedRadixTree<RouteEntry> ribTable4;
+    private InvertedRadixTree<RouteEntry> ribTable6;
+
+    // Stores all incoming route updates in a queue.
+    private final BlockingQueue<Collection<RouteUpdate>> routeUpdatesQueue
+            = new LinkedBlockingQueue<>();
+
+    // Next-hop IP address to route entry mapping for next hops pending MAC resolution
+    private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
+
+    // The IPv4 address to MAC address mapping
+    private final Map<IpAddress, MacAddress> ip2Mac = new ConcurrentHashMap<>();
+
+    private FibListener fibComponent;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected BgpService bgpService;
+
+    private ExecutorService bgpUpdatesExecutor;
+    private final HostListener hostListener = new InternalHostListener();
+
+    @Activate
+    public void activate() {
+        ribTable4 = new ConcurrentInvertedRadixTree<>(
+                new DefaultByteArrayNodeFactory());
+        ribTable6 = new ConcurrentInvertedRadixTree<>(
+                new DefaultByteArrayNodeFactory());
+        routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
+                HashMultimap.<IpAddress, RouteEntry>create());
+
+        bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
+                new ThreadFactoryBuilder()
+                .setNameFormat("sdnip-bgp-updates-%d").build());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.debug("Stopped");
+    }
+
+    @Override
+    public void start(FibListener listener) {
+        this.fibComponent = checkNotNull(listener);
+        this.hostService.addListener(hostListener);
+
+        bgpService.start(new InternalRouteListener(), 2000);
+
+        bgpUpdatesExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                doUpdatesThread();
+            }
+        });
+    }
+
+    @Override
+    public void stop() {
+        bgpService.stop();
+
+        this.hostService.removeListener(hostListener);
+
+        // Stop the thread(s)
+        bgpUpdatesExecutor.shutdownNow();
+
+        synchronized (this) {
+            // Cleanup all local state
+            ribTable4 = new ConcurrentInvertedRadixTree<>(
+                new DefaultByteArrayNodeFactory());
+            ribTable6 = new ConcurrentInvertedRadixTree<>(
+                new DefaultByteArrayNodeFactory());
+            routeUpdatesQueue.clear();
+            routesWaitingOnArp.clear();
+            ip2Mac.clear();
+        }
+    }
+
+    /**
+     * Entry point for route updates.
+     *
+     * @param routeUpdates collection of route updates to process
+     */
+    private void update(Collection<RouteUpdate> routeUpdates) {
+        try {
+            routeUpdatesQueue.put(routeUpdates);
+        } catch (InterruptedException e) {
+            log.debug("Interrupted while putting on routeUpdatesQueue", e);
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * Thread for handling route updates.
+     */
+    private void doUpdatesThread() {
+        boolean interrupted = false;
+        try {
+            while (!interrupted) {
+                try {
+                    Collection<RouteUpdate> routeUpdates =
+                        routeUpdatesQueue.take();
+                    processRouteUpdates(routeUpdates);
+                } catch (InterruptedException e) {
+                    log.debug("Interrupted while taking from updates queue", e);
+                    interrupted = true;
+                } catch (Exception e) {
+                    log.debug("exception", e);
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Gets all IPv4 routes from the RIB.
+     *
+     * @return all IPv4 routes from the RIB
+     */
+    public Collection<RouteEntry> getRoutes4() {
+        Iterator<KeyValuePair<RouteEntry>> it =
+            ribTable4.getKeyValuePairsForKeysStartingWith("").iterator();
+
+        List<RouteEntry> routes = new LinkedList<>();
+
+        while (it.hasNext()) {
+            KeyValuePair<RouteEntry> entry = it.next();
+            routes.add(entry.getValue());
+        }
+
+        return routes;
+    }
+
+    /**
+     * Gets all IPv6 routes from the RIB.
+     *
+     * @return all IPv6 routes from the RIB
+     */
+    public Collection<RouteEntry> getRoutes6() {
+        Iterator<KeyValuePair<RouteEntry>> it =
+            ribTable6.getKeyValuePairsForKeysStartingWith("").iterator();
+
+        List<RouteEntry> routes = new LinkedList<>();
+
+        while (it.hasNext()) {
+            KeyValuePair<RouteEntry> entry = it.next();
+            routes.add(entry.getValue());
+        }
+
+        return routes;
+    }
+
+    /**
+     * Finds a route in the RIB for a prefix. The prefix can be either IPv4 or
+     * IPv6.
+     *
+     * @param prefix the prefix to use
+     * @return the route if found, otherwise null
+     */
+    RouteEntry findRibRoute(IpPrefix prefix) {
+        String binaryString = RouteEntry.createBinaryString(prefix);
+        if (prefix.version() == Ip4Address.VERSION) {
+            // IPv4
+            return ribTable4.getValueForExactKey(binaryString);
+        }
+        // IPv6
+        return ribTable6.getValueForExactKey(binaryString);
+    }
+
+    /**
+     * Adds a route to the RIB. The route can be either IPv4 or IPv6.
+     *
+     * @param routeEntry the route entry to use
+     */
+    void addRibRoute(RouteEntry routeEntry) {
+        if (routeEntry.prefix().version() == Ip4Address.VERSION) {
+            // IPv4
+            ribTable4.put(RouteEntry.createBinaryString(routeEntry.prefix()),
+                          routeEntry);
+        } else {
+            // IPv6
+            ribTable6.put(RouteEntry.createBinaryString(routeEntry.prefix()),
+                          routeEntry);
+        }
+    }
+
+    /**
+     * Removes a route for a prefix from the RIB. The prefix can be either IPv4
+     * or IPv6.
+     *
+     * @param prefix the prefix to use
+     * @return true if the route was found and removed, otherwise false
+     */
+    boolean removeRibRoute(IpPrefix prefix) {
+        if (prefix.version() == Ip4Address.VERSION) {
+            // IPv4
+            return ribTable4.remove(RouteEntry.createBinaryString(prefix));
+        }
+        // IPv6
+        return ribTable6.remove(RouteEntry.createBinaryString(prefix));
+    }
+
+    /**
+     * Processes route updates.
+     *
+     * @param routeUpdates the route updates to process
+     */
+    void processRouteUpdates(Collection<RouteUpdate> routeUpdates) {
+        synchronized (this) {
+            Collection<IpPrefix> withdrawPrefixes = new LinkedList<>();
+            Collection<FibUpdate> fibUpdates = new LinkedList<>();
+            Collection<FibUpdate> fibWithdraws = new LinkedList<>();
+
+            for (RouteUpdate update : routeUpdates) {
+                switch (update.type()) {
+                case UPDATE:
+                    FibEntry fib = processRouteAdd(update.routeEntry(),
+                                                    withdrawPrefixes);
+                    if (fib != null) {
+                        fibUpdates.add(new FibUpdate(FibUpdate.Type.UPDATE, fib));
+                    }
+
+                    break;
+                case DELETE:
+                    processRouteDelete(update.routeEntry(), withdrawPrefixes);
+
+                    break;
+                default:
+                    log.error("Unknown update Type: {}", update.type());
+                    break;
+                }
+            }
+
+            withdrawPrefixes.forEach(p -> fibWithdraws.add(new FibUpdate(
+                    FibUpdate.Type.DELETE, new FibEntry(p, null, null))));
+
+            if (!fibUpdates.isEmpty() || !fibWithdraws.isEmpty()) {
+                fibComponent.update(fibUpdates, fibWithdraws);
+            }
+        }
+    }
+
+    /**
+     * Processes adding a route entry.
+     * <p>
+     * The route entry is added to the radix tree. If there was an existing
+     * next hop for this prefix, but the next hop was different, then the
+     * old route entry is deleted.
+     * </p>
+     * <p>
+     * NOTE: Currently, we don't handle routes if the next hop is within the
+     * SDN domain.
+     * </p>
+     *
+     * @param routeEntry the route entry to add
+     * @param withdrawPrefixes the collection of accumulated prefixes whose
+     * intents will be withdrawn
+     * @return the corresponding FIB entry change, or null
+     */
+    private FibEntry processRouteAdd(
+                RouteEntry routeEntry,
+                Collection<IpPrefix> withdrawPrefixes) {
+        log.debug("Processing route add: {}", routeEntry);
+
+        // Find the old next-hop if we are updating an old route entry
+        IpAddress oldNextHop = null;
+        RouteEntry oldRouteEntry = findRibRoute(routeEntry.prefix());
+        if (oldRouteEntry != null) {
+            oldNextHop = oldRouteEntry.nextHop();
+        }
+
+        // Add the new route to the RIB
+        addRibRoute(routeEntry);
+
+        if (oldNextHop != null) {
+            if (oldNextHop.equals(routeEntry.nextHop())) {
+                return null;            // No change
+            }
+            //
+            // Update an existing nexthop for the prefix.
+            // We need to remove the old flows for this prefix from the
+            // switches before the new flows are added.
+            //
+            withdrawPrefixes.add(routeEntry.prefix());
+        }
+
+        if (routeEntry.nextHop().isZero()) {
+            // Route originated by SDN domain
+            // We don't handle these at the moment
+            log.debug("Own route {} to {}",
+                      routeEntry.prefix(), routeEntry.nextHop());
+            return null;
+        }
+
+        //
+        // Find the MAC address of next hop router for this route entry.
+        // If the MAC address can not be found in ARP cache, then this prefix
+        // will be put in routesWaitingOnArp queue. Otherwise, generate
+        // a new route intent.
+        //
+
+        // Monitor the IP address for updates of the MAC address
+        hostService.startMonitoringIp(routeEntry.nextHop());
+
+        // Check if we know the MAC address of the next hop
+        MacAddress nextHopMacAddress = ip2Mac.get(routeEntry.nextHop());
+        if (nextHopMacAddress == null) {
+            Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
+            if (!hosts.isEmpty()) {
+                nextHopMacAddress = hosts.iterator().next().mac();
+            }
+            if (nextHopMacAddress != null) {
+                ip2Mac.put(routeEntry.nextHop(), nextHopMacAddress);
+            }
+        }
+        if (nextHopMacAddress == null) {
+            routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
+            return null;
+        }
+        return new FibEntry(routeEntry.prefix(), routeEntry.nextHop(),
+                             nextHopMacAddress);
+    }
+
+    /**
+     * Processes the deletion of a route entry.
+     * <p>
+     * The prefix for the routing entry is removed from radix tree.
+     * If the operation is successful, the prefix is added to the collection
+     * of prefixes whose intents that will be withdrawn.
+     * </p>
+     *
+     * @param routeEntry the route entry to delete
+     * @param withdrawPrefixes the collection of accumulated prefixes whose
+     * intents will be withdrawn
+     */
+    private void processRouteDelete(RouteEntry routeEntry,
+                                    Collection<IpPrefix> withdrawPrefixes) {
+        log.debug("Processing route delete: {}", routeEntry);
+        boolean isRemoved = removeRibRoute(routeEntry.prefix());
+
+        if (isRemoved) {
+            //
+            // Only withdraw intents if an entry was actually removed from the
+            // tree. If no entry was removed, the <prefix, nexthop> wasn't
+            // there so it's probably already been removed and we don't
+            // need to do anything.
+            //
+            withdrawPrefixes.add(routeEntry.prefix());
+        }
+
+        routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
+    }
+
+    /**
+     * Signals the Router that the MAC to IP mapping has potentially been
+     * updated. This has the effect of updating the MAC address for any
+     * installed prefixes if it has changed, as well as installing any pending
+     * prefixes that were waiting for MAC resolution.
+     *
+     * @param ipAddress the IP address that an event was received for
+     * @param macAddress the most recently known MAC address for the IP address
+     */
+    private void updateMac(IpAddress ipAddress, MacAddress macAddress) {
+        log.debug("Received updated MAC info: {} => {}", ipAddress,
+                  macAddress);
+
+        //
+        // We synchronize on "this" to prevent changes to the Radix tree
+        // while we're pushing intents. If the tree changes, the
+        // tree and the intents could get out of sync.
+        //
+        synchronized (this) {
+            Collection<FibUpdate> submitFibEntries = new LinkedList<>();
+
+            Set<RouteEntry> routesToPush =
+                routesWaitingOnArp.removeAll(ipAddress);
+
+            for (RouteEntry routeEntry : routesToPush) {
+                // These will always be adds
+                RouteEntry foundRouteEntry = findRibRoute(routeEntry.prefix());
+                if (foundRouteEntry != null &&
+                    foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
+                    // We only push FIB updates if the prefix is still in the
+                    // radix tree and the next hop is the same as our entry.
+                    // The prefix could have been removed while we were waiting
+                    // for the ARP, or the next hop could have changed.
+                    submitFibEntries.add(new FibUpdate(FibUpdate.Type.UPDATE,
+                                                       new FibEntry(routeEntry.prefix(),
+                                                       ipAddress, macAddress)));
+                } else {
+                    log.debug("{} has been revoked before the MAC was resolved",
+                              routeEntry);
+                }
+            }
+
+            if (!submitFibEntries.isEmpty()) {
+                fibComponent.update(submitFibEntries, Collections.emptyList());
+            }
+
+            ip2Mac.put(ipAddress, macAddress);
+        }
+    }
+
+    /**
+     * Listener for host events.
+     */
+    class InternalHostListener implements HostListener {
+        @Override
+        public void event(HostEvent event) {
+            log.debug("Received HostEvent {}", event);
+
+            Host host = event.subject();
+            switch (event.type()) {
+            case HOST_ADDED:
+                // FALLTHROUGH
+            case HOST_UPDATED:
+                for (IpAddress ipAddress : host.ipAddresses()) {
+                    updateMac(ipAddress, host.mac());
+                }
+                break;
+            case HOST_REMOVED:
+                for (IpAddress ipAddress : host.ipAddresses()) {
+                    ip2Mac.remove(ipAddress);
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    /**
+     * Listener for route events.
+     */
+    private class InternalRouteListener implements RouteListener {
+        @Override
+        public void update(Collection<RouteUpdate> routeUpdates) {
+            Router.this.update(routeUpdates);
+        }
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpConstants.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpConstants.java
new file mode 100644
index 0000000..96d74e2
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpConstants.java
@@ -0,0 +1,553 @@
+/*
+ * 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;
+
+/**
+ * BGP related constants.
+ */
+public final class BgpConstants {
+    /**
+     * Default constructor.
+     * <p>
+     * The constructor is private to prevent creating an instance of
+     * this utility class.
+     */
+    private BgpConstants() {
+    }
+
+    /** BGP port number (RFC 4271). */
+    public static final int BGP_PORT = 179;
+
+    /** BGP version. */
+    public static final int BGP_VERSION = 4;
+
+    /** BGP OPEN message type. */
+    public static final int BGP_TYPE_OPEN = 1;
+
+    /** BGP UPDATE message type. */
+    public static final int BGP_TYPE_UPDATE = 2;
+
+    /** BGP NOTIFICATION message type. */
+    public static final int BGP_TYPE_NOTIFICATION = 3;
+
+    /** BGP KEEPALIVE message type. */
+    public static final int BGP_TYPE_KEEPALIVE = 4;
+
+    /** BGP Header Marker field length. */
+    public static final int BGP_HEADER_MARKER_LENGTH = 16;
+
+    /** BGP Header length. */
+    public static final int BGP_HEADER_LENGTH = 19;
+
+    /** BGP message maximum length. */
+    public static final int BGP_MESSAGE_MAX_LENGTH = 4096;
+
+    /** BGP OPEN message minimum length (BGP Header included). */
+    public static final int BGP_OPEN_MIN_LENGTH = 29;
+
+    /** BGP UPDATE message minimum length (BGP Header included). */
+    public static final int BGP_UPDATE_MIN_LENGTH = 23;
+
+    /** BGP NOTIFICATION message minimum length (BGP Header included). */
+    public static final int BGP_NOTIFICATION_MIN_LENGTH = 21;
+
+    /** BGP KEEPALIVE message expected length (BGP Header included). */
+    public static final int BGP_KEEPALIVE_EXPECTED_LENGTH = 19;
+
+    /** BGP KEEPALIVE messages transmitted per Hold interval. */
+    public static final int BGP_KEEPALIVE_PER_HOLD_INTERVAL = 3;
+
+    /** BGP KEEPALIVE messages minimum Holdtime (in seconds). */
+    public static final int BGP_KEEPALIVE_MIN_HOLDTIME = 3;
+
+    /** BGP KEEPALIVE messages minimum transmission interval (in seconds). */
+    public static final int BGP_KEEPALIVE_MIN_INTERVAL = 1;
+
+    /** BGP AS 0 (zero) value. See draft-ietf-idr-as0-06.txt Internet Draft. */
+    public static final long BGP_AS_0 = 0;
+
+    /**
+     * BGP OPEN related constants.
+     */
+    public static final class Open {
+        /**
+         * Default constructor.
+         * <p>
+         * The constructor is private to prevent creating an instance of
+         * this utility class.
+         */
+        private Open() {
+        }
+
+        /**
+         * BGP OPEN: Optional Parameters related constants.
+         */
+        public static final class OptionalParameters {
+        }
+
+        /**
+         * BGP OPEN: Capabilities related constants (RFC 5492).
+         */
+        public static final class Capabilities {
+            /** BGP OPEN Optional Parameter Type: Capabilities. */
+            public static final int TYPE = 2;
+
+            /** BGP OPEN Optional Parameter minimum length. */
+            public static final int MIN_LENGTH = 2;
+
+            /**
+             * BGP OPEN: Multiprotocol Extensions Capabilities (RFC 4760).
+             */
+            public static final class MultiprotocolExtensions {
+                /** BGP OPEN Multiprotocol Extensions code. */
+                public static final int CODE = 1;
+
+                /** BGP OPEN Multiprotocol Extensions length. */
+                public static final int LENGTH = 4;
+
+                /** BGP OPEN Multiprotocol Extensions AFI: IPv4. */
+                public static final int AFI_IPV4 = 1;
+
+                /** BGP OPEN Multiprotocol Extensions AFI: IPv6. */
+                public static final int AFI_IPV6 = 2;
+
+                /** BGP OPEN Multiprotocol Extensions SAFI: unicast. */
+                public static final int SAFI_UNICAST = 1;
+
+                /** BGP OPEN Multiprotocol Extensions SAFI: multicast. */
+                public static final int SAFI_MULTICAST = 2;
+            }
+
+            /**
+             * BGP OPEN: Support for 4-octet AS Number Capability (RFC 6793).
+             */
+            public static final class As4Octet {
+                /** BGP OPEN Support for 4-octet AS Number Capability code. */
+                public static final int CODE = 65;
+
+                /** BGP OPEN 4-octet AS Number Capability length. */
+                public static final int LENGTH = 4;
+            }
+        }
+    }
+
+    /**
+     * BGP UPDATE related constants.
+     */
+    public static final class Update {
+        /**
+         * Default constructor.
+         * <p>
+         * The constructor is private to prevent creating an instance of
+         * this utility class.
+         */
+        private Update() {
+        }
+
+        /**
+         * BGP UPDATE: ORIGIN related constants.
+         */
+        public static final class Origin {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private Origin() {
+            }
+
+            /** BGP UPDATE Attributes Type Code ORIGIN. */
+            public static final int TYPE = 1;
+
+            /** BGP UPDATE Attributes Type Code ORIGIN length. */
+            public static final int LENGTH = 1;
+
+            /** BGP UPDATE ORIGIN: IGP. */
+            public static final int IGP = 0;
+
+            /** BGP UPDATE ORIGIN: EGP. */
+            public static final int EGP = 1;
+
+            /** BGP UPDATE ORIGIN: INCOMPLETE. */
+            public static final int INCOMPLETE = 2;
+
+            /**
+             * Gets the BGP UPDATE origin type as a string.
+             *
+             * @param type the BGP UPDATE origin type
+             * @return the BGP UPDATE origin type as a string
+             */
+            public static String typeToString(int type) {
+                String typeString = "UNKNOWN";
+
+                switch (type) {
+                case IGP:
+                    typeString = "IGP";
+                    break;
+                case EGP:
+                    typeString = "EGP";
+                    break;
+                case INCOMPLETE:
+                    typeString = "INCOMPLETE";
+                    break;
+                default:
+                    break;
+                }
+                return typeString;
+            }
+        }
+
+        /**
+         * BGP UPDATE: AS_PATH related constants.
+         */
+        public static final class AsPath {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private AsPath() {
+            }
+
+            /** BGP UPDATE Attributes Type Code AS_PATH. */
+            public static final int TYPE = 2;
+
+            /** BGP AS length. */
+            public static final int AS_LENGTH = 2;
+
+            /** BGP 4 Octet AS length (RFC 6793). */
+            public static final int AS_4OCTET_LENGTH = 4;
+
+            /** BGP UPDATE AS_PATH Type: AS_SET. */
+            public static final int AS_SET = 1;
+
+            /** 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.
+             *
+             * @param type the BGP AS_PATH type
+             * @return the BGP AS_PATH type as a string
+             */
+            public static String typeToString(int type) {
+                String typeString = "UNKNOWN";
+
+                switch (type) {
+                case AS_SET:
+                    typeString = "AS_SET";
+                    break;
+                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;
+                }
+                return typeString;
+            }
+        }
+
+        /**
+         * BGP UPDATE: NEXT_HOP related constants.
+         */
+        public static final class NextHop {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private NextHop() {
+            }
+
+            /** BGP UPDATE Attributes Type Code NEXT_HOP. */
+            public static final int TYPE = 3;
+
+            /** BGP UPDATE Attributes Type Code NEXT_HOP length. */
+            public static final int LENGTH = 4;
+        }
+
+        /**
+         * BGP UPDATE: MULTI_EXIT_DISC related constants.
+         */
+        public static final class MultiExitDisc {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MultiExitDisc() {
+            }
+
+            /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC. */
+            public static final int TYPE = 4;
+
+            /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC length. */
+            public static final int LENGTH = 4;
+
+            /** BGP UPDATE Attributes lowest MULTI_EXIT_DISC value. */
+            public static final int LOWEST_MULTI_EXIT_DISC = 0;
+        }
+
+        /**
+         * BGP UPDATE: LOCAL_PREF related constants.
+         */
+        public static final class LocalPref {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private LocalPref() {
+            }
+
+            /** BGP UPDATE Attributes Type Code LOCAL_PREF. */
+            public static final int TYPE = 5;
+
+            /** BGP UPDATE Attributes Type Code LOCAL_PREF length. */
+            public static final int LENGTH = 4;
+        }
+
+        /**
+         * BGP UPDATE: ATOMIC_AGGREGATE related constants.
+         */
+        public static final class AtomicAggregate {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private AtomicAggregate() {
+            }
+
+            /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE. */
+            public static final int TYPE = 6;
+
+            /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE length. */
+            public static final int LENGTH = 0;
+        }
+
+        /**
+         * BGP UPDATE: AGGREGATOR related constants.
+         */
+        public static final class Aggregator {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private Aggregator() {
+            }
+
+            /** BGP UPDATE Attributes Type Code AGGREGATOR. */
+            public static final int TYPE = 7;
+
+            /** BGP UPDATE Attributes Type Code AGGREGATOR length. */
+            public static final int LENGTH = 6;
+        }
+
+        /**
+         * BGP UPDATE: MP_REACH_NLRI related constants.
+         */
+        public static final class MpReachNlri {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MpReachNlri() {
+            }
+
+            /** BGP UPDATE Attributes Type Code MP_REACH_NLRI. */
+            public static final int TYPE = 14;
+
+            /** BGP UPDATE Attributes Type Code MP_REACH_NLRI min length. */
+            public static final int MIN_LENGTH = 5;
+        }
+
+        /**
+         * BGP UPDATE: MP_UNREACH_NLRI related constants.
+         */
+        public static final class MpUnreachNlri {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MpUnreachNlri() {
+            }
+
+            /** BGP UPDATE Attributes Type Code MP_UNREACH_NLRI. */
+            public static final int TYPE = 15;
+
+            /** BGP UPDATE Attributes Type Code MP_UNREACH_NLRI min length. */
+            public static final int MIN_LENGTH = 3;
+        }
+    }
+
+    /**
+     * BGP NOTIFICATION related constants.
+     */
+    public static final class Notifications {
+        /**
+         * Default constructor.
+         * <p>
+         * The constructor is private to prevent creating an instance of
+         * this utility class.
+         */
+        private Notifications() {
+        }
+
+        /**
+         * BGP NOTIFICATION: Message Header Error constants.
+         */
+        public static final class MessageHeaderError {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MessageHeaderError() {
+            }
+
+            /** Message Header Error code. */
+            public static final int ERROR_CODE = 1;
+
+            /** Message Header Error subcode: Connection Not Synchronized. */
+            public static final int CONNECTION_NOT_SYNCHRONIZED = 1;
+
+            /** Message Header Error subcode: Bad Message Length. */
+            public static final int BAD_MESSAGE_LENGTH = 2;
+
+            /** Message Header Error subcode: Bad Message Type. */
+            public static final int BAD_MESSAGE_TYPE = 3;
+        }
+
+        /**
+         * BGP NOTIFICATION: OPEN Message Error constants.
+         */
+        public static final class OpenMessageError {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private OpenMessageError() {
+            }
+
+            /** OPEN Message Error code. */
+            public static final int ERROR_CODE = 2;
+
+            /** OPEN Message Error subcode: Unsupported Version Number. */
+            public static final int UNSUPPORTED_VERSION_NUMBER = 1;
+
+            /** OPEN Message Error subcode: Bad PEER AS. */
+            public static final int BAD_PEER_AS = 2;
+
+            /** OPEN Message Error subcode: Unacceptable Hold Time. */
+            public static final int UNACCEPTABLE_HOLD_TIME = 6;
+        }
+
+        /**
+         * BGP NOTIFICATION: UPDATE Message Error constants.
+         */
+        public static final class UpdateMessageError {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private UpdateMessageError() {
+            }
+
+            /** UPDATE Message Error code. */
+            public static final int ERROR_CODE = 3;
+
+            /** UPDATE Message Error subcode: Malformed Attribute List. */
+            public static final int MALFORMED_ATTRIBUTE_LIST = 1;
+
+            /** UPDATE Message Error subcode: Unrecognized Well-known Attribute. */
+            public static final int UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2;
+
+            /** UPDATE Message Error subcode: Missing Well-known Attribute. */
+            public static final int MISSING_WELL_KNOWN_ATTRIBUTE = 3;
+
+           /** UPDATE Message Error subcode: Attribute Flags Error. */
+            public static final int ATTRIBUTE_FLAGS_ERROR = 4;
+
+            /** UPDATE Message Error subcode: Attribute Length Error. */
+            public static final int ATTRIBUTE_LENGTH_ERROR = 5;
+
+            /** UPDATE Message Error subcode: Invalid ORIGIN Attribute. */
+            public static final int INVALID_ORIGIN_ATTRIBUTE = 6;
+
+            /** UPDATE Message Error subcode: Invalid NEXT_HOP Attribute. */
+            public static final int INVALID_NEXT_HOP_ATTRIBUTE = 8;
+
+            /** UPDATE Message Error subcode: Optional Attribute Error. Unused. */
+            public static final int OPTIONAL_ATTRIBUTE_ERROR = 9;
+
+            /** UPDATE Message Error subcode: Invalid Network Field. */
+            public static final int INVALID_NETWORK_FIELD = 10;
+
+            /** UPDATE Message Error subcode: Malformed AS_PATH. */
+            public static final int MALFORMED_AS_PATH = 11;
+        }
+
+        /**
+         * BGP NOTIFICATION: Hold Timer Expired constants.
+         */
+        public static final class HoldTimerExpired {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private HoldTimerExpired() {
+            }
+
+            /** Hold Timer Expired code. */
+            public static final int ERROR_CODE = 4;
+        }
+
+        /** BGP NOTIFICATION message Error subcode: Unspecific. */
+        public static final int ERROR_SUBCODE_UNSPECIFIC = 0;
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpFrameDecoder.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpFrameDecoder.java
new file mode 100644
index 0000000..053d0bf
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpFrameDecoder.java
@@ -0,0 +1,177 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class for handling the decoding of the BGP messages.
+ */
+class BgpFrameDecoder extends FrameDecoder {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpFrameDecoder.class);
+
+    private final BgpSession bgpSession;
+
+    /**
+     * Constructor for a given BGP Session.
+     *
+     * @param bgpSession the BGP session state to use.
+     */
+    BgpFrameDecoder(BgpSession bgpSession) {
+        this.bgpSession = bgpSession;
+    }
+
+    @Override
+    protected Object decode(ChannelHandlerContext ctx,
+                            Channel channel,
+                            ChannelBuffer buf) throws Exception {
+        //
+        // NOTE: If we close the channel during the decoding, we might still
+        // see some incoming messages while the channel closing is completed.
+        //
+        if (bgpSession.isClosed()) {
+            return null;
+        }
+
+        log.trace("BGP Peer: decode(): remoteAddr = {} localAddr = {} " +
+                  "messageSize = {}",
+                  ctx.getChannel().getRemoteAddress(),
+                  ctx.getChannel().getLocalAddress(),
+                  buf.readableBytes());
+
+        // Test for minimum length of the BGP message
+        if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
+            // No enough data received
+            return null;
+        }
+
+        //
+        // Mark the current buffer position in case we haven't received
+        // the whole message.
+        //
+        buf.markReaderIndex();
+
+        //
+        // Read and check the BGP message Marker field: it must be all ones
+        // (See RFC 4271, Section 4.1)
+        //
+        byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
+        buf.readBytes(marker);
+        for (int i = 0; i < marker.length; i++) {
+            if (marker[i] != (byte) 0xff) {
+                log.debug("BGP RX Error: invalid marker {} at position {}",
+                          marker[i], i);
+                //
+                // ERROR: Connection Not Synchronized
+                //
+                // Send NOTIFICATION and close the connection
+                int errorCode = BgpConstants.Notifications.MessageHeaderError.ERROR_CODE;
+                int errorSubcode =
+                    BgpConstants.Notifications.MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED;
+                ChannelBuffer txMessage =
+                    BgpNotification.prepareBgpNotification(errorCode,
+                                                           errorSubcode,
+                                                           null);
+                ctx.getChannel().write(txMessage);
+                bgpSession.closeSession(ctx);
+                return null;
+            }
+        }
+
+        //
+        // Read and check the BGP message Length field
+        //
+        int length = buf.readUnsignedShort();
+        if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
+            (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
+            log.debug("BGP RX Error: invalid Length field {}. " +
+                      "Must be between {} and {}",
+                      length,
+                      BgpConstants.BGP_HEADER_LENGTH,
+                      BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+            //
+            // ERROR: Bad Message Length
+            //
+            // Send NOTIFICATION and close the connection
+            ChannelBuffer txMessage =
+                BgpNotification.prepareBgpNotificationBadMessageLength(length);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeSession(ctx);
+            return null;
+        }
+
+        //
+        // Test whether the rest of the message is received:
+        // So far we have read the Marker (16 octets) and the
+        // Length (2 octets) fields.
+        //
+        int remainingMessageLen =
+            length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
+        if (buf.readableBytes() < remainingMessageLen) {
+            // No enough data received
+            buf.resetReaderIndex();
+            return null;
+        }
+
+        //
+        // Read the BGP message Type field, and process based on that type
+        //
+        int type = buf.readUnsignedByte();
+        remainingMessageLen--;      // Adjust after reading the type
+        ChannelBuffer message = buf.readBytes(remainingMessageLen);
+
+        //
+        // Process the remaining of the message based on the message type
+        //
+        switch (type) {
+        case BgpConstants.BGP_TYPE_OPEN:
+            BgpOpen.processBgpOpen(bgpSession, ctx, message);
+            break;
+        case BgpConstants.BGP_TYPE_UPDATE:
+            BgpUpdate.processBgpUpdate(bgpSession, ctx, message);
+            break;
+        case BgpConstants.BGP_TYPE_NOTIFICATION:
+            BgpNotification.processBgpNotification(bgpSession, ctx, message);
+            break;
+        case BgpConstants.BGP_TYPE_KEEPALIVE:
+            BgpKeepalive.processBgpKeepalive(bgpSession, ctx, message);
+            break;
+        default:
+            //
+            // ERROR: Bad Message Type
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = BgpConstants.Notifications.MessageHeaderError.ERROR_CODE;
+            int errorSubcode = BgpConstants.Notifications.MessageHeaderError.BAD_MESSAGE_TYPE;
+            ChannelBuffer data = ChannelBuffers.buffer(1);
+            data.writeByte(type);
+            ChannelBuffer txMessage =
+                BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                       data);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeSession(ctx);
+            return null;
+        }
+        return null;
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpInfoService.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpInfoService.java
new file mode 100644
index 0000000..d7914e6
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpInfoService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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 java.util.Collection;
+
+/**
+ * Provides information about BGP peering and routes.
+ */
+public interface BgpInfoService {
+
+    /**
+     * Gets the BGP sessions.
+     *
+     * @return the BGP sessions
+     */
+    public Collection<BgpSession> getBgpSessions();
+
+    /**
+     * Gets the selected IPv4 BGP routes among all BGP sessions.
+     *
+     * @return the selected IPv4 BGP routes among all BGP sessions
+     */
+    public Collection<BgpRouteEntry> getBgpRoutes4();
+
+    /**
+     * Gets the selected IPv6 BGP routes among all BGP sessions.
+     *
+     * @return the selected IPv6 BGP routes among all BGP sessions
+     */
+    public Collection<BgpRouteEntry> getBgpRoutes6();
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpKeepalive.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpKeepalive.java
new file mode 100644
index 0000000..186dfeda
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpKeepalive.java
@@ -0,0 +1,94 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for handling BGP KEEPALIVE messages.
+ */
+final class BgpKeepalive {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpKeepalive.class);
+
+    /**
+     * Default constructor.
+     * <p>
+     * The constructor is private to prevent creating an instance of
+     * this utility class.
+     */
+    private BgpKeepalive() {
+    }
+
+    /**
+     * Processes BGP KEEPALIVE message.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    static void processBgpKeepalive(BgpSession bgpSession,
+                                    ChannelHandlerContext ctx,
+                                    ChannelBuffer message) {
+        if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
+            BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
+            log.debug("BGP RX KEEPALIVE Error from {}: " +
+                      "Invalid total message length {}. Expected {}",
+                      bgpSession.remoteInfo().address(),
+                      message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH,
+                      BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH);
+            //
+            // 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;
+        }
+
+        //
+        // Parse the KEEPALIVE message: nothing to do
+        //
+        log.trace("BGP RX KEEPALIVE message from {}",
+                  bgpSession.remoteInfo().address());
+
+        // Start the Session Timeout timer
+        bgpSession.restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Prepares BGP KEEPALIVE message.
+     *
+     * @return the message to transmit (BGP header included)
+     */
+    static ChannelBuffer prepareBgpKeepalive() {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        //
+        // Prepare the KEEPALIVE message payload: nothing to do
+        //
+        return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE,
+                                            message);
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpMessage.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpMessage.java
new file mode 100644
index 0000000..63a5dbd
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpMessage.java
@@ -0,0 +1,86 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for preparing BGP messages.
+ */
+final class BgpMessage {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpMessage.class);
+
+    /**
+     * Default constructor.
+     * <p>
+     * The constructor is private to prevent creating an instance of
+     * this utility class.
+     */
+    private BgpMessage() {
+    }
+
+    /**
+     * Prepares BGP message.
+     *
+     * @param type the BGP message type
+     * @param payload the message payload to transmit (BGP header excluded)
+     * @return the message to transmit (BGP header included)
+     */
+    static ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH +
+                                  payload.readableBytes());
+
+        // Write the marker
+        for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) {
+            message.writeByte(0xff);
+        }
+
+        // Write the rest of the BGP header
+        message.writeShort(BgpConstants.BGP_HEADER_LENGTH +
+                           payload.readableBytes());
+        message.writeByte(type);
+
+        // Write the payload
+        message.writeBytes(payload);
+        return message;
+    }
+
+    /**
+     * An exception indicating a parsing error of the BGP message.
+     */
+    static final class BgpParseException extends Exception {
+        /**
+         * Default constructor.
+         */
+        private BgpParseException() {
+            super();
+        }
+
+        /**
+         * Constructor for a specific exception details message.
+         *
+         * @param message the message with the exception details
+         */
+        BgpParseException(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpNotification.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpNotification.java
new file mode 100644
index 0000000..16ec951
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpNotification.java
@@ -0,0 +1,126 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for handling BGP NOTIFICATION messages.
+ */
+final class BgpNotification {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpNotification.class);
+
+    /**
+     * Default constructor.
+     * <p>
+     * The constructor is private to prevent creating an instance of
+     * this utility class.
+     */
+    private BgpNotification() {
+    }
+
+    /**
+     * Processes BGP NOTIFICATION message.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    static void processBgpNotification(BgpSession bgpSession,
+                                       ChannelHandlerContext ctx,
+                                       ChannelBuffer message) {
+        int minLength =
+            BgpConstants.BGP_NOTIFICATION_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+        if (message.readableBytes() < minLength) {
+            log.debug("BGP RX NOTIFICATION Error from {}: " +
+                      "Message length {} too short. Must be at least {}",
+                      bgpSession.remoteInfo().address(),
+                      message.readableBytes(), minLength);
+            //
+            // ERROR: Bad Message Length
+            //
+            // NOTE: We do NOT send NOTIFICATION in response to a notification
+            return;
+        }
+
+        //
+        // Parse the NOTIFICATION message
+        //
+        int errorCode = message.readUnsignedByte();
+        int errorSubcode = message.readUnsignedByte();
+        int dataLength = message.readableBytes();
+
+        log.debug("BGP RX NOTIFICATION message from {}: Error Code {} " +
+                  "Error Subcode {} Data Length {}",
+                  bgpSession.remoteInfo().address(), errorCode, errorSubcode,
+                  dataLength);
+
+        //
+        // NOTE: If the peer sent a NOTIFICATION, we leave it to the peer to
+        // close the connection.
+        //
+
+        // Start the Session Timeout timer
+        bgpSession.restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Prepares BGP NOTIFICATION message.
+     *
+     * @param errorCode the BGP NOTIFICATION Error Code
+     * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable,
+     * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC
+     * @param data the BGP NOTIFICATION Data if applicable, otherwise null
+     * @return the message to transmit (BGP header included)
+     */
+    static ChannelBuffer prepareBgpNotification(int errorCode,
+                                                int errorSubcode,
+                                                ChannelBuffer data) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        //
+        // Prepare the NOTIFICATION message payload
+        //
+        message.writeByte(errorCode);
+        message.writeByte(errorSubcode);
+        if (data != null) {
+            message.writeBytes(data);
+        }
+        return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION,
+                                            message);
+    }
+
+    /**
+     * Prepares BGP NOTIFICATION message: Bad Message Length.
+     *
+     * @param length the erroneous Length field
+     * @return the message to transmit (BGP header included)
+     */
+    static ChannelBuffer prepareBgpNotificationBadMessageLength(int length) {
+        int errorCode = BgpConstants.Notifications.MessageHeaderError.ERROR_CODE;
+        int errorSubcode = BgpConstants.Notifications.MessageHeaderError.BAD_MESSAGE_LENGTH;
+        ChannelBuffer data = ChannelBuffers.buffer(2);
+        data.writeShort(length);
+
+        return prepareBgpNotification(errorCode, errorSubcode, data);
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpOpen.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpOpen.java
new file mode 100644
index 0000000..3216aec
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpOpen.java
@@ -0,0 +1,497 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.onlab.packet.Ip4Address;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for handling BGP OPEN messages.
+ */
+final class BgpOpen {
+    private static final Logger log = LoggerFactory.getLogger(BgpOpen.class);
+
+    /**
+     * Default constructor.
+     * <p>
+     * The constructor is private to prevent creating an instance of
+     * this utility class.
+     */
+    private BgpOpen() {
+    }
+
+    /**
+     * Processes BGP OPEN message.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    static void processBgpOpen(BgpSession bgpSession,
+                               ChannelHandlerContext ctx,
+                               ChannelBuffer message) {
+        int minLength =
+            BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+        if (message.readableBytes() < minLength) {
+            log.debug("BGP RX OPEN 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;
+        }
+
+        //
+        // Parse the OPEN message
+        //
+        // Remote BGP version
+        int remoteBgpVersion = message.readUnsignedByte();
+        if (remoteBgpVersion != BgpConstants.BGP_VERSION) {
+            log.debug("BGP RX OPEN Error from {}: " +
+                      "Unsupported BGP version {}. Should be {}",
+                      bgpSession.remoteInfo().address(), remoteBgpVersion,
+                      BgpConstants.BGP_VERSION);
+            //
+            // ERROR: Unsupported Version Number
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+            int errorSubcode = BgpConstants.Notifications.OpenMessageError.UNSUPPORTED_VERSION_NUMBER;
+            ChannelBuffer data = ChannelBuffers.buffer(2);
+            data.writeShort(BgpConstants.BGP_VERSION);
+            ChannelBuffer txMessage =
+                BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                       data);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeSession(ctx);
+            return;
+        }
+        bgpSession.remoteInfo().setBgpVersion(remoteBgpVersion);
+
+        // Remote AS number
+        long remoteAs = message.readUnsignedShort();
+        bgpSession.remoteInfo().setAsNumber(remoteAs);
+        //
+        // NOTE: Currently, the local AS number is always set to the remote AS.
+        // This is done, because the peer setup is always iBGP.
+        // In the future, the local AS number should be configured as part
+        // of an explicit BGP peering configuration.
+        //
+        bgpSession.localInfo().setAsNumber(remoteAs);
+
+        // Remote Hold Time
+        long remoteHoldtime = message.readUnsignedShort();
+        if ((remoteHoldtime != 0) &&
+            (remoteHoldtime < BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME)) {
+            log.debug("BGP RX OPEN Error from {}: " +
+                      "Unacceptable Hold Time field {}. " +
+                      "Should be 0 or at least {}",
+                      bgpSession.remoteInfo().address(), remoteHoldtime,
+                      BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME);
+            //
+            // ERROR: Unacceptable Hold Time
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+            int errorSubcode = BgpConstants.Notifications.OpenMessageError.UNACCEPTABLE_HOLD_TIME;
+            ChannelBuffer txMessage =
+                BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                       null);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeSession(ctx);
+            return;
+        }
+        bgpSession.remoteInfo().setHoldtime(remoteHoldtime);
+        //
+        // NOTE: Currently. the local BGP Holdtime is always set to the remote
+        // BGP holdtime.
+        // In the future, the local BGP Holdtime should be configured as part
+        // of an explicit BGP peering configuration.
+        //
+        bgpSession.localInfo().setHoldtime(remoteHoldtime);
+
+        // Remote BGP Identifier
+        Ip4Address remoteBgpId =
+            Ip4Address.valueOf((int) message.readUnsignedInt());
+        bgpSession.remoteInfo().setBgpId(remoteBgpId);
+
+        // Parse the Optional Parameters
+        try {
+            parseOptionalParameters(bgpSession, ctx, message);
+        } catch (BgpMessage.BgpParseException e) {
+            // ERROR: Error parsing optional parameters
+            log.debug("BGP RX OPEN Error from {}: " +
+                      "Exception parsing Optional Parameters: {}",
+                      bgpSession.remoteInfo().address(), e);
+            //
+            // ERROR: Invalid Optional Parameters: Unspecific
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+            int errorSubcode = BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC;
+            ChannelBuffer txMessage =
+                BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                       null);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeSession(ctx);
+            return;
+        }
+
+        //
+        // NOTE: Prepare the BGP OPEN message before the original local AS
+        // is overwritten by the 4-octet AS number
+        //
+        ChannelBuffer txOpenMessage = prepareBgpOpen(bgpSession.localInfo());
+
+        //
+        // Use the 4-octet AS number in lieu of the "My AS" field
+        // See RFC 6793, Section 4.1, second paragraph.
+        //
+        if (bgpSession.remoteInfo().as4OctetCapability()) {
+            long as4Number = bgpSession.remoteInfo().as4Number();
+            bgpSession.remoteInfo().setAsNumber(as4Number);
+            bgpSession.localInfo().setAsNumber(as4Number);
+        }
+
+        //
+        // Verify that the AS number is same for all other BGP Sessions
+        // NOTE: This check applies only for our use-case where all BGP
+        // sessions are iBGP.
+        //
+        for (BgpSession bs : bgpSession.getBgpSessionManager().getBgpSessions()) {
+            if ((bs.remoteInfo().asNumber() != 0) &&
+                (bgpSession.remoteInfo().asNumber() !=
+                 bs.remoteInfo().asNumber())) {
+                log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
+                          "Expected {}",
+                          bgpSession.remoteInfo().address(),
+                          bgpSession.remoteInfo().asNumber(),
+                          bs.remoteInfo().asNumber());
+                //
+                // ERROR: Bad Peer AS
+                //
+                // Send NOTIFICATION and close the connection
+                int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+                int errorSubcode = BgpConstants.Notifications.OpenMessageError.BAD_PEER_AS;
+                ChannelBuffer txMessage =
+                    BgpNotification.prepareBgpNotification(errorCode,
+                                                           errorSubcode, null);
+                ctx.getChannel().write(txMessage);
+                bgpSession.closeSession(ctx);
+                return;
+            }
+        }
+
+        log.debug("BGP RX OPEN message from {}: " +
+                  "BGPv{} AS {} BGP-ID {} Holdtime {}",
+                  bgpSession.remoteInfo().address(),
+                  bgpSession.remoteInfo().bgpVersion(),
+                  bgpSession.remoteInfo().asNumber(),
+                  bgpSession.remoteInfo().bgpId(),
+                  bgpSession.remoteInfo().holdtime());
+
+        // Send my OPEN followed by KEEPALIVE
+        ctx.getChannel().write(txOpenMessage);
+        //
+        ChannelBuffer txMessage = BgpKeepalive.prepareBgpKeepalive();
+        ctx.getChannel().write(txMessage);
+
+        // Start the KEEPALIVE timer
+        bgpSession.restartKeepaliveTimer(ctx);
+
+        // Start the Session Timeout timer
+        bgpSession.restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Prepares BGP OPEN message.
+     *
+     * @param localInfo the BGP Session local information to use
+     * @return the message to transmit (BGP header included)
+     */
+    static ChannelBuffer prepareBgpOpen(BgpSessionInfo localInfo) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        //
+        // Prepare the OPEN message payload
+        //
+        message.writeByte(localInfo.bgpVersion());
+        message.writeShort((int) localInfo.asNumber());
+        message.writeShort((int) localInfo.holdtime());
+        message.writeInt(localInfo.bgpId().toInt());
+
+        // Prepare the optional BGP Capabilities
+        ChannelBuffer capabilitiesMessage =
+            prepareBgpOpenCapabilities(localInfo);
+        message.writeByte(capabilitiesMessage.readableBytes());
+        message.writeBytes(capabilitiesMessage);
+
+        return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN,
+                                            message);
+    }
+
+    /**
+     * Parses BGP OPEN Optional Parameters.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     * @throws BgpMessage.BgpParseException
+     */
+    private static void parseOptionalParameters(BgpSession bgpSession,
+                                                ChannelHandlerContext ctx,
+                                                ChannelBuffer message)
+        throws BgpMessage.BgpParseException {
+
+        //
+        // Get and verify the Optional Parameters Length
+        //
+        int optParamLength = message.readUnsignedByte();
+        if (optParamLength > message.readableBytes()) {
+            // ERROR: Invalid Optional Parameter Length
+            String errorMsg = "Invalid Optional Parameter Length field " +
+                optParamLength + ". Remaining Optional Parameters " +
+                message.readableBytes();
+            throw new BgpMessage.BgpParseException(errorMsg);
+        }
+        if (optParamLength == 0) {
+            return;                     // No Optional Parameters
+        }
+
+        //
+        // Parse the Optional Parameters
+        //
+        int optParamEnd = message.readerIndex() + optParamLength;
+        while (message.readerIndex() < optParamEnd) {
+            int paramType = message.readUnsignedByte();
+            if (message.readerIndex() >= optParamEnd) {
+                // ERROR: Malformed Optional Parameters
+                String errorMsg = "Malformed Optional Parameters";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+            int paramLen = message.readUnsignedByte();
+            if (message.readerIndex() + paramLen > optParamEnd) {
+                // ERROR: Malformed Optional Parameters
+                String errorMsg = "Malformed Optional Parameters";
+                throw new BgpMessage.BgpParseException(errorMsg);
+            }
+
+            //
+            // Extract the Optional Parameter Value based on the Parameter Type
+            //
+            switch (paramType) {
+            case BgpConstants.Open.Capabilities.TYPE:
+                // Optional Parameter Type: Capabilities
+                if (paramLen < BgpConstants.Open.Capabilities.MIN_LENGTH) {
+                    // ERROR: Malformed Capability
+                    String errorMsg = "Malformed Capability Type " + paramType;
+                    throw new BgpMessage.BgpParseException(errorMsg);
+                }
+                int capabEnd = message.readerIndex() + paramLen;
+                int capabCode = message.readUnsignedByte();
+                int capabLen = message.readUnsignedByte();
+                if (message.readerIndex() + capabLen > capabEnd) {
+                    // ERROR: Malformed Capability
+                    String errorMsg = "Malformed Capability Type " + paramType;
+                    throw new BgpMessage.BgpParseException(errorMsg);
+                }
+
+                switch (capabCode) {
+                case BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE:
+                    // Multiprotocol Extensions Capabilities (RFC 4760)
+                    if (capabLen != BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH) {
+                        // ERROR: Multiprotocol Extension Length Error
+                        String errorMsg = "Multiprotocol Extension Length Error";
+                        throw new BgpMessage.BgpParseException(errorMsg);
+                    }
+                    // Decode the AFI (2 octets) and SAFI (1 octet)
+                    int afi = message.readUnsignedShort();
+                    int reserved = message.readUnsignedByte();
+                    int safi = message.readUnsignedByte();
+                    log.debug("BGP RX OPEN Capability: AFI = {} SAFI = {}",
+                              afi, safi);
+                    //
+                    // Setup the AFI/SAFI in the BgpSession
+                    //
+                    // NOTE: For now we just copy the remote AFI/SAFI setting
+                    // to the local configuration.
+                    //
+                    if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4 &&
+                        safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST) {
+                        bgpSession.remoteInfo().setIpv4Unicast();
+                        bgpSession.localInfo().setIpv4Unicast();
+                    } else if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4 &&
+                               safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST) {
+                        bgpSession.remoteInfo().setIpv4Multicast();
+                        bgpSession.localInfo().setIpv4Multicast();
+                    } else if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6 &&
+                               safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST) {
+                        bgpSession.remoteInfo().setIpv6Unicast();
+                        bgpSession.localInfo().setIpv6Unicast();
+                    } else if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6 &&
+                               safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST) {
+                        bgpSession.remoteInfo().setIpv6Multicast();
+                        bgpSession.localInfo().setIpv6Multicast();
+                    } else {
+                        log.debug("BGP RX OPEN Capability: Unknown AFI = {} SAFI = {}",
+                                  afi, safi);
+                    }
+                    break;
+
+                case BgpConstants.Open.Capabilities.As4Octet.CODE:
+                    // Support for 4-octet AS Number Capabilities (RFC 6793)
+                    if (capabLen != BgpConstants.Open.Capabilities.As4Octet.LENGTH) {
+                        // ERROR: 4-octet AS Number Capability Length Error
+                        String errorMsg = "4-octet AS Number Capability Length Error";
+                        throw new BgpMessage.BgpParseException(errorMsg);
+                    }
+                    long as4Number = message.readUnsignedInt();
+
+                    bgpSession.remoteInfo().setAs4OctetCapability();
+                    bgpSession.remoteInfo().setAs4Number(as4Number);
+
+                    //
+                    // Copy remote 4-octet AS Number Capabilities and AS
+                    // Number. This is a temporary setting until local AS
+                    // number configuration is supported.
+                    //
+                    bgpSession.localInfo().setAs4OctetCapability();
+                    bgpSession.localInfo().setAs4Number(as4Number);
+                    log.debug("BGP RX OPEN Capability: AS4 Number = {}",
+                              as4Number);
+                    break;
+
+                default:
+                    // Unknown Capability: ignore it
+                    log.debug("BGP RX OPEN Capability Code = {} Length = {}",
+                              capabCode, capabLen);
+                    message.readBytes(capabLen);
+                    break;
+                }
+
+                break;
+
+            default:
+                // Unknown Parameter Type: ignore it
+                log.debug("BGP RX OPEN Parameter Type = {} Length = {}",
+                          paramType, paramLen);
+                message.readBytes(paramLen);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Prepares the Capabilities for the BGP OPEN message.
+     *
+     * @param localInfo the BGP Session local information to use
+     * @return the buffer with the BGP Capabilities to transmit
+     */
+    private static ChannelBuffer prepareBgpOpenCapabilities(
+                                        BgpSessionInfo localInfo) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        //
+        // Write the Multiprotocol Extensions Capabilities
+        //
+
+        // IPv4 unicast
+        if (localInfo.ipv4Unicast()) {
+            message.writeByte(BgpConstants.Open.Capabilities.TYPE);               // Param type
+            message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+                              BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Param len
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE);    // Capab. code
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Capab. len
+            message.writeShort(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4);
+            message.writeByte(0);               // Reserved field
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST);
+        }
+        // IPv4 multicast
+        if (localInfo.ipv4Multicast()) {
+            message.writeByte(BgpConstants.Open.Capabilities.TYPE);               // Param type
+            message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+                              BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Param len
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE);    // Capab. code
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Capab. len
+            message.writeShort(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4);
+            message.writeByte(0);               // Reserved field
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST);
+        }
+        // IPv6 unicast
+        if (localInfo.ipv6Unicast()) {
+            message.writeByte(BgpConstants.Open.Capabilities.TYPE);               // Param type
+            message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+                              BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Param len
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE);    // Capab. code
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Capab. len
+            message.writeShort(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6);
+            message.writeByte(0);               // Reserved field
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST);
+        }
+        // IPv6 multicast
+        if (localInfo.ipv6Multicast()) {
+            message.writeByte(BgpConstants.Open.Capabilities.TYPE);               // Param type
+            message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+                              BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Param len
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE);    // Capab. code
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH);  // Capab. len
+            message.writeShort(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6);
+            message.writeByte(0);               // Reserved field
+            message.writeByte(
+                    BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST);
+        }
+
+        // 4 octet AS path capability
+        if (localInfo.as4OctetCapability()) {
+            message.writeByte(BgpConstants.Open.Capabilities.TYPE);               // Param type
+            message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+                              BgpConstants.Open.Capabilities.As4Octet.LENGTH);                 // Param len
+            message.writeByte(BgpConstants.Open.Capabilities.As4Octet.CODE);                   // Capab. code
+            message.writeByte(BgpConstants.Open.Capabilities.As4Octet.LENGTH);                 // Capab. len
+            message.writeInt((int) localInfo.as4Number());
+        }
+        return message;
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteEntry.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteEntry.java
new file mode 100644
index 0000000..e7e7f3c
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteEntry.java
@@ -0,0 +1,494 @@
+/*
+ * 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 com.google.common.base.MoreObjects;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routingapi.RouteEntry;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a route in BGP.
+ */
+public class BgpRouteEntry extends RouteEntry {
+    private final BgpSession bgpSession; // The BGP Session the route was
+                                         // received on
+    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;
+
+    /**
+     * Class constructor.
+     *
+     * @param bgpSession the BGP Session the route was received on
+     * @param prefix the prefix of the route
+     * @param nextHop the next hop of the route
+     * @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
+     * @param asPath the AS path
+     * @param localPref the route local preference
+     */
+    public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
+                         IpAddress nextHop, byte origin,
+                         BgpRouteEntry.AsPath asPath, long localPref) {
+        super(prefix, nextHop);
+        this.bgpSession = checkNotNull(bgpSession);
+        this.origin = origin;
+        this.asPath = checkNotNull(asPath);
+        this.localPref = localPref;
+    }
+
+    /**
+     * Gets the BGP Session the route was received on.
+     *
+     * @return the BGP Session the route was received on
+     */
+    public BgpSession getBgpSession() {
+        return bgpSession;
+    }
+
+    /**
+     * Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
+     *
+     * @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
+     */
+    public byte getOrigin() {
+        return origin;
+    }
+
+    /**
+     * Gets the route AS path.
+     *
+     * @return the route AS path
+     */
+    public BgpRouteEntry.AsPath getAsPath() {
+        return asPath;
+    }
+
+    /**
+     * Gets the route local preference.
+     *
+     * @return the route local preference
+     */
+    public long getLocalPref() {
+        return localPref;
+    }
+
+    /**
+     * Gets the route MED (Multi-Exit Discriminator).
+     *
+     * @return the route MED (Multi-Exit Discriminator)
+     */
+    public long getMultiExitDisc() {
+        return multiExitDisc;
+    }
+
+    /**
+     * Sets the route MED (Multi-Exit Discriminator).
+     *
+     * @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
+     */
+    void setMultiExitDisc(long multiExitDisc) {
+        this.multiExitDisc = multiExitDisc;
+    }
+
+    /**
+     * 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 (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() {
+        PathSegment firstPathSegment = null;
+
+        // Find the first Path Segment by ignoring the AS_CONFED_* segments
+        for (PathSegment pathSegment : asPath.getPathSegments()) {
+            if ((pathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) ||
+                (pathSegment.getType() == BgpConstants.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() == BgpConstants.Update.AsPath.AS_SET) {
+            return true;
+        }
+
+        return false;                   // The route is not local
+    }
+
+    /**
+     * Gets the BGP Neighbor AS number the route was received from.
+     * <p>
+     * If the router is originated from the local AS, the return value is
+     * zero (BGP_AS_0).
+     * </p>
+     *
+     * @return the BGP Neighbor AS number the route was received from.
+     */
+    long getNeighborAs() {
+        PathSegment firstPathSegment = null;
+
+        if (isLocalRoute()) {
+            return BgpConstants.BGP_AS_0;
+        }
+
+        // Find the first Path Segment by ignoring the AS_CONFED_* segments
+        for (PathSegment pathSegment : asPath.getPathSegments()) {
+            if ((pathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) ||
+                (pathSegment.getType() == BgpConstants.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()) {
+            // NOTE: Shouldn't happen. Should check during the parsing.
+            return BgpConstants.BGP_AS_0;
+        }
+        return firstPathSegment.getSegmentAsNumbers().get(0);
+    }
+
+    /**
+     * Tests whether the AS Path contains a loop.
+     * <p>
+     * The test is done by comparing whether the AS Path contains the
+     * local AS number.
+     * </p>
+     *
+     * @param localAsNumber the local AS number to compare against
+     * @return true if the AS Path contains a loop, otherwise false
+     */
+    boolean hasAsPathLoop(long localAsNumber) {
+        for (PathSegment pathSegment : asPath.getPathSegments()) {
+            for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+                if (asNumber.equals(localAsNumber)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Compares this BGP route against another BGP route by using the
+     * BGP Decision Process.
+     * <p>
+     * NOTE: The comparison needs to be performed only on routes that have
+     * same IP Prefix.
+     * </p>
+     *
+     * @param other the BGP route to compare against
+     * @return true if this BGP route is better than the other BGP route
+     * or same, otherwise false
+     */
+    boolean isBetterThan(BgpRouteEntry other) {
+        if (this == other) {
+            return true;        // Return true if same route
+        }
+
+        // Compare the LOCAL_PREF values: larger is better
+        if (getLocalPref() != other.getLocalPref()) {
+            return (getLocalPref() > other.getLocalPref());
+        }
+
+        // Compare the AS number in the path: smaller is better
+        if (getAsPath().getAsPathLength() !=
+            other.getAsPath().getAsPathLength()) {
+            return getAsPath().getAsPathLength() <
+                other.getAsPath().getAsPathLength();
+        }
+
+        // Compare the Origin number: lower is better
+        if (getOrigin() != other.getOrigin()) {
+            return (getOrigin() < other.getOrigin());
+        }
+
+        // Compare the MED if the neighbor AS is same: larger is better
+        medLabel: {
+            if (isLocalRoute() || other.isLocalRoute()) {
+                // Compare MEDs for non-local routes only
+                break medLabel;
+            }
+            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
+            if (getMultiExitDisc() != other.getMultiExitDisc()) {
+                return (getMultiExitDisc() > other.getMultiExitDisc());
+            }
+        }
+
+        // Compare the peer BGP ID: lower is better
+        Ip4Address peerBgpId = getBgpSession().remoteInfo().bgpId();
+        Ip4Address otherPeerBgpId = other.getBgpSession().remoteInfo().bgpId();
+        if (!peerBgpId.equals(otherPeerBgpId)) {
+            return (peerBgpId.compareTo(otherPeerBgpId) < 0);
+        }
+
+        // Compare the peer BGP address: lower is better
+        Ip4Address peerAddress = getBgpSession().remoteInfo().ip4Address();
+        Ip4Address otherPeerAddress =
+            other.getBgpSession().remoteInfo().ip4Address();
+        if (!peerAddress.equals(otherPeerAddress)) {
+            return (peerAddress.compareTo(otherPeerAddress) < 0);
+        }
+
+        return true;            // Routes are same. Shouldn't happen?
+    }
+
+    /**
+     * A class to represent AS Path Segment.
+     */
+    public static class PathSegment {
+        // 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: 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) {
+            this.type = type;
+            this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
+        }
+
+        /**
+         * 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(1), AS_SEQUENCE(2),
+         * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4)
+         */
+        public byte getType() {
+            return type;
+        }
+
+        /**
+         * Gets the Path Segment AS Numbers.
+         *
+         * @return the Path Segment AS Numbers
+         */
+        public ArrayList<Long> getSegmentAsNumbers() {
+            return segmentAsNumbers;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (!(other instanceof PathSegment)) {
+                return false;
+            }
+
+            PathSegment otherPathSegment = (PathSegment) other;
+            return Objects.equals(this.type, otherPathSegment.type) &&
+                Objects.equals(this.segmentAsNumbers,
+                               otherPathSegment.segmentAsNumbers);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(type, segmentAsNumbers);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(getClass())
+                .add("type", BgpConstants.Update.AsPath.typeToString(type))
+                .add("segmentAsNumbers", this.segmentAsNumbers)
+                .toString();
+        }
+    }
+
+    /**
+     * A class to represent AS Path.
+     */
+    public static class AsPath {
+        private final ArrayList<PathSegment> pathSegments;
+        private final int asPathLength;         // Precomputed AS Path Length
+
+        /**
+         * Constructor.
+         *
+         * @param pathSegments the Path Segments of the Path
+         */
+         AsPath(ArrayList<PathSegment> pathSegments) {
+             this.pathSegments = checkNotNull(pathSegments);
+
+             //
+             // 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) {
+                 switch (pathSegment.getType()) {
+                 case BgpConstants.Update.AsPath.AS_SET:
+                     pl++;              // AS_SET counts as 1
+                     break;
+                 case BgpConstants.Update.AsPath.AS_SEQUENCE:
+                     // Count each AS number
+                     pl += pathSegment.getSegmentAsNumbers().size();
+                     break;
+                 case BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE:
+                     break;             // Ignore
+                 case BgpConstants.Update.AsPath.AS_CONFED_SET:
+                     break;             // Ignore
+                 default:
+                     // NOTE: What to do if the Path Segment type is unknown?
+                     break;
+                 }
+             }
+             asPathLength = pl;
+         }
+
+        /**
+         * Gets the AS Path Segments.
+         *
+         * @return the AS Path Segments
+         */
+        public ArrayList<PathSegment> getPathSegments() {
+            return pathSegments;
+        }
+
+        /**
+         * Gets the AS Path Length as considered by the BGP Decision Process.
+         *
+         * @return the AS Path Length as considered by the BGP Decision Process
+         */
+        int getAsPathLength() {
+            return asPathLength;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (!(other instanceof AsPath)) {
+                return false;
+            }
+
+            AsPath otherAsPath = (AsPath) other;
+            return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(pathSegments);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(getClass())
+                .add("pathSegments", this.pathSegments)
+                .toString();
+        }
+    }
+
+    /**
+     * Compares whether two objects are equal.
+     * <p>
+     * NOTE: The bgpSession field is excluded from the comparison.
+     * </p>
+     *
+     * @return true if the two objects are equal, otherwise false.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        //
+        // NOTE: Subclasses are considered as change of identity, hence
+        // equals() will return false if the class type doesn't match.
+        //
+        if (other == null || getClass() != other.getClass()) {
+            return false;
+        }
+
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        // NOTE: The bgpSession field is excluded from the comparison
+        BgpRouteEntry otherRoute = (BgpRouteEntry) other;
+        return (this.origin == otherRoute.origin) &&
+            Objects.equals(this.asPath, otherRoute.asPath) &&
+            (this.localPref == otherRoute.localPref) &&
+            (this.multiExitDisc == otherRoute.multiExitDisc);
+    }
+
+    /**
+     * Computes the hash code.
+     * <p>
+     * NOTE: We return the base class hash code to avoid expensive computation
+     * </p>
+     *
+     * @return the object hash code
+     */
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+            .add("prefix", prefix())
+            .add("nextHop", nextHop())
+            .add("bgpId", bgpSession.remoteInfo().bgpId())
+            .add("origin", BgpConstants.Update.Origin.typeToString(origin))
+            .add("asPath", asPath)
+            .add("localPref", localPref)
+            .add("multiExitDisc", multiExitDisc)
+            .toString();
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java
new file mode 100644
index 0000000..e95aaff
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2015 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.onlab.packet.IpPrefix;
+import org.onosproject.routingapi.RouteUpdate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * Class to receive and process the BGP routes from each BGP Session/Peer.
+ */
+class BgpRouteSelector {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpRouteSelector.class);
+
+    private BgpSessionManager bgpSessionManager;
+
+    /**
+     * Constructor.
+     *
+     * @param bgpSessionManager the BGP Session Manager to use
+     */
+    BgpRouteSelector(BgpSessionManager bgpSessionManager) {
+        this.bgpSessionManager = bgpSessionManager;
+    }
+
+    /**
+     * Processes route entry updates: added/updated and deleted route
+     * entries.
+     *
+     * @param bgpSession the BGP session the route entry updates were
+     * received on
+     * @param addedBgpRouteEntries the added/updated route entries to process
+     * @param deletedBgpRouteEntries the deleted route entries to process
+     */
+    synchronized void routeUpdates(BgpSession bgpSession,
+                        Collection<BgpRouteEntry> addedBgpRouteEntries,
+                        Collection<BgpRouteEntry> deletedBgpRouteEntries) {
+        Collection<RouteUpdate> routeUpdates = new LinkedList<>();
+        RouteUpdate routeUpdate;
+
+        if (bgpSessionManager.isShutdown()) {
+            return;         // Ignore any leftover updates if shutdown
+        }
+        // Process the deleted route entries
+        for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
+            routeUpdate = processDeletedRoute(bgpSession, bgpRouteEntry);
+            if (routeUpdate != null) {
+                routeUpdates.add(routeUpdate);
+            }
+        }
+
+        // Process the added/updated route entries
+        for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
+            routeUpdate = processAddedRoute(bgpSession, bgpRouteEntry);
+            if (routeUpdate != null) {
+                routeUpdates.add(routeUpdate);
+            }
+        }
+        bgpSessionManager.getRouteListener().update(routeUpdates);
+    }
+
+    /**
+     * Processes an added/updated route entry.
+     *
+     * @param bgpSession the BGP session the route entry update was received on
+     * @param bgpRouteEntry the added/updated route entry
+     * @return the result route update that should be forwarded to the
+     * Route Listener, or null if no route update should be forwarded
+     */
+    private RouteUpdate processAddedRoute(BgpSession bgpSession,
+                                          BgpRouteEntry bgpRouteEntry) {
+        RouteUpdate routeUpdate;
+        BgpRouteEntry bestBgpRouteEntry =
+            bgpSessionManager.findBgpRoute(bgpRouteEntry.prefix());
+
+        //
+        // Install the new route entry if it is better than the
+        // current best route.
+        //
+        if ((bestBgpRouteEntry == null) ||
+            bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
+            bgpSessionManager.addBgpRoute(bgpRouteEntry);
+            routeUpdate =
+                new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
+            return routeUpdate;
+        }
+
+        //
+        // If the route entry arrived on the same BGP Session as
+        // the current best route, then elect the next best route
+        // and install it.
+        //
+        if (bestBgpRouteEntry.getBgpSession() !=
+            bgpRouteEntry.getBgpSession()) {
+            return null;            // Nothing to do
+        }
+
+        // Find the next best route
+        bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+        if (bestBgpRouteEntry == null) {
+            //
+            // TODO: Shouldn't happen. Install the new route as a
+            // pre-caution.
+            //
+            log.debug("BGP next best route for prefix {} is missing. " +
+                      "Adding the route that is currently processed.",
+                      bgpRouteEntry.prefix());
+            bestBgpRouteEntry = bgpRouteEntry;
+        }
+
+        // Install the next best route
+        bgpSessionManager.addBgpRoute(bestBgpRouteEntry);
+        routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                      bestBgpRouteEntry);
+        return routeUpdate;
+    }
+
+    /**
+     * Processes a deleted route entry.
+     *
+     * @param bgpSession the BGP session the route entry update was received on
+     * @param bgpRouteEntry the deleted route entry
+     * @return the result route update that should be forwarded to the
+     * Route Listener, or null if no route update should be forwarded
+     */
+    private RouteUpdate processDeletedRoute(BgpSession bgpSession,
+                                            BgpRouteEntry bgpRouteEntry) {
+        RouteUpdate routeUpdate;
+        BgpRouteEntry bestBgpRouteEntry =
+            bgpSessionManager.findBgpRoute(bgpRouteEntry.prefix());
+
+        //
+        // Remove the route entry only if it was the best one.
+        // Install the the next best route if it exists.
+        //
+        // NOTE: We intentionally use "==" instead of method equals(),
+        // because we need to check whether this is same object.
+        //
+        if (bgpRouteEntry != bestBgpRouteEntry) {
+            return null;            // Nothing to do
+        }
+
+        //
+        // Find the next best route
+        //
+        bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+        if (bestBgpRouteEntry != null) {
+            // Install the next best route
+            bgpSessionManager.addBgpRoute(bestBgpRouteEntry);
+            routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                          bestBgpRouteEntry);
+            return routeUpdate;
+        }
+
+        //
+        // No route found. Remove the route entry
+        //
+        bgpSessionManager.removeBgpRoute(bgpRouteEntry.prefix());
+        routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE, bgpRouteEntry);
+        return routeUpdate;
+    }
+
+    /**
+     * Finds the best route entry among all BGP Sessions.
+     *
+     * @param prefix the prefix of the route
+     * @return the best route if found, otherwise null
+     */
+    private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
+        BgpRouteEntry bestRoute = null;
+
+        // Iterate across all BGP Sessions and select the best route
+        for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+            BgpRouteEntry route = bgpSession.findBgpRoute(prefix);
+            if (route == null) {
+                continue;
+            }
+            if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
+                bestRoute = route;
+            }
+        }
+        return bestRoute;
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java
new file mode 100644
index 0000000..e0a2617
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java
@@ -0,0 +1,473 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.jboss.netty.util.HashedWheelTimer;
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.Timer;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class for handling the BGP peer sessions.
+ * There is one instance per each BGP peer session.
+ */
+public class BgpSession extends SimpleChannelHandler {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpSession.class);
+
+    private final BgpSessionManager bgpSessionManager;
+
+    // Local flag to indicate the session is closed.
+    // It is used to avoid the Netty's asynchronous closing of a channel.
+    private boolean isClosed = false;
+
+    // BGP session info: local and remote
+    private final BgpSessionInfo localInfo;     // BGP session local info
+    private final BgpSessionInfo remoteInfo;    // BGP session remote info
+
+    // Timers state
+    private Timer timer = new HashedWheelTimer();
+    private volatile Timeout keepaliveTimeout;  // Periodic KEEPALIVE
+    private volatile Timeout sessionTimeout;    // Session timeout
+
+    // BGP RIB-IN routing entries from this peer
+    private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRibIn4 =
+        new ConcurrentHashMap<>();
+    private ConcurrentMap<Ip6Prefix, BgpRouteEntry> bgpRibIn6 =
+        new ConcurrentHashMap<>();
+
+    /**
+     * Constructor for a given BGP Session Manager.
+     *
+     * @param bgpSessionManager the BGP Session Manager to use
+     */
+    BgpSession(BgpSessionManager bgpSessionManager) {
+        this.bgpSessionManager = bgpSessionManager;
+        this.localInfo = new BgpSessionInfo();
+        this.remoteInfo = new BgpSessionInfo();
+
+        // NOTE: We support only BGP4
+        this.localInfo.setBgpVersion(BgpConstants.BGP_VERSION);
+    }
+
+    /**
+     * Gets the BGP Session Manager.
+     *
+     * @return the BGP Session Manager
+     */
+    BgpSessionManager getBgpSessionManager() {
+        return bgpSessionManager;
+    }
+
+    /**
+     * Gets the BGP Session local information.
+     *
+     * @return the BGP Session local information.
+     */
+    public BgpSessionInfo localInfo() {
+        return localInfo;
+    }
+
+    /**
+     * Gets the BGP Session remote information.
+     *
+     * @return the BGP Session remote information.
+     */
+    public BgpSessionInfo remoteInfo() {
+        return remoteInfo;
+    }
+
+    /**
+     * Gets the BGP Multiprotocol Extensions for the session.
+     *
+     * @return true if the BGP Multiprotocol Extensions are enabled for the
+     * session, otherwise false
+     */
+    public boolean mpExtensions() {
+        return remoteInfo.mpExtensions() && localInfo.mpExtensions();
+    }
+
+    /**
+     * Gets the BGP session 4 octet AS path capability.
+     *
+     * @return true when the BGP session is 4 octet AS path capable
+     */
+    public boolean isAs4OctetCapable() {
+        return remoteInfo.as4OctetCapability() &&
+            localInfo.as4OctetCapability();
+    }
+
+    /**
+     * Gets the IPv4 BGP RIB-IN routing entries.
+     *
+     * @return the IPv4 BGP RIB-IN routing entries
+     */
+    public Collection<BgpRouteEntry> getBgpRibIn4() {
+        return bgpRibIn4.values();
+    }
+
+    /**
+     * Gets the IPv6 BGP RIB-IN routing entries.
+     *
+     * @return the IPv6 BGP RIB-IN routing entries
+     */
+    public Collection<BgpRouteEntry> getBgpRibIn6() {
+        return bgpRibIn6.values();
+    }
+
+    /**
+     * Finds an IPv4 BGP routing entry for a prefix in the IPv4 BGP RIB-IN.
+     *
+     * @param prefix the IPv4 prefix of the route to search for
+     * @return the IPv4 BGP routing entry if found, otherwise null
+     */
+    public BgpRouteEntry findBgpRoute(Ip4Prefix prefix) {
+        return bgpRibIn4.get(prefix);
+    }
+
+    /**
+     * Finds an IPv6 BGP routing entry for a prefix in the IPv6 BGP RIB-IN.
+     *
+     * @param prefix the IPv6 prefix of the route to search for
+     * @return the IPv6 BGP routing entry if found, otherwise null
+     */
+    public BgpRouteEntry findBgpRoute(Ip6Prefix prefix) {
+        return bgpRibIn6.get(prefix);
+    }
+
+    /**
+     * Finds a BGP routing entry for a prefix in the BGP RIB-IN. The prefix
+     * can be either IPv4 or IPv6.
+     *
+     * @param prefix the IP prefix of the route to search for
+     * @return the BGP routing entry if found, otherwise null
+     */
+    public BgpRouteEntry findBgpRoute(IpPrefix prefix) {
+        if (prefix.version() == Ip4Address.VERSION) {
+            // IPv4 prefix
+            Ip4Prefix ip4Prefix = prefix.getIp4Prefix();
+            return bgpRibIn4.get(ip4Prefix);
+        }
+
+        // IPv6 prefix
+        Ip6Prefix ip6Prefix = prefix.getIp6Prefix();
+        return bgpRibIn6.get(ip6Prefix);
+    }
+
+    /**
+     * Adds a BGP route. The route can be either IPv4 or IPv6.
+     *
+     * @param bgpRouteEntry the BGP route entry to use
+     */
+    void addBgpRoute(BgpRouteEntry bgpRouteEntry) {
+        if (bgpRouteEntry.version() == Ip4Address.VERSION) {
+            // IPv4 route
+            Ip4Prefix ip4Prefix = bgpRouteEntry.prefix().getIp4Prefix();
+            bgpRibIn4.put(ip4Prefix, bgpRouteEntry);
+        } else {
+            // IPv6 route
+            Ip6Prefix ip6Prefix = bgpRouteEntry.prefix().getIp6Prefix();
+            bgpRibIn6.put(ip6Prefix, bgpRouteEntry);
+        }
+    }
+
+    /**
+     * Removes an IPv4 BGP route for a prefix.
+     *
+     * @param prefix the prefix to use
+     * @return true if the route was found and removed, otherwise false
+     */
+    boolean removeBgpRoute(Ip4Prefix prefix) {
+        return (bgpRibIn4.remove(prefix) != null);
+    }
+
+    /**
+     * Removes an IPv6 BGP route for a prefix.
+     *
+     * @param prefix the prefix to use
+     * @return true if the route was found and removed, otherwise false
+     */
+    boolean removeBgpRoute(Ip6Prefix prefix) {
+        return (bgpRibIn6.remove(prefix) != null);
+    }
+
+    /**
+     * Removes a BGP route for a prefix. The prefix can be either IPv4 or IPv6.
+     *
+     * @param prefix the prefix to use
+     * @return true if the route was found and removed, otherwise false
+     */
+    boolean removeBgpRoute(IpPrefix prefix) {
+        if (prefix.version() == Ip4Address.VERSION) {
+            return (bgpRibIn4.remove(prefix.getIp4Prefix()) != null);   // IPv4
+        }
+        return (bgpRibIn6.remove(prefix.getIp6Prefix()) != null);       // IPv6
+    }
+
+    /**
+     * Tests whether the session is closed.
+     * <p>
+     * NOTE: We use this method to avoid the Netty's asynchronous closing
+     * of a channel.
+     * </p>
+     * @return true if the session is closed
+     */
+    boolean isClosed() {
+        return isClosed;
+    }
+
+    /**
+     * Closes the session.
+     *
+     * @param ctx the Channel Handler Context
+     */
+    void closeSession(ChannelHandlerContext ctx) {
+        timer.stop();
+        closeChannel(ctx);
+    }
+
+    /**
+     * Closes the Netty channel.
+     *
+     * @param ctx the Channel Handler Context
+     */
+    void closeChannel(ChannelHandlerContext ctx) {
+        isClosed = true;
+        ctx.getChannel().close();
+    }
+
+    @Override
+    public void channelOpen(ChannelHandlerContext ctx,
+                            ChannelStateEvent channelEvent) {
+        bgpSessionManager.addSessionChannel(channelEvent.getChannel());
+    }
+
+    @Override
+    public void channelClosed(ChannelHandlerContext ctx,
+                              ChannelStateEvent channelEvent) {
+        bgpSessionManager.removeSessionChannel(channelEvent.getChannel());
+    }
+
+    @Override
+    public void channelConnected(ChannelHandlerContext ctx,
+                                 ChannelStateEvent channelEvent) {
+        localInfo.setAddress(ctx.getChannel().getLocalAddress());
+        remoteInfo.setAddress(ctx.getChannel().getRemoteAddress());
+
+        // Assign the local and remote IPv4 addresses
+        InetAddress inetAddr;
+        if (localInfo.address() instanceof InetSocketAddress) {
+            inetAddr = ((InetSocketAddress) localInfo.address()).getAddress();
+            localInfo.setIp4Address(Ip4Address.valueOf(inetAddr.getAddress()));
+        }
+        if (remoteInfo.address() instanceof InetSocketAddress) {
+            inetAddr = ((InetSocketAddress) remoteInfo.address()).getAddress();
+            remoteInfo.setIp4Address(Ip4Address.valueOf(inetAddr.getAddress()));
+        }
+
+        log.debug("BGP Session Connected from {} on {}",
+                  remoteInfo.address(), localInfo.address());
+        if (!bgpSessionManager.peerConnected(this)) {
+            log.debug("Cannot setup BGP Session Connection from {}. Closing...",
+                      remoteInfo.address());
+            ctx.getChannel().close();
+        }
+
+        //
+        // Assign the local BGP ID
+        // NOTE: This should be configuration-based
+        //
+        localInfo.setBgpId(bgpSessionManager.getMyBgpId());
+    }
+
+    @Override
+    public void channelDisconnected(ChannelHandlerContext ctx,
+                                    ChannelStateEvent channelEvent) {
+        log.debug("BGP Session Disconnected from {} on {}",
+                  ctx.getChannel().getRemoteAddress(),
+                  ctx.getChannel().getLocalAddress());
+        processChannelDisconnected();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
+        log.debug("BGP Session Exception Caught from {} on {}: {}",
+                  ctx.getChannel().getRemoteAddress(),
+                  ctx.getChannel().getLocalAddress(),
+                  e);
+        processChannelDisconnected();
+    }
+
+    /**
+     * Processes the channel being disconnected.
+     */
+    private void processChannelDisconnected() {
+        //
+        // Withdraw the routes advertised by this BGP peer
+        //
+        // NOTE: We must initialize the RIB-IN before propagating the withdraws
+        // for further processing. Otherwise, the BGP Decision Process
+        // will use those routes again.
+        //
+        Collection<BgpRouteEntry> deletedRoutes4 = bgpRibIn4.values();
+        Collection<BgpRouteEntry> deletedRoutes6 = bgpRibIn6.values();
+        bgpRibIn4 = new ConcurrentHashMap<>();
+        bgpRibIn6 = new ConcurrentHashMap<>();
+
+        // Push the updates to the BGP Merged RIB
+        BgpRouteSelector bgpRouteSelector =
+            bgpSessionManager.getBgpRouteSelector();
+        Collection<BgpRouteEntry> addedRoutes = Collections.emptyList();
+        bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes4);
+        bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes6);
+
+        bgpSessionManager.peerDisconnected(this);
+    }
+
+    /**
+     * Restarts the BGP KeepaliveTimer.
+     *
+     * @param ctx the Channel Handler Context to use
+     */
+    void restartKeepaliveTimer(ChannelHandlerContext ctx) {
+        long localKeepaliveInterval = 0;
+
+        //
+        // Compute the local Keepalive interval
+        //
+        if (localInfo.holdtime() != 0) {
+            localKeepaliveInterval = Math.max(localInfo.holdtime() /
+                         BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL,
+                         BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL);
+        }
+
+        // Restart the Keepalive timer
+        if (localKeepaliveInterval == 0) {
+            return;                 // Nothing to do
+        }
+        keepaliveTimeout = timer.newTimeout(new TransmitKeepaliveTask(ctx),
+                                            localKeepaliveInterval,
+                                            TimeUnit.SECONDS);
+    }
+
+    /**
+     * Task class for transmitting KEEPALIVE messages.
+     */
+    private final class TransmitKeepaliveTask implements TimerTask {
+        private final ChannelHandlerContext ctx;
+
+        /**
+         * Constructor for given Channel Handler Context.
+         *
+         * @param ctx the Channel Handler Context to use
+         */
+        TransmitKeepaliveTask(ChannelHandlerContext ctx) {
+            this.ctx = ctx;
+        }
+
+        @Override
+        public void run(Timeout timeout) throws Exception {
+            if (timeout.isCancelled()) {
+                return;
+            }
+            if (!ctx.getChannel().isOpen()) {
+                return;
+            }
+
+            // Transmit the KEEPALIVE
+            ChannelBuffer txMessage = BgpKeepalive.prepareBgpKeepalive();
+            ctx.getChannel().write(txMessage);
+
+            // Restart the KEEPALIVE timer
+            restartKeepaliveTimer(ctx);
+        }
+    }
+
+    /**
+     * Restarts the BGP Session Timeout Timer.
+     *
+     * @param ctx the Channel Handler Context to use
+     */
+    void restartSessionTimeoutTimer(ChannelHandlerContext ctx) {
+        if (remoteInfo.holdtime() == 0) {
+            return;                 // Nothing to do
+        }
+        if (sessionTimeout != null) {
+            sessionTimeout.cancel();
+        }
+        sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx),
+                                          remoteInfo.holdtime(),
+                                          TimeUnit.SECONDS);
+    }
+
+    /**
+     * Task class for BGP Session timeout.
+     */
+    private final class SessionTimeoutTask implements TimerTask {
+        private final ChannelHandlerContext ctx;
+
+        /**
+         * Constructor for given Channel Handler Context.
+         *
+         * @param ctx the Channel Handler Context to use
+         */
+        SessionTimeoutTask(ChannelHandlerContext ctx) {
+            this.ctx = ctx;
+        }
+
+        @Override
+        public void run(Timeout timeout) throws Exception {
+            if (timeout.isCancelled()) {
+                return;
+            }
+            if (!ctx.getChannel().isOpen()) {
+                return;
+            }
+
+            log.debug("BGP Session Timeout: peer {}", remoteInfo.address());
+            //
+            // ERROR: Invalid Optional Parameter Length field: Unspecific
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = BgpConstants.Notifications.HoldTimerExpired.ERROR_CODE;
+            int errorSubcode = BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC;
+            ChannelBuffer txMessage =
+                BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                       null);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+        }
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionInfo.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionInfo.java
new file mode 100644
index 0000000..a127455
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionInfo.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2015 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.onlab.packet.Ip4Address;
+
+import java.net.SocketAddress;
+
+/**
+ * Class for keeping information about a BGP session.
+ *
+ * There are two instances per each BGP peer session: one to keep the local
+ * information about the BGP session, and another to keep information about
+ * the remote BGP peer.
+ */
+public class BgpSessionInfo {
+    private SocketAddress address;              // IP addr/port
+    private Ip4Address ip4Address;              // IPv4 address
+    private int bgpVersion;                     // 1 octet
+    private long asNumber;                      // AS number: 2 octets
+    private long as4Number;                     // AS4 number: 4 octets
+    private long holdtime;                      // 2 octets
+    private Ip4Address bgpId;                   // 4 octets -> IPv4 address
+    private boolean mpExtensions;               // Multiprotocol Extensions
+                                                // enabled: RFC 4760
+    private boolean ipv4Unicast;                // IPv4/UNICAST AFI/SAFI
+    private boolean ipv4Multicast;              // IPv4/MULTICAST AFI/SAFI
+    private boolean ipv6Unicast;                // IPv6/UNICAST AFI/SAFI
+    private boolean ipv6Multicast;              // IPv6/MULTICAST AFI/SAFI
+    private boolean as4OctetCapability;         // AS 4 octet path capability
+
+    /**
+     * Gets the BGP session address: local or remote.
+     *
+     * @return the BGP session address
+     */
+    public SocketAddress address() {
+        return this.address;
+    }
+
+    /**
+     * Sets the BGP session address: local or remote.
+     *
+     * @param address the BGP session address to set
+     */
+    public void setAddress(SocketAddress address) {
+        this.address = address;
+    }
+
+    /**
+     * Gets the BGP session IPv4 address: local or remote.
+     *
+     * @return the BGP session IPv4 address
+     */
+    public Ip4Address ip4Address() {
+        return this.ip4Address;
+    }
+
+    /**
+     * Sets the BGP session IPv4 address: local or remote.
+     *
+     * @param ip4Address the BGP session IPv4 address to set
+     */
+    public void setIp4Address(Ip4Address ip4Address) {
+        this.ip4Address = ip4Address;
+    }
+
+    /**
+     * Gets the BGP session BGP version: local or remote.
+     *
+     * @return the BGP session BGP version
+     */
+    public int bgpVersion() {
+        return this.bgpVersion;
+    }
+
+    /**
+     * Sets the BGP session BGP version: local or remote.
+     *
+     * @param bgpVersion the BGP session BGP version to set
+     */
+    public void setBgpVersion(int bgpVersion) {
+        this.bgpVersion = bgpVersion;
+    }
+
+    /**
+     * Gets the BGP session AS number: local or remote.
+     *
+     * @return the BGP session AS number
+     */
+    public long asNumber() {
+        return this.asNumber;
+    }
+
+    /**
+     * Sets the BGP session AS number: local or remote.
+     *
+     * @param asNumber the BGP session AS number to set
+     */
+    public void setAsNumber(long asNumber) {
+        this.asNumber = asNumber;
+    }
+
+    /**
+     * Gets the BGP session AS4 number: local or remote.
+     *
+     * @return the BGP session AS4 number
+     */
+    public long as4Number() {
+        return this.as4Number;
+    }
+
+    /**
+     * Sets the BGP session AS4 number: local or remote.
+     *
+     * @param as4Number the BGP session AS4 number to set
+     */
+    public void setAs4Number(long as4Number) {
+        this.as4Number = as4Number;
+    }
+
+    /**
+     * Gets the BGP session holdtime: local or remote.
+     *
+     * @return the BGP session holdtime
+     */
+    public long holdtime() {
+        return this.holdtime;
+    }
+
+    /**
+     * Sets the BGP session holdtime: local or remote.
+     *
+     * @param holdtime the BGP session holdtime to set
+     */
+    public void setHoldtime(long holdtime) {
+        this.holdtime = holdtime;
+    }
+
+    /**
+     * Gets the BGP session BGP Identifier as an IPv4 address: local or remote.
+     *
+     * @return the BGP session BGP Identifier as an IPv4 address
+     */
+    public Ip4Address bgpId() {
+        return this.bgpId;
+    }
+
+    /**
+     * Sets the BGP session BGP Identifier as an IPv4 address: local or remote.
+     *
+     * @param bgpId the BGP session BGP Identifier to set
+     */
+    public void setBgpId(Ip4Address bgpId) {
+        this.bgpId = bgpId;
+    }
+
+    /**
+     * Gets the BGP Multiprotocol Extensions: local or remote.
+     *
+     * @return true if the BGP Multiprotocol Extensions are enabled, otherwise
+     * false
+     */
+    public boolean mpExtensions() {
+        return this.mpExtensions;
+    }
+
+    /**
+     * Gets the BGP session AFI/SAFI configuration for IPv4 unicast: local or
+     * remote.
+     *
+     * @return the BGP session AFI/SAFI configuration for IPv4 unicast
+     */
+    public boolean ipv4Unicast() {
+        return ipv4Unicast;
+    }
+
+    /**
+     * Sets the BGP session AFI/SAFI configuration for IPv4 unicast: local or
+     * remote.
+     */
+    public void setIpv4Unicast() {
+        this.mpExtensions = true;
+        this.ipv4Unicast = true;
+    }
+
+    /**
+     * Gets the BGP session AFI/SAFI configuration for IPv4 multicast: local or
+     * remote.
+     *
+     * @return the BGP session AFI/SAFI configuration for IPv4 multicast
+     */
+    public boolean ipv4Multicast() {
+        return ipv4Multicast;
+    }
+
+    /**
+     * Sets the BGP session AFI/SAFI configuration for IPv4 multicast: local or
+     * remote.
+     */
+    public void setIpv4Multicast() {
+        this.mpExtensions = true;
+        this.ipv4Multicast = true;
+    }
+
+    /**
+     * Gets the BGP session AFI/SAFI configuration for IPv6 unicast: local or
+     * remote.
+     *
+     * @return the BGP session AFI/SAFI configuration for IPv6 unicast
+     */
+    public boolean ipv6Unicast() {
+        return ipv6Unicast;
+    }
+
+    /**
+     * Sets the BGP session AFI/SAFI configuration for IPv6 unicast: local or
+     * remote.
+     */
+    void setIpv6Unicast() {
+        this.mpExtensions = true;
+        this.ipv6Unicast = true;
+    }
+
+    /**
+     * Gets the BGP session AFI/SAFI configuration for IPv6 multicast: local or
+     * remote.
+     *
+     * @return the BGP session AFI/SAFI configuration for IPv6 multicast
+     */
+    public boolean ipv6Multicast() {
+        return ipv6Multicast;
+    }
+
+    /**
+     * Sets the BGP session AFI/SAFI configuration for IPv6 multicast: local or
+     * remote.
+     */
+    public void setIpv6Multicast() {
+        this.mpExtensions = true;
+        this.ipv6Multicast = true;
+    }
+
+    /**
+     * Gets the BGP session 4 octet AS path capability: local or remote.
+     *
+     * @return true when the BGP session has 4 octet AS path capability
+     */
+    public boolean as4OctetCapability() {
+        return this.as4OctetCapability;
+    }
+
+    /**
+     * Sets the BGP session 4 octet AS path capability.
+     */
+    public void setAs4OctetCapability() {
+        this.as4OctetCapability = true;
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java
new file mode 100644
index 0000000..cfd0081
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java
@@ -0,0 +1,296 @@
+/*
+ * 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.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelException;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routingapi.BgpService;
+import org.onosproject.routingapi.RouteListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newCachedThreadPool;
+import static org.onlab.util.Tools.namedThreads;
+
+/**
+ * BGP Session Manager class.
+ */
+@Component(immediate = true)
+@Service
+public class BgpSessionManager implements BgpInfoService, BgpService {
+    private static final Logger log =
+            LoggerFactory.getLogger(BgpSessionManager.class);
+
+    boolean isShutdown = true;
+    private Channel serverChannel;     // Listener for incoming BGP connections
+    private ServerBootstrap serverBootstrap;
+    private ChannelGroup allChannels = new DefaultChannelGroup();
+    private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
+            new ConcurrentHashMap<>();
+    private Ip4Address myBgpId;        // Same BGP ID for all peers
+
+    private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector(this);
+    private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRoutes4 =
+            new ConcurrentHashMap<>();
+    private ConcurrentMap<Ip6Prefix, BgpRouteEntry> bgpRoutes6 =
+            new ConcurrentHashMap<>();
+
+    private RouteListener routeListener;
+
+    /**
+     * Checks whether the BGP Session Manager is shutdown.
+     *
+     * @return true if the BGP Session Manager is shutdown, otherwise false
+     */
+    boolean isShutdown() {
+        return this.isShutdown;
+    }
+
+    /**
+     * Gets the route listener.
+     *
+     * @return the route listener to use
+     */
+    RouteListener getRouteListener() {
+        return routeListener;
+    }
+
+    /**
+     * Gets the BGP sessions.
+     *
+     * @return the BGP sessions
+     */
+    public Collection<BgpSession> getBgpSessions() {
+        return bgpSessions.values();
+    }
+
+    /**
+     * Gets the selected IPv4 BGP routes among all BGP sessions.
+     *
+     * @return the selected IPv4 BGP routes among all BGP sessions
+     */
+    public Collection<BgpRouteEntry> getBgpRoutes4() {
+        return bgpRoutes4.values();
+    }
+
+    /**
+     * Gets the selected IPv6 BGP routes among all BGP sessions.
+     *
+     * @return the selected IPv6 BGP routes among all BGP sessions
+     */
+    public Collection<BgpRouteEntry> getBgpRoutes6() {
+        return bgpRoutes6.values();
+    }
+
+    /**
+     * Finds a BGP route for a prefix. The prefix can be either IPv4 or IPv6.
+     *
+     * @param prefix the prefix to use
+     * @return the BGP route if found, otherwise null
+     */
+    BgpRouteEntry findBgpRoute(IpPrefix prefix) {
+        if (prefix.version() == Ip4Address.VERSION) {
+            return bgpRoutes4.get(prefix.getIp4Prefix());               // IPv4
+        }
+        return bgpRoutes6.get(prefix.getIp6Prefix());                   // IPv6
+    }
+
+    /**
+     * Adds a BGP route. The route can be either IPv4 or IPv6.
+     *
+     * @param bgpRouteEntry the BGP route entry to use
+     */
+    void addBgpRoute(BgpRouteEntry bgpRouteEntry) {
+        if (bgpRouteEntry.version() == Ip4Address.VERSION) {
+            bgpRoutes4.put(bgpRouteEntry.prefix().getIp4Prefix(),       // IPv4
+                           bgpRouteEntry);
+        } else {
+            bgpRoutes6.put(bgpRouteEntry.prefix().getIp6Prefix(),       // IPv6
+                           bgpRouteEntry);
+        }
+    }
+
+    /**
+     * Removes a BGP route for a prefix. The prefix can be either IPv4 or IPv6.
+     *
+     * @param prefix the prefix to use
+     * @return true if the route was found and removed, otherwise false
+     */
+    boolean removeBgpRoute(IpPrefix prefix) {
+        if (prefix.version() == Ip4Address.VERSION) {
+            return (bgpRoutes4.remove(prefix.getIp4Prefix()) != null);  // IPv4
+        }
+        return (bgpRoutes6.remove(prefix.getIp6Prefix()) != null);      // IPv6
+    }
+
+    /**
+     * Adds the channel for a BGP session.
+     *
+     * @param channel the channel to add
+     */
+    void addSessionChannel(Channel channel) {
+        allChannels.add(channel);
+    }
+
+    /**
+     * Removes the channel for a BGP session.
+     *
+     * @param channel the channel to remove
+     */
+    void removeSessionChannel(Channel channel) {
+        allChannels.remove(channel);
+    }
+
+    /**
+     * Processes the connection from a BGP peer.
+     *
+     * @param bgpSession the BGP session for the peer
+     * @return true if the connection can be established, otherwise false
+     */
+    boolean peerConnected(BgpSession bgpSession) {
+
+        // Test whether there is already a session from the same remote
+        if (bgpSessions.get(bgpSession.remoteInfo().address()) != null) {
+            return false;               // Duplicate BGP session
+        }
+        bgpSessions.put(bgpSession.remoteInfo().address(), bgpSession);
+
+        //
+        // If the first connection, set my BGP ID to the local address
+        // of the socket.
+        //
+        if (bgpSession.localInfo().address() instanceof InetSocketAddress) {
+            InetAddress inetAddr =
+                ((InetSocketAddress) bgpSession.localInfo().address()).getAddress();
+            Ip4Address ip4Address = Ip4Address.valueOf(inetAddr.getAddress());
+            updateMyBgpId(ip4Address);
+        }
+        return true;
+    }
+
+    /**
+     * Processes the disconnection from a BGP peer.
+     *
+     * @param bgpSession the BGP session for the peer
+     */
+    void peerDisconnected(BgpSession bgpSession) {
+        bgpSessions.remove(bgpSession.remoteInfo().address());
+    }
+
+    /**
+     * Conditionally updates the local BGP ID if it wasn't set already.
+     * <p/>
+     * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
+     *
+     * @param ip4Address the IPv4 address to use as BGP ID
+     */
+    private synchronized void updateMyBgpId(Ip4Address ip4Address) {
+        if (myBgpId == null) {
+            myBgpId = ip4Address;
+            log.debug("BGP: My BGP ID is {}", myBgpId);
+        }
+    }
+
+    /**
+     * Gets the local BGP Identifier as an IPv4 address.
+     *
+     * @return the local BGP Identifier as an IPv4 address
+     */
+    Ip4Address getMyBgpId() {
+        return myBgpId;
+    }
+
+    /**
+     * Gets the BGP Route Selector.
+     *
+     * @return the BGP Route Selector
+     */
+    BgpRouteSelector getBgpRouteSelector() {
+        return bgpRouteSelector;
+    }
+
+    @Override
+    public void start(RouteListener routeListener, int listenPortNumber) {
+        log.debug("BGP Session Manager start.");
+        isShutdown = false;
+
+        this.routeListener = checkNotNull(routeListener);
+
+        ChannelFactory channelFactory = new NioServerSocketChannelFactory(
+                newCachedThreadPool(namedThreads("onos-bgp-sm-boss-%d")),
+                newCachedThreadPool(namedThreads("onos-bgp-sm-worker-%d")));
+        ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
+            @Override
+            public ChannelPipeline getPipeline() throws Exception {
+                // Allocate a new session per connection
+                BgpSession bgpSessionHandler =
+                        new BgpSession(BgpSessionManager.this);
+                BgpFrameDecoder bgpFrameDecoder =
+                        new BgpFrameDecoder(bgpSessionHandler);
+
+                // Setup the processing pipeline
+                ChannelPipeline pipeline = Channels.pipeline();
+                pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
+                pipeline.addLast("BgpSession", bgpSessionHandler);
+                return pipeline;
+            }
+        };
+        InetSocketAddress listenAddress =
+                new InetSocketAddress(listenPortNumber);
+
+        serverBootstrap = new ServerBootstrap(channelFactory);
+        // serverBootstrap.setOptions("reuseAddr", true);
+        serverBootstrap.setOption("child.keepAlive", true);
+        serverBootstrap.setOption("child.tcpNoDelay", true);
+        serverBootstrap.setPipelineFactory(pipelineFactory);
+        try {
+            serverChannel = serverBootstrap.bind(listenAddress);
+            allChannels.add(serverChannel);
+        } catch (ChannelException e) {
+            log.debug("Exception binding to BGP port {}: ",
+                      listenAddress.getPort(), e);
+        }
+    }
+
+    @Override
+    public void stop() {
+        isShutdown = true;
+        allChannels.close().awaitUninterruptibly();
+        serverBootstrap.releaseExternalResources();
+    }
+}
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<>();
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/bgp/package-info.java b/apps/routing/src/main/java/org/onosproject/routing/bgp/package-info.java
new file mode 100644
index 0000000..e4259b6
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/bgp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Implementation of the BGP protocol.
+ */
+package org.onosproject.routing.bgp;
diff --git a/apps/routing/src/main/java/org/onosproject/routing/cli/BgpNeighborsListCommand.java b/apps/routing/src/main/java/org/onosproject/routing/cli/BgpNeighborsListCommand.java
new file mode 100644
index 0000000..b3cb6d0
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/cli/BgpNeighborsListCommand.java
@@ -0,0 +1,183 @@
+/*
+ * 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.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routing.bgp.BgpInfoService;
+import org.onosproject.routing.bgp.BgpSession;
+
+import java.util.Collection;
+
+/**
+ * Command to show the BGP neighbors.
+ */
+@Command(scope = "onos", name = "bgp-neighbors",
+         description = "Lists the BGP neighbors")
+public class BgpNeighborsListCommand extends AbstractShellCommand {
+    @Option(name = "-n", aliases = "--neighbor",
+            description = "BGP neighbor to display information about",
+            required = false, multiValued = false)
+    private String bgpNeighbor;
+
+    private static final String FORMAT_NEIGHBOR_LINE1 =
+        "BGP neighbor is %s, remote AS %d, local AS %d";
+    private static final String FORMAT_NEIGHBOR_LINE2 =
+        "  Remote router ID %s, IP %s, BGP version %d, Hold time %d";
+    private static final String FORMAT_NEIGHBOR_LINE3 =
+        "  Remote AFI/SAFI IPv4 Unicast %s Multicast %s, IPv6 Unicast %s Multicast %s";
+    private static final String FORMAT_NEIGHBOR_LINE4 =
+        "  Local  router ID %s, IP %s, BGP version %d, Hold time %d";
+    private static final String FORMAT_NEIGHBOR_LINE5 =
+        "  Local  AFI/SAFI IPv4 Unicast %s Multicast %s, IPv6 Unicast %s Multicast %s";
+    private static final String FORMAT_NEIGHBOR_LINE6 =
+        "  4 Octet AS Capability: %s %s";
+
+    @Override
+    protected void execute() {
+        BgpInfoService service = AbstractShellCommand.get(BgpInfoService.class);
+        Collection<BgpSession> bgpSessions = service.getBgpSessions();
+
+        if (bgpNeighbor != null) {
+            // Print a single neighbor (if found)
+            BgpSession foundBgpSession = null;
+            for (BgpSession bgpSession : bgpSessions) {
+                if (bgpSession.remoteInfo().bgpId().toString().equals(bgpNeighbor)) {
+                    foundBgpSession = bgpSession;
+                    break;
+                }
+            }
+            if (foundBgpSession != null) {
+                printNeighbor(foundBgpSession);
+            } else {
+                print("BGP neighbor %s not found", bgpNeighbor);
+            }
+            return;
+        }
+
+        // Print all neighbors
+        printNeighbors(bgpSessions);
+    }
+
+    /**
+     * Prints all BGP neighbors.
+     *
+     * @param bgpSessions the BGP sessions for the neighbors to print
+     */
+    private void printNeighbors(Collection<BgpSession> bgpSessions) {
+        if (outputJson()) {
+            print("%s", json(bgpSessions));
+        } else {
+            for (BgpSession bgpSession : bgpSessions) {
+                printNeighbor(bgpSession);
+            }
+        }
+    }
+
+    /**
+     * Prints a BGP neighbor.
+     *
+     * @param bgpSession the BGP session for the neighbor to print
+     */
+    private void printNeighbor(BgpSession bgpSession) {
+        print(FORMAT_NEIGHBOR_LINE1,
+              bgpSession.remoteInfo().bgpId().toString(),
+              bgpSession.remoteInfo().asNumber(),
+              bgpSession.localInfo().asNumber());
+        print(FORMAT_NEIGHBOR_LINE2,
+              bgpSession.remoteInfo().bgpId().toString(),
+              bgpSession.remoteInfo().address().toString(),
+              bgpSession.remoteInfo().bgpVersion(),
+              bgpSession.remoteInfo().holdtime());
+        print(FORMAT_NEIGHBOR_LINE3,
+              bgpSession.remoteInfo().ipv4Unicast() ? "YES" : "NO",
+              bgpSession.remoteInfo().ipv4Multicast() ? "YES" : "NO",
+              bgpSession.remoteInfo().ipv6Unicast() ? "YES" : "NO",
+              bgpSession.remoteInfo().ipv6Multicast() ? "YES" : "NO");
+        print(FORMAT_NEIGHBOR_LINE4,
+              bgpSession.localInfo().bgpId().toString(),
+              bgpSession.localInfo().address().toString(),
+              bgpSession.localInfo().bgpVersion(),
+              bgpSession.localInfo().holdtime());
+        print(FORMAT_NEIGHBOR_LINE5,
+              bgpSession.localInfo().ipv4Unicast() ? "YES" : "NO",
+              bgpSession.localInfo().ipv4Multicast() ? "YES" : "NO",
+              bgpSession.localInfo().ipv6Unicast() ? "YES" : "NO",
+              bgpSession.localInfo().ipv6Multicast() ? "YES" : "NO");
+        if (bgpSession.localInfo().as4OctetCapability() ||
+            bgpSession.remoteInfo().as4OctetCapability()) {
+            print(FORMAT_NEIGHBOR_LINE6,
+                  bgpSession.localInfo().as4OctetCapability() ? "Advertised" : "",
+                  bgpSession.remoteInfo().as4OctetCapability() ? "Received" : "");
+        }
+    }
+
+    /**
+     * Produces a JSON array of BGP neighbors.
+     *
+     * @param bgpSessions the BGP sessions with the data
+     * @return JSON array with the neighbors
+     */
+    private JsonNode json(Collection<BgpSession> bgpSessions) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+
+        for (BgpSession bgpSession : bgpSessions) {
+            result.add(json(mapper, bgpSession));
+        }
+        return result;
+    }
+
+    /**
+     * Produces JSON object for a BGP neighbor.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param bgpSession the BGP session with the data
+     * @return JSON object for the route
+     */
+    private ObjectNode json(ObjectMapper mapper, BgpSession bgpSession) {
+        ObjectNode result = mapper.createObjectNode();
+
+        result.put("remoteAddress", bgpSession.remoteInfo().address().toString());
+        result.put("remoteBgpVersion", bgpSession.remoteInfo().bgpVersion());
+        result.put("remoteAs", bgpSession.remoteInfo().asNumber());
+        result.put("remoteAs4", bgpSession.remoteInfo().as4Number());
+        result.put("remoteHoldtime", bgpSession.remoteInfo().holdtime());
+        result.put("remoteBgpId", bgpSession.remoteInfo().bgpId().toString());
+        result.put("remoteIpv4Unicast", bgpSession.remoteInfo().ipv4Unicast());
+        result.put("remoteIpv4Multicast", bgpSession.remoteInfo().ipv4Multicast());
+        result.put("remoteIpv6Unicast", bgpSession.remoteInfo().ipv6Unicast());
+        result.put("remoteIpv6Multicast", bgpSession.remoteInfo().ipv6Multicast());
+        //
+        result.put("localAddress", bgpSession.localInfo().address().toString());
+        result.put("localBgpVersion", bgpSession.localInfo().bgpVersion());
+        result.put("localAs", bgpSession.localInfo().asNumber());
+        result.put("localAs4", bgpSession.localInfo().as4Number());
+        result.put("localHoldtime", bgpSession.localInfo().holdtime());
+        result.put("localBgpId", bgpSession.localInfo().bgpId().toString());
+        result.put("localIpv4Unicast", bgpSession.localInfo().ipv4Unicast());
+        result.put("localIpv4Multicast", bgpSession.localInfo().ipv4Multicast());
+        result.put("localIpv6Unicast", bgpSession.localInfo().ipv6Unicast());
+        result.put("localIpv6Multicast", bgpSession.localInfo().ipv6Multicast());
+
+        return result;
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/cli/BgpRoutesListCommand.java b/apps/routing/src/main/java/org/onosproject/routing/cli/BgpRoutesListCommand.java
new file mode 100644
index 0000000..3b032c0
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/cli/BgpRoutesListCommand.java
@@ -0,0 +1,283 @@
+/*
+ * 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.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routing.bgp.BgpConstants.Update;
+import org.onosproject.routing.bgp.BgpInfoService;
+import org.onosproject.routing.bgp.BgpRouteEntry;
+import org.onosproject.routing.bgp.BgpSession;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Command to show the routes learned through BGP.
+ */
+@Command(scope = "onos", name = "bgp-routes",
+         description = "Lists all BGP best routes")
+public class BgpRoutesListCommand extends AbstractShellCommand {
+    @Option(name = "-s", aliases = "--summary",
+            description = "BGP routes summary",
+            required = false, multiValued = false)
+    private boolean routesSummary = false;
+
+    @Option(name = "-n", aliases = "--neighbor",
+            description = "Routes from a BGP neighbor",
+            required = false, multiValued = false)
+    private String bgpNeighbor;
+
+    private static final String FORMAT_SUMMARY_V4 =
+        "Total BGP IPv4 routes = %d";
+    private static final String FORMAT_SUMMARY_V6 =
+        "Total BGP IPv6 routes = %d";
+    private static final String FORMAT_HEADER =
+        "   Network            Next Hop        Origin LocalPref       MED BGP-ID";
+    private static final String FORMAT_ROUTE_LINE1 =
+        "   %-18s %-15s %6s %9s %9s %-15s";
+    private static final String FORMAT_ROUTE_LINE2 =
+        "                      AsPath %s";
+
+    @Override
+    protected void execute() {
+        BgpInfoService service = AbstractShellCommand.get(BgpInfoService.class);
+
+        // Print summary of the routes
+        if (routesSummary) {
+            printSummary(service.getBgpRoutes4(), service.getBgpRoutes6());
+            return;
+        }
+
+        BgpSession foundBgpSession = null;
+        if (bgpNeighbor != null) {
+            // Print the routes from a single neighbor (if found)
+            for (BgpSession bgpSession : service.getBgpSessions()) {
+                if (bgpSession.remoteInfo().bgpId().toString().equals(bgpNeighbor)) {
+                    foundBgpSession = bgpSession;
+                    break;
+                }
+            }
+            if (foundBgpSession == null) {
+                print("BGP neighbor %s not found", bgpNeighbor);
+                return;
+            }
+        }
+
+        // Print the routes
+        if (foundBgpSession != null) {
+            printRoutes(foundBgpSession.getBgpRibIn4(),
+                        foundBgpSession.getBgpRibIn6());
+        } else {
+            printRoutes(service.getBgpRoutes4(), service.getBgpRoutes6());
+        }
+    }
+
+    /**
+     * Prints summary of the routes.
+     *
+     * @param routes4 the IPv4 routes
+     * @param routes6 the IPv6 routes
+     */
+    private void printSummary(Collection<BgpRouteEntry> routes4,
+                              Collection<BgpRouteEntry> routes6) {
+        if (outputJson()) {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode result = mapper.createObjectNode();
+            result.put("totalRoutes4", routes4.size());
+            result.put("totalRoutes6", routes6.size());
+            print("%s", result);
+        } else {
+            print(FORMAT_SUMMARY_V4, routes4.size());
+            print(FORMAT_SUMMARY_V6, routes6.size());
+        }
+    }
+
+    /**
+     * Prints all routes.
+     *
+     * @param routes4 the IPv4 routes to print
+     * @param routes6 the IPv6 routes to print
+     */
+    private void printRoutes(Collection<BgpRouteEntry> routes4,
+                             Collection<BgpRouteEntry> routes6) {
+        if (outputJson()) {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode result = mapper.createObjectNode();
+            result.put("routes4", json(routes4));
+            result.put("routes6", json(routes6));
+            print("%s", result);
+        } else {
+            // The IPv4 routes
+            print(FORMAT_HEADER);
+            for (BgpRouteEntry route : routes4) {
+                printRoute(route);
+            }
+            print(FORMAT_SUMMARY_V4, routes4.size());
+            print("");                  // Empty separator line
+            // The IPv6 routes
+            print(FORMAT_HEADER);
+            for (BgpRouteEntry route : routes6) {
+                printRoute(route);
+            }
+            print(FORMAT_SUMMARY_V6, routes6.size());
+        }
+    }
+
+    /**
+     * Prints a BGP route.
+     *
+     * @param route the route to print
+     */
+    private void printRoute(BgpRouteEntry route) {
+        if (route != null) {
+            print(FORMAT_ROUTE_LINE1, route.prefix(), route.nextHop(),
+                  Update.Origin.typeToString(route.getOrigin()),
+                  route.getLocalPref(), route.getMultiExitDisc(),
+                  route.getBgpSession().remoteInfo().bgpId());
+            print(FORMAT_ROUTE_LINE2, asPath4Cli(route.getAsPath()));
+        }
+    }
+
+    /**
+     * Formats the AS Path as a string that can be shown on the CLI.
+     *
+     * @param asPath the AS Path to format
+     * @return the AS Path as a string
+     */
+    private String asPath4Cli(BgpRouteEntry.AsPath asPath) {
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments =
+            asPath.getPathSegments();
+
+        if (pathSegments.isEmpty()) {
+            return "[none]";
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        for (BgpRouteEntry.PathSegment pathSegment : pathSegments) {
+            String prefix = null;
+            String suffix = null;
+            switch (pathSegment.getType()) {
+            case Update.AsPath.AS_SET:
+                prefix = "[AS-Set";
+                suffix = "]";
+                break;
+            case Update.AsPath.AS_SEQUENCE:
+                break;
+            case Update.AsPath.AS_CONFED_SEQUENCE:
+                prefix = "[AS-Confed-Seq";
+                suffix = "]";
+                break;
+            case Update.AsPath.AS_CONFED_SET:
+                prefix = "[AS-Confed-Set";
+                suffix = "]";
+                break;
+            default:
+                builder.append(String.format("(type = %s)",
+                        Update.AsPath.typeToString(pathSegment.getType())));
+                break;
+            }
+
+            if (prefix != null) {
+                if (builder.length() > 0) {
+                    builder.append(" ");        // Separator
+                }
+                builder.append(prefix);
+            }
+            // Print the AS numbers
+            for (Long asn : pathSegment.getSegmentAsNumbers()) {
+                if (builder.length() > 0) {
+                    builder.append(" ");        // Separator
+                }
+                builder.append(String.format("%d", asn));
+            }
+            if (suffix != null) {
+                // No need for separator
+                builder.append(prefix);
+            }
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Produces a JSON array of routes.
+     *
+     * @param routes the routes with the data
+     * @return JSON array with the routes
+     */
+    private JsonNode json(Collection<BgpRouteEntry> routes) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+
+        for (BgpRouteEntry route : routes) {
+            result.add(json(mapper, route));
+        }
+        return result;
+    }
+
+    /**
+     * Produces JSON object for a route.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param route the route with the data
+     * @return JSON object for the route
+     */
+    private ObjectNode json(ObjectMapper mapper, BgpRouteEntry route) {
+        ObjectNode result = mapper.createObjectNode();
+
+        result.put("prefix", route.prefix().toString());
+        result.put("nextHop", route.nextHop().toString());
+        result.put("bgpId",
+                   route.getBgpSession().remoteInfo().bgpId().toString());
+        result.put("origin", Update.Origin.typeToString(route.getOrigin()));
+        result.put("asPath", json(mapper, route.getAsPath()));
+        result.put("localPref", route.getLocalPref());
+        result.put("multiExitDisc", route.getMultiExitDisc());
+
+        return result;
+    }
+
+    /**
+     * Produces JSON object for an AS path.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param asPath the AS path with the data
+     * @return JSON object for the AS path
+     */
+    private ObjectNode json(ObjectMapper mapper, BgpRouteEntry.AsPath asPath) {
+        ObjectNode result = mapper.createObjectNode();
+        ArrayNode pathSegmentsJson = mapper.createArrayNode();
+        for (BgpRouteEntry.PathSegment pathSegment : asPath.getPathSegments()) {
+            ObjectNode pathSegmentJson = mapper.createObjectNode();
+            pathSegmentJson.put("type",
+                                Update.AsPath.typeToString(pathSegment.getType()));
+            ArrayNode segmentAsNumbersJson = mapper.createArrayNode();
+            for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+                segmentAsNumbersJson.add(asNumber);
+            }
+            pathSegmentJson.put("segmentAsNumbers", segmentAsNumbersJson);
+            pathSegmentsJson.add(pathSegmentJson);
+        }
+        result.put("pathSegments", pathSegmentsJson);
+
+        return result;
+    }
+}
diff --git a/apps/routing/src/main/java/org/onosproject/routing/cli/RoutesListCommand.java b/apps/routing/src/main/java/org/onosproject/routing/cli/RoutesListCommand.java
new file mode 100644
index 0000000..6c9d688
--- /dev/null
+++ b/apps/routing/src/main/java/org/onosproject/routing/cli/RoutesListCommand.java
@@ -0,0 +1,157 @@
+/*
+ * 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.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routingapi.RouteEntry;
+import org.onosproject.routingapi.RoutingService;
+
+import java.util.Collection;
+
+/**
+ * Command to show the list of routes in SDN-IP's routing table.
+ */
+@Command(scope = "onos", name = "routes",
+        description = "Lists all SDN-IP best routes")
+public class RoutesListCommand extends AbstractShellCommand {
+    @Option(name = "-s", aliases = "--summary",
+            description = "SDN-IP routes summary",
+            required = false, multiValued = false)
+    private boolean routesSummary = false;
+
+    private static final String FORMAT_SUMMARY_V4 =
+        "Total SDN-IP IPv4 routes = %d";
+    private static final String FORMAT_SUMMARY_V6 =
+        "Total SDN-IP IPv6 routes = %d";
+    private static final String FORMAT_HEADER =
+        "   Network            Next Hop";
+    private static final String FORMAT_ROUTE =
+        "   %-18s %-15s";
+
+    @Override
+    protected void execute() {
+        RoutingService service = AbstractShellCommand.get(RoutingService.class);
+
+        // Print summary of the routes
+        if (routesSummary) {
+            printSummary(service.getRoutes4(), service.getRoutes6());
+            return;
+        }
+
+        // Print all routes
+        printRoutes(service.getRoutes4(), service.getRoutes6());
+    }
+
+    /**
+     * Prints summary of the routes.
+     *
+     * @param routes4 the IPv4 routes
+     * @param routes6 the IPv6 routes
+     */
+    private void printSummary(Collection<RouteEntry> routes4,
+                              Collection<RouteEntry> routes6) {
+        if (outputJson()) {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode result = mapper.createObjectNode();
+            result.put("totalRoutes4", routes4.size());
+            result.put("totalRoutes6", routes6.size());
+            print("%s", result);
+        } else {
+            print(FORMAT_SUMMARY_V4, routes4.size());
+            print(FORMAT_SUMMARY_V6, routes6.size());
+        }
+    }
+
+    /**
+     * Prints all routes.
+     *
+     * @param routes4 the IPv4 routes to print
+     * @param routes6 the IPv6 routes to print
+     */
+    private void printRoutes(Collection<RouteEntry> routes4,
+                             Collection<RouteEntry> routes6) {
+        if (outputJson()) {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode result = mapper.createObjectNode();
+            result.put("routes4", json(routes4));
+            result.put("routes6", json(routes6));
+            print("%s", result);
+        } else {
+            // The IPv4 routes
+            print(FORMAT_HEADER);
+            for (RouteEntry route : routes4) {
+                printRoute(route);
+            }
+            print(FORMAT_SUMMARY_V4, routes4.size());
+            print("");                  // Empty separator line
+            // The IPv6 routes
+            print(FORMAT_HEADER);
+            for (RouteEntry route : routes6) {
+                printRoute(route);
+            }
+            print(FORMAT_SUMMARY_V6, routes6.size());
+        }
+    }
+
+    /**
+     * Prints a route.
+     *
+     * @param route the route to print
+     */
+    private void printRoute(RouteEntry route) {
+        if (route != null) {
+            print(FORMAT_ROUTE, route.prefix(), route.nextHop());
+        }
+    }
+
+    /**
+     * Produces a JSON array of routes.
+     *
+     * @param routes the routes with the data
+     * @return JSON array with the routes
+     */
+    private JsonNode json(Collection<RouteEntry> routes) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+
+        for (RouteEntry route : routes) {
+            result.add(json(mapper, route));
+        }
+        return result;
+    }
+
+    /**
+     * Produces JSON object for a route.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param route the route with the data
+     * @return JSON object for the route
+     */
+    private ObjectNode json(ObjectMapper mapper, RouteEntry route) {
+        ObjectNode result = mapper.createObjectNode();
+
+        result.put("prefix", route.prefix().toString());
+        result.put("nextHop", route.nextHop().toString());
+
+        return result;
+    }
+}
diff --git a/apps/routing/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/routing/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..5ef971d
--- /dev/null
+++ b/apps/routing/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ Copyright 2015 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.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+  <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+    <command>
+      <action class="org.onosproject.routing.cli.BgpNeighborsListCommand"/>
+    </command>
+    <command>
+      <action class="org.onosproject.routing.cli.BgpRoutesListCommand"/>
+    </command>
+    <command>
+      <action class="org.onosproject.routing.cli.RoutesListCommand"/>
+    </command>
+  </command-bundle>
+</blueprint>
diff --git a/apps/routing/src/test/java/org/onosproject/routing/RouterAsyncArpTest.java b/apps/routing/src/test/java/org/onosproject/routing/RouterAsyncArpTest.java
new file mode 100644
index 0000000..d9a5ce9
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/RouterAsyncArpTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.routing.Router.InternalHostListener;
+import org.onosproject.routingapi.BgpService;
+import org.onosproject.routingapi.FibEntry;
+import org.onosproject.routingapi.FibListener;
+import org.onosproject.routingapi.FibUpdate;
+import org.onosproject.routingapi.RouteEntry;
+import org.onosproject.routingapi.RouteListener;
+import org.onosproject.routingapi.RouteUpdate;
+
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+
+/**
+* This class tests adding a route and updating a route.
+* The HostService module answers the MAC address asynchronously.
+*/
+public class RouterAsyncArpTest {
+
+    private HostService hostService;
+    private FibListener fibListener;
+
+    private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000001"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW2_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000002"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW3_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000003"),
+            PortNumber.portNumber(1));
+
+    private Router router;
+    private InternalHostListener internalHostListener;
+
+    @Before
+    public void setUp() throws Exception {
+        hostService = createMock(HostService.class);
+
+        BgpService bgpService = createMock(BgpService.class);
+        bgpService.start(anyObject(RouteListener.class), anyInt());
+        bgpService.stop();
+        replay(bgpService);
+
+        fibListener = createMock(FibListener.class);
+
+        router = new Router();
+        router.hostService = hostService;
+        router.bgpService = bgpService;
+        router.activate();
+
+        router.start(fibListener);
+
+        internalHostListener = router.new InternalHostListener();
+    }
+
+    @After
+    public void tearDown() {
+        // Called during shutdown
+        reset(hostService);
+        hostService.removeListener(anyObject(HostListener.class));
+
+        router.stop();
+    }
+
+    /**
+     * Tests adding a route entry with asynchronous HostService replies.
+     */
+    @Test
+    public void testRouteAdd() {
+        // Construct a route entry
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+        IpAddress nextHopIp = Ip4Address.valueOf("192.168.10.1");
+
+        RouteEntry routeEntry = new RouteEntry(prefix, nextHopIp);
+
+        // Host service will reply with no hosts when asked
+        reset(hostService);
+        expect(hostService.getHostsByIp(anyObject(IpAddress.class))).andReturn(
+                Collections.emptySet()).anyTimes();
+        hostService.startMonitoringIp(IpAddress.valueOf("192.168.10.1"));
+        replay(hostService);
+
+
+        // Initially when we add the route, no FIB update will be sent
+        replay(fibListener);
+
+        router.processRouteUpdates(Collections.singletonList(
+                new RouteUpdate(RouteUpdate.Type.UPDATE, routeEntry)));
+
+        verify(fibListener);
+
+
+        // Now when we send the event, we expect the FIB update to be sent
+        reset(fibListener);
+        FibEntry fibEntry = new FibEntry(prefix, nextHopIp,
+                                         MacAddress.valueOf("00:00:00:00:00:01"));
+
+        fibListener.update(Collections.singletonList(new FibUpdate(
+                FibUpdate.Type.UPDATE, fibEntry)), Collections.emptyList());
+        replay(fibListener);
+
+        Host host = new DefaultHost(ProviderId.NONE, HostId.NONE,
+                                    MacAddress.valueOf("00:00:00:00:00:01"), VlanId.NONE,
+                                    new HostLocation(
+                                            SW1_ETH1.deviceId(),
+                                            SW1_ETH1.port(), 1),
+                                    Sets.newHashSet(IpAddress.valueOf("192.168.10.1")));
+
+        // Send in the host event
+        internalHostListener.event(
+                new HostEvent(HostEvent.Type.HOST_ADDED, host));
+
+        verify(fibListener);
+    }
+
+    /**
+     * Tests updating a route entry with asynchronous HostService replies.
+     */
+    @Test
+    public void testRouteUpdate() {
+        // Add a route
+        testRouteAdd();
+
+        // Construct a route entry
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+        IpAddress nextHopIp = Ip4Address.valueOf("192.168.20.1");
+
+        RouteEntry routeEntry = new RouteEntry(prefix, nextHopIp);
+
+        // Host service will reply with no hosts when asked
+        reset(hostService);
+        expect(hostService.getHostsByIp(anyObject(IpAddress.class))).andReturn(
+                Collections.emptySet()).anyTimes();
+        hostService.startMonitoringIp(IpAddress.valueOf("192.168.20.1"));
+        replay(hostService);
+
+
+        // Initially when we add the route, the DELETE FIB update will be sent
+        // but the UPDATE FIB update will come later when the MAC is resolved
+        reset(fibListener);
+
+        fibListener.update(Collections.emptyList(), Collections.singletonList(new FibUpdate(
+                FibUpdate.Type.DELETE, new FibEntry(prefix, null, null))));
+        replay(fibListener);
+
+        router.processRouteUpdates(Collections.singletonList(
+                new RouteUpdate(RouteUpdate.Type.UPDATE, routeEntry)));
+
+        verify(fibListener);
+
+
+        // Now when we send the event, we expect the FIB update to be sent
+        reset(fibListener);
+        FibEntry fibEntry = new FibEntry(prefix, nextHopIp,
+                                         MacAddress.valueOf("00:00:00:00:00:02"));
+
+        fibListener.update(Collections.singletonList(new FibUpdate(
+                FibUpdate.Type.UPDATE, fibEntry)), Collections.emptyList());
+        replay(fibListener);
+
+        Host host = new DefaultHost(ProviderId.NONE, HostId.NONE,
+                                    MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE,
+                                    new HostLocation(
+                                            SW1_ETH1.deviceId(),
+                                            SW1_ETH1.port(), 1),
+                                    Sets.newHashSet(IpAddress.valueOf("192.168.20.1")));
+
+        // Send in the host event
+        internalHostListener.event(
+                new HostEvent(HostEvent.Type.HOST_ADDED, host));
+
+        verify(fibListener);
+    }
+}
diff --git a/apps/routing/src/test/java/org/onosproject/routing/RouterTest.java b/apps/routing/src/test/java/org/onosproject/routing/RouterTest.java
new file mode 100644
index 0000000..176c159
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/RouterTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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;
+
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.routingapi.BgpService;
+import org.onosproject.routingapi.FibEntry;
+import org.onosproject.routingapi.FibListener;
+import org.onosproject.routingapi.FibUpdate;
+import org.onosproject.routingapi.RouteEntry;
+import org.onosproject.routingapi.RouteListener;
+import org.onosproject.routingapi.RouteUpdate;
+
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * This class tests adding a route, updating a route, deleting a route,
+ * and adding a route whose next hop is the local BGP speaker.
+ * <p/>
+ * The HostService answers requests synchronously.
+ */
+public class RouterTest {
+
+    private HostService hostService;
+
+    private FibListener fibListener;
+
+    private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000001"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW2_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000002"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW3_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000003"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW4_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000004"),
+            PortNumber.portNumber(1));
+
+    private Router router;
+
+    @Before
+    public void setUp() throws Exception {
+        setUpHostService();
+
+        BgpService bgpService = createMock(BgpService.class);
+        bgpService.start(anyObject(RouteListener.class), anyInt());
+        bgpService.stop();
+        replay(bgpService);
+
+        fibListener = createMock(FibListener.class);
+
+        router = new Router();
+        router.hostService = hostService;
+        router.bgpService = bgpService;
+        router.activate();
+
+        router.start(fibListener);
+    }
+
+    @After
+    public void tearDown() {
+        router.stop();
+    }
+
+    /**
+     * Sets up the host service with details of some hosts.
+     */
+    private void setUpHostService() {
+        hostService = createMock(HostService.class);
+
+        hostService.addListener(anyObject(HostListener.class));
+        expectLastCall().anyTimes();
+
+        IpAddress host1Address = IpAddress.valueOf("192.168.10.1");
+        Host host1 = new DefaultHost(ProviderId.NONE, HostId.NONE,
+                MacAddress.valueOf("00:00:00:00:00:01"), VlanId.NONE,
+                new HostLocation(SW1_ETH1, 1),
+                Sets.newHashSet(host1Address));
+
+        expect(hostService.getHostsByIp(host1Address))
+                .andReturn(Sets.newHashSet(host1)).anyTimes();
+        hostService.startMonitoringIp(host1Address);
+        expectLastCall().anyTimes();
+
+
+        IpAddress host2Address = IpAddress.valueOf("192.168.20.1");
+        Host host2 = new DefaultHost(ProviderId.NONE, HostId.NONE,
+                MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE,
+                new HostLocation(SW2_ETH1, 1),
+                Sets.newHashSet(host2Address));
+
+        expect(hostService.getHostsByIp(host2Address))
+                .andReturn(Sets.newHashSet(host2)).anyTimes();
+        hostService.startMonitoringIp(host2Address);
+        expectLastCall().anyTimes();
+
+        // Next hop on a VLAN
+        IpAddress host3Address = IpAddress.valueOf("192.168.40.1");
+        Host host3 = new DefaultHost(ProviderId.NONE, HostId.NONE,
+                MacAddress.valueOf("00:00:00:00:00:03"), VlanId.vlanId((short) 1),
+                new HostLocation(SW4_ETH1, 1),
+                Sets.newHashSet(host3Address));
+
+        expect(hostService.getHostsByIp(host3Address))
+                .andReturn(Sets.newHashSet(host3)).anyTimes();
+        hostService.startMonitoringIp(host3Address);
+        expectLastCall().anyTimes();
+
+        // Called during shutdown
+        hostService.removeListener(anyObject(HostListener.class));
+
+        replay(hostService);
+    }
+
+    /**
+     * Tests adding a route entry.
+     */
+    @Test
+    public void testRouteAdd() {
+        // Construct a route entry
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+        IpAddress nextHopIp = Ip4Address.valueOf("192.168.10.1");
+
+        RouteEntry routeEntry = new RouteEntry(prefix, nextHopIp);
+
+        // Expected FIB entry
+        FibEntry fibEntry = new FibEntry(prefix, nextHopIp,
+                                         MacAddress.valueOf("00:00:00:00:00:01"));
+
+        fibListener.update(Collections.singletonList(new FibUpdate(
+                FibUpdate.Type.UPDATE, fibEntry)), Collections.emptyList());
+
+        replay(fibListener);
+
+        router.processRouteUpdates(Collections.singletonList(
+                new RouteUpdate(RouteUpdate.Type.UPDATE, routeEntry)));
+
+        verify(fibListener);
+    }
+
+    /**
+     * Tests updating a route entry.
+     */
+    @Test
+    public void testRouteUpdate() {
+        // Firstly add a route
+        testRouteAdd();
+
+        // Route entry with updated next hop for the original prefix
+        RouteEntry routeEntryUpdate = new RouteEntry(
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.20.1"));
+
+        // The old FIB entry will be withdrawn
+        FibEntry withdrawFibEntry = new FibEntry(
+                Ip4Prefix.valueOf("1.1.1.0/24"), null, null);
+
+        // A new FIB entry will be added
+        FibEntry updateFibEntry = new FibEntry(
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.20.1"),
+                MacAddress.valueOf("00:00:00:00:00:02"));
+
+        reset(fibListener);
+        fibListener.update(Collections.singletonList(new FibUpdate(
+                                    FibUpdate.Type.UPDATE, updateFibEntry)),
+                           Collections.singletonList(new FibUpdate(
+                                    FibUpdate.Type.DELETE, withdrawFibEntry)));
+
+        replay(fibListener);
+
+        router.processRouteUpdates(Collections.singletonList(new RouteUpdate(
+                RouteUpdate.Type.UPDATE, routeEntryUpdate)));
+
+        verify(fibListener);
+    }
+
+    /**
+     * Tests deleting a route entry.
+     */
+    @Test
+    public void testRouteDelete() {
+        // Firstly add a route
+        testRouteAdd();
+
+        RouteEntry deleteRouteEntry = new RouteEntry(
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
+
+        FibEntry deleteFibEntry = new FibEntry(
+                Ip4Prefix.valueOf("1.1.1.0/24"), null, null);
+
+        reset(fibListener);
+        fibListener.update(Collections.emptyList(), Collections.singletonList(
+                new FibUpdate(FibUpdate.Type.DELETE, deleteFibEntry)));
+
+        replay(fibListener);
+
+        router.processRouteUpdates(Collections.singletonList(
+                new RouteUpdate(RouteUpdate.Type.DELETE, deleteRouteEntry)));
+
+        verify(fibListener);
+    }
+
+    /**
+     * Tests adding a route whose next hop is the local BGP speaker.
+     */
+    @Test
+    public void testLocalRouteAdd() {
+        // Construct a route entry, the next hop is the local BGP speaker
+        RouteEntry routeEntry = new RouteEntry(
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("0.0.0.0"));
+
+        // No methods on the FIB listener should be called
+        replay(fibListener);
+
+        // Call the processRouteUpdates() method in Router class
+        RouteUpdate routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                                  routeEntry);
+        router.processRouteUpdates(Collections.singletonList(routeUpdate));
+
+        // Verify
+        assertEquals(1, router.getRoutes4().size());
+        assertTrue(router.getRoutes4().contains(routeEntry));
+        verify(fibListener);
+    }
+}
diff --git a/apps/routing/src/test/java/org/onosproject/routing/bgp/AsPathTest.java b/apps/routing/src/test/java/org/onosproject/routing/bgp/AsPathTest.java
new file mode 100644
index 0000000..2414eb2
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/bgp/AsPathTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpRouteEntry.AsPath class.
+ */
+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 =
+            generatePathSegments();
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+
+        return asPath;
+    }
+
+    /**
+     * Tests valid class constructor.
+     */
+    @Test
+    public void testConstructor() {
+        BgpRouteEntry.AsPath asPath = generateAsPath();
+
+        String expectedString =
+            "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));
+    }
+
+    /**
+     * Tests invalid class constructor for null Path Segments.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConstructorNullPathSegments() {
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = null;
+        new BgpRouteEntry.AsPath(pathSegments);
+    }
+
+    /**
+     * Tests getting the fields of an AS Path.
+     */
+    @Test
+    public void testGetFields() {
+        // Create the fields to compare against
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments =
+            generatePathSegments();
+
+        // Generate the entry to test
+        BgpRouteEntry.AsPath asPath = generateAsPath();
+
+        assertThat(asPath.getPathSegments(), is(pathSegments));
+    }
+
+    /**
+     * Tests getting the AS Path Length.
+     */
+    @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));
+
+        // Create an empty AS Path
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+        asPath = new BgpRouteEntry.AsPath(pathSegments);
+        assertThat(asPath.getAsPathLength(), is(0));
+    }
+
+    /**
+     * Tests equality of {@link BgpRouteEntry.AsPath}.
+     */
+    @Test
+    public void testEquality() {
+        BgpRouteEntry.AsPath asPath1 = generateAsPath();
+        BgpRouteEntry.AsPath asPath2 = generateAsPath();
+
+        assertThat(asPath1, is(asPath2));
+    }
+
+    /**
+     * Tests non-equality of {@link BgpRouteEntry.AsPath}.
+     */
+    @Test
+    public void testNonEquality() {
+        BgpRouteEntry.AsPath asPath1 = generateAsPath();
+
+        // Setup AS Path 2
+        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) 111);                       // Different
+        segmentAsNumbers.add((long) 12);
+        pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+        pathSegments.add(pathSegment);
+        //
+        BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments);
+
+        assertThat(asPath1, Matchers.is(not(asPath2)));
+    }
+
+    /**
+     * Tests object string representation.
+     */
+    @Test
+    public void testToString() {
+        BgpRouteEntry.AsPath asPath = generateAsPath();
+
+        String expectedString =
+            "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));
+    }
+}
diff --git a/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpRouteEntryTest.java b/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpRouteEntryTest.java
new file mode 100644
index 0000000..ab72b6f
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpRouteEntryTest.java
@@ -0,0 +1,520 @@
+/*
+ * 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.easymock.EasyMock;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+
+import java.util.ArrayList;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpRouteEntry class.
+ */
+public class BgpRouteEntryTest {
+    private BgpSession bgpSession;
+    private static final Ip4Address BGP_SESSION_BGP_ID =
+        Ip4Address.valueOf("10.0.0.1");
+    private static final Ip4Address BGP_SESSION_IP_ADDRESS =
+        Ip4Address.valueOf("20.0.0.1");
+
+    private BgpSession bgpSession2;
+    private static final Ip4Address BGP_SESSION_BGP_ID2 =
+        Ip4Address.valueOf("10.0.0.2");
+    private static final Ip4Address BGP_SESSION_IP_ADDRESS2 =
+        Ip4Address.valueOf("20.0.0.1");
+
+    private BgpSession bgpSession3;
+    private static final Ip4Address BGP_SESSION_BGP_ID3 =
+        Ip4Address.valueOf("10.0.0.1");
+    private static final Ip4Address BGP_SESSION_IP_ADDRESS3 =
+        Ip4Address.valueOf("20.0.0.2");
+
+    private final BgpSessionInfo localInfo = new BgpSessionInfo();
+    private final BgpSessionInfo remoteInfo = new BgpSessionInfo();
+
+    private final BgpSessionInfo localInfo2 = new BgpSessionInfo();
+    private final BgpSessionInfo remoteInfo2 = new BgpSessionInfo();
+
+    private final BgpSessionInfo localInfo3 = new BgpSessionInfo();
+    private final BgpSessionInfo remoteInfo3 = new BgpSessionInfo();
+
+    @Before
+    public void setUp() throws Exception {
+        // Mock objects for testing
+        bgpSession = EasyMock.createMock(BgpSession.class);
+        bgpSession2 = EasyMock.createMock(BgpSession.class);
+        bgpSession3 = EasyMock.createMock(BgpSession.class);
+
+        // Setup the BGP Sessions
+        remoteInfo.setIp4Address(BGP_SESSION_IP_ADDRESS);
+        remoteInfo2.setIp4Address(BGP_SESSION_IP_ADDRESS2);
+        remoteInfo3.setIp4Address(BGP_SESSION_IP_ADDRESS3);
+        remoteInfo.setBgpId(BGP_SESSION_BGP_ID);
+        remoteInfo2.setBgpId(BGP_SESSION_BGP_ID2);
+        remoteInfo3.setBgpId(BGP_SESSION_BGP_ID3);
+
+        EasyMock.expect(bgpSession.localInfo()).andReturn(localInfo).anyTimes();
+        EasyMock.expect(bgpSession.remoteInfo()).andReturn(remoteInfo).anyTimes();
+        EasyMock.expect(bgpSession2.localInfo()).andReturn(localInfo2).anyTimes();
+        EasyMock.expect(bgpSession2.remoteInfo()).andReturn(remoteInfo2).anyTimes();
+        EasyMock.expect(bgpSession3.localInfo()).andReturn(localInfo3).anyTimes();
+        EasyMock.expect(bgpSession3.remoteInfo()).andReturn(remoteInfo3).anyTimes();
+
+        EasyMock.replay(bgpSession);
+        EasyMock.replay(bgpSession2);
+        EasyMock.replay(bgpSession3);
+    }
+
+    /**
+     * Generates a BGP Route Entry.
+     *
+     * @return a generated BGP Route Entry
+     */
+    private BgpRouteEntry generateBgpRouteEntry() {
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        // Setup the AS Path
+        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);
+        //
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+        //
+        long localPref = 100;
+        long multiExitDisc = 20;
+
+        BgpRouteEntry bgpRouteEntry =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+
+        return bgpRouteEntry;
+    }
+
+    /**
+     * Tests valid class constructor.
+     */
+    @Test
+    public void testConstructor() {
+        BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+        String expectedString =
+            "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
+            "bgpId=10.0.0.1, origin=IGP, asPath=AsPath{pathSegments=" +
+            "[PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+            "PathSegment{type=AS_SET, segmentAsNumbers=[4, 5, 6]}]}, " +
+            "localPref=100, multiExitDisc=20}";
+        assertThat(bgpRouteEntry.toString(), is(expectedString));
+    }
+
+    /**
+     * Tests invalid class constructor for null BGP Session.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConstructorNullBgpSession() {
+        BgpSession bgpSessionNull = null;
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        // Setup the AS Path
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+        //
+        long localPref = 100;
+
+        new BgpRouteEntry(bgpSessionNull, prefix, nextHop, origin, asPath,
+                    localPref);
+    }
+
+    /**
+     * Tests invalid class constructor for null AS Path.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConstructorNullAsPath() {
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        BgpRouteEntry.AsPath asPath = null;
+        long localPref = 100;
+
+        new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                    localPref);
+    }
+
+    /**
+     * Tests getting the fields of a BGP route entry.
+     */
+    @Test
+    public void testGetFields() {
+        // Create the fields to compare against
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        // Setup the AS Path
+        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);
+        //
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+        //
+        long localPref = 100;
+        long multiExitDisc = 20;
+
+        // Generate the entry to test
+        BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+        assertThat(bgpRouteEntry.prefix(), is(prefix));
+        assertThat(bgpRouteEntry.nextHop(), is(nextHop));
+        assertThat(bgpRouteEntry.getBgpSession(), is(bgpSession));
+        assertThat(bgpRouteEntry.getOrigin(), is(origin));
+        assertThat(bgpRouteEntry.getAsPath(), is(asPath));
+        assertThat(bgpRouteEntry.getLocalPref(), is(localPref));
+        assertThat(bgpRouteEntry.getMultiExitDisc(), is(multiExitDisc));
+    }
+
+    /**
+     * Tests whether a BGP route entry is a local route.
+     */
+    @Test
+    public void testIsLocalRoute() {
+        //
+        // Test non-local route
+        //
+        BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+        assertThat(bgpRouteEntry.isLocalRoute(), is(false));
+
+        //
+        // Test local route with AS Path that begins with AS_SET
+        //
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        // Setup the AS Path
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+        byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SET;
+        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_SEQUENCE;
+        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);
+        //
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+        //
+        long localPref = 100;
+        long multiExitDisc = 20;
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+        assertThat(bgpRouteEntry.isLocalRoute(), is(true));
+
+        //
+        // Test local route with empty AS Path
+        //
+        pathSegments = new ArrayList<>();
+        asPath = new BgpRouteEntry.AsPath(pathSegments);
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+        assertThat(bgpRouteEntry.isLocalRoute(), is(true));
+    }
+
+    /**
+     * Tests getting the BGP Neighbor AS number for a route.
+     */
+    @Test
+    public void testGetNeighborAs() {
+        //
+        // Get neighbor AS for non-local route
+        //
+        BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+        assertThat(bgpRouteEntry.getNeighborAs(), is((long) 1));
+
+        //
+        // Get neighbor AS for a local route
+        //
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        // Setup the AS Path
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+        //
+        long localPref = 100;
+        long multiExitDisc = 20;
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+        assertThat(bgpRouteEntry.getNeighborAs(), is(BgpConstants.BGP_AS_0));
+    }
+
+    /**
+     * Tests whether a BGP route entry has AS Path loop.
+     */
+    @Test
+    public void testHasAsPathLoop() {
+        BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+        // Test for loops: test each AS number in the interval [1, 6]
+        for (int i = 1; i <= 6; i++) {
+            assertThat(bgpRouteEntry.hasAsPathLoop(i), is(true));
+        }
+
+        // Test for non-loops
+        assertThat(bgpRouteEntry.hasAsPathLoop(500), is(false));
+    }
+
+    /**
+     * Tests the BGP Decision Process comparison of BGP routes.
+     */
+    @Test
+    public void testBgpDecisionProcessComparison() {
+        BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+        BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
+
+        //
+        // Compare two routes that are same
+        //
+        assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+        assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
+
+        //
+        // Compare two routes with different LOCAL_PREF
+        //
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        // Setup the AS Path
+        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);
+        //
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+        //
+        long localPref = 50;                                    // Different
+        long multiExitDisc = 20;
+        bgpRouteEntry2 =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+        //
+        assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+        assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+        localPref = bgpRouteEntry1.getLocalPref();              // Restore
+
+        //
+        // Compare two routes with different AS_PATH length
+        //
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments2 = new ArrayList<>();
+        pathSegments2.add(pathSegment1);
+        // Different AS Path
+        BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments2);
+        bgpRouteEntry2 =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath2,
+                              localPref);
+        bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+        //
+        assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(false));
+        assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
+
+        //
+        // Compare two routes with different ORIGIN
+        //
+        origin = BgpConstants.Update.Origin.EGP;                // Different
+        bgpRouteEntry2 =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+        //
+        assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+        assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+        origin = bgpRouteEntry1.getOrigin();                    // Restore
+
+        //
+        // Compare two routes with different MULTI_EXIT_DISC
+        //
+        multiExitDisc = 10;                                     // Different
+        bgpRouteEntry2 =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+        //
+        assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+        assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+        multiExitDisc = bgpRouteEntry1.getMultiExitDisc();      // Restore
+
+        //
+        // Compare two routes with different BGP ID
+        //
+        bgpRouteEntry2 =
+            new BgpRouteEntry(bgpSession2, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+        //
+        assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+        assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+
+        //
+        // Compare two routes with different BGP address
+        //
+        bgpRouteEntry2 =
+            new BgpRouteEntry(bgpSession3, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+        //
+        assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+        assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+    }
+
+    /**
+     * Tests equality of {@link BgpRouteEntry}.
+     */
+    @Test
+    public void testEquality() {
+        BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+        BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
+
+        assertThat(bgpRouteEntry1, is(bgpRouteEntry2));
+    }
+
+    /**
+     * Tests non-equality of {@link BgpRouteEntry}.
+     */
+    @Test
+    public void testNonEquality() {
+        BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+
+        // Setup BGP Route 2
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        byte origin = BgpConstants.Update.Origin.IGP;
+        // Setup the AS Path
+        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);
+        //
+        BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+        //
+        long localPref = 500;                                   // Different
+        long multiExitDisc = 20;
+        BgpRouteEntry bgpRouteEntry2 =
+            new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+                              localPref);
+        bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+
+        assertThat(bgpRouteEntry1, Matchers.is(not(bgpRouteEntry2)));
+    }
+
+    /**
+     * Tests object string representation.
+     */
+    @Test
+    public void testToString() {
+        BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+        String expectedString =
+            "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
+            "bgpId=10.0.0.1, origin=IGP, asPath=AsPath{pathSegments=" +
+            "[PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+            "PathSegment{type=AS_SET, segmentAsNumbers=[4, 5, 6]}]}, " +
+            "localPref=100, multiExitDisc=20}";
+        assertThat(bgpRouteEntry.toString(), is(expectedString));
+    }
+}
diff --git a/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java b/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java
new file mode 100644
index 0000000..c73eaff
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java
@@ -0,0 +1,876 @@
+/*
+ * 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 com.google.common.net.InetAddresses;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.junit.TestUtils.TestUtilsException;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onosproject.routingapi.RouteListener;
+import org.onosproject.routingapi.RouteUpdate;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpSessionManager class.
+ */
+public class BgpSessionManagerTest {
+    private static final Ip4Address IP_LOOPBACK_ID =
+        Ip4Address.valueOf("127.0.0.1");
+    private static final Ip4Address BGP_PEER1_ID =
+        Ip4Address.valueOf("10.0.0.1");
+    private static final Ip4Address BGP_PEER2_ID =
+        Ip4Address.valueOf("10.0.0.2");
+    private static final Ip4Address BGP_PEER3_ID =
+        Ip4Address.valueOf("10.0.0.3");
+    private static final Ip4Address NEXT_HOP1_ROUTER =
+        Ip4Address.valueOf("10.20.30.41");
+    private static final Ip4Address NEXT_HOP2_ROUTER =
+        Ip4Address.valueOf("10.20.30.42");
+    private static final Ip4Address NEXT_HOP3_ROUTER =
+        Ip4Address.valueOf("10.20.30.43");
+
+    private static final long DEFAULT_LOCAL_PREF = 10;
+    private static final long BETTER_LOCAL_PREF = 20;
+    private static final long DEFAULT_MULTI_EXIT_DISC = 20;
+    private static final long BETTER_MULTI_EXIT_DISC = 30;
+
+    BgpRouteEntry.AsPath asPathShort;
+    BgpRouteEntry.AsPath asPathLong;
+
+    // Timeout waiting for a message to be received
+    private static final int MESSAGE_TIMEOUT_MS = 5000; // 5s
+
+    // The BGP Session Manager to test
+    private BgpSessionManager bgpSessionManager;
+
+    // Remote Peer state
+    private final Collection<TestBgpPeer> peers = new LinkedList<>();
+    TestBgpPeer peer1;
+    TestBgpPeer peer2;
+    TestBgpPeer peer3;
+
+    // Local BGP per-peer session state
+    BgpSession bgpSession1;
+    BgpSession bgpSession2;
+    BgpSession bgpSession3;
+
+    // The socket that the remote peers should connect to
+    private InetSocketAddress connectToSocket;
+
+    private final DummyRouteListener dummyRouteListener =
+        new DummyRouteListener();
+
+    /**
+     * Dummy implementation for the RouteListener interface.
+     */
+    private class DummyRouteListener implements RouteListener {
+        @Override
+        public void update(Collection<RouteUpdate> routeUpdate) {
+            // Nothing to do
+        }
+    }
+
+    /**
+     * A class to capture the state for a BGP peer.
+     */
+    private final class TestBgpPeer {
+        private final Ip4Address peerId;
+        private ClientBootstrap peerBootstrap;
+        private TestBgpPeerChannelHandler peerChannelHandler;
+        private TestBgpPeerFrameDecoder peerFrameDecoder =
+            new TestBgpPeerFrameDecoder();
+
+        /**
+         * Constructor.
+         *
+         * @param peerId the peer ID
+         */
+        private TestBgpPeer(Ip4Address peerId) {
+            this.peerId = peerId;
+            peerChannelHandler = new TestBgpPeerChannelHandler(peerId);
+        }
+
+        /**
+         * Starts up the BGP peer and connects it to the tested SDN-IP
+         * instance.
+         *
+         * @param connectToSocket the socket to connect to
+         */
+        private void connect(InetSocketAddress connectToSocket)
+            throws InterruptedException {
+            //
+            // Setup the BGP Peer, i.e., the "remote" BGP router that will
+            // initiate the BGP connection, send BGP UPDATE messages, etc.
+            //
+            ChannelFactory channelFactory =
+                new NioClientSocketChannelFactory(
+                        Executors.newCachedThreadPool(),
+                        Executors.newCachedThreadPool());
+            ChannelPipelineFactory pipelineFactory =
+                new ChannelPipelineFactory() {
+                    @Override
+                    public ChannelPipeline getPipeline() throws Exception {
+                        // Setup the transmitting pipeline
+                        ChannelPipeline pipeline = Channels.pipeline();
+                        pipeline.addLast("TestBgpPeerFrameDecoder",
+                                         peerFrameDecoder);
+                        pipeline.addLast("TestBgpPeerChannelHandler",
+                                         peerChannelHandler);
+                        return pipeline;
+                    }
+                };
+
+            peerBootstrap = new ClientBootstrap(channelFactory);
+            peerBootstrap.setOption("child.keepAlive", true);
+            peerBootstrap.setOption("child.tcpNoDelay", true);
+            peerBootstrap.setPipelineFactory(pipelineFactory);
+            peerBootstrap.connect(connectToSocket);
+
+            boolean result;
+            // Wait until the OPEN message is received
+            result = peerFrameDecoder.receivedOpenMessageLatch.await(
+                MESSAGE_TIMEOUT_MS,
+                TimeUnit.MILLISECONDS);
+            assertThat(result, is(true));
+            // Wait until the KEEPALIVE message is received
+            result = peerFrameDecoder.receivedKeepaliveMessageLatch.await(
+                MESSAGE_TIMEOUT_MS,
+                TimeUnit.MILLISECONDS);
+            assertThat(result, is(true));
+
+            for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+                if (bgpSession.remoteInfo().bgpId().equals(BGP_PEER1_ID)) {
+                    bgpSession1 = bgpSession;
+                }
+                if (bgpSession.remoteInfo().bgpId().equals(BGP_PEER2_ID)) {
+                    bgpSession2 = bgpSession;
+                }
+                if (bgpSession.remoteInfo().bgpId().equals(BGP_PEER3_ID)) {
+                    bgpSession3 = bgpSession;
+                }
+            }
+        }
+    }
+
+    /**
+     * Class that implements a matcher for BgpRouteEntry by considering
+     * the BGP peer the entry was received from.
+     */
+    private static final class BgpRouteEntryAndPeerMatcher
+        extends TypeSafeMatcher<Collection<BgpRouteEntry>> {
+        private final BgpRouteEntry bgpRouteEntry;
+
+        private BgpRouteEntryAndPeerMatcher(BgpRouteEntry bgpRouteEntry) {
+            this.bgpRouteEntry = bgpRouteEntry;
+        }
+
+        @Override
+        public boolean matchesSafely(Collection<BgpRouteEntry> entries) {
+            for (BgpRouteEntry entry : entries) {
+                if (bgpRouteEntry.equals(entry) &&
+                    bgpRouteEntry.getBgpSession() == entry.getBgpSession()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("BGP route entry lookup for entry \"").
+                appendText(bgpRouteEntry.toString()).
+                appendText("\"");
+        }
+    }
+
+    /**
+     * A helper method used for testing whether a collection of
+     * BGP route entries contains an entry from a specific BGP peer.
+     *
+     * @param bgpRouteEntry the BGP route entry to test
+     * @return an instance of BgpRouteEntryAndPeerMatcher that implements
+     * the matching logic
+     */
+    private static BgpRouteEntryAndPeerMatcher hasBgpRouteEntry(
+        BgpRouteEntry bgpRouteEntry) {
+        return new BgpRouteEntryAndPeerMatcher(bgpRouteEntry);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        peer1 = new TestBgpPeer(BGP_PEER1_ID);
+        peer2 = new TestBgpPeer(BGP_PEER2_ID);
+        peer3 = new TestBgpPeer(BGP_PEER3_ID);
+        peers.clear();
+        peers.add(peer1);
+        peers.add(peer2);
+        peers.add(peer3);
+
+        //
+        // Setup the BGP Session Manager to test, and start listening for BGP
+        // connections.
+        //
+        bgpSessionManager = new BgpSessionManager();
+        // NOTE: We use port 0 to bind on any available port
+        bgpSessionManager.start(dummyRouteListener, 0);
+
+        // Get the port number the BGP Session Manager is listening on
+        Channel serverChannel = TestUtils.getField(bgpSessionManager,
+                                                   "serverChannel");
+        SocketAddress socketAddress = serverChannel.getLocalAddress();
+        InetSocketAddress inetSocketAddress =
+            (InetSocketAddress) socketAddress;
+        InetAddress connectToAddress = InetAddresses.forString("127.0.0.1");
+        connectToSocket = new InetSocketAddress(connectToAddress,
+                                                inetSocketAddress.getPort());
+
+        //
+        // Setup the AS Paths
+        //
+        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+        byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+        ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+        segmentAsNumbers1.add((long) 65010);
+        segmentAsNumbers1.add((long) 65020);
+        segmentAsNumbers1.add((long) 65030);
+        BgpRouteEntry.PathSegment pathSegment1 =
+            new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+        pathSegments.add(pathSegment1);
+        asPathShort = new BgpRouteEntry.AsPath(new ArrayList(pathSegments));
+        //
+        byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+        ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+        segmentAsNumbers2.add((long) 65041);
+        segmentAsNumbers2.add((long) 65042);
+        segmentAsNumbers2.add((long) 65043);
+        BgpRouteEntry.PathSegment pathSegment2 =
+            new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+        pathSegments.add(pathSegment2);
+        //
+        asPathLong = new BgpRouteEntry.AsPath(pathSegments);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        bgpSessionManager.stop();
+        bgpSessionManager = null;
+    }
+
+    /**
+     * Gets BGP RIB-IN routes by waiting until they are received.
+     * <p>
+     * NOTE: We keep checking once every 10ms the number of received routes,
+     * up to 5 seconds.
+     * </p>
+     *
+     * @param bgpSession the BGP session that is expected to receive the
+     * routes
+     * @param expectedRoutes the expected number of routes
+     * @return the BGP RIB-IN routes as received within the expected
+     * time interval
+     */
+    private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession,
+                                                      long expectedRoutes)
+        throws InterruptedException {
+        Collection<BgpRouteEntry> bgpRibIn = bgpSession.getBgpRibIn4();
+
+        final int maxChecks = 500;              // Max wait of 5 seconds
+        for (int i = 0; i < maxChecks; i++) {
+            if (bgpRibIn.size() == expectedRoutes) {
+                break;
+            }
+            Thread.sleep(10);
+            bgpRibIn = bgpSession.getBgpRibIn4();
+        }
+
+        return bgpRibIn;
+    }
+
+    /**
+     * Gets BGP merged routes by waiting until they are received.
+     * <p>
+     * NOTE: We keep checking once every 10ms the number of received routes,
+     * up to 5 seconds.
+     * </p>
+     *
+     * @param expectedRoutes the expected number of routes
+     * @return the BGP Session Manager routes as received within the expected
+     * time interval
+     */
+    private Collection<BgpRouteEntry> waitForBgpRoutes(long expectedRoutes)
+        throws InterruptedException {
+        Collection<BgpRouteEntry> bgpRoutes =
+            bgpSessionManager.getBgpRoutes4();
+
+        final int maxChecks = 500;              // Max wait of 5 seconds
+        for (int i = 0; i < maxChecks; i++) {
+            if (bgpRoutes.size() == expectedRoutes) {
+                break;
+            }
+            Thread.sleep(10);
+            bgpRoutes = bgpSessionManager.getBgpRoutes4();
+        }
+
+        return bgpRoutes;
+    }
+
+    /**
+     * Gets a merged BGP route by waiting until it is received.
+     * <p>
+     * NOTE: We keep checking once every 10ms whether the route is received,
+     * up to 5 seconds.
+     * </p>
+     *
+     * @param expectedRoute the expected route
+     * @return the merged BGP route if received within the expected time
+     * interval, otherwise null
+     */
+    private BgpRouteEntry waitForBgpRoute(BgpRouteEntry expectedRoute)
+        throws InterruptedException {
+        Collection<BgpRouteEntry> bgpRoutes =
+            bgpSessionManager.getBgpRoutes4();
+
+        final int maxChecks = 500;              // Max wait of 5 seconds
+        for (int i = 0; i < maxChecks; i++) {
+            for (BgpRouteEntry bgpRouteEntry : bgpRoutes) {
+                if (bgpRouteEntry.equals(expectedRoute) &&
+                    bgpRouteEntry.getBgpSession() ==
+                    expectedRoute.getBgpSession()) {
+                    return bgpRouteEntry;
+                }
+            }
+            Thread.sleep(10);
+            bgpRoutes = bgpSessionManager.getBgpRoutes4();
+        }
+
+        return null;
+    }
+
+    /**
+     * Tests that the BGP OPEN messages have been exchanged, followed by
+     * KEEPALIVE.
+     * <p>
+     * The BGP Peer opens the sessions and transmits OPEN Message, eventually
+     * followed by KEEPALIVE. The tested BGP listener should respond by
+     * OPEN Message, followed by KEEPALIVE.
+     * </p>
+     *
+     * @throws TestUtilsException TestUtils error
+     */
+    @Test
+    public void testExchangedBgpOpenMessages()
+            throws InterruptedException, TestUtilsException {
+        // Initiate the connections
+        peer1.connect(connectToSocket);
+        peer2.connect(connectToSocket);
+        peer3.connect(connectToSocket);
+
+        //
+        // Test the fields from the BGP OPEN message:
+        // BGP version, AS number, BGP ID
+        //
+        for (TestBgpPeer peer : peers) {
+            assertThat(peer.peerFrameDecoder.remoteInfo.bgpVersion(),
+                       is(BgpConstants.BGP_VERSION));
+            assertThat(peer.peerFrameDecoder.remoteInfo.bgpId(),
+                       is(IP_LOOPBACK_ID));
+            assertThat(peer.peerFrameDecoder.remoteInfo.asNumber(),
+                       is(TestBgpPeerChannelHandler.PEER_AS));
+        }
+
+        //
+        // Test that the BgpSession instances have been created
+        //
+        assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID));
+        assertThat(bgpSessionManager.getBgpSessions(), hasSize(3));
+        assertThat(bgpSession1, notNullValue());
+        assertThat(bgpSession2, notNullValue());
+        assertThat(bgpSession3, notNullValue());
+        for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+            long sessionAs = bgpSession.localInfo().asNumber();
+            assertThat(sessionAs, is(TestBgpPeerChannelHandler.PEER_AS));
+        }
+    }
+
+
+    /**
+     * Tests that the BGP OPEN with Capability messages have been exchanged,
+     * followed by KEEPALIVE.
+     * <p>
+     * The BGP Peer opens the sessions and transmits OPEN Message, eventually
+     * followed by KEEPALIVE. The tested BGP listener should respond by
+     * OPEN Message, followed by KEEPALIVE.
+     * </p>
+     *
+     * @throws TestUtilsException TestUtils error
+     */
+    @Test
+    public void testExchangedBgpOpenCapabilityMessages()
+            throws InterruptedException, TestUtilsException {
+        //
+        // Setup the BGP Capabilities for all peers
+        //
+        for (TestBgpPeer peer : peers) {
+            peer.peerChannelHandler.localInfo.setIpv4Unicast();
+            peer.peerChannelHandler.localInfo.setIpv4Multicast();
+            peer.peerChannelHandler.localInfo.setIpv6Unicast();
+            peer.peerChannelHandler.localInfo.setIpv6Multicast();
+            peer.peerChannelHandler.localInfo.setAs4OctetCapability();
+            peer.peerChannelHandler.localInfo.setAs4Number(
+                TestBgpPeerChannelHandler.PEER_AS4);
+        }
+
+        // Initiate the connections
+        peer1.connect(connectToSocket);
+        peer2.connect(connectToSocket);
+        peer3.connect(connectToSocket);
+
+        //
+        // Test the fields from the BGP OPEN message:
+        // BGP version, BGP ID
+        //
+        for (TestBgpPeer peer : peers) {
+            assertThat(peer.peerFrameDecoder.remoteInfo.bgpVersion(),
+                       is(BgpConstants.BGP_VERSION));
+            assertThat(peer.peerFrameDecoder.remoteInfo.bgpId(),
+                       is(IP_LOOPBACK_ID));
+        }
+
+        //
+        // Test that the BgpSession instances have been created,
+        // and contain the appropriate BGP session information.
+        //
+        assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID));
+        assertThat(bgpSessionManager.getBgpSessions(), hasSize(3));
+        assertThat(bgpSession1, notNullValue());
+        assertThat(bgpSession2, notNullValue());
+        assertThat(bgpSession3, notNullValue());
+        for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+            BgpSessionInfo localInfo = bgpSession.localInfo();
+            assertThat(localInfo.ipv4Unicast(), is(true));
+            assertThat(localInfo.ipv4Multicast(), is(true));
+            assertThat(localInfo.ipv6Unicast(), is(true));
+            assertThat(localInfo.ipv6Multicast(), is(true));
+            assertThat(localInfo.as4OctetCapability(), is(true));
+            assertThat(localInfo.asNumber(),
+                       is(TestBgpPeerChannelHandler.PEER_AS4));
+            assertThat(localInfo.as4Number(),
+                       is(TestBgpPeerChannelHandler.PEER_AS4));
+        }
+    }
+
+    /**
+     * Tests that the BGP UPDATE messages have been received and processed.
+     */
+    @Test
+    public void testProcessedBgpUpdateMessages() throws InterruptedException {
+        ChannelBuffer message;
+        BgpRouteEntry bgpRouteEntry;
+        Collection<BgpRouteEntry> bgpRibIn1;
+        Collection<BgpRouteEntry> bgpRibIn2;
+        Collection<BgpRouteEntry> bgpRibIn3;
+        Collection<BgpRouteEntry> bgpRoutes;
+
+        // Initiate the connections
+        peer1.connect(connectToSocket);
+        peer2.connect(connectToSocket);
+        peer3.connect(connectToSocket);
+
+        // Prepare routes to add/delete
+        Collection<Ip4Prefix> addedRoutes = new LinkedList<>();
+        Collection<Ip4Prefix> withdrawnRoutes = new LinkedList<>();
+
+        //
+        // Add and delete some routes
+        //
+        addedRoutes.add(Ip4Prefix.valueOf("0.0.0.0/0"));
+        addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+        addedRoutes.add(Ip4Prefix.valueOf("30.0.0.0/16"));
+        addedRoutes.add(Ip4Prefix.valueOf("40.0.0.0/24"));
+        addedRoutes.add(Ip4Prefix.valueOf("50.0.0.0/32"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("60.0.0.0/8"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("70.0.0.0/16"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("80.0.0.0/24"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("90.0.0.0/32"));
+        // Write the routes
+        message = peer1.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP1_ROUTER,
+                        DEFAULT_LOCAL_PREF,
+                        DEFAULT_MULTI_EXIT_DISC,
+                        asPathLong,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+        //
+        // Check that the routes have been received, processed and stored
+        //
+        bgpRibIn1 = waitForBgpRibIn(bgpSession1, 5);
+        assertThat(bgpRibIn1, hasSize(5));
+        bgpRoutes = waitForBgpRoutes(5);
+        assertThat(bgpRoutes, hasSize(5));
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("0.0.0.0/0"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("30.0.0.0/16"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("40.0.0.0/24"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("50.0.0.0/32"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+        //
+        // Delete some routes
+        //
+        addedRoutes = new LinkedList<>();
+        withdrawnRoutes = new LinkedList<>();
+        withdrawnRoutes.add(Ip4Prefix.valueOf("0.0.0.0/0"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("50.0.0.0/32"));
+        // Write the routes
+        message = peer1.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP1_ROUTER,
+                        DEFAULT_LOCAL_PREF,
+                        DEFAULT_MULTI_EXIT_DISC,
+                        asPathLong,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+        //
+        // Check that the routes have been received, processed and stored
+        //
+        bgpRibIn1 = waitForBgpRibIn(bgpSession1, 3);
+        assertThat(bgpRibIn1, hasSize(3));
+        bgpRoutes = waitForBgpRoutes(3);
+        assertThat(bgpRoutes, hasSize(3));
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("30.0.0.0/16"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("40.0.0.0/24"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              DEFAULT_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+
+        // Close the channels and test there are no routes
+        peer1.peerChannelHandler.closeChannel();
+        peer2.peerChannelHandler.closeChannel();
+        peer3.peerChannelHandler.closeChannel();
+        bgpRoutes = waitForBgpRoutes(0);
+        assertThat(bgpRoutes, hasSize(0));
+    }
+
+    /**
+     * Tests the BGP route preference.
+     */
+    @Test
+    public void testBgpRoutePreference() throws InterruptedException {
+        ChannelBuffer message;
+        BgpRouteEntry bgpRouteEntry;
+        Collection<BgpRouteEntry> bgpRibIn1;
+        Collection<BgpRouteEntry> bgpRibIn2;
+        Collection<BgpRouteEntry> bgpRibIn3;
+        Collection<BgpRouteEntry> bgpRoutes;
+        Collection<Ip4Prefix> addedRoutes = new LinkedList<>();
+        Collection<Ip4Prefix> withdrawnRoutes = new LinkedList<>();
+
+        // Initiate the connections
+        peer1.connect(connectToSocket);
+        peer2.connect(connectToSocket);
+        peer3.connect(connectToSocket);
+
+        //
+        // Setup the initial set of routes to Peer1
+        //
+        addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+        addedRoutes.add(Ip4Prefix.valueOf("30.0.0.0/16"));
+        // Write the routes
+        message = peer1.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP1_ROUTER,
+                        DEFAULT_LOCAL_PREF,
+                        DEFAULT_MULTI_EXIT_DISC,
+                        asPathLong,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+        bgpRoutes = waitForBgpRoutes(2);
+        assertThat(bgpRoutes, hasSize(2));
+
+        //
+        // Add a route entry to Peer2 with a better LOCAL_PREF
+        //
+        addedRoutes = new LinkedList<>();
+        withdrawnRoutes = new LinkedList<>();
+        addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+        // Write the routes
+        message = peer2.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP2_ROUTER,
+                        BETTER_LOCAL_PREF,
+                        DEFAULT_MULTI_EXIT_DISC,
+                        asPathLong,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer2.peerChannelHandler.savedCtx.getChannel().write(message);
+        //
+        // Check that the routes have been received, processed and stored
+        //
+        bgpRibIn2 = waitForBgpRibIn(bgpSession2, 1);
+        assertThat(bgpRibIn2, hasSize(1));
+        bgpRoutes = waitForBgpRoutes(2);
+        assertThat(bgpRoutes, hasSize(2));
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession2,
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
+                              NEXT_HOP2_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathLong,
+                              BETTER_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn2, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+        //
+        // Add a route entry to Peer3 with a shorter AS path
+        //
+        addedRoutes = new LinkedList<>();
+        withdrawnRoutes = new LinkedList<>();
+        addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+        // Write the routes
+        message = peer3.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP3_ROUTER,
+                        BETTER_LOCAL_PREF,
+                        DEFAULT_MULTI_EXIT_DISC,
+                        asPathShort,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer3.peerChannelHandler.savedCtx.getChannel().write(message);
+        //
+        // Check that the routes have been received, processed and stored
+        //
+        bgpRibIn3 = waitForBgpRibIn(bgpSession3, 1);
+        assertThat(bgpRibIn3, hasSize(1));
+        bgpRoutes = waitForBgpRoutes(2);
+        assertThat(bgpRoutes, hasSize(2));
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession3,
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
+                              NEXT_HOP3_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathShort,
+                              BETTER_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn3, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+        //
+        // Cleanup in preparation for next test: delete old route entry from
+        // Peer2
+        //
+        addedRoutes = new LinkedList<>();
+        withdrawnRoutes = new LinkedList<>();
+        withdrawnRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+        // Write the routes
+        message = peer2.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP2_ROUTER,
+                        BETTER_LOCAL_PREF,
+                        BETTER_MULTI_EXIT_DISC,
+                        asPathShort,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer2.peerChannelHandler.savedCtx.getChannel().write(message);
+        //
+        // Check that the routes have been received, processed and stored
+        //
+        bgpRibIn2 = waitForBgpRibIn(bgpSession2, 0);
+        assertThat(bgpRibIn2, hasSize(0));
+
+        //
+        // Add a route entry to Peer2 with a better MED
+        //
+        addedRoutes = new LinkedList<>();
+        withdrawnRoutes = new LinkedList<>();
+        addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+        // Write the routes
+        message = peer2.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP2_ROUTER,
+                        BETTER_LOCAL_PREF,
+                        BETTER_MULTI_EXIT_DISC,
+                        asPathShort,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer2.peerChannelHandler.savedCtx.getChannel().write(message);
+        //
+        // Check that the routes have been received, processed and stored
+        //
+        bgpRibIn2 = waitForBgpRibIn(bgpSession2, 1);
+        assertThat(bgpRibIn2, hasSize(1));
+        bgpRoutes = waitForBgpRoutes(2);
+        assertThat(bgpRoutes, hasSize(2));
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession2,
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
+                              NEXT_HOP2_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathShort,
+                              BETTER_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(BETTER_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn2, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+        //
+        // Add a route entry to Peer1 with a better (lower) BGP ID
+        //
+        addedRoutes = new LinkedList<>();
+        withdrawnRoutes = new LinkedList<>();
+        addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("30.0.0.0/16"));
+        // Write the routes
+        message = peer1.peerChannelHandler.prepareBgpUpdate(
+                        NEXT_HOP1_ROUTER,
+                        BETTER_LOCAL_PREF,
+                        BETTER_MULTI_EXIT_DISC,
+                        asPathShort,
+                        addedRoutes,
+                        withdrawnRoutes);
+        peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+        //
+        // Check that the routes have been received, processed and stored
+        //
+        bgpRibIn1 = waitForBgpRibIn(bgpSession1, 1);
+        assertThat(bgpRibIn1, hasSize(1));
+        bgpRoutes = waitForBgpRoutes(1);
+        assertThat(bgpRoutes, hasSize(1));
+        //
+        bgpRouteEntry =
+            new BgpRouteEntry(bgpSession1,
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
+                              NEXT_HOP1_ROUTER,
+                              (byte) BgpConstants.Update.Origin.IGP,
+                              asPathShort,
+                              BETTER_LOCAL_PREF);
+        bgpRouteEntry.setMultiExitDisc(BETTER_MULTI_EXIT_DISC);
+        assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+        assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+
+        // Close the channels and test there are no routes
+        peer1.peerChannelHandler.closeChannel();
+        peer2.peerChannelHandler.closeChannel();
+        peer3.peerChannelHandler.closeChannel();
+        bgpRoutes = waitForBgpRoutes(0);
+        assertThat(bgpRoutes, hasSize(0));
+    }
+}
diff --git a/apps/routing/src/test/java/org/onosproject/routing/bgp/PathSegmentTest.java b/apps/routing/src/test/java/org/onosproject/routing/bgp/PathSegmentTest.java
new file mode 100644
index 0000000..4c6c5fc
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/bgp/PathSegmentTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpRouteEntry.PathSegment class.
+ */
+public class PathSegmentTest {
+    /**
+     * Generates a Path Segment.
+     *
+     * @return a generated PathSegment
+     */
+    private BgpRouteEntry.PathSegment generatePathSegment() {
+        byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+        ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 1);
+        segmentAsNumbers.add((long) 2);
+        segmentAsNumbers.add((long) 3);
+        BgpRouteEntry.PathSegment pathSegment =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+
+        return pathSegment;
+    }
+
+    /**
+     * Tests valid class constructor.
+     */
+    @Test
+    public void testConstructor() {
+        BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+        String expectedString =
+            "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}";
+        assertThat(pathSegment.toString(), is(expectedString));
+    }
+
+    /**
+     * Tests invalid class constructor for null Segment AS Numbers.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConstructorNullSegmentAsNumbers() {
+        byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+        ArrayList<Long> segmentAsNumbers = null;
+        new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+    }
+
+    /**
+     * Tests getting the fields of a Path Segment.
+     */
+    @Test
+    public void testGetFields() {
+        // Create the fields to compare against
+        byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+        ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 1);
+        segmentAsNumbers.add((long) 2);
+        segmentAsNumbers.add((long) 3);
+
+        // Generate the entry to test
+        BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+        assertThat(pathSegment.getType(), is(pathSegmentType));
+        assertThat(pathSegment.getSegmentAsNumbers(), is(segmentAsNumbers));
+    }
+
+    /**
+     * Tests equality of {@link BgpRouteEntry.PathSegment}.
+     */
+    @Test
+    public void testEquality() {
+        BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
+        BgpRouteEntry.PathSegment pathSegment2 = generatePathSegment();
+
+        assertThat(pathSegment1, is(pathSegment2));
+    }
+
+    /**
+     * Tests non-equality of {@link BgpRouteEntry.PathSegment}.
+     */
+    @Test
+    public void testNonEquality() {
+        BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
+
+        // Setup Path Segment 2
+        byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+        ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+        segmentAsNumbers.add((long) 1);
+        segmentAsNumbers.add((long) 22);                        // Different
+        segmentAsNumbers.add((long) 3);
+        //
+        BgpRouteEntry.PathSegment pathSegment2 =
+            new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+
+        assertThat(pathSegment1, Matchers.is(not(pathSegment2)));
+    }
+
+    /**
+     * Tests object string representation.
+     */
+    @Test
+    public void testToString() {
+        BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+        String expectedString =
+            "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}";
+        assertThat(pathSegment.toString(), is(expectedString));
+    }
+}
diff --git a/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerChannelHandler.java b/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerChannelHandler.java
new file mode 100644
index 0000000..ac887aa
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerChannelHandler.java
@@ -0,0 +1,207 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+
+import java.util.Collection;
+
+/**
+ * Class for handling the remote BGP Peer session.
+ */
+class TestBgpPeerChannelHandler extends SimpleChannelHandler {
+    static final long PEER_AS = 65001;
+    static final long PEER_AS4 = 0x12345678;
+    static final int PEER_HOLDTIME = 120;       // 120 seconds
+
+    final BgpSessionInfo localInfo = new BgpSessionInfo();
+    ChannelHandlerContext savedCtx;
+
+    /**
+     * Constructor for given BGP ID.
+     *
+     * @param bgpId the BGP ID to use
+     */
+    TestBgpPeerChannelHandler(Ip4Address bgpId) {
+        this.localInfo.setBgpVersion(BgpConstants.BGP_VERSION);
+        this.localInfo.setBgpId(bgpId);
+        this.localInfo.setAsNumber(PEER_AS);
+        this.localInfo.setHoldtime(PEER_HOLDTIME);
+    }
+
+    /**
+     * Closes the channel.
+     */
+    void closeChannel() {
+        savedCtx.getChannel().close();
+    }
+
+    @Override
+    public void channelConnected(ChannelHandlerContext ctx,
+                                 ChannelStateEvent channelEvent) {
+        this.savedCtx = ctx;
+        // Prepare and transmit BGP OPEN message
+        ChannelBuffer message = BgpOpen.prepareBgpOpen(localInfo);
+        ctx.getChannel().write(message);
+
+        // Prepare and transmit BGP KEEPALIVE message
+        message = BgpKeepalive.prepareBgpKeepalive();
+        ctx.getChannel().write(message);
+    }
+
+    @Override
+    public void channelDisconnected(ChannelHandlerContext ctx,
+                                    ChannelStateEvent channelEvent) {
+        // Nothing to do
+    }
+
+    /**
+     * Prepares BGP UPDATE message.
+     *
+     * @param nextHopRouter the next-hop router address for the routes to add
+     * @param localPref the local preference for the routes to use
+     * @param multiExitDisc the MED value
+     * @param asPath the AS path for the routes to add
+     * @param addedRoutes the routes to add
+     * @param withdrawnRoutes the routes to withdraw
+     * @return the message to transmit (BGP header included)
+     */
+    ChannelBuffer prepareBgpUpdate(Ip4Address nextHopRouter,
+                                   long localPref,
+                                   long multiExitDisc,
+                                   BgpRouteEntry.AsPath asPath,
+                                   Collection<Ip4Prefix> addedRoutes,
+                                   Collection<Ip4Prefix> withdrawnRoutes) {
+        int attrFlags;
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+        ChannelBuffer pathAttributes =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        // Encode the Withdrawn Routes
+        ChannelBuffer encodedPrefixes = encodePackedPrefixes(withdrawnRoutes);
+        message.writeShort(encodedPrefixes.readableBytes());
+        message.writeBytes(encodedPrefixes);
+
+        // Encode the Path Attributes
+        // ORIGIN: IGP
+        attrFlags = 0x40;                               // Transitive flag
+        pathAttributes.writeByte(attrFlags);
+        pathAttributes.writeByte(BgpConstants.Update.Origin.TYPE);
+        pathAttributes.writeByte(1);                    // Data length
+        pathAttributes.writeByte(BgpConstants.Update.Origin.IGP);
+
+        // AS_PATH: asPath
+        attrFlags = 0x40;                               // Transitive flag
+        pathAttributes.writeByte(attrFlags);
+        pathAttributes.writeByte(BgpConstants.Update.AsPath.TYPE);
+        ChannelBuffer encodedAsPath = encodeAsPath(asPath);
+        pathAttributes.writeByte(encodedAsPath.readableBytes()); // Data length
+        pathAttributes.writeBytes(encodedAsPath);
+        // NEXT_HOP: nextHopRouter
+        attrFlags = 0x40;                               // Transitive flag
+        pathAttributes.writeByte(attrFlags);
+        pathAttributes.writeByte(BgpConstants.Update.NextHop.TYPE);
+        pathAttributes.writeByte(4);                    // Data length
+        pathAttributes.writeInt(nextHopRouter.toInt()); // Next-hop router
+        // LOCAL_PREF: localPref
+        attrFlags = 0x40;                               // Transitive flag
+        pathAttributes.writeByte(attrFlags);
+        pathAttributes.writeByte(BgpConstants.Update.LocalPref.TYPE);
+        pathAttributes.writeByte(4);                    // Data length
+        pathAttributes.writeInt((int) localPref);       // Preference value
+        // MULTI_EXIT_DISC: multiExitDisc
+        attrFlags = 0x80;                               // Optional
+                                                        // Non-Transitive flag
+        pathAttributes.writeByte(attrFlags);
+        pathAttributes.writeByte(BgpConstants.Update.MultiExitDisc.TYPE);
+        pathAttributes.writeByte(4);                    // Data length
+        pathAttributes.writeInt((int) multiExitDisc);   // Preference value
+        // The NLRI prefixes
+        encodedPrefixes = encodePackedPrefixes(addedRoutes);
+
+        // Write the Path Attributes, beginning with its length
+        message.writeShort(pathAttributes.readableBytes());
+        message.writeBytes(pathAttributes);
+        message.writeBytes(encodedPrefixes);
+
+        return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_UPDATE,
+                                            message);
+    }
+
+    /**
+     * Encodes a collection of IPv4 network prefixes in a packed format.
+     * <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 prefixes the prefixes to encode
+     * @return the buffer with the encoded prefixes
+     */
+    private ChannelBuffer encodePackedPrefixes(Collection<Ip4Prefix> prefixes) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        // Write each of the prefixes
+        for (Ip4Prefix prefix : prefixes) {
+            int prefixBitlen = prefix.prefixLength();
+            int prefixBytelen = (prefixBitlen + 7) / 8;         // Round-up
+            message.writeByte(prefixBitlen);
+
+            Ip4Address address = prefix.address();
+            long value = address.toInt() & 0xffffffffL;
+            for (int i = 0; i < Ip4Address.BYTE_LENGTH; i++) {
+                if (prefixBytelen-- == 0) {
+                    break;
+                }
+                long nextByte =
+                    (value >> ((Ip4Address.BYTE_LENGTH - i - 1) * 8)) & 0xff;
+                message.writeByte((int) nextByte);
+            }
+        }
+
+        return message;
+    }
+
+    /**
+     * Encodes an AS path.
+     *
+     * @param asPath the AS path to encode
+     * @return the buffer with the encoded AS path
+     */
+    private ChannelBuffer encodeAsPath(BgpRouteEntry.AsPath asPath) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        for (BgpRouteEntry.PathSegment pathSegment : asPath.getPathSegments()) {
+            message.writeByte(pathSegment.getType());
+            message.writeByte(pathSegment.getSegmentAsNumbers().size());
+            for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+                message.writeShort(asNumber.intValue());
+            }
+        }
+
+        return message;
+    }
+}
diff --git a/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerFrameDecoder.java b/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerFrameDecoder.java
new file mode 100644
index 0000000..9747ea9
--- /dev/null
+++ b/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerFrameDecoder.java
@@ -0,0 +1,175 @@
+/*
+ * 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.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.onlab.packet.Ip4Address;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Class for handling the decoding of the BGP messages at the remote
+ * BGP peer session.
+ */
+class TestBgpPeerFrameDecoder extends FrameDecoder {
+    final BgpSessionInfo remoteInfo = new BgpSessionInfo();
+
+    final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1);
+    final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1);
+
+    @Override
+    protected Object decode(ChannelHandlerContext ctx,
+                            Channel channel,
+                            ChannelBuffer buf) throws Exception {
+        // Test for minimum length of the BGP message
+        if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
+            // No enough data received
+            return null;
+        }
+
+        //
+        // Mark the current buffer position in case we haven't received
+        // the whole message.
+        //
+        buf.markReaderIndex();
+
+        //
+        // Read and check the BGP message Marker field: it must be all ones
+        //
+        byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
+        buf.readBytes(marker);
+        for (int i = 0; i < marker.length; i++) {
+            if (marker[i] != (byte) 0xff) {
+                // ERROR: Connection Not Synchronized. Close the channel.
+                ctx.getChannel().close();
+                return null;
+            }
+        }
+
+        //
+        // Read and check the BGP message Length field
+        //
+        int length = buf.readUnsignedShort();
+        if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
+            (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
+            // ERROR: Bad Message Length. Close the channel.
+            ctx.getChannel().close();
+            return null;
+        }
+
+        //
+        // Test whether the rest of the message is received:
+        // So far we have read the Marker (16 octets) and the
+        // Length (2 octets) fields.
+        //
+        int remainingMessageLen =
+            length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
+        if (buf.readableBytes() < remainingMessageLen) {
+            // No enough data received
+            buf.resetReaderIndex();
+            return null;
+        }
+
+        //
+        // Read the BGP message Type field, and process based on that type
+        //
+        int type = buf.readUnsignedByte();
+        remainingMessageLen--;      // Adjust after reading the type
+        ChannelBuffer message = buf.readBytes(remainingMessageLen);
+
+        //
+        // Process the remaining of the message based on the message type
+        //
+        switch (type) {
+        case BgpConstants.BGP_TYPE_OPEN:
+            processBgpOpen(ctx, message);
+            break;
+        case BgpConstants.BGP_TYPE_UPDATE:
+            // NOTE: Not used as part of the test, because ONOS does not
+            // originate UPDATE messages.
+            break;
+        case BgpConstants.BGP_TYPE_NOTIFICATION:
+            // NOTE: Not used as part of the testing (yet)
+            break;
+        case BgpConstants.BGP_TYPE_KEEPALIVE:
+            processBgpKeepalive(ctx, message);
+            break;
+        default:
+            // ERROR: Bad Message Type. Close the channel.
+            ctx.getChannel().close();
+            return null;
+        }
+
+        return null;
+    }
+
+    /**
+     * Processes BGP OPEN message.
+     *
+     * @param ctx the Channel Handler Context.
+     * @param message the message to process.
+     */
+    private void processBgpOpen(ChannelHandlerContext ctx,
+                                ChannelBuffer message) {
+        int minLength =
+            BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+        if (message.readableBytes() < minLength) {
+            // ERROR: Bad Message Length. Close the channel.
+            ctx.getChannel().close();
+            return;
+        }
+
+        //
+        // Parse the OPEN message
+        //
+        remoteInfo.setBgpVersion(message.readUnsignedByte());
+        remoteInfo.setAsNumber(message.readUnsignedShort());
+        remoteInfo.setHoldtime(message.readUnsignedShort());
+        remoteInfo.setBgpId(Ip4Address.valueOf((int) message.readUnsignedInt()));
+        // Optional Parameters
+        int optParamLen = message.readUnsignedByte();
+        if (message.readableBytes() < optParamLen) {
+            // ERROR: Bad Message Length. Close the channel.
+            ctx.getChannel().close();
+            return;
+        }
+        message.readBytes(optParamLen);             // NOTE: data ignored
+
+        // BGP OPEN message successfully received
+        receivedOpenMessageLatch.countDown();
+    }
+
+    /**
+     * Processes BGP KEEPALIVE message.
+     *
+     * @param ctx the Channel Handler Context.
+     * @param message the message to process.
+     */
+    private void processBgpKeepalive(ChannelHandlerContext ctx,
+                                     ChannelBuffer message) {
+        if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
+            BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
+            // ERROR: Bad Message Length. Close the channel.
+            ctx.getChannel().close();
+            return;
+        }
+        // BGP KEEPALIVE message successfully received
+        receivedKeepaliveMessageLatch.countDown();
+    }
+}