Merge remote-tracking branch 'origin/master'
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/PortAddresses.java b/core/api/src/main/java/org/onlab/onos/net/host/PortAddresses.java
index 31e2e76..7d22877 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/PortAddresses.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/PortAddresses.java
@@ -1,6 +1,8 @@
 package org.onlab.onos.net.host;
 
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
 import org.onlab.onos.net.ConnectPoint;
@@ -29,7 +31,7 @@
     public PortAddresses(ConnectPoint connectPoint,
             Set<IpPrefix> ips, MacAddress mac) {
         this.connectPoint = connectPoint;
-        this.ipAddresses = (ips == null) ? null : new HashSet<>(ips);
+        this.ipAddresses = (ips == null) ? Collections.<IpPrefix>emptySet() : new HashSet<>(ips);
         this.macAddress = mac;
     }
 
@@ -60,4 +62,25 @@
         return macAddress;
     }
 
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (!(other instanceof PortAddresses)) {
+            return false;
+        }
+
+        PortAddresses otherPa = (PortAddresses) other;
+
+        return Objects.equals(this.connectPoint, otherPa.connectPoint)
+                && Objects.equals(this.ipAddresses, otherPa.ipAddresses)
+                && Objects.equals(this.macAddress, otherPa.macAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(connectPoint, ipAddresses, macAddress);
+    }
 }
diff --git a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
index 661d2b5..49ac5b8 100644
--- a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
@@ -2,9 +2,13 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_MOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_UPDATED;
 
 import java.util.List;
 import java.util.Set;
@@ -14,6 +18,7 @@
 import org.junit.Test;
 import org.onlab.onos.event.Event;
 import org.onlab.onos.event.impl.TestEventDispatcher;
+import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Host;
 import org.onlab.onos.net.HostId;
@@ -26,6 +31,7 @@
 import org.onlab.onos.net.host.HostProvider;
 import org.onlab.onos.net.host.HostProviderRegistry;
 import org.onlab.onos.net.host.HostProviderService;
+import org.onlab.onos.net.host.PortAddresses;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.net.trivial.impl.SimpleHostStore;
@@ -36,8 +42,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
-import static org.onlab.onos.net.host.HostEvent.Type.*;
-
 /**
  * Test codifying the host service & host provider service contracts.
  */
@@ -63,6 +67,12 @@
     private static final PortNumber P2 = PortNumber.portNumber(200);
     private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L);
     private static final HostLocation LOC2 = new HostLocation(DID1, P2, 123L);
+    private static final ConnectPoint CP1 = new ConnectPoint(DID1, P1);
+    private static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
+
+    private static final IpPrefix PREFIX1 = IpPrefix.valueOf("10.0.1.0/24");
+    private static final IpPrefix PREFIX2 = IpPrefix.valueOf("10.1.0.0/16");
+    private static final IpPrefix PREFIX3 = IpPrefix.valueOf("5.8.2.0/23");
 
     private HostManager mgr;
 
@@ -196,4 +206,130 @@
         }
 
     }
+
+    @Test
+    public void bindAddressesToPort() {
+        PortAddresses add1 = new PortAddresses(CP1,
+                Sets.newHashSet(PREFIX1, PREFIX2), MAC1);
+
+        mgr.bindAddressesToPort(add1);
+        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(add1.ips().equals(storedAddresses.ips()));
+        assertTrue(add1.mac().equals(storedAddresses.mac()));
+
+        // Add some more addresses and check that they're added correctly
+        PortAddresses add2 = new PortAddresses(CP1, Sets.newHashSet(PREFIX3), null);
+
+        mgr.bindAddressesToPort(add2);
+        storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().equals(
+                Sets.newHashSet(PREFIX1, PREFIX2, PREFIX3)));
+        assertTrue(storedAddresses.mac().equals(MAC1));
+
+        PortAddresses add3 = new PortAddresses(CP1, null, MAC2);
+
+        mgr.bindAddressesToPort(add3);
+        storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().equals(
+                Sets.newHashSet(PREFIX1, PREFIX2, PREFIX3)));
+        assertTrue(storedAddresses.mac().equals(MAC2));
+    }
+
+    @Test
+    public void unbindAddressesFromPort() {
+        PortAddresses add1 = new PortAddresses(CP1,
+                Sets.newHashSet(PREFIX1, PREFIX2), MAC1);
+
+        mgr.bindAddressesToPort(add1);
+        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().size() == 2);
+        assertNotNull(storedAddresses.mac());
+
+        PortAddresses rem1 = new PortAddresses(CP1,
+                Sets.newHashSet(PREFIX1), null);
+
+        mgr.unbindAddressesFromPort(rem1);
+        storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().equals(Sets.newHashSet(PREFIX2)));
+        assertTrue(storedAddresses.mac().equals(MAC1));
+
+        PortAddresses rem2 = new PortAddresses(CP1, null, MAC1);
+
+        mgr.unbindAddressesFromPort(rem2);
+        storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().equals(Sets.newHashSet(PREFIX2)));
+        assertNull(storedAddresses.mac());
+
+        PortAddresses rem3 = new PortAddresses(CP1,
+                Sets.newHashSet(PREFIX2), MAC1);
+
+        mgr.unbindAddressesFromPort(rem3);
+        storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().isEmpty());
+        assertNull(storedAddresses.mac());
+    }
+
+    @Test
+    public void clearAddresses() {
+        PortAddresses add1 = new PortAddresses(CP1,
+                Sets.newHashSet(PREFIX1, PREFIX2), MAC1);
+
+        mgr.bindAddressesToPort(add1);
+        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().size() == 2);
+        assertNotNull(storedAddresses.mac());
+
+        mgr.clearAddresses(CP1);
+        storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.ips().isEmpty());
+        assertNull(storedAddresses.mac());
+    }
+
+    @Test
+    public void getAddressBindingsForPort() {
+        PortAddresses add1 = new PortAddresses(CP1,
+                Sets.newHashSet(PREFIX1, PREFIX2), MAC1);
+
+        mgr.bindAddressesToPort(add1);
+        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+
+        assertTrue(storedAddresses.connectPoint().equals(CP1));
+        assertTrue(storedAddresses.ips().equals(Sets.newHashSet(PREFIX1, PREFIX2)));
+        assertTrue(storedAddresses.mac().equals(MAC1));
+    }
+
+    @Test
+    public void getAddressBindings() {
+        Set<PortAddresses> storedAddresses = mgr.getAddressBindings();
+
+        assertTrue(storedAddresses.isEmpty());
+
+        PortAddresses add1 = new PortAddresses(CP1,
+                Sets.newHashSet(PREFIX1, PREFIX2), MAC1);
+
+        mgr.bindAddressesToPort(add1);
+
+        storedAddresses = mgr.getAddressBindings();
+
+        assertTrue(storedAddresses.size() == 1);
+
+        PortAddresses add2 = new PortAddresses(CP2,
+                Sets.newHashSet(PREFIX3), MAC2);
+
+        mgr.bindAddressesToPort(add2);
+
+        storedAddresses = mgr.getAddressBindings();
+
+        assertTrue(storedAddresses.size() == 2);
+        assertTrue(storedAddresses.equals(Sets.newHashSet(add1, add2)));
+    }
 }
diff --git a/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleHostStore.java b/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleHostStore.java
index be609a8..94a3f05 100644
--- a/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleHostStore.java
+++ b/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleHostStore.java
@@ -36,6 +36,7 @@
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
 
 /**
  * Manages inventory of end-station hosts using trivial in-memory
@@ -202,29 +203,75 @@
 
     @Override
     public void updateAddressBindings(PortAddresses addresses) {
-        // TODO portAddresses.put(addresses.connectPoint(), addresses);
+        synchronized (portAddresses) {
+            PortAddresses existing = portAddresses.get(addresses.connectPoint());
+            if (existing == null) {
+                portAddresses.put(addresses.connectPoint(), addresses);
+            } else {
+                Set<IpPrefix> union = Sets.union(existing.ips(), addresses.ips())
+                        .immutableCopy();
+
+                MacAddress newMac = (addresses.mac() == null) ? existing.mac()
+                        : addresses.mac();
+
+                PortAddresses newAddresses =
+                        new PortAddresses(addresses.connectPoint(), union, newMac);
+
+                portAddresses.put(newAddresses.connectPoint(), newAddresses);
+            }
+        }
     }
 
     @Override
     public void removeAddressBindings(PortAddresses addresses) {
-        // TODO Auto-generated method stub
+        synchronized (portAddresses) {
+            PortAddresses existing = portAddresses.get(addresses.connectPoint());
+            if (existing != null) {
+                Set<IpPrefix> difference =
+                        Sets.difference(existing.ips(), addresses.ips()).immutableCopy();
 
+                // If they removed the existing mac, set the new mac to null.
+                // Otherwise, keep the existing mac.
+                MacAddress newMac = existing.mac();
+                if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
+                    newMac = null;
+                }
+
+                PortAddresses newAddresses =
+                        new PortAddresses(addresses.connectPoint(), difference, newMac);
+
+                portAddresses.put(newAddresses.connectPoint(), newAddresses);
+            }
+        }
     }
 
     @Override
     public void clearAddressBindings(ConnectPoint connectPoint) {
-        // TODO Auto-generated method stub
-
+        synchronized (portAddresses) {
+            portAddresses.remove(connectPoint);
+        }
     }
 
     @Override
     public Set<PortAddresses> getAddressBindings() {
-        return new HashSet<>(portAddresses.values());
+        synchronized (portAddresses) {
+            return new HashSet<>(portAddresses.values());
+        }
     }
 
     @Override
     public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
-        return portAddresses.get(connectPoint);
+        PortAddresses addresses;
+
+        synchronized (portAddresses) {
+            addresses = portAddresses.get(connectPoint);
+        }
+
+        if (addresses == null) {
+            addresses = new PortAddresses(connectPoint, null, null);
+        }
+
+        return addresses;
     }
 
 }