Merge "Basic equality and creation tests for Constraint objects"
diff --git a/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java b/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
index 4b9bc9f..c73f9be 100644
--- a/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
+++ b/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
@@ -26,7 +26,6 @@
 import java.util.Set;
 
 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;
@@ -65,7 +64,7 @@
  */
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@Component(immediate = true)
+//@Component(immediate = true)
 public class OpticalConfigProvider extends AbstractProvider implements DeviceProvider, LinkProvider {
 
     protected static final Logger log = LoggerFactory
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteEntry.java
index 95169bb..07dd691 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteEntry.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteEntry.java
@@ -19,8 +19,8 @@
 
 import java.util.Objects;
 
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 
 import com.google.common.base.MoreObjects;
 
@@ -28,8 +28,8 @@
  * 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
+    private final Ip4Prefix prefix;             // The IP prefix
+    private final Ip4Address nextHop;           // Next-hop IP address
 
     /**
      * Class constructor.
@@ -37,7 +37,7 @@
      * @param prefix the IP prefix of the route
      * @param nextHop the next hop IP address for the route
      */
-    public RouteEntry(IpPrefix prefix, IpAddress nextHop) {
+    public RouteEntry(Ip4Prefix prefix, Ip4Address nextHop) {
         this.prefix = checkNotNull(prefix);
         this.nextHop = checkNotNull(nextHop);
     }
@@ -47,7 +47,7 @@
      *
      * @return the IP prefix of the route
      */
-    public IpPrefix prefix() {
+    public Ip4Prefix prefix() {
         return prefix;
     }
 
@@ -56,7 +56,7 @@
      *
      * @return the next hop IP address for the route
      */
-    public IpAddress nextHop() {
+    public Ip4Address nextHop() {
         return nextHop;
     }
 
@@ -67,7 +67,7 @@
      * @param ip4Prefix the IPv4 prefix to use
      * @return the binary string representation
      */
-    static String createBinaryString(IpPrefix ip4Prefix) {
+    static String createBinaryString(Ip4Prefix ip4Prefix) {
         if (ip4Prefix.prefixLength() == 0) {
             return "";
         }
@@ -75,7 +75,7 @@
         StringBuilder result = new StringBuilder(ip4Prefix.prefixLength());
         long value = ip4Prefix.address().toInt() & 0xffffffffL;
         for (int i = 0; i < ip4Prefix.prefixLength(); i++) {
-            long mask = 1 << (IpPrefix.MAX_INET_MASK_LENGTH - 1 - i);
+            long mask = 1 << (Ip4Prefix.MAX_MASK_LENGTH - 1 - i);
             result.append(((value & mask) == 0) ? "0" : "1");
         }
         return result.toString();
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
index f1a14e7..cffdc09 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
@@ -53,7 +53,8 @@
 import org.onlab.onos.sdnip.config.SdnIpConfigService;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.MacAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -83,9 +84,9 @@
     // Stores all incoming route updates in a queue.
     private BlockingQueue<RouteUpdate> routeUpdates;
 
-    // The IpAddress is the next hop address of each route update.
-    private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
-    private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
+    // The Ip4Address is the next hop address of each route update.
+    private SetMultimap<Ip4Address, RouteEntry> routesWaitingOnArp;
+    private ConcurrentHashMap<Ip4Prefix, MultiPointToSinglePointIntent> pushedRouteIntents;
 
     private IntentService intentService;
     private HostService hostService;
@@ -106,7 +107,8 @@
 
     // For routes announced by local BGP daemon in SDN network,
     // the next hop will be 0.0.0.0.
-    public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
+    public static final Ip4Address LOCAL_NEXT_HOP =
+        Ip4Address.valueOf("0.0.0.0");
 
     /**
      * Class constructor.
@@ -130,7 +132,7 @@
                 new DefaultByteArrayNodeFactory());
         routeUpdates = new LinkedBlockingQueue<>();
         routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
-                HashMultimap.<IpAddress, RouteEntry>create());
+                HashMultimap.<Ip4Address, RouteEntry>create());
         pushedRouteIntents = new ConcurrentHashMap<>();
 
         bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
@@ -273,7 +275,7 @@
             }
             log.debug("Syncing SDN-IP Route Intents...");
 
-            Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
+            Map<Ip4Prefix, MultiPointToSinglePointIntent> fetchedIntents =
                     new HashMap<>();
 
             //
@@ -292,7 +294,12 @@
                 Criterion c = mp2pIntent.selector().getCriterion(Type.IPV4_DST);
                 if (c != null && c instanceof IPCriterion) {
                     IPCriterion ipCriterion = (IPCriterion) c;
-                    fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
+                    Ip4Prefix ip4Prefix = ipCriterion.ip().getIp4Prefix();
+                    if (ip4Prefix == null) {
+                        // TODO: For now we support only IPv4
+                        continue;
+                    }
+                    fetchedIntents.put(ip4Prefix, mp2pIntent);
                 } else {
                     log.warn("No IPV4_DST criterion found for intent {}",
                             mp2pIntent.id());
@@ -316,15 +323,15 @@
             //    Intent for same prefix, then delete/withdraw the FETCHED
             //    Intent.
             //
-            Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
+            Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
                     storeInMemoryIntents = new LinkedList<>();
-            Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
+            Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
                     addIntents = new LinkedList<>();
-            Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
+            Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
                     deleteIntents = new LinkedList<>();
-            for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
+            for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
                     pushedRouteIntents.entrySet()) {
-                IpPrefix prefix = entry.getKey();
+                Ip4Prefix prefix = entry.getKey();
                 MultiPointToSinglePointIntent inMemoryIntent =
                         entry.getValue();
                 MultiPointToSinglePointIntent fetchedIntent =
@@ -369,9 +376,9 @@
             //
             // Any remaining FETCHED Intents have to be deleted/withdrawn
             //
-            for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
+            for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
                     fetchedIntents.entrySet()) {
-                IpPrefix prefix = entry.getKey();
+                Ip4Prefix prefix = entry.getKey();
                 MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
                 deleteIntents.add(Pair.of(prefix, fetchedIntent));
             }
@@ -383,9 +390,9 @@
             // 2. Delete intents: check if the leader before each operation
             // 3. Add intents: check if the leader before each operation
             //
-            for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
+            for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
                     storeInMemoryIntents) {
-                IpPrefix prefix = pair.getLeft();
+                Ip4Prefix prefix = pair.getLeft();
                 MultiPointToSinglePointIntent intent = pair.getRight();
                 log.debug("Intent synchronization: updating in-memory " +
                                   "Intent for prefix: {}", prefix);
@@ -393,9 +400,9 @@
             }
             //
             isActivatedLeader = true;           // Allow push of Intents
-            for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
+            for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
                     deleteIntents) {
-                IpPrefix prefix = pair.getLeft();
+                Ip4Prefix prefix = pair.getLeft();
                 MultiPointToSinglePointIntent intent = pair.getRight();
                 if (!isElectedLeader) {
                     isActivatedLeader = false;
@@ -406,9 +413,9 @@
                 intentService.withdraw(intent);
             }
             //
-            for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
+            for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
                     addIntents) {
-                IpPrefix prefix = pair.getLeft();
+                Ip4Prefix prefix = pair.getLeft();
                 MultiPointToSinglePointIntent intent = pair.getRight();
                 if (!isElectedLeader) {
                     isActivatedLeader = false;
@@ -460,8 +467,8 @@
         synchronized (this) {
             log.debug("Processing route add: {}", routeEntry);
 
-            IpPrefix prefix = routeEntry.prefix();
-            IpAddress nextHop = null;
+            Ip4Prefix prefix = routeEntry.prefix();
+            Ip4Address nextHop = null;
             RouteEntry foundRouteEntry =
                     bgpRoutes.put(RouteEntry.createBinaryString(prefix),
                                   routeEntry);
@@ -535,8 +542,8 @@
      * @param nextHopIpAddress  IP address of the next hop
      * @param nextHopMacAddress MAC address of the next hop
      */
-    private void addRouteIntentToNextHop(IpPrefix prefix,
-                                         IpAddress nextHopIpAddress,
+    private void addRouteIntentToNextHop(Ip4Prefix prefix,
+                                         Ip4Address nextHopIpAddress,
                                          MacAddress nextHopMacAddress) {
 
         // Find the attachment point (egress interface) of the next hop
@@ -573,7 +580,7 @@
      * @param egressInterface   egress Interface connected to next hop router
      * @param nextHopMacAddress MAC address of next hop router
      */
-    private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
+    private void doAddRouteIntent(Ip4Prefix prefix, Interface egressInterface,
                                   MacAddress nextHopMacAddress) {
         log.debug("Adding intent for prefix {}, next hop mac {}",
                   prefix, nextHopMacAddress);
@@ -634,7 +641,7 @@
     protected void processRouteDelete(RouteEntry routeEntry) {
         synchronized (this) {
             log.debug("Processing route delete: {}", routeEntry);
-            IpPrefix prefix = routeEntry.prefix();
+            Ip4Prefix prefix = routeEntry.prefix();
 
             if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
                 //
@@ -659,7 +666,7 @@
     private void executeRouteDelete(RouteEntry routeEntry) {
         log.debug("Executing route delete: {}", routeEntry);
 
-        IpPrefix prefix = routeEntry.prefix();
+        Ip4Prefix prefix = routeEntry.prefix();
 
         MultiPointToSinglePointIntent intent =
                 pushedRouteIntents.remove(prefix);
@@ -685,11 +692,12 @@
      * @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) {
+    private void updateMac(Ip4Address ipAddress, MacAddress macAddress) {
         log.debug("Received updated MAC info: {} => {}", ipAddress, macAddress);
 
         // TODO here we should check whether the next hop for any of our
-        // installed prefixes has changed, not just prefixes pending installation.
+        // installed prefixes has changed, not just prefixes pending
+        // installation.
 
         // We synchronize on this to prevent changes to the radix tree
         // while we're pushing intents. If the tree changes, the
@@ -701,7 +709,7 @@
 
             for (RouteEntry routeEntry : routesToPush) {
                 // These will always be adds
-                IpPrefix prefix = routeEntry.prefix();
+                Ip4Prefix prefix = routeEntry.prefix();
                 String binaryString = RouteEntry.createBinaryString(prefix);
                 RouteEntry foundRouteEntry =
                         bgpRoutes.getValueForExactKey(binaryString);
@@ -748,7 +756,7 @@
     public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
         List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
 
-        for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
+        for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
             pushedRouteIntents.entrySet()) {
             pushedIntents.add(entry.getValue());
         }
@@ -765,7 +773,12 @@
                     event.type() == HostEvent.Type.HOST_UPDATED) {
                 Host host = event.subject();
                 for (IpAddress ip : host.ipAddresses()) {
-                    updateMac(ip, host.mac());
+                    Ip4Address ip4Address = ip.getIp4Address();
+                    if (ip4Address == null) {
+                        // TODO: For now we support only IPv4
+                        continue;
+                    }
+                    updateMac(ip4Address, host.mac());
                 }
             }
         }
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
index 8d48a27..cd36f72 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
@@ -21,8 +21,8 @@
 import java.util.Objects;
 
 import org.onlab.onos.sdnip.RouteEntry;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 
 import com.google.common.base.MoreObjects;
 
@@ -48,8 +48,8 @@
      * @param asPath the AS path
      * @param localPref the route local preference
      */
-    public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
-                         IpAddress nextHop, byte origin,
+    public BgpRouteEntry(BgpSession bgpSession, Ip4Prefix prefix,
+                         Ip4Address nextHop, byte origin,
                          BgpRouteEntry.AsPath asPath, long localPref) {
         super(prefix, nextHop);
         this.bgpSession = checkNotNull(bgpSession);
@@ -232,15 +232,15 @@
         }
 
         // Compare the peer BGP ID: lower is better
-        IpAddress peerBgpId = getBgpSession().getRemoteBgpId();
-        IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
+        Ip4Address peerBgpId = getBgpSession().getRemoteBgpId();
+        Ip4Address otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
         if (!peerBgpId.equals(otherPeerBgpId)) {
             return (peerBgpId.compareTo(otherPeerBgpId) < 0);
         }
 
         // Compare the peer BGP address: lower is better
-        IpAddress peerAddress = getBgpSession().getRemoteIp4Address();
-        IpAddress otherPeerAddress =
+        Ip4Address peerAddress = getBgpSession().getRemoteIp4Address();
+        Ip4Address otherPeerAddress =
             other.getBgpSession().getRemoteIp4Address();
         if (!peerAddress.equals(otherPeerAddress)) {
             return (peerAddress.compareTo(otherPeerAddress) < 0);
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
index 901ac90..98477ae 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
@@ -42,8 +42,8 @@
 import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
 import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.OpenMessageError;
 import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.UpdateMessageError;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,18 +62,18 @@
     private boolean isClosed = false;
 
     private SocketAddress remoteAddress;        // Peer IP addr/port
-    private IpAddress remoteIp4Address;        // Peer IPv4 address
+    private Ip4Address remoteIp4Address;        // Peer IPv4 address
     private int remoteBgpVersion;               // 1 octet
     private long remoteAs;                      // 2 octets
     private long remoteHoldtime;                // 2 octets
-    private IpAddress remoteBgpId;             // 4 octets -> IPv4 address
+    private Ip4Address remoteBgpId;             // 4 octets -> IPv4 address
     //
     private SocketAddress localAddress;         // Local IP addr/port
-    private IpAddress localIp4Address;         // Local IPv4 address
+    private Ip4Address localIp4Address;         // Local IPv4 address
     private int localBgpVersion;                // 1 octet
     private long localAs;                       // 2 octets
     private long localHoldtime;                 // 2 octets
-    private IpAddress localBgpId;              // 4 octets -> IPv4 address
+    private Ip4Address localBgpId;              // 4 octets -> IPv4 address
     //
     private long localKeepaliveInterval;        // Keepalive interval
 
@@ -83,7 +83,7 @@
     private volatile Timeout sessionTimeout;    // Session timeout
 
     // BGP RIB-IN routing entries from this peer
-    private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRibIn =
+    private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRibIn =
         new ConcurrentHashMap<>();
 
     /**
@@ -110,7 +110,7 @@
      * @param prefix the prefix of the route to search for
      * @return the BGP routing entry if found, otherwise null
      */
-    public BgpRouteEntry findBgpRouteEntry(IpPrefix prefix) {
+    public BgpRouteEntry findBgpRouteEntry(Ip4Prefix prefix) {
         return bgpRibIn.get(prefix);
     }
 
@@ -128,7 +128,7 @@
      *
      * @return the BGP session remote IPv4 address
      */
-    public IpAddress getRemoteIp4Address() {
+    public Ip4Address getRemoteIp4Address() {
         return remoteIp4Address;
     }
 
@@ -164,7 +164,7 @@
      *
      * @return the BGP session remote BGP Identifier as an IPv4 address
      */
-    public IpAddress getRemoteBgpId() {
+    public Ip4Address getRemoteBgpId() {
         return remoteBgpId;
     }
 
@@ -209,7 +209,7 @@
      *
      * @return the BGP session local BGP Identifier as an IPv4 address
      */
-    public IpAddress getLocalBgpId() {
+    public Ip4Address getLocalBgpId() {
         return localBgpId;
     }
 
@@ -246,13 +246,11 @@
         InetAddress inetAddr;
         if (localAddress instanceof InetSocketAddress) {
             inetAddr = ((InetSocketAddress) localAddress).getAddress();
-            localIp4Address = IpAddress.valueOf(IpAddress.Version.INET,
-                                                inetAddr.getAddress());
+            localIp4Address = Ip4Address.valueOf(inetAddr.getAddress());
         }
         if (remoteAddress instanceof InetSocketAddress) {
             inetAddr = ((InetSocketAddress) remoteAddress).getAddress();
-            remoteIp4Address = IpAddress.valueOf(IpAddress.Version.INET,
-                                                 inetAddr.getAddress());
+            remoteIp4Address = Ip4Address.valueOf(inetAddr.getAddress());
         }
 
         log.debug("BGP Session Connected from {} on {}",
@@ -388,7 +386,7 @@
         }
 
         // Remote BGP Identifier
-        remoteBgpId = IpAddress.valueOf((int) message.readUnsignedInt());
+        remoteBgpId = Ip4Address.valueOf((int) message.readUnsignedInt());
 
         // Optional Parameters
         int optParamLen = message.readUnsignedByte();
@@ -460,7 +458,7 @@
      */
     void processBgpUpdate(ChannelHandlerContext ctx, ChannelBuffer message) {
         Collection<BgpRouteEntry> addedRoutes = null;
-        Map<IpPrefix, BgpRouteEntry> deletedRoutes = new HashMap<>();
+        Map<Ip4Prefix, BgpRouteEntry> deletedRoutes = new HashMap<>();
 
         int minLength =
             BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
@@ -494,7 +492,7 @@
             actionsBgpUpdateMalformedAttributeList(ctx);
             return;
         }
-        Collection<IpPrefix> withdrawnPrefixes = null;
+        Collection<Ip4Prefix> withdrawnPrefixes = null;
         try {
             withdrawnPrefixes = parsePackedPrefixes(withdrawnRoutesLength,
                                                     message);
@@ -505,7 +503,7 @@
             actionsBgpUpdateInvalidNetworkField(ctx);
             return;
         }
-        for (IpPrefix prefix : withdrawnPrefixes) {
+        for (Ip4Prefix prefix : withdrawnPrefixes) {
             log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
                       remoteAddress, prefix);
             BgpRouteEntry bgpRouteEntry = bgpRibIn.get(prefix);
@@ -560,19 +558,19 @@
                                 ChannelHandlerContext ctx,
                                 ChannelBuffer message)
         throws BgpParseException {
-        Map<IpPrefix, BgpRouteEntry> addedRoutes = new HashMap<>();
+        Map<Ip4Prefix, BgpRouteEntry> addedRoutes = new HashMap<>();
 
         //
         // Parsed values
         //
         Short origin = -1;                      // Mandatory
         BgpRouteEntry.AsPath asPath = null;     // Mandatory
-        IpAddress nextHop = null;              // Mandatory
+        Ip4Address nextHop = null;              // Mandatory
         long multiExitDisc =                    // Optional
             BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
         Long localPref = null;                  // Mandatory
         Long aggregatorAsNumber = null;         // Optional: unused
-        IpAddress aggregatorIpAddress = null;  // Optional: unused
+        Ip4Address aggregatorIpAddress = null;  // Optional: unused
 
         //
         // Get and verify the Path Attributes Length
@@ -684,7 +682,7 @@
 
             case BgpConstants.Update.Aggregator.TYPE:
                 // Attribute Type Code AGGREGATOR
-                Pair<Long, IpAddress> aggregator =
+                Pair<Long, Ip4Address> aggregator =
                     parseAttributeTypeAggregator(ctx, attrTypeCode, attrLen,
                                                  attrFlags, message);
                 aggregatorAsNumber = aggregator.getLeft();
@@ -720,7 +718,7 @@
         //
         // Parse the NLRI (Network Layer Reachability Information)
         //
-        Collection<IpPrefix> addedPrefixes = null;
+        Collection<Ip4Prefix> addedPrefixes = null;
         int nlriLength = message.readableBytes();
         try {
             addedPrefixes = parsePackedPrefixes(nlriLength, message);
@@ -734,7 +732,7 @@
         }
 
         // Generate the added routes
-        for (IpPrefix prefix : addedPrefixes) {
+        for (Ip4Prefix prefix : addedPrefixes) {
             BgpRouteEntry bgpRouteEntry =
                 new BgpRouteEntry(this, prefix, nextHop,
                                   origin.byteValue(), asPath, localPref);
@@ -768,7 +766,7 @@
                         ChannelHandlerContext ctx,
                         Short origin,
                         BgpRouteEntry.AsPath asPath,
-                        IpAddress nextHop,
+                        Ip4Address nextHop,
                         Long localPref)
         throws BgpParseException {
         //
@@ -1025,7 +1023,7 @@
      * @return the parsed NEXT_HOP value
      * @throws BgpParseException
      */
-    private IpAddress parseAttributeTypeNextHop(
+    private Ip4Address parseAttributeTypeNextHop(
                         ChannelHandlerContext ctx,
                         int attrTypeCode,
                         int attrLen,
@@ -1043,8 +1041,8 @@
         }
 
         message.markReaderIndex();
-        long address = message.readUnsignedInt();
-        IpAddress nextHopAddress = IpAddress.valueOf((int) address);
+        Ip4Address nextHopAddress =
+            Ip4Address.valueOf((int) message.readUnsignedInt());
         //
         // Check whether the NEXT_HOP IP address is semantically correct.
         // As per RFC 4271, Section 6.3:
@@ -1173,7 +1171,7 @@
      * @return the parsed AGGREGATOR value: a tuple of <AS-Number, IP-Address>
      * @throws BgpParseException
      */
-    private Pair<Long, IpAddress> parseAttributeTypeAggregator(
+    private Pair<Long, Ip4Address> parseAttributeTypeAggregator(
                         ChannelHandlerContext ctx,
                         int attrTypeCode,
                         int attrLen,
@@ -1193,11 +1191,10 @@
         // The AGGREGATOR AS number
         long aggregatorAsNumber = message.readUnsignedShort();
         // The AGGREGATOR IP address
-        long aggregatorAddress = message.readUnsignedInt();
-        IpAddress aggregatorIpAddress =
-                IpAddress.valueOf((int) aggregatorAddress);
+        Ip4Address aggregatorIpAddress =
+            Ip4Address.valueOf((int) message.readUnsignedInt());
 
-        Pair<Long, IpAddress> aggregator = Pair.of(aggregatorAsNumber,
+        Pair<Long, Ip4Address> aggregator = Pair.of(aggregatorAsNumber,
                                                     aggregatorIpAddress);
         return aggregator;
     }
@@ -1215,10 +1212,10 @@
      * @return a collection of parsed IPv4 network prefixes
      * @throws BgpParseException
      */
-    private Collection<IpPrefix> parsePackedPrefixes(int totalLength,
+    private Collection<Ip4Prefix> parsePackedPrefixes(int totalLength,
                                                       ChannelBuffer message)
         throws BgpParseException {
-        Collection<IpPrefix> result = new ArrayList<>();
+        Collection<Ip4Prefix> result = new ArrayList<>();
 
         if (totalLength == 0) {
             return result;
@@ -1242,9 +1239,9 @@
                 prefixBytelen--;
             }
             address <<= extraShift;
-            IpPrefix prefix =
-                IpPrefix.valueOf(IpAddress.valueOf((int) address),
-                                 prefixBitlen);
+            Ip4Prefix prefix =
+                Ip4Prefix.valueOf(Ip4Address.valueOf((int) address),
+                                  prefixBitlen);
             result.add(prefix);
         }
 
@@ -1413,7 +1410,7 @@
                         int attrLen,
                         int attrFlags,
                         ChannelBuffer message,
-                        IpAddress nextHop) {
+                        Ip4Address nextHop) {
         log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}",
                   remoteAddress, nextHop);
 
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
index 247c95f..8b5ed41 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
@@ -35,8 +35,8 @@
 import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
 import org.onlab.onos.sdnip.RouteListener;
 import org.onlab.onos.sdnip.RouteUpdate;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,10 +49,10 @@
     private Channel serverChannel;     // Listener for incoming BGP connections
     private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
         new ConcurrentHashMap<>();
-    private IpAddress myBgpId;         // Same BGP ID for all peers
+    private Ip4Address myBgpId;        // Same BGP ID for all peers
 
     private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
-    private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes =
+    private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRoutes =
         new ConcurrentHashMap<>();
 
     private final RouteListener routeListener;
@@ -105,8 +105,7 @@
         if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
             InetAddress inetAddr =
                 ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
-            IpAddress ip4Address = IpAddress.valueOf(IpAddress.Version.INET,
-                                                     inetAddr.getAddress());
+            Ip4Address ip4Address = Ip4Address.valueOf(inetAddr.getAddress());
             updateMyBgpId(ip4Address);
         }
         return true;
@@ -128,7 +127,7 @@
      *
      * @param ip4Address the IPv4 address to use as BGP ID
      */
-    private synchronized void updateMyBgpId(IpAddress ip4Address) {
+    private synchronized void updateMyBgpId(Ip4Address ip4Address) {
         if (myBgpId == null) {
             myBgpId = ip4Address;
             log.debug("BGP: My BGP ID is {}", myBgpId);
@@ -140,7 +139,7 @@
      *
      * @return the local BGP Identifier as an IPv4 address
      */
-    IpAddress getMyBgpId() {
+    Ip4Address getMyBgpId() {
         return myBgpId;
     }
 
@@ -352,7 +351,7 @@
          * @param prefix the prefix of the route
          * @return the best route if found, otherwise null
          */
-        private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
+        private BgpRouteEntry findBestBgpRoute(Ip4Prefix prefix) {
             BgpRouteEntry bestRoute = null;
 
             // Iterate across all BGP Sessions and select the best route
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java
index 45b0c90..7be7d11 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/IntentSyncTest.java
@@ -42,7 +42,9 @@
 import org.onlab.onos.sdnip.config.Interface;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 
@@ -209,36 +211,36 @@
         // 6. RouteEntry6 was newly added, but the intent was not submitted.
         //
         RouteEntry routeEntry1 = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         RouteEntry routeEntry2 = new RouteEntry(
-                IpPrefix.valueOf("2.2.2.0/24"),
-                IpAddress.valueOf("192.168.20.1"));
+                Ip4Prefix.valueOf("2.2.2.0/24"),
+                Ip4Address.valueOf("192.168.20.1"));
 
         RouteEntry routeEntry3 = new RouteEntry(
-                IpPrefix.valueOf("3.3.3.0/24"),
-                IpAddress.valueOf("192.168.30.1"));
+                Ip4Prefix.valueOf("3.3.3.0/24"),
+                Ip4Address.valueOf("192.168.30.1"));
 
         RouteEntry routeEntry4 = new RouteEntry(
-                IpPrefix.valueOf("4.4.4.0/24"),
-                IpAddress.valueOf("192.168.30.1"));
+                Ip4Prefix.valueOf("4.4.4.0/24"),
+                Ip4Address.valueOf("192.168.30.1"));
 
         RouteEntry routeEntry4Update = new RouteEntry(
-                IpPrefix.valueOf("4.4.4.0/24"),
-                IpAddress.valueOf("192.168.20.1"));
+                Ip4Prefix.valueOf("4.4.4.0/24"),
+                Ip4Address.valueOf("192.168.20.1"));
 
         RouteEntry routeEntry5 = new RouteEntry(
-                IpPrefix.valueOf("5.5.5.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("5.5.5.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         RouteEntry routeEntry6 = new RouteEntry(
-                IpPrefix.valueOf("6.6.6.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("6.6.6.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         RouteEntry routeEntry7 = new RouteEntry(
-                IpPrefix.valueOf("7.7.7.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("7.7.7.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         MultiPointToSinglePointIntent intent1 = intentBuilder(
                 routeEntry1.prefix(), "00:00:00:00:00:01", SW1_ETH1);
@@ -286,7 +288,7 @@
                 routeEntry7);
         TestUtils.setField(router, "bgpRoutes", bgpRoutes);
 
-        ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent>
+        ConcurrentHashMap<Ip4Prefix, MultiPointToSinglePointIntent>
         pushedRouteIntents =  new ConcurrentHashMap<>();
         pushedRouteIntents.put(routeEntry1.prefix(), intent1);
         pushedRouteIntents.put(routeEntry3.prefix(), intent3);
@@ -355,7 +357,7 @@
      * @param egressPoint to which packets should be sent
      * @return the constructed MultiPointToSinglePointIntent
      */
-    private MultiPointToSinglePointIntent intentBuilder(IpPrefix ipPrefix,
+    private MultiPointToSinglePointIntent intentBuilder(Ip4Prefix ipPrefix,
             String nextHopMacAddress, ConnectPoint egressPoint) {
 
         TrafficSelector.Builder selectorBuilder =
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouteEntryTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouteEntryTest.java
index 9af90a7..ca5b1f8 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouteEntryTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouteEntryTest.java
@@ -20,8 +20,8 @@
 import static org.junit.Assert.assertThat;
 
 import org.junit.Test;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 
 /**
  * Unit tests for the RouteEntry class.
@@ -32,8 +32,8 @@
      */
     @Test
     public void testConstructor() {
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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(),
@@ -45,8 +45,8 @@
      */
     @Test(expected = NullPointerException.class)
     public void testInvalidConstructorNullPrefix() {
-        IpPrefix prefix = null;
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        Ip4Prefix prefix = null;
+        Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
 
         new RouteEntry(prefix, nextHop);
     }
@@ -56,8 +56,8 @@
      */
     @Test(expected = NullPointerException.class)
     public void testInvalidConstructorNullNextHop() {
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = null;
+        Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop = null;
 
         new RouteEntry(prefix, nextHop);
     }
@@ -67,8 +67,8 @@
      */
     @Test
     public void testGetFields() {
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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));
@@ -80,28 +80,28 @@
      */
     @Test
     public void testCreateBinaryString() {
-        IpPrefix prefix;
+        Ip4Prefix prefix;
 
-        prefix = IpPrefix.valueOf("0.0.0.0/0");
+        prefix = Ip4Prefix.valueOf("0.0.0.0/0");
         assertThat(RouteEntry.createBinaryString(prefix), is(""));
 
-        prefix = IpPrefix.valueOf("192.168.166.0/22");
+        prefix = Ip4Prefix.valueOf("192.168.166.0/22");
         assertThat(RouteEntry.createBinaryString(prefix),
                    is("1100000010101000101001"));
 
-        prefix = IpPrefix.valueOf("192.168.166.0/23");
+        prefix = Ip4Prefix.valueOf("192.168.166.0/23");
         assertThat(RouteEntry.createBinaryString(prefix),
                    is("11000000101010001010011"));
 
-        prefix = IpPrefix.valueOf("192.168.166.0/24");
+        prefix = Ip4Prefix.valueOf("192.168.166.0/24");
         assertThat(RouteEntry.createBinaryString(prefix),
                    is("110000001010100010100110"));
 
-        prefix = IpPrefix.valueOf("130.162.10.1/25");
+        prefix = Ip4Prefix.valueOf("130.162.10.1/25");
         assertThat(RouteEntry.createBinaryString(prefix),
                    is("1000001010100010000010100"));
 
-        prefix = IpPrefix.valueOf("255.255.255.255/32");
+        prefix = Ip4Prefix.valueOf("255.255.255.255/32");
         assertThat(RouteEntry.createBinaryString(prefix),
                    is("11111111111111111111111111111111"));
     }
@@ -111,12 +111,12 @@
      */
     @Test
     public void testEquality() {
-        IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8");
+        Ip4Prefix prefix1 = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop1 = Ip4Address.valueOf("5.6.7.8");
         RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
 
-        IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8");
+        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));
@@ -127,16 +127,16 @@
      */
     @Test
     public void testNonEquality() {
-        IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8");
+        Ip4Prefix prefix1 = Ip4Prefix.valueOf("1.2.3.0/24");
+        Ip4Address nextHop1 = Ip4Address.valueOf("5.6.7.8");
         RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
 
-        IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/25");        // Different
-        IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8");
+        Ip4Prefix prefix2 = Ip4Prefix.valueOf("1.2.3.0/25");      // Different
+        Ip4Address nextHop2 = Ip4Address.valueOf("5.6.7.8");
         RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2);
 
-        IpPrefix prefix3 = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop3 = IpAddress.valueOf("5.6.7.9");        // Different
+        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, is(not(routeEntry2)));
@@ -148,8 +148,8 @@
      */
     @Test
     public void testToString() {
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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(),
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTest.java
index b361123..b4b0551 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTest.java
@@ -57,7 +57,9 @@
 import org.onlab.onos.sdnip.config.SdnIpConfigService;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 
@@ -228,8 +230,8 @@
     public void testProcessRouteAdd() throws TestUtilsException {
         // Construct a route entry
         RouteEntry routeEntry = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         // Construct a MultiPointToSinglePointIntent intent
         TrafficSelector.Builder selectorBuilder =
@@ -281,8 +283,8 @@
 
         // Construct the existing route entry
         RouteEntry routeEntry = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         // Construct the existing MultiPointToSinglePointIntent intent
         TrafficSelector.Builder selectorBuilder =
@@ -305,8 +307,8 @@
 
         // Start to construct a new route entry and new intent
         RouteEntry routeEntryUpdate = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.20.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.20.1"));
 
         // Construct a new MultiPointToSinglePointIntent intent
         TrafficSelector.Builder selectorBuilderNew =
@@ -359,8 +361,8 @@
 
         // Construct the existing route entry
         RouteEntry routeEntry = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         // Construct the existing MultiPointToSinglePointIntent intent
         TrafficSelector.Builder selectorBuilder =
@@ -406,7 +408,8 @@
     public void testLocalRouteAdd() throws TestUtilsException {
         // Construct a route entry, the next hop is the local BGP speaker
         RouteEntry routeEntry = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"), IpAddress.valueOf("0.0.0.0"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("0.0.0.0"));
 
         // Reset intentService to check whether the submit method is called
         reset(intentService);
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTestWithAsyncArp.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTestWithAsyncArp.java
index 2ea5f17..6798670 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTestWithAsyncArp.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTestWithAsyncArp.java
@@ -58,7 +58,9 @@
 import org.onlab.onos.sdnip.config.SdnIpConfigService;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 
@@ -191,8 +193,8 @@
 
         // Construct a route entry
         RouteEntry routeEntry = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         // Construct a route intent
         MultiPointToSinglePointIntent intent = staticIntentBuilder();
@@ -243,8 +245,8 @@
 
         // Construct the existing route entry
         RouteEntry routeEntry = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         // Construct the existing MultiPointToSinglePointIntent intent
         MultiPointToSinglePointIntent intent = staticIntentBuilder();
@@ -256,8 +258,8 @@
 
         // Start to construct a new route entry and new intent
         RouteEntry routeEntryUpdate = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.20.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.20.1"));
 
         // Construct a new MultiPointToSinglePointIntent intent
         TrafficSelector.Builder selectorBuilderNew =
@@ -323,8 +325,8 @@
 
         // Construct the existing route entry
         RouteEntry routeEntry = new RouteEntry(
-                IpPrefix.valueOf("1.1.1.0/24"),
-                IpAddress.valueOf("192.168.10.1"));
+                Ip4Prefix.valueOf("1.1.1.0/24"),
+                Ip4Address.valueOf("192.168.10.1"));
 
         // Construct the existing MultiPointToSinglePointIntent intent
         MultiPointToSinglePointIntent intent = staticIntentBuilder();
@@ -401,8 +403,8 @@
             MultiPointToSinglePointIntent intent)
             throws TestUtilsException {
 
-        ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent>
-        pushedRouteIntents =  new ConcurrentHashMap<>();
+        ConcurrentHashMap<Ip4Prefix, MultiPointToSinglePointIntent>
+            pushedRouteIntents =  new ConcurrentHashMap<>();
         pushedRouteIntents.put(routeEntry.prefix(), intent);
         TestUtils.setField(router, "pushedRouteIntents", pushedRouteIntents);
     }
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/SdnIpTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/SdnIpTest.java
index 5f18bca..348a62fd 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/SdnIpTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/SdnIpTest.java
@@ -207,7 +207,7 @@
         reset(intentService);
 
         for (RouteUpdate update : routeUpdates) {
-            IpAddress nextHopAddress = update.routeEntry().nextHop();
+            Ip4Address nextHopAddress = update.routeEntry().nextHop();
 
             // Find out the egress ConnectPoint
             ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
@@ -268,7 +268,7 @@
         reset(intentService);
 
         for (RouteUpdate update : routeUpdates) {
-            IpAddress nextHopAddress = update.routeEntry().nextHop();
+            Ip4Address nextHopAddress = update.routeEntry().nextHop();
 
             // Find out the egress ConnectPoint
             ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
@@ -342,11 +342,6 @@
                                       prefixLength);
                 // We have to ensure we don't generate the same prefix twice
                 // (this is quite easy to happen with small prefix lengths).
-                // TODO:
-                // The IpPrefix does the comparison using 32 bits length,
-                // but we need to compare only the prefix length. So I use
-                // Ip4Prefix for this moment and changed to IpPrefix. This
-                // can be improved in the future.
             } while (prefixes.contains(prefix));
 
             prefixes.add(prefix);
@@ -366,9 +361,9 @@
             assertNotNull(nextHop);
 
             RouteUpdate update =
-                    new RouteUpdate(RouteUpdate.Type.UPDATE,
-                                    new RouteEntry(prefix,
-                                                   nextHop.ipAddress()));
+                new RouteUpdate(RouteUpdate.Type.UPDATE,
+                        new RouteEntry(prefix,
+                                       nextHop.ipAddress().getIp4Address()));
 
             routeUpdates.add(update);
         }
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpRouteEntryTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpRouteEntryTest.java
index aa54874..d1ad3d4 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpRouteEntryTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpRouteEntryTest.java
@@ -26,30 +26,30 @@
 
 import org.junit.Before;
 import org.junit.Test;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 
 /**
  * Unit tests for the BgpRouteEntry class.
  */
 public class BgpRouteEntryTest {
     private BgpSession bgpSession;
-    private static final IpAddress BGP_SESSION_BGP_ID =
-        IpAddress.valueOf("10.0.0.1");
-    private static final IpAddress BGP_SESSION_IP_ADDRESS =
-        IpAddress.valueOf("20.0.0.1");
+    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 IpAddress BGP_SESSION_BGP_ID2 =
-        IpAddress.valueOf("10.0.0.2");
-    private static final IpAddress BGP_SESSION_IP_ADDRESS2 =
-        IpAddress.valueOf("20.0.0.1");
+    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 IpAddress BGP_SESSION_BGP_ID3 =
-        IpAddress.valueOf("10.0.0.1");
-    private static final IpAddress BGP_SESSION_IP_ADDRESS3 =
-        IpAddress.valueOf("20.0.0.2");
+    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");
 
     @Before
     public void setUp() throws Exception {
@@ -85,8 +85,8 @@
      * @return a generated BGP Route Entry
      */
     private BgpRouteEntry generateBgpRouteEntry() {
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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<>();
@@ -143,8 +143,8 @@
     @Test(expected = NullPointerException.class)
     public void testInvalidConstructorNullBgpSession() {
         BgpSession bgpSessionNull = null;
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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<>();
@@ -161,8 +161,8 @@
      */
     @Test(expected = NullPointerException.class)
     public void testInvalidConstructorNullAsPath() {
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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;
@@ -177,8 +177,8 @@
     @Test
     public void testGetFields() {
         // Create the fields to compare against
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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<>();
@@ -231,8 +231,8 @@
         //
         // Test local route with AS Path that begins with AS_SET
         //
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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<>();
@@ -291,8 +291,8 @@
         //
         // Get neighbor AS for a local route
         //
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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<>();
@@ -341,8 +341,8 @@
         //
         // Compare two routes with different LOCAL_PREF
         //
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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<>();
@@ -460,8 +460,8 @@
         BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
 
         // Setup BGP Route 2
-        IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
-        IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+        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<>();
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpSessionManagerTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpSessionManagerTest.java
index 4b288af..5fdd6eb 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpSessionManagerTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpSessionManagerTest.java
@@ -45,8 +45,8 @@
 import org.onlab.junit.TestUtils.TestUtilsException;
 import org.onlab.onos.sdnip.RouteListener;
 import org.onlab.onos.sdnip.RouteUpdate;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 
 import com.google.common.net.InetAddresses;
 
@@ -54,9 +54,10 @@
  * Unit tests for the BgpSessionManager class.
  */
 public class BgpSessionManagerTest {
-    private static final IpAddress IP_LOOPBACK_ID =
-        IpAddress.valueOf("127.0.0.1");
-    private static final IpAddress BGP_PEER1_ID = IpAddress.valueOf("10.0.0.1");
+    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 long DEFAULT_LOCAL_PREF = 10;
     private static final long DEFAULT_MULTI_EXIT_DISC = 20;
 
@@ -245,7 +246,7 @@
     @Test
     public void testProcessedBgpUpdateMessages() throws InterruptedException {
         BgpSession bgpSession;
-        IpAddress nextHopRouter;
+        Ip4Address nextHopRouter;
         BgpRouteEntry bgpRouteEntry;
         ChannelBuffer message;
         Collection<BgpRouteEntry> bgpRibIn;
@@ -265,18 +266,18 @@
         bgpSession = bgpSessionManager.getBgpSessions().iterator().next();
 
         // Prepare routes to add/delete
-        nextHopRouter = IpAddress.valueOf("10.20.30.40");
-        Collection<IpPrefix> addedRoutes = new LinkedList<>();
-        Collection<IpPrefix> withdrawnRoutes = new LinkedList<>();
-        addedRoutes.add(IpPrefix.valueOf("0.0.0.0/0"));
-        addedRoutes.add(IpPrefix.valueOf("20.0.0.0/8"));
-        addedRoutes.add(IpPrefix.valueOf("30.0.0.0/16"));
-        addedRoutes.add(IpPrefix.valueOf("40.0.0.0/24"));
-        addedRoutes.add(IpPrefix.valueOf("50.0.0.0/32"));
-        withdrawnRoutes.add(IpPrefix.valueOf("60.0.0.0/8"));
-        withdrawnRoutes.add(IpPrefix.valueOf("70.0.0.0/16"));
-        withdrawnRoutes.add(IpPrefix.valueOf("80.0.0.0/24"));
-        withdrawnRoutes.add(IpPrefix.valueOf("90.0.0.0/32"));
+        nextHopRouter = Ip4Address.valueOf("10.20.30.40");
+        Collection<Ip4Prefix> addedRoutes = new LinkedList<>();
+        Collection<Ip4Prefix> withdrawnRoutes = new LinkedList<>();
+        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 = peerChannelHandler.prepareBgpUpdate(nextHopRouter,
                                                       addedRoutes,
@@ -314,7 +315,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("0.0.0.0/0"),
+                              Ip4Prefix.valueOf("0.0.0.0/0"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
@@ -324,7 +325,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("20.0.0.0/8"),
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
@@ -334,7 +335,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("30.0.0.0/16"),
+                              Ip4Prefix.valueOf("30.0.0.0/16"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
@@ -344,7 +345,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("40.0.0.0/24"),
+                              Ip4Prefix.valueOf("40.0.0.0/24"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
@@ -354,7 +355,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("50.0.0.0/32"),
+                              Ip4Prefix.valueOf("50.0.0.0/32"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
@@ -365,8 +366,8 @@
         // Delete some routes
         addedRoutes = new LinkedList<>();
         withdrawnRoutes = new LinkedList<>();
-        withdrawnRoutes.add(IpPrefix.valueOf("0.0.0.0/0"));
-        withdrawnRoutes.add(IpPrefix.valueOf("50.0.0.0/32"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("0.0.0.0/0"));
+        withdrawnRoutes.add(Ip4Prefix.valueOf("50.0.0.0/32"));
 
         // Write the routes
         message = peerChannelHandler.prepareBgpUpdate(nextHopRouter,
@@ -382,7 +383,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("20.0.0.0/8"),
+                              Ip4Prefix.valueOf("20.0.0.0/8"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
@@ -392,7 +393,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("30.0.0.0/16"),
+                              Ip4Prefix.valueOf("30.0.0.0/16"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
@@ -402,7 +403,7 @@
         //
         bgpRouteEntry =
             new BgpRouteEntry(bgpSession,
-                              IpPrefix.valueOf("40.0.0.0/24"),
+                              Ip4Prefix.valueOf("40.0.0.0/24"),
                               nextHopRouter,
                               (byte) BgpConstants.Update.Origin.IGP,
                               asPath,
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerChannelHandler.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerChannelHandler.java
index 4d94e1d..22f8a0a 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerChannelHandler.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerChannelHandler.java
@@ -22,8 +22,8 @@
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.channel.ChannelStateEvent;
 import org.jboss.netty.channel.SimpleChannelHandler;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
 
 /**
  * Class for handling the remote BGP Peer session.
@@ -31,7 +31,7 @@
 class TestBgpPeerChannelHandler extends SimpleChannelHandler {
     static final long PEER_AS = 65001;
     static final int PEER_HOLDTIME = 120;       // 120 seconds
-    final IpAddress bgpId;                     // The BGP ID
+    final Ip4Address bgpId;                     // The BGP ID
     final long localPref;                       // Local preference for routes
     final long multiExitDisc = 20;              // MED value
 
@@ -43,8 +43,7 @@
      * @param bgpId the BGP ID to use
      * @param localPref the local preference for the routes to use
      */
-    TestBgpPeerChannelHandler(IpAddress bgpId,
-                              long localPref) {
+    TestBgpPeerChannelHandler(Ip4Address bgpId, long localPref) {
         this.bgpId = bgpId;
         this.localPref = localPref;
     }
@@ -99,9 +98,9 @@
      * @param withdrawnRoutes the routes to withdraw
      * @return the message to transmit (BGP header included)
      */
-    ChannelBuffer prepareBgpUpdate(IpAddress nextHopRouter,
-                                   Collection<IpPrefix> addedRoutes,
-                                   Collection<IpPrefix> withdrawnRoutes) {
+    ChannelBuffer prepareBgpUpdate(Ip4Address nextHopRouter,
+                                   Collection<Ip4Prefix> addedRoutes,
+                                   Collection<Ip4Prefix> withdrawnRoutes) {
         int attrFlags;
         ChannelBuffer message =
             ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
@@ -178,24 +177,24 @@
      * @param prefixes the prefixes to encode
      * @return the buffer with the encoded prefixes
      */
-    private ChannelBuffer encodePackedPrefixes(Collection<IpPrefix> prefixes) {
+    private ChannelBuffer encodePackedPrefixes(Collection<Ip4Prefix> prefixes) {
         ChannelBuffer message =
             ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
 
         // Write each of the prefixes
-        for (IpPrefix prefix : prefixes) {
+        for (Ip4Prefix prefix : prefixes) {
             int prefixBitlen = prefix.prefixLength();
             int prefixBytelen = (prefixBitlen + 7) / 8;         // Round-up
             message.writeByte(prefixBitlen);
 
-            IpAddress address = prefix.address();
+            Ip4Address address = prefix.address();
             long value = address.toInt() & 0xffffffffL;
-            for (int i = 0; i < IpAddress.INET_BYTE_LENGTH; i++) {
+            for (int i = 0; i < Ip4Address.BYTE_LENGTH; i++) {
                 if (prefixBytelen-- == 0) {
                     break;
                 }
                 long nextByte =
-                    (value >> ((IpAddress.INET_BYTE_LENGTH - i - 1) * 8)) & 0xff;
+                    (value >> ((Ip4Address.BYTE_LENGTH - i - 1) * 8)) & 0xff;
                 message.writeByte((int) nextByte);
             }
         }
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerFrameDecoder.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerFrameDecoder.java
index 970f0ed..2aa8bd0 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerFrameDecoder.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerFrameDecoder.java
@@ -21,7 +21,7 @@
 import org.jboss.netty.channel.Channel;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.handler.codec.frame.FrameDecoder;
-import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
 
 /**
  * Class for handling the decoding of the BGP messages at the remote
@@ -31,7 +31,7 @@
     int remoteBgpVersion;               // 1 octet
     long remoteAs;                      // 2 octets
     long remoteHoldtime;                // 2 octets
-    IpAddress remoteBgpIdentifier;     // 4 octets -> IPv4 address
+    Ip4Address remoteBgpIdentifier;     // 4 octets -> IPv4 address
 
     final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1);
     final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1);
@@ -144,7 +144,8 @@
         remoteBgpVersion = message.readUnsignedByte();
         remoteAs = message.readUnsignedShort();
         remoteHoldtime = message.readUnsignedShort();
-        remoteBgpIdentifier = IpAddress.valueOf((int) message.readUnsignedInt());
+        remoteBgpIdentifier =
+            Ip4Address.valueOf((int) message.readUnsignedInt());
         // Optional Parameters
         int optParamLen = message.readUnsignedByte();
         if (message.readableBytes() < optParamLen) {
diff --git a/core/api/src/main/java/org/onlab/onos/codec/CodecContext.java b/core/api/src/main/java/org/onlab/onos/codec/CodecContext.java
new file mode 100644
index 0000000..6c8493e
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/codec/CodecContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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.onlab.onos.codec;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Context for codecs to use while encoding/decoding.
+ */
+public interface CodecContext {
+
+    /**
+     * Returns the JSON object mapper.
+     *
+     * @return object mapper
+     */
+    ObjectMapper mapper();
+
+    /**
+     * Returns the JSON codec for the specified entity class.
+     *
+     * @param entityClass entity class
+     * @param <T>         entity type
+     * @return JSON codec; null if no codec available for the class
+     */
+    <T> JsonCodec<T> codec(Class<T> entityClass);
+
+    /**
+     * Returns reference to the specified service implementation.
+     *
+     * @param serviceClass service class
+     * @param <T>          service type
+     * @return JSON codec; null if no codec available for the class
+     */
+    <T> T get(Class<T> serviceClass);
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/codec/CodecService.java b/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
index b8f3285..100a28c 100644
--- a/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
+++ b/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
@@ -33,9 +33,10 @@
      * Returns the JSON codec for the specified entity class.
      *
      * @param entityClass entity class
+     * @param <T>         entity type
      * @return JSON codec; null if no codec available for the class
      */
-    JsonCodec getCodec(Class<?> entityClass);
+    <T> JsonCodec<T> getCodec(Class<T> entityClass);
 
     /**
      * Registers the specified JSON codec for the given entity class.
@@ -43,7 +44,7 @@
      * @param entityClass entity class
      * @param codec       JSON codec
      */
-    void registerCodec(Class<?> entityClass, JsonCodec codec);
+    <T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec);
 
     /**
      * Unregisters the JSON codec for the specified entity class.
diff --git a/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java b/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
index 5dcf25c..8a8f426 100644
--- a/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
+++ b/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
@@ -16,7 +16,6 @@
 package org.onlab.onos.codec;
 
 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;
 
@@ -31,37 +30,42 @@
     /**
      * Encodes the specified entity into JSON.
      *
-     * @param entity entity to encode
-     * @param mapper object mapper
+     * @param entity  entity to encode
+     * @param context encoding context
      * @return JSON node
      * @throws java.lang.UnsupportedOperationException if the codec does not
      *                                                 support encode operations
      */
-    public abstract ObjectNode encode(T entity, ObjectMapper mapper);
+    public ObjectNode encode(T entity, CodecContext context) {
+        throw new UnsupportedOperationException("encode() not supported");
+    }
 
     /**
      * Decodes the specified entity from JSON.
      *
-     * @param json JSON to decode
+     * @param json    JSON to decode
+     * @param context decoding context
      * @return decoded entity
      * @throws java.lang.UnsupportedOperationException if the codec does not
      *                                                 support decode operations
      */
-    public abstract T decode(ObjectNode json);
+    public T decode(ObjectNode json, CodecContext context) {
+        throw new UnsupportedOperationException("decode() not supported");
+    }
 
     /**
      * Encodes the collection of the specified entities.
      *
      * @param entities collection of entities to encode
-     * @param mapper   object mapper
+     * @param context  encoding context
      * @return JSON array
      * @throws java.lang.UnsupportedOperationException if the codec does not
      *                                                 support encode operations
      */
-    public ArrayNode encode(Iterable<T> entities, ObjectMapper mapper) {
-        ArrayNode result = mapper.createArrayNode();
+    public ArrayNode encode(Iterable<T> entities, CodecContext context) {
+        ArrayNode result = context.mapper().createArrayNode();
         for (T entity : entities) {
-            result.add(encode(entity, mapper));
+            result.add(encode(entity, context));
         }
         return result;
     }
@@ -69,15 +73,16 @@
     /**
      * Decodes the specified JSON array into a collection of entities.
      *
-     * @param json JSON array to decode
+     * @param json    JSON array to decode
+     * @param context decoding context
      * @return collection of decoded entities
      * @throws java.lang.UnsupportedOperationException if the codec does not
      *                                                 support decode operations
      */
-    public List<T> decode(ArrayNode json) {
+    public List<T> decode(ArrayNode json, CodecContext context) {
         List<T> result = new ArrayList<>();
         for (JsonNode node : json) {
-            result.add(decode((ObjectNode) node));
+            result.add(decode((ObjectNode) node, context));
         }
         return result;
     }
diff --git a/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java b/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java
index 70775f0..2039aa5 100644
--- a/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java
@@ -92,6 +92,8 @@
      */
     Set<Link> getIngressLinks(ConnectPoint connectPoint);
 
+    // FIXME: I don't think this makes sense; discuss and remove or adjust return
+    // to be a Set<Link> or add Link.Type parameter
     /**
      * Returns the infrastructure links between the specified source
      * and destination connection points.
diff --git a/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java b/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
index f482a48..05460fb 100644
--- a/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
+++ b/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
@@ -58,12 +58,12 @@
 
     private static class FooCodec extends JsonCodec<Foo> {
         @Override
-        public ObjectNode encode(Foo entity, ObjectMapper mapper) {
-            return mapper.createObjectNode().put("name", entity.name);
+        public ObjectNode encode(Foo entity, CodecContext context) {
+            return context.mapper().createObjectNode().put("name", entity.name);
         }
 
         @Override
-        public Foo decode(ObjectNode json) {
+        public Foo decode(ObjectNode json, CodecContext context) {
             return new Foo(json.get("name").asText());
         }
     }
@@ -74,9 +74,26 @@
         Foo f2 = new Foo("bar");
         FooCodec codec = new FooCodec();
         ImmutableList<Foo> entities = ImmutableList.of(f1, f2);
-        ArrayNode json = codec.encode(entities, new ObjectMapper());
-        List<Foo> foos = codec.decode(json);
+        ArrayNode json = codec.encode(entities, new TestContext());
+        List<Foo> foos = codec.decode(json, new TestContext());
         assertEquals("incorrect encode/decode", entities, foos);
     }
 
+    private class TestContext implements CodecContext {
+        private ObjectMapper mapper = new ObjectMapper();
+        @Override
+        public ObjectMapper mapper() {
+            return mapper;
+        }
+
+        @Override
+        public <T> JsonCodec<T> codec(Class<T> entityClass) {
+            return null;
+        }
+
+        @Override
+        public <T> T get(Class<T> serviceClass) {
+            return null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/docs/pom.xml b/docs/pom.xml
index c0d1fcd..655cc31 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -80,7 +80,7 @@
                         <group>
                             <title>GUI, REST &amp; Command-Line</title>
                             <packages>
-                                org.onlab.onos.gui:org.onlab.onos.rest:org.onlab.onos.cli:org.onlab.onos.gui.*:org.onlab.onos.rest.*:org.onlab.onos.cli.*
+                                org.onlab.onos.gui:org.onlab.onos.rest:org.onlab.onos.cli:org.onlab.onos.gui.*:org.onlab.onos.rest.*:org.onlab.onos.cli.*:org.onlab.onos.codec.impl
                             </packages>
                         </group>
                         <group>
diff --git a/utils/misc/src/main/java/org/onlab/api/ItemNotFoundException.java b/utils/misc/src/main/java/org/onlab/api/ItemNotFoundException.java
new file mode 100644
index 0000000..fe484d4
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/api/ItemNotFoundException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.onlab.api;
+
+/**
+ * Represents condition where an item is not found or not available.
+ */
+public class ItemNotFoundException extends RuntimeException {
+
+    /**
+     * Creates a new exception with no message.
+     */
+    public ItemNotFoundException() {
+    }
+
+    /**
+     * Creates a new exception with the supplied message.
+     * @param message error message
+     */
+    public ItemNotFoundException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new exception with the supplied message and cause.
+     * @param message error message
+     * @param cause cause of the error
+     */
+    public ItemNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
index 8a8aca2..10850b0 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
@@ -125,17 +125,6 @@
     }
 
     /**
-     * Returns the integer value of this IP address.
-     * TODO: This method should be moved to Ip4Address.
-     *
-     * @return the IP address's value as an integer
-     */
-    public int toInt() {
-        ByteBuffer bb = ByteBuffer.wrap(octets);
-        return bb.getInt();
-    }
-
-    /**
      * Computes the IP address byte length for a given IP version.
      *
      * @param version the IP version
diff --git a/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java b/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
index ad3ca4b..7d6a5de 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
@@ -175,23 +175,6 @@
     }
 
     /**
-     * Tests returning an IPv4 address as an integer.
-     */
-    @Test
-    public void testToInt() {
-        IpAddress ipAddress;
-
-        ipAddress = IpAddress.valueOf("1.2.3.4");
-        assertThat(ipAddress.toInt(), is(0x01020304));
-
-        ipAddress = IpAddress.valueOf("0.0.0.0");
-        assertThat(ipAddress.toInt(), is(0));
-
-        ipAddress = IpAddress.valueOf("255.255.255.255");
-        assertThat(ipAddress.toInt(), is(-1));
-    }
-
-    /**
      * Tests valueOf() converter for IPv4 integer value.
      */
     @Test
diff --git a/utils/pom.xml b/utils/pom.xml
index 54a5467..f46bc61 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -38,7 +38,7 @@
         <module>nio</module>
         <module>osgi</module>
         <module>rest</module>
-	<module>thirdparty</module>
+        <module>thirdparty</module>
     </modules>
 
     <dependencies>
diff --git a/utils/rest/src/main/java/org/onlab/rest/BaseResource.java b/utils/rest/src/main/java/org/onlab/rest/BaseResource.java
index 5886afe..50b4d0f 100644
--- a/utils/rest/src/main/java/org/onlab/rest/BaseResource.java
+++ b/utils/rest/src/main/java/org/onlab/rest/BaseResource.java
@@ -18,6 +18,8 @@
 import org.onlab.osgi.DefaultServiceDirectory;
 import org.onlab.osgi.ServiceDirectory;
 
+import javax.ws.rs.core.Response;
+
 /**
  * Base abstraction of a JAX-RS resource.
  */
@@ -44,8 +46,12 @@
      * @param <T>     type of service
      * @return service implementation
      */
-    protected static <T> T get(Class<T> service) {
+    public <T> T get(Class<T> service) {
         return services.get(service);
     }
 
+    protected static Response.ResponseBuilder ok(Object obj) {
+        return Response.ok(obj);
+    }
+
 }
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotatedCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotatedCodec.java
new file mode 100644
index 0000000..1a6c511
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotatedCodec.java
@@ -0,0 +1,45 @@
+/*
+ * 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.onlab.onos.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.codec.CodecContext;
+import org.onlab.onos.codec.JsonCodec;
+import org.onlab.onos.net.Annotated;
+import org.onlab.onos.net.Annotations;
+
+/**
+ * Base JSON codec for annotated entities.
+ */
+public abstract class AnnotatedCodec<T extends Annotated> extends JsonCodec<T> {
+
+    /**
+     * Adds JSON encoding of the given item annotations to the specified node.
+     *
+     * @param node    node to add annotations to
+     * @param entity  annotated entity
+     * @param context encode context
+     * @return the given node
+     */
+    protected ObjectNode annotate(ObjectNode node, T entity, CodecContext context) {
+        if (!entity.annotations().keys().isEmpty()) {
+            JsonCodec<Annotations> codec = context.codec(Annotations.class);
+            node.set("annotations", codec.encode(entity.annotations(), context));
+        }
+        return node;
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotationsCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotationsCodec.java
new file mode 100644
index 0000000..a8d2907
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotationsCodec.java
@@ -0,0 +1,37 @@
+/*
+ * 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.onlab.onos.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.codec.CodecContext;
+import org.onlab.onos.codec.JsonCodec;
+import org.onlab.onos.net.Annotations;
+
+/**
+ * Annotations JSON codec.
+ */
+public class AnnotationsCodec extends JsonCodec<Annotations> {
+
+    @Override
+    public ObjectNode encode(Annotations annotations, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        for (String key : annotations.keys()) {
+            result.put(key, annotations.value(key));
+        }
+        return result;
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/CodecManager.java b/web/api/src/main/java/org/onlab/onos/codec/impl/CodecManager.java
new file mode 100644
index 0000000..30be730
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/CodecManager.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.onlab.onos.codec.impl;
+
+import com.google.common.collect.ImmutableSet;
+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.Service;
+import org.onlab.onos.codec.CodecService;
+import org.onlab.onos.codec.JsonCodec;
+import org.onlab.onos.net.Annotations;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Port;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implementation of the JSON codec brokering service.
+ */
+@Component(immediate = true)
+@Service
+public class CodecManager implements CodecService {
+
+    private static Logger log = LoggerFactory.getLogger(CodecManager.class);
+
+    private final Map<Class<?>, JsonCodec> codecs = new ConcurrentHashMap<>();
+
+    @Activate
+    public void activate() {
+        codecs.clear();
+        registerCodec(Annotations.class, new AnnotationsCodec());
+        registerCodec(Device.class, new DeviceCodec());
+        registerCodec(Port.class, new PortCodec());
+        registerCodec(ConnectPoint.class, new ConnectPointCodec());
+        registerCodec(Link.class, new LinkCodec());
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deativate() {
+        codecs.clear();
+        log.info("Stopped");
+    }
+
+    @Override
+    public Set<Class<?>> getCodecs() {
+        return ImmutableSet.copyOf(codecs.keySet());
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> JsonCodec<T> getCodec(Class<T> entityClass) {
+        return codecs.get(entityClass);
+    }
+
+    @Override
+    public <T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec) {
+        codecs.putIfAbsent(entityClass, codec);
+    }
+
+    @Override
+    public void unregisterCodec(Class<?> entityClass) {
+        codecs.remove(entityClass);
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/ConnectPointCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/ConnectPointCodec.java
new file mode 100644
index 0000000..3b5bf02
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/ConnectPointCodec.java
@@ -0,0 +1,38 @@
+/*
+ * 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.onlab.onos.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.codec.CodecContext;
+import org.onlab.onos.codec.JsonCodec;
+import org.onlab.onos.net.ConnectPoint;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Connection point JSON codec.
+ */
+public class ConnectPointCodec extends JsonCodec<ConnectPoint> {
+
+    @Override
+    public ObjectNode encode(ConnectPoint point, CodecContext context) {
+        checkNotNull(point, "Connect point cannot be null");
+        return context.mapper().createObjectNode()
+                .put("device", point.deviceId().toString())
+                .put("port", point.port().toString());
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/DeviceCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/DeviceCodec.java
new file mode 100644
index 0000000..5835382
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/DeviceCodec.java
@@ -0,0 +1,45 @@
+/*
+ * 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.onlab.onos.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.codec.CodecContext;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.device.DeviceService;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Device JSON codec.
+ */
+public class DeviceCodec extends AnnotatedCodec<Device> {
+
+    @Override
+    public ObjectNode encode(Device device, CodecContext context) {
+        checkNotNull(device, "Device cannot be null");
+        DeviceService service = context.get(DeviceService.class);
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("id", device.id().toString())
+                .put("available", service.isAvailable(device.id()))
+                .put("role", service.getRole(device.id()).toString())
+                .put("mfr", device.manufacturer())
+                .put("hw", device.hwVersion())
+                .put("sw", device.swVersion())
+                .put("serial", device.serialNumber());
+        return annotate(result, device, context);
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/LinkCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/LinkCodec.java
new file mode 100644
index 0000000..ad09663
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/LinkCodec.java
@@ -0,0 +1,43 @@
+/*
+ * 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.onlab.onos.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.codec.CodecContext;
+import org.onlab.onos.codec.JsonCodec;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.device.DeviceService;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Link JSON codec.
+ */
+public class LinkCodec extends AnnotatedCodec<Link> {
+
+    @Override
+    public ObjectNode encode(Link link, CodecContext context) {
+        checkNotNull(link, "Link cannot be null");
+        DeviceService service = context.get(DeviceService.class);
+        JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
+        ObjectNode result = context.mapper().createObjectNode();
+        result.set("src", codec.encode(link.src(), context));
+        result.set("dst", codec.encode(link.dst(), context));
+        return annotate(result, link, context);
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/PortCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/PortCodec.java
new file mode 100644
index 0000000..4bfcc6a
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/PortCodec.java
@@ -0,0 +1,45 @@
+/*
+ * 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.onlab.onos.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.codec.CodecContext;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Device port JSON codec.
+ */
+public class PortCodec extends AnnotatedCodec<Port> {
+
+    @Override
+    public ObjectNode encode(Port port, CodecContext context) {
+        checkNotNull(port, "Port cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("port", portName(port.number()))
+                .put("isEnabled", port.isEnabled())
+                .put("type", port.type().toString().toLowerCase())
+                .put("portSpeed", port.portSpeed());
+        return annotate(result, port, context);
+    }
+
+    private String portName(PortNumber port) {
+        return port.equals(PortNumber.LOCAL) ? "local" : port.toString();
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/package-info.java b/web/api/src/main/java/org/onlab/onos/codec/impl/package-info.java
new file mode 100644
index 0000000..31db517
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/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.
+ */
+
+/**
+ * Implementations of the codec broker and built-in entity JSON codecs.
+ */
+package org.onlab.onos.codec.impl;
\ No newline at end of file
diff --git a/web/api/src/main/java/org/onlab/onos/rest/AbstractWebResource.java b/web/api/src/main/java/org/onlab/onos/rest/AbstractWebResource.java
new file mode 100644
index 0000000..ee9adb0
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/AbstractWebResource.java
@@ -0,0 +1,81 @@
+/*
+ * 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.onlab.onos.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.api.ItemNotFoundException;
+import org.onlab.onos.codec.CodecContext;
+import org.onlab.onos.codec.CodecService;
+import org.onlab.onos.codec.JsonCodec;
+import org.onlab.rest.BaseResource;
+
+/**
+ * Abstract REST resource.
+ */
+public class AbstractWebResource extends BaseResource implements CodecContext {
+
+    @Override
+    public ObjectMapper mapper() {
+        return new ObjectMapper();
+    }
+
+    /**
+     * Returns the JSON codec for the specified entity class.
+     *
+     * @param entityClass entity class
+     * @param <T>         entity type
+     * @return JSON codec
+     */
+    public <T> JsonCodec<T> codec(Class<T> entityClass) {
+        return get(CodecService.class).getCodec(entityClass);
+    }
+
+    /**
+     * Returns JSON object wrapping the array encoding of the specified
+     * collection of items.
+     *
+     * @param codecClass codec item class
+     * @param field      field holding the array
+     * @param items      collection of items to be encoded into array
+     * @param <T>        item type
+     * @return JSON object
+     */
+    protected <T> ObjectNode encodeArray(Class<T> codecClass, String field,
+                                         Iterable<T> items) {
+        ObjectNode result = mapper().createObjectNode();
+        result.set(field, codec(codecClass).encode(items, this));
+        return result;
+    }
+
+    /**
+     * Returns the specified item if that items is null; otherwise throws
+     * not found exception.
+     *
+     * @param item    item to check
+     * @param message not found message
+     * @param <T>     item type
+     * @return item if not null
+     * @throws org.onlab.api.ItemNotFoundException if item is null
+     */
+    protected <T> T nullIsNotFound(T item, String message) {
+        if (item == null) {
+            throw new ItemNotFoundException(message);
+        }
+        return item;
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/DevicesWebResource.java b/web/api/src/main/java/org/onlab/onos/rest/DevicesWebResource.java
new file mode 100644
index 0000000..52fca58
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/DevicesWebResource.java
@@ -0,0 +1,65 @@
+/*
+ * 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.onlab.onos.rest;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.device.DeviceService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.onos.net.DeviceId.deviceId;
+
+/**
+ * REST resource for interacting with the inventory of infrastructure devices.
+ */
+@Path("devices")
+public class DevicesWebResource extends AbstractWebResource {
+
+    public static final String DEVICE_NOT_FOUND = "Device is not found";
+
+    @GET
+    public Response getDevices() {
+        Iterable<Device> devices = get(DeviceService.class).getDevices();
+        return ok(encodeArray(Device.class, "devices", devices)).build();
+    }
+
+    @GET
+    @Path("{id}")
+    public Response getDevice(@PathParam("id") String id) {
+        Device device = nullIsNotFound(get(DeviceService.class).getDevice(deviceId(id)),
+                                       DEVICE_NOT_FOUND);
+        return ok(codec(Device.class).encode(device, this)).build();
+    }
+
+    @GET
+    @Path("{id}/ports")
+    public Response getDevicePorts(@PathParam("id") String id) {
+        DeviceService service = get(DeviceService.class);
+        Device device = nullIsNotFound(service.getDevice(deviceId(id)), DEVICE_NOT_FOUND);
+        List<Port> ports = checkNotNull(service.getPorts(deviceId(id)), "Ports could not be retrieved");
+        ObjectNode result = codec(Device.class).encode(device, this);
+        result.set("ports", codec(Port.class).encode(ports, this));
+        return ok(result).build();
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/JsonBodyWriter.java b/web/api/src/main/java/org/onlab/onos/rest/JsonBodyWriter.java
new file mode 100644
index 0000000..d34e30e
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/JsonBodyWriter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.onlab.onos.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+/**
+ * JAX-RS Response message body writer.
+ */
+@Produces("application/json")
+public class JsonBodyWriter implements MessageBodyWriter<ObjectNode> {
+
+    private ObjectMapper mapper = new ObjectMapper();
+
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType,
+                               Annotation[] annotations, MediaType mediaType) {
+        return type == ObjectNode.class;
+    }
+
+    @Override
+    public long getSize(ObjectNode node, Class<?> type, Type genericType,
+                        Annotation[] annotations, MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(ObjectNode node, Class<?> type, Type genericType,
+                        Annotation[] annotations, MediaType mediaType,
+                        MultivaluedMap<String, Object> httpHeaders,
+                        OutputStream entityStream) throws IOException {
+        mapper.writer().writeValue(entityStream, node);
+        entityStream.flush();
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/LinksWebResource.java b/web/api/src/main/java/org/onlab/onos/rest/LinksWebResource.java
new file mode 100644
index 0000000..479b6a0
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/LinksWebResource.java
@@ -0,0 +1,89 @@
+/*
+ * 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.onlab.onos.rest;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.link.LinkService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+import static org.onlab.onos.rest.LinksWebResource.Direction.valueOf;
+
+/**
+ * REST resource for interacting with the inventory of infrastructure links.
+ */
+@Path("links")
+public class LinksWebResource extends AbstractWebResource {
+
+    enum Direction { ALL, INGRESS, EGRESS }
+
+    @GET
+    public Response getLinks(@QueryParam("device") String deviceId,
+                             @QueryParam("port") String port,
+                             @QueryParam("direction") String direction) {
+        LinkService service = get(LinkService.class);
+        Iterable<Link> links;
+
+        if (deviceId != null && port != null) {
+            links = getConnectPointLinks(new ConnectPoint(deviceId(deviceId),
+                                                          portNumber(port)),
+                                         direction, service);
+        } else if (deviceId != null) {
+            links = getDeviceLinks(deviceId(deviceId), direction, service);
+        } else {
+            links = service.getLinks();
+        }
+        return ok(encodeArray(Link.class, "links", links)).build();
+    }
+
+    private Iterable<Link> getConnectPointLinks(ConnectPoint point,
+                                                String direction,
+                                                LinkService service) {
+        Direction dir = direction != null ?
+                valueOf(direction.toUpperCase()) : Direction.ALL;
+        switch (dir) {
+            case INGRESS:
+                return service.getIngressLinks(point);
+            case EGRESS:
+                return service.getEgressLinks(point);
+            default:
+                return service.getLinks(point);
+        }
+    }
+
+    private Iterable<Link> getDeviceLinks(DeviceId deviceId,
+                                          String direction,
+                                          LinkService service) {
+        Direction dir = direction != null ?
+                valueOf(direction.toUpperCase()) : Direction.ALL;
+        switch (dir) {
+            case INGRESS:
+                return service.getDeviceIngressLinks(deviceId);
+            case EGRESS:
+                return service.getDeviceEgressLinks(deviceId);
+            default:
+                return service.getDeviceLinks(deviceId);
+        }
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/AbstractMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/AbstractMapper.java
new file mode 100644
index 0000000..50c27ae
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/AbstractMapper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.onlab.onos.rest.exceptions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+/**
+ * Base exception mapper implementation.
+ */
+public abstract class AbstractMapper<E extends Throwable> implements ExceptionMapper<E> {
+
+    /**
+     * Returns the response status to be given when the exception occurs.
+     *
+     * @return response status
+     */
+    protected abstract Response.Status responseStatus();
+
+    @Override
+    public Response toResponse(E exception) {
+        return response(responseStatus(), exception).build();
+    }
+
+    /**
+     * Produces a response builder primed with the supplied status code
+     * and JSON entity with the status code and exception message.
+     *
+     * @param status    response status
+     * @param exception exception to encode
+     * @return response builder
+     */
+    protected Response.ResponseBuilder response(Response.Status status,
+                                                Throwable exception) {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode result = mapper.createObjectNode()
+                .put("code", status.getStatusCode())
+                .put("message", exception.getMessage());
+        return Response.status(status).entity(result.toString());
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/EntityNotFoundMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/EntityNotFoundMapper.java
new file mode 100644
index 0000000..4c86fe6
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/EntityNotFoundMapper.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.onlab.onos.rest.exceptions;
+
+import org.onlab.api.ItemNotFoundException;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Mapper for service not found exceptions to the NOT_FOUND response code.
+ */
+public class EntityNotFoundMapper extends AbstractMapper<ItemNotFoundException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.NOT_FOUND;
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServerErrorMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServerErrorMapper.java
new file mode 100644
index 0000000..a9d95d0
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServerErrorMapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.onlab.onos.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Mapper for service not found exceptions to the NOT_FOUND response code.
+ */
+public class ServerErrorMapper extends AbstractMapper<RuntimeException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.INTERNAL_SERVER_ERROR;
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServiceNotFoundMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServiceNotFoundMapper.java
new file mode 100644
index 0000000..297882b
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServiceNotFoundMapper.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.onlab.onos.rest.exceptions;
+
+import org.onlab.osgi.ServiceNotFoundException;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Mapper for service not found exceptions to the SERVICE_UNAVAILABLE response code.
+ */
+public class ServiceNotFoundMapper extends AbstractMapper<ServiceNotFoundException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.SERVICE_UNAVAILABLE;
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/package-info.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/package-info.java
new file mode 100644
index 0000000..5f733e7
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/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.
+ */
+
+/**
+ * Various exception mappers to map errors to proper response status codes.
+ */
+package org.onlab.onos.rest.exceptions;
\ No newline at end of file
diff --git a/web/api/src/main/webapp/WEB-INF/web.xml b/web/api/src/main/webapp/WEB-INF/web.xml
index c634728..1342203 100644
--- a/web/api/src/main/webapp/WEB-INF/web.xml
+++ b/web/api/src/main/webapp/WEB-INF/web.xml
@@ -24,8 +24,21 @@
         <servlet-name>JAX-RS Service</servlet-name>
         <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
         <init-param>
-            <param-name>com.sun.jersey.config.property.packages</param-name>
-            <param-value>org.onlab.onos.rest</param-value>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>
+                org.onlab.onos.rest.exceptions.EntityNotFoundMapper,
+                org.onlab.onos.rest.exceptions.ServiceNotFoundMapper,
+                org.onlab.onos.rest.exceptions.ServerErrorMapper,
+                org.onlab.onos.rest.JsonBodyWriter,
+
+                org.onlab.onos.rest.DevicesWebResource,
+                org.onlab.onos.rest.LinksWebResource,
+                org.onlab.onos.rest.ConfigResource
+            </param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
diff --git a/web/pom.xml b/web/pom.xml
index 0efbd59..f846078 100644
--- a/web/pom.xml
+++ b/web/pom.xml
@@ -109,6 +109,10 @@
         <plugins>
             <plugin>
                 <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <extensions>true</extensions>
                 <configuration>
@@ -120,13 +124,15 @@
                         <Import-Package>
                             org.slf4j,
                             org.osgi.framework,
-                            javax.ws.rs,javax.ws.rs.core,
+                            javax.ws.rs,javax.ws.rs.core,javax.ws.rs.ext,
                             com.sun.jersey.api.core,
                             com.sun.jersey.spi.container.servlet,
                             com.sun.jersey.server.impl.container.servlet,
                             com.fasterxml.jackson.databind,
                             com.fasterxml.jackson.databind.node,
                             com.google.common.base.*,
+                            org.onlab.api.*,
+                            org.onlab.osgi.*,
                             org.onlab.packet.*,
                             org.onlab.rest.*,
                             org.onlab.onos.*