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-api/pom.xml b/apps/routing-api/pom.xml
new file mode 100644
index 0000000..c83cb8f
--- /dev/null
+++ b/apps/routing-api/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onos-apps</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>onos-app-routing-api</artifactId>
+
+    <packaging>bundle</packaging>
+    <description>API for routing applications</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/BgpService.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/BgpService.java
new file mode 100644
index 0000000..c7e58d4
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/BgpService.java
@@ -0,0 +1,35 @@
+/*
+ * 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.routingapi;
+
+/**
+ * Provides a way of interacting with the BGP protocol component.
+ */
+public interface BgpService {
+
+    /**
+     * Starts the BGP service.
+     *
+     * @param routeListener listener to send route updates to
+     * @param bgpPort port number to listen on
+     */
+    void start(RouteListener routeListener, int bgpPort);
+
+    /**
+     * Stops the BGP service.
+     */
+    void stop();
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/FibEntry.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/FibEntry.java
new file mode 100644
index 0000000..86b40f8
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/FibEntry.java
@@ -0,0 +1,100 @@
+/*
+ * 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.routingapi;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+
+import java.util.Objects;
+
+/**
+ * An entry in the Forwarding Information Base (FIB).
+ */
+public class FibEntry {
+
+    private final IpPrefix prefix;
+    private final IpAddress nextHopIp;
+    private final MacAddress nextHopMac;
+
+    /**
+     * Creates a new FIB entry.
+     *
+     * @param prefix IP prefix of the FIB entry
+     * @param nextHopIp IP address of the next hop
+     * @param nextHopMac MAC address of the next hop
+     */
+    public FibEntry(IpPrefix prefix, IpAddress nextHopIp, MacAddress nextHopMac) {
+        this.prefix = prefix;
+        this.nextHopIp = nextHopIp;
+        this.nextHopMac = nextHopMac;
+    }
+
+    /**
+     * Returns the IP prefix of the FIB entry.
+     *
+     * @return the IP prefix
+     */
+    public IpPrefix prefix() {
+        return prefix;
+    }
+
+    /**
+     * Returns the IP address of the next hop.
+     *
+     * @return the IP address
+     */
+    public IpAddress nextHopIp() {
+        return nextHopIp;
+    }
+
+    /**
+     * Returns the MAC address of the next hop.
+     *
+     * @return the MAC address
+     */
+    public MacAddress nextHopMac() {
+        return nextHopMac;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof FibEntry)) {
+            return false;
+        }
+
+        FibEntry that = (FibEntry) o;
+
+        return Objects.equals(this.prefix, that.prefix) &&
+                Objects.equals(this.nextHopIp, that.nextHopIp) &&
+                Objects.equals(this.nextHopMac, that.nextHopMac);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(prefix, nextHopIp, nextHopMac);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("prefix", prefix)
+                .add("nextHopIp", nextHopIp)
+                .add("nextHopMac", nextHopMac)
+                .toString();
+    }
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/FibListener.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/FibListener.java
new file mode 100644
index 0000000..218139e
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/FibListener.java
@@ -0,0 +1,35 @@
+/*
+ * 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.routingapi;
+
+import java.util.Collection;
+
+/**
+ * A component that is able to process Forwarding Information Base (FIB) updates.
+ */
+public interface FibListener {
+
+    /**
+     * Signals the FIB component of changes to the FIB.
+     *
+     * @param updates FIB updates of the UDPATE type
+     * @param withdraws FIB updates of the WITHDRAW type
+     */
+    // TODO this interface should use only one collection when we have the new
+    // intent key API
+    void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws);
+
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/FibUpdate.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/FibUpdate.java
new file mode 100644
index 0000000..2b0215c
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/FibUpdate.java
@@ -0,0 +1,98 @@
+/*
+ * 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.routingapi;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+/**
+ * Represents a change to the Forwarding Information Base (FIB).
+ */
+public class FibUpdate {
+
+    /**
+     * Specifies the type of the FIB update.
+     */
+    public enum Type {
+        /**
+         * The update contains a new or updated FIB entry for a prefix.
+         */
+        UPDATE,
+
+        /**
+         * The update signals that a prefix should be removed from the FIB.
+         */
+        DELETE
+    }
+
+    private final Type type;
+    private final FibEntry entry;
+
+    /**
+     * Creates a new FIB update.
+     *
+     * @param type type of the update
+     * @param entry FIB entry describing the update
+     */
+    public FibUpdate(Type type, FibEntry entry) {
+        this.type = type;
+        this.entry = entry;
+    }
+
+    /**
+     * Returns the type of the update.
+     *
+     * @return update type
+     */
+    public Type type() {
+        return type;
+    }
+
+    /**
+     * Returns the FIB entry which contains update information.
+     *
+     * @return the FIB entry
+     */
+    public FibEntry entry() {
+        return entry;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof FibUpdate)) {
+            return false;
+        }
+
+        FibUpdate that = (FibUpdate) o;
+
+        return Objects.equals(this.type, that.type) &&
+                Objects.equals(this.entry, that.entry);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, entry);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("type", type)
+                .add("entry", entry)
+                .toString();
+    }
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteEntry.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteEntry.java
new file mode 100644
index 0000000..3e861e5
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteEntry.java
@@ -0,0 +1,128 @@
+/*
+ * 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.routingapi;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a route entry for an IP prefix.
+ */
+public class RouteEntry {
+    private final IpPrefix prefix;              // The IP prefix
+    private final IpAddress nextHop;            // Next-hop IP address
+
+    /**
+     * Class constructor.
+     *
+     * @param prefix the IP prefix of the route
+     * @param nextHop the next hop IP address for the route
+     */
+    public RouteEntry(IpPrefix prefix, IpAddress nextHop) {
+        this.prefix = checkNotNull(prefix);
+        this.nextHop = checkNotNull(nextHop);
+    }
+
+    /**
+     * Returns the IP version of the route.
+     *
+     * @return the IP version of the route
+     */
+    public IpAddress.Version version() {
+        return nextHop.version();
+    }
+
+    /**
+     * Returns the IP prefix of the route.
+     *
+     * @return the IP prefix of the route
+     */
+    public IpPrefix prefix() {
+        return prefix;
+    }
+
+    /**
+     * Returns the next hop IP address for the route.
+     *
+     * @return the next hop IP address for the route
+     */
+    public IpAddress nextHop() {
+        return nextHop;
+    }
+
+    /**
+     * Creates the binary string representation of an IP prefix.
+     * The prefix can be either IPv4 or IPv6.
+     * The string length is equal to the prefix length.
+     *
+     * @param ipPrefix the IP prefix to use
+     * @return the binary string representation
+     */
+    public static String createBinaryString(IpPrefix ipPrefix) {
+        if (ipPrefix.prefixLength() == 0) {
+            return "";
+        }
+
+        byte[] octets = ipPrefix.address().toOctets();
+        StringBuilder result = new StringBuilder(ipPrefix.prefixLength());
+        for (int i = 0; i < ipPrefix.prefixLength(); i++) {
+            int byteOffset = i / Byte.SIZE;
+            int bitOffset = i % Byte.SIZE;
+            int mask = 1 << (Byte.SIZE - 1 - bitOffset);
+            byte value = octets[byteOffset];
+            boolean isSet = ((value & mask) != 0);
+            result.append(isSet ? "1" : "0");
+        }
+        return result.toString();
+    }
+
+    @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;
+        }
+
+        RouteEntry otherRoute = (RouteEntry) other;
+        return Objects.equals(this.prefix, otherRoute.prefix) &&
+            Objects.equals(this.nextHop, otherRoute.nextHop);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(prefix, nextHop);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+            .add("prefix", prefix)
+            .add("nextHop", nextHop)
+            .toString();
+    }
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteListener.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteListener.java
new file mode 100644
index 0000000..55fa70d
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteListener.java
@@ -0,0 +1,30 @@
+/*
+ * 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.routingapi;
+
+import java.util.Collection;
+
+/**
+ * An interface to receive route updates from route providers.
+ */
+public interface RouteListener {
+    /**
+     * Receives a route update from a route provider.
+     *
+     * @param routeUpdates the collection with updated route information
+     */
+    public void update(Collection<RouteUpdate> routeUpdates);
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteUpdate.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteUpdate.java
new file mode 100644
index 0000000..47aa3c2
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/RouteUpdate.java
@@ -0,0 +1,107 @@
+/*
+ * 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.routingapi;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a change in routing information.
+ */
+public class RouteUpdate {
+    private final Type type;                    // The route update type
+    private final RouteEntry routeEntry;        // The updated route entry
+
+    /**
+     * Specifies the type of a route update.
+     * <p>
+     * Route updates can either provide updated information for a route, or
+     * withdraw a previously updated route.
+     * </p>
+     */
+    public enum Type {
+        /**
+         * The update contains updated route information for a route.
+         */
+        UPDATE,
+        /**
+         * The update withdraws the route, meaning any previous information is
+         * no longer valid.
+         */
+        DELETE
+    }
+
+    /**
+     * Class constructor.
+     *
+     * @param type the type of the route update
+     * @param routeEntry the route entry with the update
+     */
+    public RouteUpdate(Type type, RouteEntry routeEntry) {
+        this.type = type;
+        this.routeEntry = checkNotNull(routeEntry);
+    }
+
+    /**
+     * Returns the type of the route update.
+     *
+     * @return the type of the update
+     */
+    public Type type() {
+        return type;
+    }
+
+    /**
+     * Returns the route entry the route update is for.
+     *
+     * @return the route entry the route update is for
+     */
+    public RouteEntry routeEntry() {
+        return routeEntry;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+
+        if (!(other instanceof RouteUpdate)) {
+            return false;
+        }
+
+        RouteUpdate otherUpdate = (RouteUpdate) other;
+
+        return Objects.equals(this.type, otherUpdate.type) &&
+            Objects.equals(this.routeEntry, otherUpdate.routeEntry);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, routeEntry);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+            .add("type", type)
+            .add("routeEntry", routeEntry)
+            .toString();
+    }
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routingapi/RoutingService.java b/apps/routing-api/src/main/java/org/onosproject/routingapi/RoutingService.java
new file mode 100644
index 0000000..e0f9383
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routingapi/RoutingService.java
@@ -0,0 +1,50 @@
+/*
+ * 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.routingapi;
+
+import java.util.Collection;
+
+/**
+ * Provides a way of interacting with the RIB management component.
+ */
+public interface RoutingService {
+
+    /**
+     * Starts the routing service.
+     *
+     * @param listener listener to send FIB updates to
+     */
+    public void start(FibListener listener);
+
+    /**
+     * Stops the routing service.
+     */
+    public void stop();
+
+    /**
+     * Gets all IPv4 routes known to SDN-IP.
+     *
+     * @return the SDN-IP IPv4 routes
+     */
+    public Collection<RouteEntry> getRoutes4();
+
+    /**
+     * Gets all IPv6 routes known to SDN-IP.
+     *
+     * @return the SDN-IP IPv6 routes
+     */
+    public Collection<RouteEntry> getRoutes6();
+}
diff --git a/apps/routing-api/src/test/java/org/onosproject/routingapi/RouteEntryTest.java b/apps/routing-api/src/test/java/org/onosproject/routingapi/RouteEntryTest.java
new file mode 100644
index 0000000..acce45a
--- /dev/null
+++ b/apps/routing-api/src/test/java/org/onosproject/routingapi/RouteEntryTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.routingapi;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the RouteEntry class.
+ */
+public class RouteEntryTest {
+    /**
+     * Tests valid class constructor.
+     */
+    @Test
+    public void testConstructor() {
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+
+        RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
+        assertThat(routeEntry.toString(),
+                   is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}"));
+    }
+
+    /**
+     * Tests invalid class constructor for null IPv4 prefix.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConstructorNullPrefix() {
+        Ip4Prefix prefix = null;
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+
+        new RouteEntry(prefix, nextHop);
+    }
+
+    /**
+     * Tests invalid class constructor for null IPv4 next-hop.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConstructorNullNextHop() {
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = null;
+
+        new RouteEntry(prefix, nextHop);
+    }
+
+    /**
+     * Tests getting the fields of a route entry.
+     */
+    @Test
+    public void testGetFields() {
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+
+        RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
+        assertThat(routeEntry.prefix(), is(prefix));
+        assertThat(routeEntry.nextHop(), is(nextHop));
+    }
+
+    /**
+     * Tests creating a binary string from IPv4 prefix.
+     */
+    @Test
+    public void testCreateBinaryString() {
+        Ip4Prefix prefix;
+
+        prefix = Ip4Prefix.valueOf("0.0.0.0/0");
+        assertThat(RouteEntry.createBinaryString(prefix), is(""));
+
+        prefix = Ip4Prefix.valueOf("192.168.166.0/22");
+        assertThat(RouteEntry.createBinaryString(prefix),
+                   is("1100000010101000101001"));
+
+        prefix = Ip4Prefix.valueOf("192.168.166.0/23");
+        assertThat(RouteEntry.createBinaryString(prefix),
+                   is("11000000101010001010011"));
+
+        prefix = Ip4Prefix.valueOf("192.168.166.0/24");
+        assertThat(RouteEntry.createBinaryString(prefix),
+                   is("110000001010100010100110"));
+
+        prefix = Ip4Prefix.valueOf("130.162.10.1/25");
+        assertThat(RouteEntry.createBinaryString(prefix),
+                   is("1000001010100010000010100"));
+
+        prefix = Ip4Prefix.valueOf("255.255.255.255/32");
+        assertThat(RouteEntry.createBinaryString(prefix),
+                   is("11111111111111111111111111111111"));
+    }
+
+    /**
+     * Tests equality of {@link RouteEntry}.
+     */
+    @Test
+    public void testEquality() {
+        Ip4Prefix prefix1 = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop1 = Ip4Address.valueOf("5.6.7.8");
+        RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
+
+        Ip4Prefix prefix2 = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop2 = Ip4Address.valueOf("5.6.7.8");
+        RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2);
+
+        assertThat(routeEntry1, is(routeEntry2));
+    }
+
+    /**
+     * Tests non-equality of {@link RouteEntry}.
+     */
+    @Test
+    public void testNonEquality() {
+        Ip4Prefix prefix1 = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop1 = Ip4Address.valueOf("5.6.7.8");
+        RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
+
+        Ip4Prefix prefix2 = Ip4Prefix.valueOf("1.2.3.0/25");      // Different
+        Ip4Address nextHop2 = Ip4Address.valueOf("5.6.7.8");
+        RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2);
+
+        Ip4Prefix prefix3 = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop3 = Ip4Address.valueOf("5.6.7.9");      // Different
+        RouteEntry routeEntry3 = new RouteEntry(prefix3, nextHop3);
+
+        assertThat(routeEntry1, Matchers.is(not(routeEntry2)));
+        assertThat(routeEntry1, Matchers.is(not(routeEntry3)));
+    }
+
+    /**
+     * Tests object string representation.
+     */
+    @Test
+    public void testToString() {
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+        RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
+
+        assertThat(routeEntry.toString(),
+                   is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}"));
+    }
+}