merge pom.xml
diff --git a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
index aeea443..9a2bda8 100644
--- a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
@@ -10,15 +10,25 @@
 public abstract class AbstractShellCommand extends OsgiCommandSupport {
 
     /**
-     * Returns the reference to the implementaiton of the specified service.
+     * Returns the reference to the implementation of the specified service.
      *
      * @param serviceClass service class
      * @param <T>          type of service
      * @return service implementation
      */
-    static <T> T get(Class<T> serviceClass) {
+    public static <T> T get(Class<T> serviceClass) {
         BundleContext bc = FrameworkUtil.getBundle(AbstractShellCommand.class).getBundleContext();
         return bc.getService(bc.getServiceReference(serviceClass));
     }
 
+    /**
+     * Prints the arguments using the specified format.
+     *
+     * @param format format string; see {@link String#format}
+     * @param args   arguments
+     */
+    public static void print(String format, Object... args) {
+        System.out.println(String.format(format, args));
+    }
+
 }
diff --git a/cli/src/main/java/org/onlab/onos/cli/GreetCommand.java b/cli/src/main/java/org/onlab/onos/cli/GreetCommand.java
index 46d4b2c..71e56f1 100644
--- a/cli/src/main/java/org/onlab/onos/cli/GreetCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/GreetCommand.java
@@ -2,7 +2,6 @@
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
 import org.onlab.onos.GreetService;
 
 /**
@@ -10,7 +9,7 @@
  * use of an optional parameter as well.
  */
 @Command(scope = "onos", name = "greet", description = "Issues a greeting")
-public class GreetCommand extends OsgiCommandSupport {
+public class GreetCommand extends AbstractShellCommand {
 
     @Argument(index = 0, name = "name", description = "Name to greet",
               required = false, multiValued = false)
@@ -18,7 +17,7 @@
 
     @Override
     protected Object doExecute() throws Exception {
-        System.out.println(getService(GreetService.class).yo(name));
+        print(getService(GreetService.class).yo(name));
         return null;
     }
 }
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DeviceIdCompleter.java b/cli/src/main/java/org/onlab/onos/cli/net/DeviceIdCompleter.java
new file mode 100644
index 0000000..5d38bca
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DeviceIdCompleter.java
@@ -0,0 +1,34 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.device.DeviceService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Device ID completer.
+ */
+public class DeviceIdCompleter implements Completer {
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        // Delegate string completer
+        StringsCompleter delegate = new StringsCompleter();
+
+        // Fetch our service and feed it's offerings to the string completer
+        DeviceService service = AbstractShellCommand.get(DeviceService.class);
+        Iterator<Device> it = service.getDevices().iterator();
+        SortedSet<String> strings = delegate.getStrings();
+        while (it.hasNext()) {
+            strings.add(it.next().id().toString());
+        }
+
+        // Now let the completer do the work for figuring out what to offer.
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
new file mode 100644
index 0000000..03fc555
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
@@ -0,0 +1,60 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.device.DeviceService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+
+/**
+ * Lists all ports or all ports of a device.
+ */
+@Command(scope = "onos", name = "ports",
+         description = "Lists all ports or all ports of a device")
+public class DevicePortsListCommand extends DevicesListCommand {
+
+    private static final String FMT = "  port=%s, state=%s";
+
+    @Argument(index = 0, name = "uri", description = "Device ID",
+              required = false, multiValued = false)
+    String uri = null;
+
+    private static final Comparator<Port> PORT_COMPARATOR = new Comparator<Port>() {
+        @Override
+        public int compare(Port p1, Port p2) {
+            long delta = p1.number().toLong() - p2.number().toLong();
+            return delta == 0 ? 0 : (delta < 0 ? -1 : +1);
+        }
+    };
+
+    @Override
+    protected Object doExecute() throws Exception {
+        DeviceService service = getService(DeviceService.class);
+        if (uri == null) {
+            for (Device device : getSortedDevices(service)) {
+                printDevice(service, device);
+            }
+        } else {
+            printDevice(service, service.getDevice(deviceId(uri)));
+        }
+        return null;
+    }
+
+    @Override
+    protected void printDevice(DeviceService service, Device device) {
+        super.printDevice(service, device);
+        List<Port> ports = new ArrayList<>(service.getPorts(device.id()));
+        Collections.sort(ports, PORT_COMPARATOR);
+        for (Port port : ports) {
+            print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled");
+        }
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DeviceRemoveCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DeviceRemoveCommand.java
new file mode 100644
index 0000000..b4f2385
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DeviceRemoveCommand.java
@@ -0,0 +1,26 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.DeviceAdminService;
+
+/**
+ * Removes an infrastructure device.
+ */
+@Command(scope = "onos", name = "device-remove",
+         description = "Removes an infrastructure device")
+public class DeviceRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "uri", description = "Device ID",
+              required = true, multiValued = false)
+    String uri = null;
+
+    @Override
+    protected Object doExecute() throws Exception {
+        getService(DeviceAdminService.class).removeDevice(DeviceId.deviceId(uri));
+        return null;
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
new file mode 100644
index 0000000..1cbeb6b
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
@@ -0,0 +1,65 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.device.DeviceService;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * Lists all infrastructure devices.
+ */
+@Command(scope = "onos", name = "devices",
+         description = "Lists all infrastructure devices")
+public class DevicesListCommand extends AbstractShellCommand {
+
+    private static final String FMT =
+            "id=%s, available=%s, role=%s, type=%s, mfr=%s, hw=%s, sw=%s, serial=%s";
+
+    protected static final Comparator<Device> ID_COMPARATOR = new Comparator<Device>() {
+        @Override
+        public int compare(Device d1, Device d2) {
+            return d1.id().uri().toString().compareTo(d2.id().uri().toString());
+        }
+    };
+
+    @Override
+    protected Object doExecute() throws Exception {
+        DeviceService service = getService(DeviceService.class);
+        for (Device device : getSortedDevices(service)) {
+            printDevice(service, device);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the list of devices sorted using the device ID URIs.
+     *
+     * @param service device service
+     * @return sorted device list
+     */
+    protected List<Device> getSortedDevices(DeviceService service) {
+        List<Device> devices = newArrayList(service.getDevices());
+        Collections.sort(devices, ID_COMPARATOR);
+        return devices;
+    }
+
+    /**
+     * Prints information about the specified device.
+     *
+     * @param service device service
+     * @param device  infrastructure device
+     */
+    protected void printDevice(DeviceService service, Device device) {
+        print(FMT, device.id(), service.isAvailable(device.id()),
+              service.getRole(device.id()), device.type(),
+              device.manufacturer(), device.hwVersion(), device.swVersion(),
+              device.serialNumber());
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
new file mode 100644
index 0000000..cb6fcb1
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
@@ -0,0 +1,35 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.link.LinkService;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+
+/**
+ * Lists all infrastructure links.
+ */
+@Command(scope = "onos", name = "links",
+         description = "Lists all infrastructure links")
+public class LinksListCommand extends AbstractShellCommand {
+
+    private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s";
+
+    @Argument(index = 0, name = "uri", description = "Device ID",
+              required = false, multiValued = false)
+    String uri = null;
+
+    @Override
+    protected Object doExecute() throws Exception {
+        LinkService service = getService(LinkService.class);
+        Iterable<Link> links = uri != null ?
+                service.getDeviceLinks(deviceId(uri)) : service.getLinks();
+        for (Link link : links) {
+            print(FMT, link.src().deviceId(), link.src().port(),
+                  link.dst().deviceId(), link.dst().port(), link.type());
+        }
+        return null;
+    }
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 5cc83ef..7aa67be 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -2,6 +2,29 @@
 
     <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
         <command>
+            <action class="org.onlab.onos.cli.net.DevicesListCommand"/>
+        </command>
+        <command>
+            <action class="org.onlab.onos.cli.net.DevicePortsListCommand"/>
+            <completers>
+                <ref component-id="deviceIdCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onlab.onos.cli.net.DeviceRemoveCommand"/>
+            <completers>
+                <ref component-id="deviceIdCompleter"/>
+            </completers>
+        </command>
+
+        <command>
+            <action class="org.onlab.onos.cli.net.LinksListCommand"/>
+            <completers>
+                <ref component-id="deviceIdCompleter"/>
+            </completers>
+        </command>
+
+        <command>
             <action class="org.onlab.onos.cli.GreetCommand"/>
             <completers>
                 <ref component-id="nameCompleter"/>
@@ -9,6 +32,8 @@
         </command>
     </command-bundle>
 
+    <bean id="deviceIdCompleter" class="org.onlab.onos.cli.net.DeviceIdCompleter"/>
+
     <bean id="nameCompleter" class="org.onlab.onos.cli.NameCompleter"/>
 
 </blueprint>
diff --git a/features/features.xml b/features/features.xml
index 9bb4519..1d4da58 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -5,7 +5,8 @@
 
     <feature name="onos-thirdparty-base" version="1.0.0"
              description="ONOS 3rd party dependencies">
-        <bundle>mvn:com.google.guava/guava/17.0</bundle>
+        <bundle>mvn:commons-lang/commons-lang/2.6</bundle>
+        <bundle>mvn:com.google.guava/guava/18.0</bundle>
     </feature>
 
     <feature name="onos-thirdparty-web" version="1.0.0"
diff --git a/net/api/src/main/java/org/onlab/onos/net/ConnectPoint.java b/net/api/src/main/java/org/onlab/onos/net/ConnectPoint.java
index 72ea3f4..f870698 100644
--- a/net/api/src/main/java/org/onlab/onos/net/ConnectPoint.java
+++ b/net/api/src/main/java/org/onlab/onos/net/ConnectPoint.java
@@ -1,23 +1,86 @@
 package org.onlab.onos.net;
 
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Abstraction of a network connection point expressed as a pair of the
- * device identifier and the device port number.
+ * network element identifier and port number.
  */
-public interface ConnectPoint {
+public class ConnectPoint {
+
+    private final ElementId elementId;
+    private final PortNumber portNumber;
 
     /**
-     * Returns the connection device identifier.
+     * Creates a new connection point.
      *
-     * @return device id
+     * @param elementId  network element identifier
+     * @param portNumber port number
      */
-    DeviceId deviceId();
+    public ConnectPoint(ElementId elementId, PortNumber portNumber) {
+        this.elementId = elementId;
+        this.portNumber = portNumber;
+    }
+
+    /**
+     * Returns the network element identifier.
+     *
+     * @return element identifier
+     */
+    public ElementId elementId() {
+        return elementId;
+    }
+
+    /**
+     * Returns the identifier of the infrastructure device if the connection
+     * point belongs to a network element which is indeed an infrastructure
+     * device.
+     *
+     * @return network element identifier as a device identifier
+     * @throws java.lang.IllegalStateException if connection point is not
+     *                                         associated with a device
+     */
+    @SuppressWarnings("unchecked")
+    public DeviceId deviceId() {
+        if (elementId instanceof DeviceId) {
+            return (DeviceId) elementId;
+        }
+        throw new IllegalStateException("Connection point not associated " +
+                                                "with an infrastructure device");
+    }
 
     /**
      * Returns the connection port number.
      *
      * @return port number
      */
-    PortNumber port();
+    public PortNumber port() {
+        return portNumber;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(elementId, portNumber);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof ConnectPoint) {
+            final ConnectPoint other = (ConnectPoint) obj;
+            return Objects.equals(this.elementId, other.elementId) &&
+                    Objects.equals(this.portNumber, other.portNumber);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("elementId", elementId)
+                .add("portNumber", portNumber)
+                .toString();
+    }
 
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/DefaultDevice.java b/net/api/src/main/java/org/onlab/onos/net/DefaultDevice.java
index 42a3526..69c10b7 100644
--- a/net/api/src/main/java/org/onlab/onos/net/DefaultDevice.java
+++ b/net/api/src/main/java/org/onlab/onos/net/DefaultDevice.java
@@ -4,10 +4,10 @@
 
 import java.util.Objects;
 
-import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * Default device model implementation.
+ * Default infrastructure device model implementation.
  */
 public class DefaultDevice extends AbstractElement implements Device {
 
diff --git a/net/api/src/main/java/org/onlab/onos/net/DefaultLink.java b/net/api/src/main/java/org/onlab/onos/net/DefaultLink.java
new file mode 100644
index 0000000..1f7783c
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/DefaultLink.java
@@ -0,0 +1,74 @@
+package org.onlab.onos.net;
+
+import org.onlab.onos.net.provider.ProviderId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default infrastructure link model implementation.
+ */
+public class DefaultLink extends AbstractModel implements Link {
+
+    private final ConnectPoint src;
+    private final ConnectPoint dst;
+    private final Type type;
+
+    /**
+     * Creates a link description using the supplied information.
+     *
+     * @param providerId provider identity
+     * @param src        link source
+     * @param dst        link destination
+     * @param type       link type
+     */
+    public DefaultLink(ProviderId providerId, ConnectPoint src, ConnectPoint dst,
+                       Type type) {
+        super(providerId);
+        this.src = src;
+        this.dst = dst;
+        this.type = type;
+    }
+
+    @Override
+    public ConnectPoint src() {
+        return src;
+    }
+
+    @Override
+    public ConnectPoint dst() {
+        return dst;
+    }
+
+    @Override
+    public Type type() {
+        return type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(src, dst, type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof DefaultLink) {
+            final DefaultLink other = (DefaultLink) obj;
+            return Objects.equals(this.src, other.src) &&
+                    Objects.equals(this.dst, other.dst) &&
+                    Objects.equals(this.type, other.type);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("src", src)
+                .add("dst", dst)
+                .add("type", type)
+                .toString();
+    }
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/DefaultPort.java b/net/api/src/main/java/org/onlab/onos/net/DefaultPort.java
index 56a2979..378cc37 100644
--- a/net/api/src/main/java/org/onlab/onos/net/DefaultPort.java
+++ b/net/api/src/main/java/org/onlab/onos/net/DefaultPort.java
@@ -2,7 +2,7 @@
 
 import java.util.Objects;
 
-import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
  * Default port implementation.
diff --git a/net/api/src/main/java/org/onlab/onos/net/ElementId.java b/net/api/src/main/java/org/onlab/onos/net/ElementId.java
index 9f7dffb..e205bb6 100644
--- a/net/api/src/main/java/org/onlab/onos/net/ElementId.java
+++ b/net/api/src/main/java/org/onlab/onos/net/ElementId.java
@@ -3,8 +3,6 @@
 import java.net.URI;
 import java.util.Objects;
 
-import static com.google.common.base.Objects.toStringHelper;
-
 /**
  * Immutable representation of a network element identity.
  */
@@ -47,7 +45,7 @@
 
     @Override
     public String toString() {
-        return toStringHelper(this).add("uri", uri).toString();
+        return uri.toString();
     }
 
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/HostLocation.java b/net/api/src/main/java/org/onlab/onos/net/HostLocation.java
index 022a6f9..22673a6 100644
--- a/net/api/src/main/java/org/onlab/onos/net/HostLocation.java
+++ b/net/api/src/main/java/org/onlab/onos/net/HostLocation.java
@@ -1,10 +1,19 @@
 package org.onlab.onos.net;
 
+import java.util.Objects;
+
 /**
  * Representation of a network edge location where an end-station host is
  * connected.
  */
-public interface HostLocation extends ConnectPoint {
+public class HostLocation extends ConnectPoint {
+
+    private final long time;
+
+    public HostLocation(DeviceId deviceId, PortNumber portNumber, long time) {
+        super(deviceId, portNumber);
+        this.time = time;
+    }
 
     /**
      * Returns the timestamp when the location was established, given in
@@ -12,6 +21,22 @@
      *
      * @return timestamp in milliseconds since start of epoch
      */
-    long time();
+    public long time() {
+        return time;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hash(time);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof HostLocation) {
+            final HostLocation other = (HostLocation) obj;
+            return super.equals(obj) && Objects.equals(this.time, other.time);
+        }
+        return false;
+    }
 
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/Link.java b/net/api/src/main/java/org/onlab/onos/net/Link.java
index 38b6f69..0f4275e 100644
--- a/net/api/src/main/java/org/onlab/onos/net/Link.java
+++ b/net/api/src/main/java/org/onlab/onos/net/Link.java
@@ -4,7 +4,6 @@
  * Abstraction of a network infrastructure link.
  */
 public interface Link extends Provided {
-// TODO: Consider extending graph Edge<Element> once the graph module is available
 
     /**
      * Coarse representation of the link type.
@@ -38,6 +37,13 @@
      */
     ConnectPoint dst();
 
+    /**
+     * Returns the link type.
+     *
+     * @return link type
+     */
+    Type type();
+
     // LinkInfo info(); // Additional link information / decorations
 
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java b/net/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
index 3d340b2..833625d 100644
--- a/net/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
+++ b/net/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
@@ -2,7 +2,7 @@
 
 import java.net.URI;
 
-import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.onos.net.Device.Type;
 
diff --git a/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java b/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java
index 6c46627..8364935 100644
--- a/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java
+++ b/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java
@@ -55,13 +55,22 @@
 
     /**
      * Returns the port with the specified number and hosted by the given device.
-     * @param deviceId device identifier
+     *
+     * @param deviceId   device identifier
      * @param portNumber port number
      * @return device port
      */
     Port getPort(DeviceId deviceId, PortNumber portNumber);
 
     /**
+     * Indicates whether or not the device is presently online and available.
+     *
+     * @param deviceId device identifier
+     * @return true if the device is available
+     */
+    boolean isAvailable(DeviceId deviceId);
+
+    /**
      * Adds the specified device listener.
      *
      * @param listener device listener
diff --git a/net/api/src/main/java/org/onlab/onos/net/link/DefaultLinkDescription.java b/net/api/src/main/java/org/onlab/onos/net/link/DefaultLinkDescription.java
new file mode 100644
index 0000000..ffdc2fa
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/link/DefaultLinkDescription.java
@@ -0,0 +1,43 @@
+package org.onlab.onos.net.link;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+
+/**
+ * Default implementation of immutable link description entity.
+ */
+public class DefaultLinkDescription implements LinkDescription {
+
+    private final ConnectPoint src;
+    private final ConnectPoint dst;
+    private final Link.Type type;
+
+    /**
+     * Creates a link description using the supplied information.
+     *
+     * @param src  link source
+     * @param dst  link destination
+     * @param type link type
+     */
+    public DefaultLinkDescription(ConnectPoint src, ConnectPoint dst, Link.Type type) {
+        this.src = src;
+        this.dst = dst;
+        this.type = type;
+    }
+
+    @Override
+    public ConnectPoint src() {
+        return src;
+    }
+
+    @Override
+    public ConnectPoint dst() {
+        return dst;
+    }
+
+    @Override
+    public Link.Type type() {
+        return type;
+    }
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/link/LinkAdminService.java b/net/api/src/main/java/org/onlab/onos/net/link/LinkAdminService.java
new file mode 100644
index 0000000..228b3c9
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/link/LinkAdminService.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net.link;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+
+/**
+ * Service for administering the inventory of infrastructure links.
+ */
+public interface LinkAdminService {
+
+    /**
+     * Removes all infrastructure links leading to and from the
+     * specified connection point.
+     *
+     * @param connectPoint connection point
+     */
+    void removeLinks(ConnectPoint connectPoint);
+
+    /**
+     * Removes all infrastructure links leading to and from the
+     * specified device.
+     *
+     * @param deviceId device identifier
+     */
+    void removeLinks(DeviceId deviceId);
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java b/net/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
index 8d712f0..b1be82c 100644
--- a/net/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
+++ b/net/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
@@ -1,17 +1,34 @@
 package org.onlab.onos.net.link;
 
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+
 /**
  * Describes an infrastructure link.
  */
 public interface LinkDescription {
 
-    // TODO: src, dst connection points, which are pairs of (DeviceId, PortNumber)
+    /**
+     * Returns the link source.
+     *
+     * @return links source
+     */
+    ConnectPoint src();
 
-//    On the north:
-//    Link = (ConnectPoint src, ConnectPoint dst);
-//    ConnectPoint = (DeviceId, PortNumber);
+    /**
+     * Returns the link destination.
+     *
+     * @return links destination
+     */
+    ConnectPoint dst();
 
-//    On the south
-//    LinkDescription ~ Link
+    /**
+     * Returns the link type.
+     *
+     * @return link type
+     */
+    Link.Type type();
 
+
+    // Add further link attributes
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java b/net/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java
index 18987ea..a85965a 100644
--- a/net/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java
+++ b/net/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java
@@ -18,6 +18,11 @@
         LINK_ADDED,
 
         /**
+         * Signifies that a link has been updated.
+         */
+        LINK_UPDATED,
+
+        /**
          * Signifies that a link has been removed.
          */
         LINK_REMOVED
diff --git a/net/api/src/main/java/org/onlab/onos/net/link/LinkProviderService.java b/net/api/src/main/java/org/onlab/onos/net/link/LinkProviderService.java
index 7969022..7019660 100644
--- a/net/api/src/main/java/org/onlab/onos/net/link/LinkProviderService.java
+++ b/net/api/src/main/java/org/onlab/onos/net/link/LinkProviderService.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.net.link;
 
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.provider.ProviderService;
 
 /**
@@ -21,4 +23,20 @@
      */
     void linkVanished(LinkDescription linkDescription);
 
+    /**
+     * Signals that infrastructure links associated with the specified
+     * connect point have vanished.
+     *
+     * @param connectPoint connect point
+     */
+    void linksVanished(ConnectPoint connectPoint);
+
+    /**
+     * Signals that infrastructure links associated with the specified
+     * device have vanished.
+     *
+     * @param deviceId device identifier
+     */
+    void linksVanished(DeviceId deviceId);
+
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/link/LinkService.java b/net/api/src/main/java/org/onlab/onos/net/link/LinkService.java
index fadecdb..1257d97 100644
--- a/net/api/src/main/java/org/onlab/onos/net/link/LinkService.java
+++ b/net/api/src/main/java/org/onlab/onos/net/link/LinkService.java
@@ -1,5 +1,6 @@
 package org.onlab.onos.net.link;
 
+import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Link;
 
@@ -11,6 +12,13 @@
 public interface LinkService {
 
     /**
+     * Returns the count of all known infrastructure links.
+     *
+     * @return number of infrastructure links
+     */
+    int getLinkCount();
+
+    /**
      * Returns a collection of all known infrastructure links.
      *
      * @return all infrastructure links
@@ -40,7 +48,44 @@
      * @param deviceId device identifier
      * @return set of device ingress links
      */
-    Set<Link> getDeviceInressLinks(DeviceId deviceId);
+    Set<Link> getDeviceIngressLinks(DeviceId deviceId);
+
+    /**
+     * Returns set of all infrastructure links leading to and from the
+     * specified connection point.
+     *
+     * @param connectPoint connection point
+     * @return set of links
+     */
+    Set<Link> getLinks(ConnectPoint connectPoint);
+
+    /**
+     * Returns set of all infrastructure links leading from the specified
+     * connection point.
+     *
+     * @param connectPoint connection point
+     * @return set of device egress links
+     */
+    Set<Link> getEgressLinks(ConnectPoint connectPoint);
+
+    /**
+     * Returns set of all infrastructure links leading to the specified
+     * connection point.
+     *
+     * @param connectPoint connection point
+     * @return set of device ingress links
+     */
+    Set<Link> getIngressLinks(ConnectPoint connectPoint);
+
+    /**
+     * Returns the infrastructure links between the specified source
+     * and destination connection points.
+     *
+     * @param src source connection point
+     * @param dst destination connection point
+     * @return link from source to destination; null if none found
+     */
+    Link getLink(ConnectPoint src, ConnectPoint dst);
 
     /**
      * Adds the specified link listener.
diff --git a/net/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java b/net/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java
index 3af20ad..a945354 100644
--- a/net/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java
+++ b/net/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java
@@ -2,7 +2,7 @@
 
 import java.util.Objects;
 
-import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
  * Notion of provider identity.
diff --git a/net/api/src/test/java/org/onlab/onos/net/DefaultLinkTest.java b/net/api/src/test/java/org/onlab/onos/net/DefaultLinkTest.java
new file mode 100644
index 0000000..37757af
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/net/DefaultLinkTest.java
@@ -0,0 +1,50 @@
+package org.onlab.onos.net;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onlab.onos.net.provider.ProviderId;
+
+import static org.junit.Assert.assertEquals;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Link.Type.DIRECT;
+import static org.onlab.onos.net.Link.Type.INDIRECT;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Test of the default link model entity.
+ */
+public class DefaultLinkTest {
+
+    private static final ProviderId PID = new ProviderId("foo");
+    private static final DeviceId DID1 = deviceId("of:foo");
+    private static final DeviceId DID2 = deviceId("of:bar");
+    private static final PortNumber P1 = portNumber(1);
+    private static final PortNumber P2 = portNumber(2);
+
+    public static ConnectPoint cp(DeviceId id, PortNumber pn) {
+        return new ConnectPoint(id, pn);
+    }
+
+    @Test
+    public void testEquality() {
+        Link l1 = new DefaultLink(PID, cp(DID1, P1), cp(DID2, P2), DIRECT);
+        Link l2 = new DefaultLink(PID, cp(DID1, P1), cp(DID2, P2), DIRECT);
+        Link l3 = new DefaultLink(PID, cp(DID1, P2), cp(DID2, P2), DIRECT);
+        Link l4 = new DefaultLink(PID, cp(DID1, P2), cp(DID2, P2), DIRECT);
+        Link l5 = new DefaultLink(PID, cp(DID1, P2), cp(DID2, P2), INDIRECT);
+
+        new EqualsTester().addEqualityGroup(l1, l2)
+                .addEqualityGroup(l3, l4)
+                .addEqualityGroup(l5)
+                .testEquals();
+    }
+
+    @Test
+    public void basics() {
+        Link link = new DefaultLink(PID, cp(DID1, P1), cp(DID2, P2), DIRECT);
+        assertEquals("incorrect src", cp(DID1, P1), link.src());
+        assertEquals("incorrect dst", cp(DID2, P2), link.dst());
+        assertEquals("incorrect type", DIRECT, link.type());
+    }
+
+}
diff --git a/net/api/src/test/java/org/onlab/onos/net/link/DefaultLinkDescriptionTest.java b/net/api/src/test/java/org/onlab/onos/net/link/DefaultLinkDescriptionTest.java
new file mode 100644
index 0000000..2c69625
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/net/link/DefaultLinkDescriptionTest.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.net.link;
+
+import org.junit.Test;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+
+import static org.junit.Assert.assertEquals;
+import static org.onlab.onos.net.DefaultLinkTest.cp;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Link.Type.DIRECT;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Test of the default link description.
+ */
+public class DefaultLinkDescriptionTest {
+
+    private static final DeviceId DID1 = deviceId("of:foo");
+    private static final DeviceId DID2 = deviceId("of:bar");
+    private static final PortNumber P1 = portNumber(1);
+
+    @Test
+    public void basics() {
+        LinkDescription desc = new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P1), DIRECT);
+        assertEquals("incorrect src", cp(DID1, P1), desc.src());
+        assertEquals("incorrect dst", cp(DID2, P1), desc.dst());
+        assertEquals("incorrect type", DIRECT, desc.type());
+    }
+
+}
diff --git a/net/api/src/test/java/org/onlab/onos/net/link/LinkEventTest.java b/net/api/src/test/java/org/onlab/onos/net/link/LinkEventTest.java
new file mode 100644
index 0000000..dae9f85
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/net/link/LinkEventTest.java
@@ -0,0 +1,41 @@
+package org.onlab.onos.net.link;
+
+import org.junit.Test;
+import org.onlab.onos.event.AbstractEventTest;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultLink;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.provider.ProviderId;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Tests of the device event.
+ */
+public class LinkEventTest extends AbstractEventTest {
+
+    private Link createLink() {
+        return new DefaultLink(new ProviderId("foo"),
+                               new ConnectPoint(deviceId("of:foo"), portNumber(1)),
+                               new ConnectPoint(deviceId("of:bar"), portNumber(2)),
+                               Link.Type.INDIRECT);
+    }
+
+    @Test
+    public void withTime() {
+        Link link = createLink();
+        LinkEvent event = new LinkEvent(LinkEvent.Type.LINK_ADDED, link, 123L);
+        validateEvent(event, LinkEvent.Type.LINK_ADDED, link, 123L);
+    }
+
+    @Test
+    public void withoutTime() {
+        Link link = createLink();
+        long before = System.currentTimeMillis();
+        LinkEvent event = new LinkEvent(LinkEvent.Type.LINK_ADDED, link);
+        long after = System.currentTimeMillis();
+        validateEvent(event, LinkEvent.Type.LINK_ADDED, link, before, after);
+    }
+
+}
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
index b8f5788..e522064 100644
--- a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
+++ b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
@@ -104,6 +104,12 @@
     }
 
     @Override
+    public boolean isAvailable(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+        return store.isAvailable(deviceId);
+    }
+
+    @Override
     public void addListener(DeviceListener listener) {
         listenerRegistry.addListener(listener);
     }
@@ -152,6 +158,7 @@
         public void deviceConnected(DeviceId deviceId, DeviceDescription deviceDescription) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(deviceDescription, DEVICE_DESCRIPTION_NULL);
+            checkValidity();
             log.info("Device {} connected", deviceId);
             DeviceEvent event = store.createOrUpdateDevice(provider().id(),
                                                            deviceId, deviceDescription);
@@ -161,6 +168,7 @@
         @Override
         public void deviceDisconnected(DeviceId deviceId) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
+            checkValidity();
             log.info("Device {} disconnected", deviceId);
             DeviceEvent event = store.markOffline(deviceId);
             post(event);
@@ -170,6 +178,7 @@
         public void updatePorts(DeviceId deviceId, List<PortDescription> portDescriptions) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(portDescriptions, "Port descriptions list cannot be null");
+            checkValidity();
             log.info("Device {} ports updated", deviceId);
             List<DeviceEvent> events = store.updatePorts(deviceId, portDescriptions);
             for (DeviceEvent event : events) {
@@ -181,13 +190,16 @@
         public void portStatusChanged(DeviceId deviceId, PortDescription portDescription) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(portDescription, PORT_DESCRIPTION_NULL);
-            log.info("Device {} port status changed", deviceId);
+            checkValidity();
             DeviceEvent event = store.updatePortStatus(deviceId, portDescription);
+            if (event != null) {
+                log.info("Device {} port status changed", deviceId);
+            }
             post(event);
         }
     }
 
-    // Posts the specified event to a local event dispatcher
+    // Posts the specified event to the local event dispatcher.
     private void post(DeviceEvent event) {
         if (event != null && eventDispatcher != null) {
             eventDispatcher.post(event);
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
index dd63469..fdffad2 100644
--- a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
+++ b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
@@ -45,7 +45,7 @@
      *
      * @return number of devices
      */
-    public int getDeviceCount() {
+    int getDeviceCount() {
         return devices.size();
     }
 
@@ -271,6 +271,16 @@
     }
 
     /**
+     * Indicates whether the specified device is available/online.
+     *
+     * @param deviceId device identifier
+     * @return true if device is available
+     */
+    boolean isAvailable(DeviceId deviceId) {
+        return availableDevices.contains(deviceId);
+    }
+
+    /**
      * Returns the mastership role determined for this device.
      *
      * @param deviceId device identifier
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkManager.java b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkManager.java
index 8efa526..6e7c567 100644
--- a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkManager.java
+++ b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkManager.java
@@ -1,5 +1,6 @@
 package org.onlab.onos.net.trivial.impl;
 
+import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -8,16 +9,24 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.event.AbstractListenerRegistry;
 import org.onlab.onos.event.EventDeliveryService;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.link.LinkAdminService;
 import org.onlab.onos.net.link.LinkDescription;
 import org.onlab.onos.net.link.LinkEvent;
 import org.onlab.onos.net.link.LinkListener;
 import org.onlab.onos.net.link.LinkProvider;
 import org.onlab.onos.net.link.LinkProviderRegistry;
 import org.onlab.onos.net.link.LinkProviderService;
+import org.onlab.onos.net.link.LinkService;
 import org.onlab.onos.net.provider.AbstractProviderRegistry;
 import org.onlab.onos.net.provider.AbstractProviderService;
 import org.slf4j.Logger;
 
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -27,15 +36,21 @@
 @Service
 public class SimpleLinkManager
         extends AbstractProviderRegistry<LinkProvider, LinkProviderService>
-        implements LinkProviderRegistry {
+        implements LinkService, LinkAdminService, LinkProviderRegistry {
+
+    private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+    private static final String LINK_DESC_NULL = "Link description cannot be null";
+    private static final String CONNECT_POINT_NULL = "Connection point cannot be null";
 
     private final Logger log = getLogger(getClass());
 
     private final AbstractListenerRegistry<LinkEvent, LinkListener>
             listenerRegistry = new AbstractListenerRegistry<>();
 
+    private final SimpleLinkStore store = new SimpleLinkStore();
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    private EventDeliveryService eventDispatcher;
+    protected EventDeliveryService eventDispatcher;
 
     @Activate
     public void activate() {
@@ -54,6 +69,81 @@
         return new InternalLinkProviderService(provider);
     }
 
+    @Override
+    public int getLinkCount() {
+        return store.getLinkCount();
+    }
+
+    @Override
+    public Iterable<Link> getLinks() {
+        return store.getLinks();
+    }
+
+    @Override
+    public Set<Link> getDeviceLinks(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+        return Sets.union(store.getDeviceEgressLinks(deviceId),
+                          store.getDeviceIngressLinks(deviceId));
+    }
+
+    @Override
+    public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+        return store.getDeviceEgressLinks(deviceId);
+    }
+
+    @Override
+    public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+        return store.getDeviceIngressLinks(deviceId);
+    }
+
+    @Override
+    public Set<Link> getLinks(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECT_POINT_NULL);
+        return Sets.union(store.getEgressLinks(connectPoint),
+                          store.getIngressLinks(connectPoint));
+    }
+
+    @Override
+    public Set<Link> getEgressLinks(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECT_POINT_NULL);
+        return store.getEgressLinks(connectPoint);
+    }
+
+    @Override
+    public Set<Link> getIngressLinks(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECT_POINT_NULL);
+        return store.getIngressLinks(connectPoint);
+    }
+
+    @Override
+    public Link getLink(ConnectPoint src, ConnectPoint dst) {
+        checkNotNull(src, CONNECT_POINT_NULL);
+        checkNotNull(dst, CONNECT_POINT_NULL);
+        return store.getLink(src, dst);
+    }
+
+    @Override
+    public void removeLinks(ConnectPoint connectPoint) {
+        removeLinks(getLinks(connectPoint));
+    }
+
+    @Override
+    public void removeLinks(DeviceId deviceId) {
+        removeLinks(getDeviceLinks(deviceId));
+    }
+
+    @Override
+    public void addListener(LinkListener listener) {
+        listenerRegistry.addListener(listener);
+    }
+
+    @Override
+    public void removeListener(LinkListener listener) {
+        listenerRegistry.removeListener(listener);
+    }
+
     // Personalized link provider service issued to the supplied provider.
     private class InternalLinkProviderService extends AbstractProviderService<LinkProvider>
             implements LinkProviderService {
@@ -64,12 +154,54 @@
 
         @Override
         public void linkDetected(LinkDescription linkDescription) {
+            checkNotNull(linkDescription, LINK_DESC_NULL);
+            checkValidity();
             log.info("Link {} detected", linkDescription);
+            LinkEvent event = store.createOrUpdateLink(provider().id(),
+                                                       linkDescription);
+            post(event);
         }
 
         @Override
         public void linkVanished(LinkDescription linkDescription) {
+            checkNotNull(linkDescription, LINK_DESC_NULL);
+            checkValidity();
             log.info("Link {} vanished", linkDescription);
+            LinkEvent event = store.removeLink(linkDescription.src(),
+                                               linkDescription.dst());
+            post(event);
+        }
+
+        @Override
+        public void linksVanished(ConnectPoint connectPoint) {
+            checkNotNull(connectPoint, "Connect point cannot be null");
+            checkValidity();
+            log.info("Link for connection point {} vanished", connectPoint);
+            removeLinks(getLinks(connectPoint));
+        }
+
+        @Override
+        public void linksVanished(DeviceId deviceId) {
+            checkNotNull(deviceId, DEVICE_ID_NULL);
+            checkValidity();
+            log.info("Link for device {} vanished", deviceId);
+            removeLinks(getDeviceLinks(deviceId));
         }
     }
+
+    // Removes all links in the specified set and emits appropriate events.
+    private void removeLinks(Set<Link> links) {
+        for (Link link : links) {
+            LinkEvent event = store.removeLink(link.src(), link.dst());
+            post(event);
+        }
+    }
+
+    // Posts the specified event to the local event dispatcher.
+    private void post(LinkEvent event) {
+        if (event != null && eventDispatcher != null) {
+            eventDispatcher.post(event);
+        }
+    }
+
 }
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkStore.java b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkStore.java
new file mode 100644
index 0000000..d10c3a4
--- /dev/null
+++ b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkStore.java
@@ -0,0 +1,218 @@
+package org.onlab.onos.net.trivial.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultLink;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.link.LinkDescription;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.provider.ProviderId;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.onlab.onos.net.Link.Type.DIRECT;
+import static org.onlab.onos.net.Link.Type.INDIRECT;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED;
+
+/**
+ * Manages inventory of infrastructure links using trivial in-memory link
+ * implementation.
+ */
+class SimpleLinkStore {
+
+    // Link inventory
+    private final Map<LinkKey, DefaultLink> links = new ConcurrentHashMap<>();
+
+    // Egress and ingress link sets
+    private final Multimap<DeviceId, Link> srcLinks = HashMultimap.create();
+    private final Multimap<DeviceId, Link> dstLinks = HashMultimap.create();
+
+    private static final Set<Link> EMPTY = ImmutableSet.copyOf(new Link[]{});
+
+    /**
+     * Returns the number of links in the store.
+     *
+     * @return number of links
+     */
+    int getLinkCount() {
+        return links.size();
+    }
+
+    /**
+     * Returns an iterable collection of all links in the inventory.
+     *
+     * @return collection of all links
+     */
+    Iterable<Link> getLinks() {
+        return Collections.unmodifiableSet(new HashSet<Link>(links.values()));
+    }
+
+    /**
+     * Returns all links egressing from the specified device.
+     *
+     * @param deviceId device identifier
+     * @return set of device links
+     */
+    Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+        return ImmutableSet.copyOf(srcLinks.get(deviceId));
+    }
+
+    /**
+     * Returns all links ingressing from the specified device.
+     *
+     * @param deviceId device identifier
+     * @return set of device links
+     */
+    Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+        return ImmutableSet.copyOf(dstLinks.get(deviceId));
+    }
+
+    /**
+     * Returns the link between the two end-points.
+     *
+     * @param src source connection point
+     * @param dst destination connection point
+     * @return link or null if one not found between the end-points
+     */
+    Link getLink(ConnectPoint src, ConnectPoint dst) {
+        return links.get(new LinkKey(src, dst));
+    }
+
+    /**
+     * Returns all links egressing from the specified connection point.
+     *
+     * @param src source connection point
+     * @return set of connection point links
+     */
+    Set<Link> getEgressLinks(ConnectPoint src) {
+        Set<Link> egress = new HashSet<>();
+        for (Link link : srcLinks.get(src.deviceId())) {
+            if (link.src().equals(src)) {
+                egress.add(link);
+            }
+        }
+        return egress;
+    }
+
+    /**
+     * Returns all links ingressing to the specified connection point.
+     *
+     * @param dst destination connection point
+     * @return set of connection point links
+     */
+    Set<Link> getIngressLinks(ConnectPoint dst) {
+        Set<Link> ingress = new HashSet<>();
+        for (Link link : dstLinks.get(dst.deviceId())) {
+            if (link.dst().equals(dst)) {
+                ingress.add(link);
+            }
+        }
+        return ingress;
+    }
+
+    /**
+     * Creates a new link, or updates an existing one, based on the given
+     * information.
+     *
+     * @param providerId      provider identity
+     * @param linkDescription link description
+     * @return create or update link event, or null if no change resulted
+     */
+    public LinkEvent createOrUpdateLink(ProviderId providerId,
+                                        LinkDescription linkDescription) {
+        LinkKey key = new LinkKey(linkDescription.src(), linkDescription.dst());
+        DefaultLink link = links.get(key);
+        if (link == null) {
+            return createLink(providerId, key, linkDescription);
+        }
+        return updateLink(providerId, link, key, linkDescription);
+    }
+
+    // Creates and stores the link and returns the appropriate event.
+    private LinkEvent createLink(ProviderId providerId, LinkKey key,
+                                 LinkDescription linkDescription) {
+        DefaultLink link = new DefaultLink(providerId, key.src, key.dst,
+                                           linkDescription.type());
+        synchronized (this) {
+            links.put(key, link);
+            srcLinks.put(link.src().deviceId(), link);
+            dstLinks.put(link.dst().deviceId(), link);
+        }
+        return new LinkEvent(LINK_ADDED, link);
+    }
+
+    // Updates, if necessary the specified link and returns the appropriate event.
+    private LinkEvent updateLink(ProviderId providerId, DefaultLink link,
+                                 LinkKey key, LinkDescription linkDescription) {
+        if (link.type() == INDIRECT && linkDescription.type() == DIRECT) {
+            synchronized (this) {
+                srcLinks.remove(link.src().deviceId(), link);
+                dstLinks.remove(link.dst().deviceId(), link);
+
+                DefaultLink updated =
+                        new DefaultLink(providerId, link.src(), link.dst(),
+                                        linkDescription.type());
+                links.put(key, updated);
+                srcLinks.put(link.src().deviceId(), updated);
+                dstLinks.put(link.dst().deviceId(), updated);
+                return new LinkEvent(LINK_UPDATED, updated);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes the link based on the specified information.
+     *
+     * @param src link source
+     * @param dst link destination
+     * @return remove link event, or null if no change resulted
+     */
+    LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
+        synchronized (this) {
+            Link link = links.remove(new LinkKey(src, dst));
+            if (link != null) {
+                srcLinks.remove(link.src().deviceId(), link);
+                dstLinks.remove(link.dst().deviceId(), link);
+                return new LinkEvent(LINK_REMOVED, link);
+            }
+            return null;
+        }
+    }
+
+    // Auxiliary key to track links.
+    private class LinkKey {
+        final ConnectPoint src;
+        final ConnectPoint dst;
+
+        LinkKey(ConnectPoint src, ConnectPoint dst) {
+            this.src = src;
+            this.dst = dst;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(src, dst);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof LinkKey) {
+                final LinkKey other = (LinkKey) obj;
+                return Objects.equals(this.src, other.src) &&
+                        Objects.equals(this.dst, other.dst);
+            }
+            return false;
+        }
+    }
+}
diff --git a/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java b/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java
index a6c9414..e5224db 100644
--- a/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java
+++ b/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java
@@ -104,6 +104,7 @@
         assertNotNull("one device expected", it.next());
         assertFalse("only one device expected", it.hasNext());
         assertEquals("incorrect device count", 1, service.getDeviceCount());
+        assertTrue("device should be available", service.isAvailable(DID1));
     }
 
     @Test
@@ -111,10 +112,12 @@
         connectDevice(DID1, SW1);
         connectDevice(DID2, SW1);
         validateEvents(DEVICE_ADDED, DEVICE_ADDED);
+        assertTrue("device should be available", service.isAvailable(DID1));
 
         // Disconnect
         providerService.deviceDisconnected(DID1);
         assertNotNull("device should not be found", service.getDevice(DID1));
+        assertFalse("device should not be available", service.isAvailable(DID1));
         validateEvents(DEVICE_AVAILABILITY_CHANGED);
 
         // Reconnect
diff --git a/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleLinkManagerTest.java b/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleLinkManagerTest.java
new file mode 100644
index 0000000..8dd9e1d
--- /dev/null
+++ b/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleLinkManagerTest.java
@@ -0,0 +1,257 @@
+package org.onlab.onos.net.trivial.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.event.Event;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.link.DefaultLinkDescription;
+import org.onlab.onos.net.link.LinkAdminService;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.link.LinkListener;
+import org.onlab.onos.net.link.LinkProvider;
+import org.onlab.onos.net.link.LinkProviderRegistry;
+import org.onlab.onos.net.link.LinkProviderService;
+import org.onlab.onos.net.link.LinkService;
+import org.onlab.onos.net.provider.AbstractProvider;
+import org.onlab.onos.net.provider.ProviderId;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Link.Type.DIRECT;
+import static org.onlab.onos.net.Link.Type.INDIRECT;
+import static org.onlab.onos.net.link.LinkEvent.Type.*;
+
+/**
+ * Test codifying the link service & link provider service contracts.
+ */
+public class SimpleLinkManagerTest {
+
+    private static final ProviderId PID = new ProviderId("foo");
+    private static final DeviceId DID1 = deviceId("of:foo");
+    private static final DeviceId DID2 = deviceId("of:bar");
+    private static final DeviceId DID3 = deviceId("of:goo");
+
+    private static final PortNumber P1 = PortNumber.portNumber(1);
+    private static final PortNumber P2 = PortNumber.portNumber(2);
+    private static final PortNumber P3 = PortNumber.portNumber(3);
+
+
+    private SimpleLinkManager mgr;
+
+    protected LinkService service;
+    protected LinkAdminService admin;
+    protected LinkProviderRegistry registry;
+    protected LinkProviderService providerService;
+    protected TestProvider provider;
+    protected TestListener listener = new TestListener();
+
+    @Before
+    public void setUp() {
+        mgr = new SimpleLinkManager();
+        service = mgr;
+        admin = mgr;
+        registry = mgr;
+        mgr.eventDispatcher = new TestEventDispatcher();
+        mgr.activate();
+
+        service.addListener(listener);
+
+        provider = new TestProvider();
+        providerService = registry.register(provider);
+        assertTrue("provider should be registered",
+                   registry.getProviders().contains(provider.id()));
+    }
+
+    @After
+    public void tearDown() {
+        registry.unregister(provider);
+        assertFalse("provider should not be registered",
+                    registry.getProviders().contains(provider.id()));
+        service.removeListener(listener);
+        mgr.deactivate();
+    }
+
+    @Test
+    public void createLink() {
+        addLink(DID1, P1, DID2, P2, DIRECT);
+        addLink(DID2, P2, DID1, P1, DIRECT);
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+
+        Iterator<Link> it = service.getLinks().iterator();
+        it.next();
+        it.next();
+        assertFalse("incorrect link count", it.hasNext());
+    }
+
+    @Test
+    public void updateLink() {
+        addLink(DID1, P1, DID2, P2, DIRECT);
+        addLink(DID2, P2, DID1, P1, INDIRECT);
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+
+        providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), DIRECT));
+        validateEvents(LINK_UPDATED);
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+
+        providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), INDIRECT));
+        providerService.linkDetected(new DefaultLinkDescription(cp(DID2, P2), cp(DID1, P1), DIRECT));
+        assertEquals("no events expected", 0, listener.events.size());
+    }
+
+    @Test
+    public void removeLink() {
+        addLink(DID1, P1, DID2, P2, DIRECT);
+        addLink(DID2, P2, DID1, P1, DIRECT);
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+
+        providerService.linkVanished(new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P2), DIRECT));
+        validateEvents(LINK_REMOVED);
+        assertEquals("incorrect link count", 1, service.getLinkCount());
+        assertNull("link should not be found", service.getLink(cp(DID1, P1), cp(DID2, P2)));
+        assertNotNull("link should be found", service.getLink(cp(DID2, P2), cp(DID1, P1)));
+
+        providerService.linkVanished(new DefaultLinkDescription(cp(DID1, P1), cp(DID2, P2), DIRECT));
+        assertEquals("no events expected", 0, listener.events.size());
+    }
+
+    @Test
+    public void removeLinksByConnectionPoint() {
+        Link l1 = addLink(DID1, P1, DID2, P2, DIRECT);
+        Link l2 = addLink(DID2, P2, DID1, P1, DIRECT);
+        addLink(DID3, P3, DID2, P1, DIRECT);
+        addLink(DID2, P1, DID3, P3, DIRECT);
+        assertEquals("incorrect link count", 4, service.getLinkCount());
+
+        providerService.linksVanished(cp(DID1, P1));
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+        assertNull("link should be gone", service.getLink(l1.src(), l1.dst()));
+        assertNull("link should be gone", service.getLink(l2.src(), l2.dst()));
+    }
+
+    @Test
+    public void removeLinksByDevice() {
+        addLink(DID1, P1, DID2, P2, DIRECT);
+        addLink(DID2, P2, DID1, P1, DIRECT);
+        addLink(DID3, P3, DID2, P1, DIRECT);
+        addLink(DID2, P1, DID3, P3, DIRECT);
+        Link l5 = addLink(DID3, P1, DID1, P2, DIRECT);
+        Link l6 = addLink(DID1, P2, DID3, P1, DIRECT);
+        assertEquals("incorrect link count", 6, service.getLinkCount());
+
+        providerService.linksVanished(DID2);
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+        assertNotNull("link should not be gone", service.getLink(l5.src(), l5.dst()));
+        assertNotNull("link should not be gone", service.getLink(l6.src(), l6.dst()));
+    }
+
+    @Test
+    public void removeLinksAsAdminByConnectionPoint() {
+        Link l1 = addLink(DID1, P1, DID2, P2, DIRECT);
+        Link l2 = addLink(DID2, P2, DID1, P1, DIRECT);
+        addLink(DID3, P3, DID2, P1, DIRECT);
+        addLink(DID2, P1, DID3, P3, DIRECT);
+        assertEquals("incorrect link count", 4, service.getLinkCount());
+
+        admin.removeLinks(cp(DID1, P1));
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+        assertNull("link should be gone", service.getLink(l1.src(), l1.dst()));
+        assertNull("link should be gone", service.getLink(l2.src(), l2.dst()));
+    }
+
+    @Test
+    public void removeLinksAsAdminByDevice() {
+        addLink(DID1, P1, DID2, P2, DIRECT);
+        addLink(DID2, P2, DID1, P1, DIRECT);
+        addLink(DID3, P3, DID2, P1, DIRECT);
+        addLink(DID2, P1, DID3, P3, DIRECT);
+        Link l5 = addLink(DID3, P1, DID1, P2, DIRECT);
+        Link l6 = addLink(DID1, P2, DID3, P1, DIRECT);
+        assertEquals("incorrect link count", 6, service.getLinkCount());
+
+        admin.removeLinks(DID2);
+        assertEquals("incorrect link count", 2, service.getLinkCount());
+        assertNotNull("link should not be gone", service.getLink(l5.src(), l5.dst()));
+        assertNotNull("link should not be gone", service.getLink(l6.src(), l6.dst()));
+    }
+
+    @Test
+    public void getLinks() {
+        Link l1 = addLink(DID1, P1, DID2, P2, DIRECT);
+        Link l2 = addLink(DID2, P2, DID1, P1, DIRECT);
+        Link l3 = addLink(DID3, P3, DID2, P1, DIRECT);
+        Link l4 = addLink(DID2, P1, DID3, P3, DIRECT);
+        assertEquals("incorrect link count", 4, service.getLinkCount());
+
+        Set<Link> links = service.getLinks(cp(DID1, P1));
+        assertEquals("incorrect links", ImmutableSet.of(l1, l2), links);
+        links = service.getEgressLinks(cp(DID1, P1));
+        assertEquals("incorrect links", ImmutableSet.of(l1), links);
+        links = service.getIngressLinks(cp(DID1, P1));
+        assertEquals("incorrect links", ImmutableSet.of(l2), links);
+
+        links = service.getDeviceLinks(DID2);
+        assertEquals("incorrect links", ImmutableSet.of(l1, l2, l3, l4), links);
+        links = service.getDeviceLinks(DID3);
+        assertEquals("incorrect links", ImmutableSet.of(l3, l4), links);
+
+        links = service.getDeviceEgressLinks(DID2);
+        assertEquals("incorrect links", ImmutableSet.of(l2, l4), links);
+        links = service.getDeviceIngressLinks(DID2);
+        assertEquals("incorrect links", ImmutableSet.of(l1, l3), links);
+    }
+
+
+    private Link addLink(DeviceId sd, PortNumber sp, DeviceId dd, PortNumber dp,
+                         Link.Type type) {
+        providerService.linkDetected(new DefaultLinkDescription(cp(sd, sp), cp(dd, dp), type));
+        Link link = listener.events.get(0).subject();
+        validateEvents(LINK_ADDED);
+        return link;
+    }
+
+    private ConnectPoint cp(DeviceId id, PortNumber portNumber) {
+        return new ConnectPoint(id, portNumber);
+    }
+
+    protected void validateEvents(Enum... types) {
+        int i = 0;
+        assertEquals("wrong events received", types.length, listener.events.size());
+        for (Event event : listener.events) {
+            assertEquals("incorrect event type", types[i], event.type());
+            i++;
+        }
+        listener.events.clear();
+    }
+
+
+    private class TestProvider extends AbstractProvider implements LinkProvider {
+        private Device deviceReceived;
+        private MastershipRole roleReceived;
+
+        public TestProvider() {
+            super(PID);
+        }
+    }
+
+    private static class TestListener implements LinkListener {
+        final List<LinkEvent> events = new ArrayList<>();
+
+        @Override
+        public void event(LinkEvent event) {
+            events.add(event);
+        }
+    }
+
+}
diff --git a/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/Controller.java b/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/Controller.java
index 44d70ac..7c6b2ce 100644
--- a/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/Controller.java
+++ b/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/Controller.java
@@ -232,7 +232,7 @@
     }
 
     public void start(OpenFlowAgent ag) {
-        log.info("Initialising OpenFlow Lib and IO");
+        log.info("Starting OpenFlow IO");
         this.agent = ag;
         this.init(new HashMap<String, String>());
         this.run();
@@ -240,6 +240,7 @@
 
 
     public void stop() {
+        log.info("Stopping OpenFlow IO");
         execFactory.shutdown();
         cg.close();
     }
diff --git a/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/OpenFlowControllerImpl.java b/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/OpenFlowControllerImpl.java
index f7ff997..2b1d1da 100644
--- a/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/OpenFlowControllerImpl.java
+++ b/of/ctl/src/main/java/org/onlab/onos/of/controller/impl/OpenFlowControllerImpl.java
@@ -1,12 +1,9 @@
 package org.onlab.onos.of.controller.impl;
 
-import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
 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.of.controller.Dpid;
 import org.onlab.onos.of.controller.OpenFlowController;
 import org.onlab.onos.of.controller.OpenFlowSwitch;
@@ -19,6 +16,16 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Component(immediate = true)
+@Service
 public class OpenFlowControllerImpl implements OpenFlowController {
 
     private static final Logger log =
@@ -32,11 +39,11 @@
             new ConcurrentHashMap<Dpid, OpenFlowSwitch>();
 
     protected OpenFlowSwitchAgent agent = new OpenFlowSwitchAgent();
-    protected ArrayList<OpenFlowSwitchListener> ofEventListener =
-            new ArrayList<OpenFlowSwitchListener>();
+    protected Set<OpenFlowSwitchListener> ofEventListener =
+            new HashSet<>();
 
-    protected ArrayList<PacketListener> ofPacketListener =
-            new ArrayList<PacketListener>();
+    protected List<PacketListener> ofPacketListener =
+            new ArrayList<>();
 
     private final Controller ctrl = new Controller();
 
@@ -110,19 +117,19 @@
     @Override
     public void processPacket(Dpid dpid, OFMessage msg) {
         switch (msg.getType()) {
-        case PORT_STATUS:
-            for (OpenFlowSwitchListener l : ofEventListener) {
-                l.portChanged(dpid, (OFPortStatus) msg);
-            }
-            break;
-        case PACKET_IN:
-            for (PacketListener p : ofPacketListener) {
-                //TODO fix me!
-                p.handlePacket(null);
-            }
-            break;
-        default:
-            log.warn("Handling message type {} not yet implemented", msg.getType());
+            case PORT_STATUS:
+                for (OpenFlowSwitchListener l : ofEventListener) {
+                    l.portChanged(dpid, (OFPortStatus) msg);
+                }
+                break;
+            case PACKET_IN:
+                for (PacketListener p : ofPacketListener) {
+                    //TODO fix me!
+                    p.handlePacket(null);
+                }
+                break;
+            default:
+                log.warn("Handling message type {} not yet implemented", msg.getType());
         }
     }
 
@@ -135,7 +142,6 @@
      * Implementation of an OpenFlow Agent which is responsible for
      * keeping track of connected switches and the state in which
      * they are.
-     *
      */
     public class OpenFlowSwitchAgent implements OpenFlowAgent {
 
@@ -146,7 +152,7 @@
         public boolean addConnectedSwitch(Dpid dpid, OpenFlowSwitch sw) {
             if (connectedSwitches.get(dpid) != null) {
                 log.error("Trying to add connectedSwitch but found a previous "
-                        + "value for dpid: {}", dpid);
+                                  + "value for dpid: {}", dpid);
                 return false;
             } else {
                 log.error("Added switch {}", dpid);
@@ -162,18 +168,18 @@
         public boolean validActivation(Dpid dpid) {
             if (connectedSwitches.get(dpid) == null) {
                 log.error("Trying to activate switch but is not in "
-                        + "connected switches: dpid {}. Aborting ..",
-                        dpid);
+                                  + "connected switches: dpid {}. Aborting ..",
+                          dpid);
                 return false;
             }
             if (activeMasterSwitches.get(dpid) != null ||
                     activeEqualSwitches.get(dpid) != null) {
                 log.error("Trying to activate switch but it is already "
-                        + "activated: dpid {}. Found in activeMaster: {} "
-                        + "Found in activeEqual: {}. Aborting ..", new Object[] {
-                                dpid,
-                                (activeMasterSwitches.get(dpid) == null) ? 'N' : 'Y',
-                                        (activeEqualSwitches.get(dpid) == null) ? 'N' : 'Y'});
+                                  + "activated: dpid {}. Found in activeMaster: {} "
+                                  + "Found in activeEqual: {}. Aborting ..", new Object[]{
+                        dpid,
+                        (activeMasterSwitches.get(dpid) == null) ? 'N' : 'Y',
+                        (activeEqualSwitches.get(dpid) == null) ? 'N' : 'Y'});
                 return false;
             }
             return true;
@@ -219,7 +225,7 @@
                 OpenFlowSwitch sw = activeEqualSwitches.remove(dpid);
                 if (sw == null) {
                     log.error("Transition to master called on sw {}, but switch "
-                            + "was not found in controller-cache", dpid);
+                                      + "was not found in controller-cache", dpid);
                     return;
                 }
                 log.info("Transitioned switch {} to MASTER", dpid);
@@ -240,7 +246,7 @@
                 OpenFlowSwitch sw = activeMasterSwitches.remove(dpid);
                 if (sw == null) {
                     log.error("Transition to equal called on sw {}, but switch "
-                            + "was not found in controller-cache", dpid);
+                                      + "was not found in controller-cache", dpid);
                     return;
                 }
                 log.info("Transitioned switch {} to EQUAL", dpid);
@@ -270,5 +276,4 @@
     }
 
 
-
 }
diff --git a/pom.xml b/pom.xml
index 4f0dd3c..25557a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,16 +58,21 @@
             <dependency>
                 <groupId>com.google.guava</groupId>
                 <artifactId>guava</artifactId>
-                <version>17.0</version>
+                <version>18.0</version>
             </dependency>
 
             <dependency>
                 <groupId>com.google.guava</groupId>
                 <artifactId>guava-testlib</artifactId>
-                <version>17.0</version>
+                <version>18.0</version>
                 <scope>test</scope>
             </dependency>
 
+            <dependency>
+                <groupId>commons-lang</groupId>
+                <artifactId>commons-lang</artifactId>
+                <version>2.6</version>
+            </dependency>
 
             <!-- Web related -->
             <dependency>
diff --git a/providers/of/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java b/providers/of/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
index 2309de4..eb26256 100644
--- a/providers/of/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
+++ b/providers/of/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
@@ -85,19 +85,19 @@
     @Override
     public void roleChanged(Device device, MastershipRole newRole) {
         switch (newRole) {
-        case MASTER:
-            controller.setRole(new Dpid(device.id().uri().getSchemeSpecificPart()),
-                    RoleState.MASTER);
-            break;
-        case STANDBY:
-            controller.setRole(new Dpid(device.id().uri().getSchemeSpecificPart()),
-                    RoleState.EQUAL);
-        case NONE:
-            controller.setRole(new Dpid(device.id().uri().getSchemeSpecificPart()),
-                    RoleState.SLAVE);
-            break;
-        default:
-            log.error("Unknown Mastership state : {}", newRole);
+            case MASTER:
+                controller.setRole(new Dpid(device.id().uri().getSchemeSpecificPart()),
+                                   RoleState.MASTER);
+                break;
+            case STANDBY:
+                controller.setRole(new Dpid(device.id().uri().getSchemeSpecificPart()),
+                                   RoleState.EQUAL);
+            case NONE:
+                controller.setRole(new Dpid(device.id().uri().getSchemeSpecificPart()),
+                                   RoleState.SLAVE);
+                break;
+            default:
+                log.error("Unknown Mastership state : {}", newRole);
 
         }
         log.info("Accepting mastership role change for device {}", device.id());
@@ -114,10 +114,10 @@
 
             DeviceDescription description =
                     new DefaultDeviceDescription(buildURI(dpid), Device.Type.SWITCH,
-                            sw.manfacturerDescription(),
-                            sw.hardwareDescription(),
-                            sw.softwareDescription(),
-                            sw.softwareDescription());
+                                                 sw.manfacturerDescription(),
+                                                 sw.hardwareDescription(),
+                                                 sw.softwareDescription(),
+                                                 sw.serialNumber());
             providerService.deviceConnected(deviceId(uri), description);
             providerService.updatePorts(deviceId(uri), buildPortDescriptions(sw.getPorts()));
         }
@@ -140,6 +140,7 @@
 
         /**
          * Given a dpid builds a URI for the device.
+         *
          * @param dpid the dpid to build the uri from
          * @return returns a uri of the form of:<dpidHexForm>
          */
@@ -155,6 +156,7 @@
 
         /**
          * Builds a list of port descriptions for a given list of ports.
+         *
          * @param ports the list of ports
          * @return list of portdescriptions
          */
@@ -169,6 +171,7 @@
 
         /**
          * Build a portDescription from a given port.
+         *
          * @param port the port to build from.
          * @return portDescription for the port.
          */
diff --git a/utils/misc/pom.xml b/utils/misc/pom.xml
index 39d9042..2ff34ec 100644
--- a/utils/misc/pom.xml
+++ b/utils/misc/pom.xml
@@ -20,7 +20,6 @@
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava-testlib</artifactId>
-            <version>17.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java b/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java
index 7ee86f4..163a147 100644
--- a/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java
+++ b/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java
@@ -2,6 +2,7 @@
 
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -49,7 +50,7 @@
 
     @Override
     public String toString() {
-        return com.google.common.base.Objects.toStringHelper(this)
+        return toStringHelper(this)
                 .add("src", src)
                 .add("dst", dst)
                 .toString();
diff --git a/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java b/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java
index e1e0524..07dc70b 100644
--- a/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java
+++ b/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java
@@ -6,6 +6,7 @@
 import java.util.Objects;
 import java.util.Set;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -94,7 +95,7 @@
 
     @Override
     public String toString() {
-        return com.google.common.base.Objects.toStringHelper(this)
+        return toStringHelper(this)
                 .add("vertexes", vertexes)
                 .add("edges", edges)
                 .toString();
diff --git a/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java b/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java
index be7ad18..401e9e9 100644
--- a/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java
+++ b/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java
@@ -6,6 +6,7 @@
 import java.util.List;
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -91,7 +92,7 @@
 
     @Override
     public String toString() {
-        return com.google.common.base.Objects.toStringHelper(this)
+        return toStringHelper(this)
                 .add("src", src())
                 .add("dst", dst())
                 .add("cost", cost)
diff --git a/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java b/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java
index d7fc9e8a..a43dd0f 100644
--- a/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java
+++ b/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java
@@ -6,6 +6,7 @@
 import java.util.List;
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -56,7 +57,7 @@
 
     @Override
     public String toString() {
-        return com.google.common.base.Objects.toStringHelper(this)
+        return toStringHelper(this)
                 .add("src", src)
                 .add("dst", dst)
                 .add("cost", cost)
diff --git a/utils/misc/src/main/java/org/onlab/graph/Heap.java b/utils/misc/src/main/java/org/onlab/graph/Heap.java
index 21eeb85..ecc4759 100644
--- a/utils/misc/src/main/java/org/onlab/graph/Heap.java
+++ b/utils/misc/src/main/java/org/onlab/graph/Heap.java
@@ -7,6 +7,7 @@
 import java.util.List;
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -181,7 +182,7 @@
 
     @Override
     public String toString() {
-        return com.google.common.base.Objects.toStringHelper(this)
+        return toStringHelper(this)
                 .add("data", data)
                 .add("comparator", comparator)
                 .toString();
diff --git a/utils/misc/src/test/java/org/onlab/graph/TestEdge.java b/utils/misc/src/test/java/org/onlab/graph/TestEdge.java
index 225690c..0accab3 100644
--- a/utils/misc/src/test/java/org/onlab/graph/TestEdge.java
+++ b/utils/misc/src/test/java/org/onlab/graph/TestEdge.java
@@ -2,7 +2,7 @@
 
 import java.util.Objects;
 
-import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
  * Test edge.
diff --git a/utils/pom.xml b/utils/pom.xml
index a34aa02..c956abe 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -27,11 +27,6 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
-        <dependency>
-            <groupId>commons-lang</groupId>
-            <artifactId>commons-lang</artifactId>
-            <version>2.3</version>
-        </dependency>
     </dependencies>
 
     <build>