Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/calendar/pom.xml b/apps/calendar/pom.xml
index dd73c99..396a4d0 100644
--- a/apps/calendar/pom.xml
+++ b/apps/calendar/pom.xml
@@ -62,6 +62,32 @@
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
         </dependency>
+
+    <dependency>
+      <groupId>org.onlab.onos</groupId>
+      <artifactId>onlab-thirdparty</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.onlab.onos</groupId>
+      <artifactId>onlab-misc</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.onlab.onos</groupId>
+      <artifactId>onlab-junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.onlab.onos</groupId>
+      <artifactId>onos-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.karaf.shell</groupId>
+      <artifactId>org.apache.karaf.shell.console</artifactId>
+    </dependency>
     </dependencies>
 
     <build>
@@ -77,6 +103,7 @@
                             ${project.groupId}.${project.artifactId}
                         </Bundle-SymbolicName>
                         <Import-Package>
+                            org.slf4j,
                             org.osgi.framework,
                             javax.ws.rs,javax.ws.rs.core,
                             com.sun.jersey.api.core,
diff --git a/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java b/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java
index cd8cc5c..cfa1a63 100644
--- a/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java
+++ b/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java
@@ -16,43 +16,47 @@
 package org.onlab.onos.calendar;
 
 import java.net.URI;
-
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.core.Response;
-
-import org.onlab.onos.core.ApplicationId;
-import org.onlab.onos.core.CoreService;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.rest.BaseResource;
+import javax.ws.rs.POST;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import org.onlab.onos.core.ApplicationId;
+import org.onlab.onos.core.CoreService;
 import org.onlab.onos.net.flow.DefaultTrafficSelector;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
-import org.onlab.onos.net.intent.Intent;
-import org.onlab.onos.net.intent.IntentService;
-import org.onlab.onos.net.intent.PointToPointIntentWithBandwidthConstraint;
-import org.onlab.onos.net.resource.BandwidthResourceRequest;
+import org.onlab.onos.net.intent.PointToPointIntent;
 import org.onlab.packet.Ethernet;
-import org.onlab.rest.BaseResource;
-
 import static org.onlab.onos.net.PortNumber.portNumber;
 import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
 
+import static org.slf4j.LoggerFactory.getLogger;
+import org.slf4j.Logger;
+
 /**
  * Web resource for triggering calendared intents.
  */
-@Path("intent")
+@javax.ws.rs.Path("intent")
 public class BandwidthCalendarResource extends BaseResource {
 
+    private static final Logger log = getLogger(BandwidthCalendarResource.class);
+
+    @javax.ws.rs.Path("/{src}/{dst}/{srcPort}/{dstPort}/{bandwidth}")
     @POST
-    @Path("{src}/{dst}/{srcPort}/{dstPort}/{bandwidth}")
     public Response createIntent(@PathParam("src") String src,
                                  @PathParam("dst") String dst,
                                  @PathParam("srcPort") String srcPort,
                                  @PathParam("dstPort") String dstPort,
                                  @PathParam("bandwidth") String bandwidth) {
-        // TODO: implement calls to intent framework
+
+        log.info("Receiving Create Intent request...");
+        log.info("Path Constraints: Src = {} SrcPort = {} Dest = {} DestPort = {} BW = {}",
+                src, srcPort, dst, dstPort, bandwidth);
+
         IntentService service = get(IntentService.class);
 
         ConnectPoint srcPoint = new ConnectPoint(deviceId(src), portNumber(srcPort));
@@ -61,13 +65,38 @@
         TrafficSelector selector = buildTrafficSelector();
         TrafficTreatment treatment = builder().build();
 
-        Intent intent = new PointToPointIntentWithBandwidthConstraint(
-                appId(), selector, treatment,
-                srcPoint, dstPoint, new BandwidthResourceRequest(Double.parseDouble(bandwidth)));
-        service.submit(intent);
+        PointToPointIntent intentP2P =
+                        new PointToPointIntent(appId(), selector, treatment,
+                                               srcPoint, dstPoint);
+        service.submit(intentP2P);
+        log.info("Submitted Calendar App intent: src = " + src + "dest = " + dst
+                + "srcPort = " + srcPort + "destPort" + dstPort + "intentID = " + intentP2P.id().toString());
+        String reply =  intentP2P.id().toString() + "\n";
 
-        return Response.ok("Yo! We got src=" + srcPoint + "; dst=" + dstPoint +
-                                   "; bw=" + bandwidth + "; intent service " + service).build();
+        return Response.ok(reply).build();
+    }
+
+    @javax.ws.rs.Path("/cancellation/{intentId}")
+    @DELETE
+    public Response withdrawIntent(@PathParam("intentId") String intentId) {
+
+        log.info("Receiving Teardown request...");
+        log.info("Withdraw intentId = {} ", intentId);
+
+        String reply =  "ok\n";
+        return Response.ok(reply).build();
+    }
+
+    @javax.ws.rs.Path("/modification/{intentId}/{bandwidth}")
+    @POST
+    public Response modifyBandwidth(@PathParam("intentId") String intentId,
+                                 @PathParam("bandwidth") String bandwidth) {
+
+        log.info("Receiving Modify request...");
+        log.info("Modify bw for intentId = {} with new bandwidth = {}", intentId, bandwidth);
+
+        String reply =  "ok\n";
+        return Response.ok(reply).build();
     }
 
     private TrafficSelector buildTrafficSelector() {
@@ -86,5 +115,4 @@
     protected ApplicationId appId() {
         return get(CoreService.class).registerApplication("org.onlab.onos.calendar");
     }
-
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
index 960eb02..352f604 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
@@ -188,7 +188,7 @@
         }
 
         @Override
-        public Builder matchOpticalSignalType(Byte signalType) {
+        public Builder matchOpticalSignalType(Short signalType) {
             return add(Criteria.matchOpticalSignalType(signalType));
 
         }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
index d5c762d..1dbf8bd 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
@@ -27,11 +27,14 @@
      */
     public enum Type {
 
+        // Request has been forwarded to MASTER Node
         /**
          * Signifies that a batch operation has been initiated.
          */
         BATCH_OPERATION_REQUESTED,
 
+        // MASTER Node has pushed the batch down to the Device
+        // (e.g., Received barrier reply)
         /**
          * Signifies that a batch operation has completed.
          */
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
index 4a2bcf9..f75c663 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
@@ -25,29 +25,29 @@
 public class FlowRuleBatchRequest {
 
     private final int batchId;
-    private final List<FlowEntry> toAdd;
-    private final List<FlowEntry> toRemove;
+    private final List<FlowRule> toAdd;
+    private final List<FlowRule> toRemove;
 
-    public FlowRuleBatchRequest(int batchId, List<? extends FlowEntry> toAdd, List<? extends FlowEntry> toRemove) {
+    public FlowRuleBatchRequest(int batchId, List<? extends FlowRule> toAdd, List<? extends FlowRule> toRemove) {
         this.batchId = batchId;
         this.toAdd = Collections.unmodifiableList(toAdd);
         this.toRemove = Collections.unmodifiableList(toRemove);
     }
 
-    public List<FlowEntry> toAdd() {
+    public List<FlowRule> toAdd() {
         return toAdd;
     }
 
-    public List<FlowEntry> toRemove() {
+    public List<FlowRule> toRemove() {
         return toRemove;
     }
 
     public FlowRuleBatchOperation asBatchOperation() {
         List<FlowRuleBatchEntry> entries = Lists.newArrayList();
-        for (FlowEntry e : toAdd) {
+        for (FlowRule e : toAdd) {
             entries.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, e));
         }
-        for (FlowEntry e : toRemove) {
+        for (FlowRule e : toRemove) {
             entries.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, e));
         }
         return new FlowRuleBatchOperation(entries);
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
index aaf0ece..dd8e2ae 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
@@ -147,7 +147,7 @@
          * @param signalType
          * @return a selection builder
          */
-        public Builder matchOpticalSignalType(Byte signalType);
+        public Builder matchOpticalSignalType(Short signalType);
 
         /**
          * Builds an immutable traffic selector.
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
index aba5680..a1ac5f8 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
@@ -161,11 +161,11 @@
     /**
      * Creates a match on lambda field using the specified value.
      *
-     * @param lambda
+     * @param sigType
      * @return match criterion
      */
-    public static Criterion matchOpticalSignalType(Byte lambda) {
-        return new OpticalSignalTypeCriterion(lambda, Type.OCH_SIGTYPE);
+    public static Criterion matchOpticalSignalType(Short sigType) {
+        return new OpticalSignalTypeCriterion(sigType, Type.OCH_SIGTYPE);
     }
 
 
@@ -587,10 +587,10 @@
 
     public static final class OpticalSignalTypeCriterion implements Criterion {
 
-        private final byte signalType;
+        private final Short signalType;
         private final Type type;
 
-        public OpticalSignalTypeCriterion(byte signalType, Type type) {
+        public OpticalSignalTypeCriterion(Short signalType, Type type) {
             this.signalType = signalType;
             this.type = type;
         }
@@ -600,7 +600,7 @@
             return this.type;
         }
 
-        public Byte signalType() {
+        public Short signalType() {
             return this.signalType;
         }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
index 0789db0..60ba70c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
@@ -15,7 +15,11 @@
  */
 package org.onlab.onos.net.intent;
 
+import java.util.List;
+
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
 import org.onlab.onos.core.ApplicationId;
 import org.onlab.onos.net.Path;
 import org.onlab.onos.net.flow.TrafficSelector;
@@ -28,7 +32,7 @@
 public class PathIntent extends ConnectivityIntent {
 
     private final Path path;
-    private final LinkResourceRequest[] resourceRequests;
+    private final List<LinkResourceRequest> resourceRequests;
 
     /**
      * Creates a new point-to-point intent with the supplied ingress/egress
@@ -45,7 +49,7 @@
         super(id(PathIntent.class, selector, treatment, path), appId,
               resources(path.links()), selector, treatment);
         this.path = path;
-        this.resourceRequests = resourceRequests;
+        this.resourceRequests = ImmutableList.copyOf(resourceRequests);
     }
 
     /**
@@ -54,7 +58,7 @@
     protected PathIntent() {
         super();
         this.path = null;
-        this.resourceRequests = new LinkResourceRequest[0];
+        this.resourceRequests = ImmutableList.of();
     }
 
     /**
@@ -71,8 +75,9 @@
         return true;
     }
 
+    // TODO: consider changing return type
     public LinkResourceRequest[] resourceRequests() {
-        return resourceRequests;
+        return resourceRequests.toArray(new LinkResourceRequest[resourceRequests.size()]);
     }
 
     @Override
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index 4fe9022..bd7fb94 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -371,10 +371,11 @@
             final FlowRuleBatchRequest request = event.subject();
             switch (event.type()) {
             case BATCH_OPERATION_REQUESTED:
-                for (FlowEntry entry : request.toAdd()) {
+                // Request has been forwarded to MASTER Node, and was
+                for (FlowRule entry : request.toAdd()) {
                     eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADD_REQUESTED, entry));
                 }
-                for (FlowEntry entry : request.toRemove()) {
+                for (FlowRule entry : request.toRemove()) {
                     eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, entry));
                 }
                 // FIXME: what about op.equals(FlowRuleOperation.MODIFY) ?
@@ -392,21 +393,15 @@
                                                                                   Futures.getUnchecked(result)));
                     }
                 }, futureListeners);
+                break;
 
-                break;
             case BATCH_OPERATION_COMPLETED:
-                Set<FlowRule> failedItems = event.result().failedItems();
-                for (FlowEntry entry : request.toAdd()) {
-                    if (!failedItems.contains(entry)) {
-                        eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, entry));
-                    }
-                }
-                for (FlowEntry entry : request.toRemove()) {
-                    if (!failedItems.contains(entry)) {
-                            eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVED, entry));
-                    }
-                }
+                // MASTER Node has pushed the batch down to the Device
+
+                // Note: RULE_ADDED will be posted
+                // when Flow was actually confirmed by stats reply.
                 break;
+
             default:
                 break;
             }
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
index 5faae4d..27b5387 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
@@ -79,6 +79,7 @@
     private ApplicationId appId;
 
     //final short WAVELENGTH = 80;
+    static final short SIGNAL_TYPE = (short) 1;
 
     @Activate
     public void activate() {
@@ -151,7 +152,9 @@
 
             prev = link.dst();
             selectorBuilder.matchInport(link.dst().port());
+            selectorBuilder.matchOpticalSignalType(SIGNAL_TYPE); //todo
             selectorBuilder.matchLambda((short) la.toInt());
+
         }
 
         // build the last T port rule
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
index b986d6d..f67d992 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
@@ -148,7 +148,7 @@
         int i = 0;
         System.err.println("events :" + listener.events);
         for (FlowRuleEvent e : listener.events) {
-            assertTrue("unexpected event", e.type().equals(events[i]));
+            assertEquals("unexpected event", events[i], e.type());
             i++;
         }
 
@@ -178,15 +178,13 @@
                        RULE_ADDED, RULE_ADDED);
 
         addFlowRule(1);
+        System.err.println("events :" + listener.events);
         assertEquals("should still be 2 rules", 2, flowCount());
 
         providerService.pushFlowMetrics(DID, ImmutableList.of(fe1));
         validateEvents(RULE_UPDATED);
     }
 
-
-    // TODO: If preserving iteration order is a requirement, redo FlowRuleStore.
-    //backing store is sensitive to the order of additions/removals
     private boolean validateState(Map<FlowRule, FlowEntryState> expected) {
         Map<FlowRule, FlowEntryState> expectedToCheck = new HashMap<>(expected);
         Iterable<FlowEntry> rules = service.getFlowEntries(DID);
@@ -539,17 +537,17 @@
 
             @Override
             public boolean cancel(boolean mayInterruptIfRunning) {
-                return true;
+                return false;
             }
 
             @Override
             public boolean isCancelled() {
-                return true;
+                return false;
             }
 
             @Override
             public boolean isDone() {
-                return false;
+                return true;
             }
 
             @Override
@@ -562,12 +560,14 @@
             public CompletedBatchOperation get(long timeout, TimeUnit unit)
                     throws InterruptedException,
                     ExecutionException, TimeoutException {
-                return null;
+                return new CompletedBatchOperation(true, Collections.<FlowRule>emptySet());
             }
 
             @Override
             public void addListener(Runnable task, Executor executor) {
-                // TODO: add stuff.
+                if (isDone()) {
+                    executor.execute(task);
+                }
             }
         }
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
index c9d4422..713be4e 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
@@ -447,7 +447,13 @@
             RoleValue oldValue = event.getOldValue();
             RoleValue newValue = event.getValue();
 
-            if (Objects.equal(oldValue.get(MASTER), newValue.get(MASTER))) {
+            NodeId oldMaster = null;
+            if (oldValue != null) {
+                oldMaster = oldValue.get(MASTER);
+            }
+            NodeId newMaster = newValue.get(MASTER);
+
+            if (Objects.equal(oldMaster, newMaster)) {
                 notifyDelegate(new MastershipEvent(
                         MASTER_CHANGED, event.getKey(), event.getValue().roleInfo()));
             } else {
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
index 49b0c71..b7d26fb 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
@@ -16,8 +16,12 @@
 package org.onlab.onos.store.trivial.impl;
 
 import com.google.common.base.Function;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import com.google.common.collect.FluentIterable;
 import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.SettableFuture;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -43,13 +47,15 @@
 import org.onlab.util.NewConcurrentHashMap;
 import org.slf4j.Logger;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
@@ -72,6 +78,18 @@
     private final ConcurrentMap<DeviceId, ConcurrentMap<FlowId, List<StoredFlowEntry>>>
             flowEntries = new ConcurrentHashMap<>();
 
+    private final AtomicInteger localBatchIdGen = new AtomicInteger();
+
+    // TODO: make this configurable
+    private int pendingFutureTimeoutMinutes = 5;
+
+    private Cache<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures =
+            CacheBuilder.newBuilder()
+                .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
+                // TODO Explicitly fail the future if expired?
+                //.removalListener(listener)
+                .build();
+
     @Activate
     public void activate() {
         log.info("Started");
@@ -173,10 +191,6 @@
             }
             // new flow rule added
             existing.add(f);
-            notifyDelegate(FlowRuleBatchEvent.requested(
-                    new FlowRuleBatchRequest(1, /* FIXME generate something */
-                                             Arrays.<FlowEntry>asList(f),
-                                             Collections.<FlowEntry>emptyList())));
         }
     }
 
@@ -190,11 +204,6 @@
                 if (entry.equals(rule)) {
                     synchronized (entry) {
                         entry.setState(FlowEntryState.PENDING_REMOVE);
-                        // TODO: Should we notify only if it's "remote" event?
-                        notifyDelegate(FlowRuleBatchEvent.requested(
-                                new FlowRuleBatchRequest(1, /* FIXME generate something */
-                                                         Collections.<FlowEntry>emptyList(),
-                                                         Arrays.<FlowEntry>asList(entry))));
                     }
                 }
             }
@@ -251,20 +260,47 @@
     @Override
     public Future<CompletedBatchOperation> storeBatch(
             FlowRuleBatchOperation batchOperation) {
+        List<FlowRule> toAdd = new ArrayList<>();
+        List<FlowRule> toRemove = new ArrayList<>();
         for (FlowRuleBatchEntry entry : batchOperation.getOperations()) {
+            final FlowRule flowRule = entry.getTarget();
             if (entry.getOperator().equals(FlowRuleOperation.ADD)) {
-                storeFlowRule(entry.getTarget());
+                if (!getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+                    storeFlowRule(flowRule);
+                    toAdd.add(flowRule);
+                }
             } else if (entry.getOperator().equals(FlowRuleOperation.REMOVE)) {
-                deleteFlowRule(entry.getTarget());
+                if (getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+                    deleteFlowRule(flowRule);
+                    toRemove.add(flowRule);
+                }
             } else {
                 throw new UnsupportedOperationException("Unsupported operation type");
             }
         }
-        return Futures.immediateFuture(new CompletedBatchOperation(true, Collections.<FlowEntry>emptySet()));
+
+        if (toAdd.isEmpty() && toRemove.isEmpty()) {
+            return Futures.immediateFuture(new CompletedBatchOperation(true, Collections.<FlowRule>emptySet()));
+        }
+
+        SettableFuture<CompletedBatchOperation> r = SettableFuture.create();
+        final int batchId = localBatchIdGen.incrementAndGet();
+
+        pendingFutures.put(batchId, r);
+        notifyDelegate(FlowRuleBatchEvent.requested(new FlowRuleBatchRequest(batchId, toAdd, toRemove)));
+
+        return r;
     }
 
     @Override
     public void batchOperationComplete(FlowRuleBatchEvent event) {
+        final Integer batchId = event.subject().batchId();
+        SettableFuture<CompletedBatchOperation> future
+            = pendingFutures.getIfPresent(batchId);
+        if (future != null) {
+            future.set(event.result());
+            pendingFutures.invalidate(batchId);
+        }
         notifyDelegate(event);
     }
 }
diff --git a/pom.xml b/pom.xml
index deefb48..5c24516 100644
--- a/pom.xml
+++ b/pom.xml
@@ -352,13 +352,6 @@
     </dependencies>
 
     <build>
-        <extensions>
-            <extension>
-                <groupId>kr.motd.maven</groupId>
-                <artifactId>os-maven-plugin</artifactId>
-                <version>1.2.3.Final</version>
-            </extension>
-        </extensions>
         <pluginManagement>
             <plugins>
                 <plugin>
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
index 02ec827..e5131df 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
@@ -289,7 +289,10 @@
             case OCH_SIGID:
                 builder.matchLambda(match.get(MatchField.OCH_SIGID).getChannelNumber());
                 break;
-            case OCH_SIGTYPE_BASIC:
+            case OCH_SIGTYPE:
+                builder.matchOpticalSignalType(match.get(MatchField
+                                                                 .OCH_SIGTYPE).getValue());
+                break;
             case ARP_OP:
             case ARP_SHA:
             case ARP_SPA:
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
index b191784..0caf06b 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
@@ -19,6 +19,7 @@
 
 import org.onlab.onos.net.flow.FlowRule;
 import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.criteria.Criteria;
 import org.onlab.onos.net.flow.criteria.Criteria.EthCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.EthTypeCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
@@ -46,6 +47,7 @@
 import org.projectfloodlight.openflow.types.OFPort;
 import org.projectfloodlight.openflow.types.OFVlanVidMatch;
 import org.projectfloodlight.openflow.types.TransportPort;
+import org.projectfloodlight.openflow.types.U8;
 import org.projectfloodlight.openflow.types.VlanPcp;
 import org.projectfloodlight.openflow.types.VlanVid;
 import org.slf4j.Logger;
@@ -197,6 +199,12 @@
                 mBuilder.setExact(MatchField.OCH_SIGID,
                         new CircuitSignalID((byte) 1, (byte) 2, lc.lambda(), (short) 1));
                 break;
+            case OCH_SIGTYPE:
+                Criteria.OpticalSignalTypeCriterion sc =
+                        (Criteria.OpticalSignalTypeCriterion) c;
+                mBuilder.setExact(MatchField.OCH_SIGTYPE,
+                                  U8.of(sc.signalType()));
+                break;
             case ARP_OP:
             case ARP_SHA:
             case ARP_SPA:
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
index 77ba9b0..f685e79 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
@@ -30,7 +30,6 @@
 
 /**
  * A class representing an IP address.
- * TODO: Add support for IPv6 as well.
  */
 public final class IpAddress implements Comparable<IpAddress> {
     // IP Versions
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
index 3cde79d..4fef5a4 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
@@ -17,8 +17,6 @@
 
 import java.util.Objects;
 
-// TODO: Add support for IPv6 as well.
-
 /**
  * A class representing an IP prefix. A prefix consists of an IP address and
  * a subnet mask.
@@ -40,82 +38,12 @@
      *
      * @param address the IP address
      * @param prefixLength the prefix length
-     */
-    private IpPrefix(IpAddress address, int prefixLength) {
-        checkPrefixLength(prefixLength);
-        this.address = IpAddress.makeMaskedAddress(address, prefixLength);
-        this.prefixLength = (short) prefixLength;
-    }
-
-    /**
-     * Checks whether the prefix length is valid.
-     *
-     * @param prefixLength the prefix length value to check
      * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    private static void checkPrefixLength(int prefixLength) {
-        if ((prefixLength < 0) || (prefixLength > MAX_INET_MASK_LENGTH)) {
-            String msg = "Invalid prefix length " + prefixLength + ". " +
-                "The value must be in the interval [0, " +
-                MAX_INET_MASK_LENGTH + "]";
-            throw new IllegalArgumentException(msg);
-        }
-    }
-
-    /**
-     * Converts an integer and a prefix length into an IPv4 prefix.
-     *
-     * @param address an integer representing the IPv4 address
-     * @param prefixLength the prefix length
-     * @return an IP prefix
-     */
-    public static IpPrefix valueOf(int address, int prefixLength) {
-        return new IpPrefix(IpAddress.valueOf(address), prefixLength);
-    }
-
-    /**
-     * Converts a byte array and a prefix length into an IP prefix.
-     *
-     * @param version the IP address version
-     * @param address the IP address value stored in network byte order
-     * @param prefixLength the prefix length
-     * @return an IP prefix
-     */
-    public static IpPrefix valueOf(IpAddress.Version version, byte[] address,
-                                   int prefixLength) {
-        return new IpPrefix(IpAddress.valueOf(version, address),
-                            prefixLength);
-    }
-
-    /**
-     * Converts an IP address and a prefix length into IP prefix.
-     *
-     * @param address the IP address
-     * @param prefixLength the prefix length
-     * @return an IP prefix
-     */
-    public static IpPrefix valueOf(IpAddress address, int prefixLength) {
-        return new IpPrefix(address, prefixLength);
-    }
-
-    /**
-     * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16") into an
-     * IP prefix.
-     *
-     * @param address an IP prefix in string form, e.g. "10.1.0.0/16"
-     * @return an IP prefix
-     */
-    public static IpPrefix valueOf(String address) {
-        final String[] parts = address.split("/");
-        if (parts.length != 2) {
-            String msg = "Malformed IP prefix string: " + address + "." +
-                "Address must take form \"x.x.x.x/y\"";
-            throw new IllegalArgumentException(msg);
-        }
-        IpAddress ipAddress = IpAddress.valueOf(parts[0]);
-        int prefixLength = Integer.parseInt(parts[1]);
-
-        return new IpPrefix(ipAddress, prefixLength);
+    private IpPrefix(IpAddress address, int prefixLength) {
+        checkPrefixLength(address.version(), prefixLength);
+        this.address = IpAddress.makeMaskedAddress(address, prefixLength);
+        this.prefixLength = (short) prefixLength;
     }
 
     /**
@@ -146,6 +74,65 @@
     }
 
     /**
+     * Converts an integer and a prefix length into an IPv4 prefix.
+     *
+     * @param address an integer representing the IPv4 address
+     * @param prefixLength the prefix length
+     * @return an IP prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
+     */
+    public static IpPrefix valueOf(int address, int prefixLength) {
+        return new IpPrefix(IpAddress.valueOf(address), prefixLength);
+    }
+
+    /**
+     * Converts a byte array and a prefix length into an IP prefix.
+     *
+     * @param version the IP address version
+     * @param address the IP address value stored in network byte order
+     * @param prefixLength the prefix length
+     * @return an IP prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
+     */
+    public static IpPrefix valueOf(IpAddress.Version version, byte[] address,
+                                   int prefixLength) {
+        return new IpPrefix(IpAddress.valueOf(version, address), prefixLength);
+    }
+
+    /**
+     * Converts an IP address and a prefix length into IP prefix.
+     *
+     * @param address the IP address
+     * @param prefixLength the prefix length
+     * @return an IP prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
+     */
+    public static IpPrefix valueOf(IpAddress address, int prefixLength) {
+        return new IpPrefix(address, prefixLength);
+    }
+
+    /**
+     * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16") into an
+     * IP prefix.
+     *
+     * @param address an IP prefix in string form, e.g. "10.1.0.0/16"
+     * @return an IP prefix
+     * @throws IllegalArgumentException if the arguments are invalid
+     */
+    public static IpPrefix valueOf(String address) {
+        final String[] parts = address.split("/");
+        if (parts.length != 2) {
+            String msg = "Malformed IP prefix string: " + address + "." +
+                "Address must take form \"x.x.x.x/y\"";
+            throw new IllegalArgumentException(msg);
+        }
+        IpAddress ipAddress = IpAddress.valueOf(parts[0]);
+        int prefixLength = Integer.parseInt(parts[1]);
+
+        return new IpPrefix(ipAddress, prefixLength);
+    }
+
+    /**
      * Determines whether a given IP prefix is contained within this prefix.
      *
      * @param other the IP prefix to test
@@ -217,4 +204,35 @@
         builder.append(String.format("%d", prefixLength));
         return builder.toString();
     }
+
+    /**
+     * Checks whether the prefix length is valid.
+     *
+     * @param version the IP address version
+     * @param prefixLength the prefix length value to check
+     * @throws IllegalArgumentException if the prefix length value is invalid
+     */
+    private static void checkPrefixLength(IpAddress.Version version,
+                                          int prefixLength) {
+        int maxPrefixLen = 0;
+
+        switch (version) {
+        case INET:
+            maxPrefixLen = MAX_INET_MASK_LENGTH;
+            break;
+        case INET6:
+            maxPrefixLen = MAX_INET6_MASK_LENGTH;
+            break;
+        default:
+            String msg = "Invalid IP version " + version;
+            throw new IllegalArgumentException(msg);
+        }
+
+        if ((prefixLength < 0) || (prefixLength > maxPrefixLen)) {
+            String msg = "Invalid prefix length " + prefixLength + ". " +
+                "The value must be in the interval [0, " +
+                maxPrefixLen + "]";
+            throw new IllegalArgumentException(msg);
+        }
+    }
 }
diff --git a/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java b/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
index 4237829..0176a8c 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
@@ -135,7 +135,7 @@
      * Tests returning an IPv4 address asn an integer.
      */
     @Test
-    public void testToint() {
+    public void testToInt() {
         IpAddress ipAddress;
 
         ipAddress = IpAddress.valueOf("1.2.3.4");
@@ -149,10 +149,10 @@
     }
 
     /**
-     * Tests valueOf() converter for an integer value.
+     * Tests valueOf() converter for IPv4 integer value.
      */
     @Test
-    public void testValueOfForInteger() {
+    public void testValueOfForIntegerIPv4() {
         IpAddress ipAddress;
 
         ipAddress = IpAddress.valueOf(0x01020304);
diff --git a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
index f5ad88b..e12867b 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
@@ -15,123 +15,970 @@
  */
 package org.onlab.packet;
 
-import static org.junit.Assert.assertEquals;
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 
-import java.util.Arrays;
-
-import org.junit.Test;
-import org.onlab.packet.IpAddress.Version;
-
-import com.google.common.testing.EqualsTester;
-
+/**
+ * Tests for class {@link IpPrefix}.
+ */
 public class IpPrefixTest {
-
-    private static final byte [] BYTES1 = new byte [] {0xa, 0x0, 0x0, 0xa};
-    private static final byte [] BYTES2 = new byte [] {0xa, 0x0, 0x0, 0xb};
-    private static final int INTVAL0 = 0x0a000000;
-    private static final int INTVAL1 = 0x0a00000a;
-    private static final int INTVAL2 = 0x0a00000b;
-    private static final String STRVAL = "10.0.0.12/16";
-    private static final int MASK_LENGTH = 16;
-
+    /**
+     * Tests the immutability of {@link IpPrefix}.
+     */
     @Test
-    public void testEquality() {
-        IpPrefix ip1 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                        BYTES1, IpPrefix.MAX_INET_MASK_LENGTH);
-        IpPrefix ip2 = IpPrefix.valueOf(INTVAL1, IpPrefix.MAX_INET_MASK_LENGTH);
-        IpPrefix ip3 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                        BYTES2, IpPrefix.MAX_INET_MASK_LENGTH);
-        IpPrefix ip4 = IpPrefix.valueOf(INTVAL2, IpPrefix.MAX_INET_MASK_LENGTH);
-        IpPrefix ip5 = IpPrefix.valueOf(STRVAL);
-
-        new EqualsTester().addEqualityGroup(ip1, ip2)
-        .addEqualityGroup(ip3, ip4)
-        .addEqualityGroup(ip5)
-        .testEquals();
-
-        // string conversions
-        IpPrefix ip6 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                        BYTES1, MASK_LENGTH);
-        IpPrefix ip7 = IpPrefix.valueOf("10.0.0.10/16");
-        IpPrefix ip8 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                        new byte [] {0xa, 0x0, 0x0, 0xc}, 16);
-        assertEquals("incorrect address conversion", ip6, ip7);
-        assertEquals("incorrect address conversion", ip5, ip8);
+    public void testImmutable() {
+        assertThatClassIsImmutable(IpPrefix.class);
     }
 
+    /**
+     * Tests the maximum mask length.
+     */
     @Test
-    public void basics() {
-        IpPrefix ip1 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                        BYTES1, MASK_LENGTH);
-        final byte [] bytes = new byte [] {0xa, 0x0, 0x0, 0x0};
-
-        // check fields
-        assertEquals("incorrect IP Version", Version.INET, ip1.version());
-        assertEquals("incorrect netmask", 16, ip1.prefixLength());
-        assertTrue("faulty toOctets()",
-                   Arrays.equals(bytes, ip1.address().toOctets()));
-        assertEquals("faulty toInt()", INTVAL0, ip1.address().toInt());
-        assertEquals("faulty toString()", "10.0.0.0/16", ip1.toString());
+    public void testMaxMaskLength() {
+        assertThat(IpPrefix.MAX_INET_MASK_LENGTH, is(32));
+        assertThat(IpPrefix.MAX_INET6_MASK_LENGTH, is(128));
     }
 
+    /**
+     * Tests returning the IP version of the prefix.
+     */
     @Test
-    public void netmasks() {
-        // masked
-        IpPrefix ip1 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                        BYTES1, MASK_LENGTH);
-        IpPrefix ip2 = IpPrefix.valueOf("10.0.0.10/16");
-        IpPrefix ip3 = IpPrefix.valueOf("10.0.0.0/16");
-        assertEquals("incorrect binary masked address",
-                     ip1.toString(), "10.0.0.0/16");
-        assertEquals("incorrect string masked address",
-                     ip2.toString(), "10.0.0.0/16");
-        assertEquals("incorrect network address",
-                     ip2.toString(), "10.0.0.0/16");
+    public void testVersion() {
+        IpPrefix ipPrefix;
+
+        // IPv4
+        ipPrefix = IpPrefix.valueOf("0.0.0.0/0");
+        assertThat(ipPrefix.version(), is(IpAddress.Version.INET));
+
+        // IPv6
+        ipPrefix = IpPrefix.valueOf("::/0");
+        assertThat(ipPrefix.version(), is(IpAddress.Version.INET6));
     }
 
+    /**
+     * Tests returning the IP address value and IP address prefix length of
+     * an IPv4 prefix.
+     */
     @Test
-    public void testContainsIpPrefix() {
-        IpPrefix slash31 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                            BYTES1, 31);
-        IpPrefix slash32 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                            BYTES1, 32);
-        IpPrefix differentSlash32 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                                     BYTES2, 32);
+    public void testAddressAndPrefixLengthIPv4() {
+        IpPrefix ipPrefix;
 
-        assertTrue(slash31.contains(differentSlash32));
-        assertFalse(differentSlash32.contains(slash31));
+        ipPrefix = IpPrefix.valueOf("1.2.3.0/24");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.0")));
+        assertThat(ipPrefix.prefixLength(), is(24));
 
-        assertTrue(slash31.contains(slash32));
-        assertFalse(slash32.contains(differentSlash32));
-        assertFalse(differentSlash32.contains(slash32));
+        ipPrefix = IpPrefix.valueOf("1.2.3.4/24");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.0")));
+        assertThat(ipPrefix.prefixLength(), is(24));
 
-        IpPrefix zero = IpPrefix.valueOf("0.0.0.0/0");
-        assertTrue(zero.contains(differentSlash32));
-        assertFalse(differentSlash32.contains(zero));
+        ipPrefix = IpPrefix.valueOf("1.2.3.4/32");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.4")));
+        assertThat(ipPrefix.prefixLength(), is(32));
 
-        IpPrefix slash8 = IpPrefix.valueOf("10.0.0.0/8");
-        assertTrue(slash8.contains(slash31));
-        assertFalse(slash31.contains(slash8));
+        ipPrefix = IpPrefix.valueOf("1.2.3.5/32");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1.2.3.5")));
+        assertThat(ipPrefix.prefixLength(), is(32));
+
+        ipPrefix = IpPrefix.valueOf("0.0.0.0/0");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("0.0.0.0")));
+        assertThat(ipPrefix.prefixLength(), is(0));
+
+        ipPrefix = IpPrefix.valueOf("255.255.255.255/32");
+        assertThat(ipPrefix.address(),
+                   equalTo(IpAddress.valueOf("255.255.255.255")));
+        assertThat(ipPrefix.prefixLength(), is(32));
     }
 
+    /**
+     * Tests returning the IP address value and IP address prefix length of
+     * an IPv6 prefix.
+     */
     @Test
-    public void testContainsIpAddress() {
-        IpPrefix slash31 = IpPrefix.valueOf(IpAddress.Version.INET,
-                                            BYTES1, 31);
-        IpAddress addr32 = IpAddress.valueOf(IpAddress.Version.INET, BYTES1);
+    public void testAddressAndPrefixLengthIPv6() {
+        IpPrefix ipPrefix;
 
-        assertTrue(slash31.contains(addr32));
+        ipPrefix = IpPrefix.valueOf("1100::/8");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1100::")));
+        assertThat(ipPrefix.prefixLength(), is(8));
 
-        IpPrefix intf = IpPrefix.valueOf("192.168.10.101/24");
-        IpAddress addr = IpAddress.valueOf("192.168.10.1");
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/8");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("1100::")));
+        assertThat(ipPrefix.prefixLength(), is(8));
 
-        assertTrue(intf.contains(addr));
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8800/120");
+        assertThat(ipPrefix.address(),
+                   equalTo(IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8800")));
+        assertThat(ipPrefix.prefixLength(), is(120));
 
-        IpPrefix intf1 = IpPrefix.valueOf("10.0.0.101/24");
-        IpAddress addr1 = IpAddress.valueOf("10.0.0.4");
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/128");
+        assertThat(ipPrefix.address(),
+                   equalTo(IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885")));
+        assertThat(ipPrefix.prefixLength(), is(128));
 
-        assertTrue(intf1.contains(addr1));
+        ipPrefix = IpPrefix.valueOf("::/0");
+        assertThat(ipPrefix.address(), equalTo(IpAddress.valueOf("::")));
+        assertThat(ipPrefix.prefixLength(), is(0));
+
+        ipPrefix =
+            IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
+        assertThat(ipPrefix.address(),
+                   equalTo(IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+        assertThat(ipPrefix.prefixLength(), is(128));
+
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/64");
+        assertThat(ipPrefix.address(),
+                   equalTo(IpAddress.valueOf("1111:2222:3333:4444::")));
+        assertThat(ipPrefix.prefixLength(), is(64));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 integer value.
+     */
+    @Test
+    public void testValueOfForIntegerIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf(0x01020304, 24);
+        assertThat(ipPrefix.toString(), is("1.2.3.0/24"));
+
+        ipPrefix = IpPrefix.valueOf(0x01020304, 32);
+        assertThat(ipPrefix.toString(), is("1.2.3.4/32"));
+
+        ipPrefix = IpPrefix.valueOf(0x01020305, 32);
+        assertThat(ipPrefix.toString(), is("1.2.3.5/32"));
+
+        ipPrefix = IpPrefix.valueOf(0, 0);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf(0, 32);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/32"));
+
+        ipPrefix = IpPrefix.valueOf(0xffffffff, 0);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf(0xffffffff, 16);
+        assertThat(ipPrefix.toString(), is("255.255.0.0/16"));
+
+        ipPrefix = IpPrefix.valueOf(0xffffffff, 32);
+        assertThat(ipPrefix.toString(), is("255.255.255.255/32"));
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 integer value and
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfIntegerNegativePrefixLengthIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf(0x01020304, -1);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 integer value and
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfIntegerTooLongPrefixLengthIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf(0x01020304, 33);
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 byte array.
+     */
+    @Test
+    public void testValueOfByteArrayIPv4() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {1, 2, 3, 4};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 24);
+        assertThat(ipPrefix.toString(), is("1.2.3.0/24"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32);
+        assertThat(ipPrefix.toString(), is("1.2.3.4/32"));
+
+        value = new byte[] {1, 2, 3, 5};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32);
+        assertThat(ipPrefix.toString(), is("1.2.3.5/32"));
+
+        value = new byte[] {0, 0, 0, 0};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 0);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/32"));
+
+        value = new byte[] {(byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 0);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 16);
+        assertThat(ipPrefix.toString(), is("255.255.0.0/16"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 32);
+        assertThat(ipPrefix.toString(), is("255.255.255.255/32"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv6 byte array.
+     */
+    @Test
+    public void testValueOfByteArrayIPv6() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {0x11, 0x11, 0x22, 0x22,
+                            0x33, 0x33, 0x44, 0x44,
+                            0x55, 0x55, 0x66, 0x66,
+                            0x77, 0x77, (byte) 0x88, (byte) 0x88};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 120);
+        assertThat(ipPrefix.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8800/120"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 128);
+        assertThat(ipPrefix.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888/128"));
+
+        value = new byte[] {0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 0);
+        assertThat(ipPrefix.toString(), is("::/0"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 128);
+        assertThat(ipPrefix.toString(), is("::/128"));
+
+        value = new byte[] {(byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff,
+                            (byte) 0xff, (byte) 0xff};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 0);
+        assertThat(ipPrefix.toString(), is("::/0"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 64);
+        assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64"));
+
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 128);
+        assertThat(ipPrefix.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"));
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a null array for IPv4.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidValueOfNullArrayIPv4() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = null;
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 24);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a null array for IPv6.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidValueOfNullArrayIPv6() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = null;
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 120);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a short array for IPv4.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfShortArrayIPv4() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {1, 2, 3};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 24);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a short array for IPv6.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfShortArrayIPv6() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 120);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 byte array and
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfByteArrayNegativePrefixLengthIPv4() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {1, 2, 3, 4};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, -1);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv6 byte array and
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfByteArrayNegativePrefixLengthIPv6() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {0x11, 0x11, 0x22, 0x22,
+                            0x33, 0x33, 0x44, 0x44,
+                            0x55, 0x55, 0x66, 0x66,
+                            0x77, 0x77, (byte) 0x88, (byte) 0x88};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, -1);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 byte array and
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfByteArrayTooLongPrefixLengthIPv4() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {1, 2, 3, 4};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET, value, 33);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv6 byte array and
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfByteArrayTooLongPrefixLengthIPv6() {
+        IpPrefix ipPrefix;
+        byte[] value;
+
+        value = new byte[] {0x11, 0x11, 0x22, 0x22,
+                            0x33, 0x33, 0x44, 0x44,
+                            0x55, 0x55, 0x66, 0x66,
+                            0x77, 0x77, (byte) 0x88, (byte) 0x88};
+        ipPrefix = IpPrefix.valueOf(IpAddress.Version.INET6, value, 129);
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 address.
+     */
+    @Test
+    public void testValueOfAddressIPv4() {
+        IpAddress ipAddress;
+        IpPrefix ipPrefix;
+
+        ipAddress = IpAddress.valueOf("1.2.3.4");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 24);
+        assertThat(ipPrefix.toString(), is("1.2.3.0/24"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 32);
+        assertThat(ipPrefix.toString(), is("1.2.3.4/32"));
+
+        ipAddress = IpAddress.valueOf("1.2.3.5");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 32);
+        assertThat(ipPrefix.toString(), is("1.2.3.5/32"));
+
+        ipAddress = IpAddress.valueOf("0.0.0.0");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 0);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 32);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/32"));
+
+        ipAddress = IpAddress.valueOf("255.255.255.255");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 0);
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 16);
+        assertThat(ipPrefix.toString(), is("255.255.0.0/16"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 32);
+        assertThat(ipPrefix.toString(), is("255.255.255.255/32"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv6 address.
+     */
+    @Test
+    public void testValueOfAddressIPv6() {
+        IpAddress ipAddress;
+        IpPrefix ipPrefix;
+
+        ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 120);
+        assertThat(ipPrefix.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8800/120"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 128);
+        assertThat(ipPrefix.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888/128"));
+
+        ipAddress = IpAddress.valueOf("::");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 0);
+        assertThat(ipPrefix.toString(), is("::/0"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 128);
+        assertThat(ipPrefix.toString(), is("::/128"));
+
+        ipAddress =
+            IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 0);
+        assertThat(ipPrefix.toString(), is("::/0"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 64);
+        assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64"));
+
+        ipPrefix = IpPrefix.valueOf(ipAddress, 128);
+        assertThat(ipPrefix.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"));
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a null IP address.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidValueOfNullAddress() {
+        IpAddress ipAddress;
+        IpPrefix ipPrefix;
+
+        ipAddress = null;
+        ipPrefix = IpPrefix.valueOf(ipAddress, 24);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 address and
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfAddressNegativePrefixLengthIPv4() {
+        IpAddress ipAddress;
+        IpPrefix ipPrefix;
+
+        ipAddress = IpAddress.valueOf("1.2.3.4");
+        ipPrefix = IpPrefix.valueOf(ipAddress, -1);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv6 address and
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfAddressNegativePrefixLengthIPv6() {
+        IpAddress ipAddress;
+        IpPrefix ipPrefix;
+
+        ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        ipPrefix = IpPrefix.valueOf(ipAddress, -1);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 address and
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfAddressTooLongPrefixLengthIPv4() {
+        IpAddress ipAddress;
+        IpPrefix ipPrefix;
+
+        ipAddress = IpAddress.valueOf("1.2.3.4");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 33);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv6 address and
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfAddressTooLongPrefixLengthIPv6() {
+        IpAddress ipAddress;
+        IpPrefix ipPrefix;
+
+        ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        ipPrefix = IpPrefix.valueOf(ipAddress, 129);
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 string.
+     */
+    @Test
+    public void testValueOfStringIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1.2.3.4/24");
+        assertThat(ipPrefix.toString(), is("1.2.3.0/24"));
+
+        ipPrefix = IpPrefix.valueOf("1.2.3.4/32");
+        assertThat(ipPrefix.toString(), is("1.2.3.4/32"));
+
+        ipPrefix = IpPrefix.valueOf("1.2.3.5/32");
+        assertThat(ipPrefix.toString(), is("1.2.3.5/32"));
+
+        ipPrefix = IpPrefix.valueOf("0.0.0.0/0");
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf("0.0.0.0/32");
+        assertThat(ipPrefix.toString(), is("0.0.0.0/32"));
+
+        ipPrefix = IpPrefix.valueOf("255.255.255.255/0");
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf("255.255.255.255/16");
+        assertThat(ipPrefix.toString(), is("255.255.0.0/16"));
+
+        ipPrefix = IpPrefix.valueOf("255.255.255.255/32");
+        assertThat(ipPrefix.toString(), is("255.255.255.255/32"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv6 string.
+     */
+    @Test
+    public void testValueOfStringIPv6() {
+        IpPrefix ipPrefix;
+
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/120");
+        assertThat(ipPrefix.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8800/120"));
+
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/128");
+        assertThat(ipPrefix.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888/128"));
+
+        ipPrefix = IpPrefix.valueOf("::/0");
+        assertThat(ipPrefix.toString(), is("::/0"));
+
+        ipPrefix = IpPrefix.valueOf("::/128");
+        assertThat(ipPrefix.toString(), is("::/128"));
+
+        ipPrefix =
+            IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0");
+        assertThat(ipPrefix.toString(), is("::/0"));
+
+        ipPrefix =
+            IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64");
+        assertThat(ipPrefix.toString(), is("ffff:ffff:ffff:ffff::/64"));
+
+        ipPrefix =
+            IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
+        assertThat(ipPrefix.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"));
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a null string.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidValueOfNullString() {
+        IpPrefix ipPrefix;
+        String fromString;
+
+        fromString = null;
+        ipPrefix = IpPrefix.valueOf(fromString);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for an empty string.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfEmptyString() {
+        IpPrefix ipPrefix;
+        String fromString;
+
+        fromString = "";
+        ipPrefix = IpPrefix.valueOf(fromString);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for an incorrect string.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfIncorrectString() {
+        IpPrefix ipPrefix;
+        String fromString;
+
+        fromString = "NoSuchIpPrefix";
+        ipPrefix = IpPrefix.valueOf(fromString);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 string and
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfStringNegativePrefixLengthIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1.2.3.4/-1");
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv6 string and
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfStringNegativePrefixLengthIPv6() {
+        IpPrefix ipPrefix;
+
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/-1");
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv4 string and
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfStringTooLongPrefixLengthIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1.2.3.4/33");
+    }
+
+    /**
+     * Tests invalid valueOf() converter for IPv6 string and
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfStringTooLongPrefixLengthIPv6() {
+        IpPrefix ipPrefix;
+
+        ipPrefix =
+            IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8888/129");
+    }
+
+    /**
+     * Tests IP prefix contains another IP prefix for IPv4.
+     */
+    @Test
+    public void testContainsIpPrefixIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1.2.0.0/24");
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32")));
+
+        ipPrefix = IpPrefix.valueOf("1.2.0.0/32");
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32")));
+
+        ipPrefix = IpPrefix.valueOf("0.0.0.0/0");
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32")));
+
+        ipPrefix = IpPrefix.valueOf("255.255.255.255/32");
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/24")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/32")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.4/32")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.2.0.0/16")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("1.3.0.0/24")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/16")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("0.0.0.0/0")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("255.255.255.255/32")));
+    }
+
+    /**
+     * Tests IP prefix contains another IP prefix for IPv6.
+     */
+    @Test
+    public void testContainsIpPrefixIPv6() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/120");
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/120")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/128")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::1/128")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/64")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4445::/120")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/64")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/0")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")));
+
+        ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/128");
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/120")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/128")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::1/128")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/64")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4445::/120")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/64")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/0")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")));
+
+        ipPrefix = IpPrefix.valueOf("::/0");
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/120")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/128")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::1/128")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/64")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4445::/120")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("::/64")));
+        assertTrue(ipPrefix.contains(IpPrefix.valueOf("::/0")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")));
+
+        ipPrefix =
+            IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/120")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/128")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::1/128")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4444::/64")));
+        assertFalse(ipPrefix.contains(
+                IpPrefix.valueOf("1111:2222:3333:4445::/120")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/64")));
+        assertFalse(ipPrefix.contains(IpPrefix.valueOf("::/0")));
+        assertTrue(ipPrefix.contains(
+                IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")));
+    }
+
+    /**
+     * Tests IP prefix contains IP address for IPv4.
+     */
+    @Test
+    public void testContainsIpAddressIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1.2.0.0/24");
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.0")));
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.4")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("1.3.0.0")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("0.0.0.0")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("255.255.255.255")));
+
+        ipPrefix = IpPrefix.valueOf("1.2.0.0/32");
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.0")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("1.2.0.4")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("1.3.0.0")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("0.0.0.0")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("255.255.255.255")));
+
+        ipPrefix = IpPrefix.valueOf("0.0.0.0/0");
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.0")));
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("1.2.0.4")));
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("1.3.0.0")));
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("0.0.0.0")));
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("255.255.255.255")));
+
+        ipPrefix = IpPrefix.valueOf("255.255.255.255/32");
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("1.2.0.0")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("1.2.0.4")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("1.3.0.0")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("0.0.0.0")));
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("255.255.255.255")));
+    }
+
+    /**
+     * Tests IP prefix contains IP address for IPv6.
+     */
+    @Test
+    public void testContainsIpAddressIPv6() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/120");
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::")));
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::1")));
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4445::")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("::")));
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+        ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444::/128");
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::")));
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::1")));
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4445::")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("::")));
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+        ipPrefix = IpPrefix.valueOf("::/0");
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::")));
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::1")));
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4445::")));
+        assertTrue(ipPrefix.contains(IpAddress.valueOf("::")));
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+        ipPrefix =
+            IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::")));
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4444::1")));
+        assertFalse(ipPrefix.contains(
+                IpAddress.valueOf("1111:2222:3333:4445::")));
+        assertFalse(ipPrefix.contains(IpAddress.valueOf("::")));
+        assertTrue(ipPrefix.contains(
+                IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+    }
+
+    /**
+     * Tests equality of {@link IpPrefix} for IPv4.
+     */
+    @Test
+    public void testEqualityIPv4() {
+        new EqualsTester()
+            .addEqualityGroup(IpPrefix.valueOf("1.2.0.0/24"),
+                              IpPrefix.valueOf("1.2.0.0/24"),
+                              IpPrefix.valueOf("1.2.0.4/24"))
+            .addEqualityGroup(IpPrefix.valueOf("1.2.0.0/16"),
+                              IpPrefix.valueOf("1.2.0.0/16"))
+            .addEqualityGroup(IpPrefix.valueOf("1.2.0.0/32"),
+                              IpPrefix.valueOf("1.2.0.0/32"))
+            .addEqualityGroup(IpPrefix.valueOf("1.3.0.0/24"),
+                              IpPrefix.valueOf("1.3.0.0/24"))
+            .addEqualityGroup(IpPrefix.valueOf("0.0.0.0/0"),
+                              IpPrefix.valueOf("0.0.0.0/0"))
+            .addEqualityGroup(IpPrefix.valueOf("255.255.255.255/32"),
+                              IpPrefix.valueOf("255.255.255.255/32"))
+            .testEquals();
+    }
+
+    /**
+     * Tests equality of {@link IpPrefix} for IPv6.
+     */
+    @Test
+    public void testEqualityIPv6() {
+        new EqualsTester()
+            .addEqualityGroup(
+                IpPrefix.valueOf("1111:2222:3333:4444::/120"),
+                IpPrefix.valueOf("1111:2222:3333:4444::1/120"),
+                IpPrefix.valueOf("1111:2222:3333:4444::/120"))
+            .addEqualityGroup(
+                IpPrefix.valueOf("1111:2222:3333:4444::/64"),
+                IpPrefix.valueOf("1111:2222:3333:4444::/64"))
+            .addEqualityGroup(
+                IpPrefix.valueOf("1111:2222:3333:4444::/128"),
+                IpPrefix.valueOf("1111:2222:3333:4444::/128"))
+            .addEqualityGroup(
+                IpPrefix.valueOf("1111:2222:3333:4445::/64"),
+                IpPrefix.valueOf("1111:2222:3333:4445::/64"))
+            .addEqualityGroup(
+                IpPrefix.valueOf("::/0"),
+                IpPrefix.valueOf("::/0"))
+            .addEqualityGroup(
+                IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"),
+                IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"))
+            .testEquals();
+    }
+
+    /**
+     * Tests object string representation for IPv4.
+     */
+    @Test
+    public void testToStringIPv4() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1.2.3.0/24");
+        assertThat(ipPrefix.toString(), is("1.2.3.0/24"));
+
+        ipPrefix = IpPrefix.valueOf("1.2.3.4/24");
+        assertThat(ipPrefix.toString(), is("1.2.3.0/24"));
+
+        ipPrefix = IpPrefix.valueOf("0.0.0.0/0");
+        assertThat(ipPrefix.toString(), is("0.0.0.0/0"));
+
+        ipPrefix = IpPrefix.valueOf("255.255.255.255/32");
+        assertThat(ipPrefix.toString(), is("255.255.255.255/32"));
+    }
+
+    /**
+     * Tests object string representation for IPv6.
+     */
+    @Test
+    public void testToStringIPv6() {
+        IpPrefix ipPrefix;
+
+        ipPrefix = IpPrefix.valueOf("1100::/8");
+        assertThat(ipPrefix.toString(), is("1100::/8"));
+
+        ipPrefix = IpPrefix.valueOf("1111:2222:3333:4444:5555:6666:7777:8885/8");
+        assertThat(ipPrefix.toString(), is("1100::/8"));
+
+        ipPrefix = IpPrefix.valueOf("::/0");
+        assertThat(ipPrefix.toString(), is("::/0"));
+
+        ipPrefix = IpPrefix.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
+        assertThat(ipPrefix.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"));
     }
 }