Fixed service port create failure

Also enhanced service network and port codec and added more unit tests

Change-Id: I523acc49dc1472520bd15a47f9a591cd95297ea0
diff --git a/src/main/java/org/opencord/cordvtn/api/net/SegmentId.java b/src/main/java/org/opencord/cordvtn/api/net/SegmentId.java
index 45568d1..3b9cf51 100644
--- a/src/main/java/org/opencord/cordvtn/api/net/SegmentId.java
+++ b/src/main/java/org/opencord/cordvtn/api/net/SegmentId.java
@@ -38,6 +38,10 @@
      * @return segmentation identifier
      */
     public static SegmentId of(Long id) {
+        if (id <= 0) {
+            throw new IllegalArgumentException("Value 0 or less then 0 is not" +
+                    "allowed for segment ID");
+        }
         return new SegmentId(id);
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java b/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java
index 331bffe..02e27b7 100644
--- a/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java
+++ b/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java
@@ -33,6 +33,7 @@
 import java.util.Map;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
 import static java.lang.Boolean.FALSE;
 import static java.lang.Boolean.TRUE;
 import static org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType.BIDIRECTIONAL;
@@ -87,134 +88,113 @@
         return result;
     }
 
+    // TODO allow removing existing value when explicit null received
     @Override
     public ServiceNetwork decode(ObjectNode json, CodecContext context) {
-        validateJson(json);
+        checkArgument(json != null && json.isObject(), ERR_JSON);
+        checkArgument(!json.path(ID).isMissingNode() && !json.path(ID).isNull(), ERR_ID);
+        checkArgument(!Strings.isNullOrEmpty(json.path(ID).asText()), ERR_ID);
+
         ServiceNetwork.Builder snetBuilder = DefaultServiceNetwork.builder()
                 .id(NetworkId.of(json.get(ID).asText()));
 
-        // TODO remove existing values when explicit null received
-        if (json.get(NAME) != null && !json.get(NAME).isNull()) {
-            snetBuilder.name(json.get(NAME).asText());
-        }
-        if (json.get(TYPE) != null && !json.get(TYPE).isNull()) {
-            snetBuilder.type(valueOf(json.get(TYPE).asText().toUpperCase()));
-        }
-        if (json.get(SEGMENT_ID) != null && !json.get(SEGMENT_ID).isNull()) {
-            snetBuilder.segmentId(SegmentId.of(json.get(SEGMENT_ID).asLong()));
-        }
-        if (json.get(SUBNET) != null && !json.get(SUBNET).isNull()) {
-            snetBuilder.subnet(IpPrefix.valueOf(json.get(SUBNET).asText()));
-        }
-        if (json.get(SERVICE_IP) != null && !json.get(SERVICE_IP).isNull()) {
-            snetBuilder.serviceIp(IpAddress.valueOf(json.get(SERVICE_IP).asText()));
-        }
-        if (json.get(PROVIDERS) != null) {
-            if (json.get(PROVIDERS).isNull()) {
-                snetBuilder.providers(ImmutableMap.of());
-            } else {
-                Map<NetworkId, DependencyType> providers = Maps.newHashMap();
-                json.get(PROVIDERS).forEach(provider -> {
-                    DependencyType type = provider.get(DEP_TYPE).asBoolean() ?
-                            BIDIRECTIONAL : UNIDIRECTIONAL;
-                    providers.put(NetworkId.of(provider.get(ID).asText()), type);
-                });
-                snetBuilder.providers(providers);
-            }
-        }
-        if (json.get(PROVIDER_NETWORKS) != null) {
-            if (json.get(PROVIDER_NETWORKS).isNull()) {
-                snetBuilder.providers(ImmutableMap.of());
-            } else {
-                Map<NetworkId, DependencyType> providers = Maps.newHashMap();
-                json.get(PROVIDER_NETWORKS).forEach(provider -> {
-                    DependencyType type = provider.get(DEP_TYPE).asBoolean() ?
-                            BIDIRECTIONAL : UNIDIRECTIONAL;
-                    providers.put(NetworkId.of(provider.get(ID).asText()), type);
-                });
-                snetBuilder.providers(providers);
-            }
-        }
-        return snetBuilder.build();
-    }
-
-    private void validateJson(ObjectNode json) {
-        checkArgument(json != null && json.isObject(), ERR_JSON);
-        checkArgument(json.get(ID) != null && !json.get(ID).isNull(), ERR_ID);
-        checkArgument(!Strings.isNullOrEmpty(json.get(ID).asText()), ERR_ID);
-
-        // allow explicit null for removing the existing value
-        if (json.get(NAME) != null && !json.get(NAME).isNull()) {
-            if (Strings.isNullOrEmpty(json.get(NAME).asText())) {
+        if (!json.path(NAME).isMissingNode()) {
+            if (json.path(NAME).isNull() || isNullOrEmpty(json.path(NAME).asText())) {
                 final String error = "Null or empty ServiceNetwork name received";
                 throw new IllegalArgumentException(error);
+            } else {
+                snetBuilder.name(json.get(NAME).asText());
+            }
+        }
+
+        if (!json.path(TYPE).isMissingNode()) {
+            try {
+                snetBuilder.type(valueOf(json.get(TYPE).asText().toUpperCase()));
+            } catch (IllegalArgumentException | NullPointerException e) {
+                final String error = "Invalid ServiceNetwork type received";
+                throw new IllegalArgumentException(error);
             }
         }
 
-        if (json.get(TYPE) != null && !json.get(TYPE).isNull()) {
+        if (!json.path(SEGMENT_ID).isMissingNode()) {
             try {
-                valueOf(json.get(TYPE).asText().toUpperCase());
-            } catch (IllegalArgumentException e) {
-                final String error = "Invalid ServiceNetwork type received: ";
-                throw new IllegalArgumentException(error + json.get(TYPE).asText());
+                snetBuilder.segmentId(SegmentId.of(json.path(SEGMENT_ID).asLong()));
+            } catch (IllegalArgumentException | NullPointerException e) {
+                final String error = "Invalid ServiecNetwork segment ID received";
+                throw new IllegalArgumentException(error);
             }
         }
 
-        if (json.get(SEGMENT_ID) != null && !json.get(SEGMENT_ID).isNull()) {
-            if (json.get(SEGMENT_ID).asLong() == 0) {
-                final String error = "Invalid ServiecNetwork segment ID received: ";
-                throw new IllegalArgumentException(error + json.get(SEGMENT_ID).asText());
-            }
-        }
-
-        if (json.get(SUBNET) != null && !json.get(SUBNET).isNull()) {
+        if (!json.path(SUBNET).isMissingNode()) {
             try {
-                IpPrefix.valueOf(json.get(SUBNET).asText());
-            } catch (IllegalArgumentException e) {
-                final String error = "Invalid ServiceNetwork subnet received: ";
-                throw new IllegalArgumentException(error + json.get(SUBNET).asText());
+                snetBuilder.subnet(IpPrefix.valueOf(json.path(SUBNET).asText()));
+            } catch (IllegalArgumentException | NullPointerException e) {
+                final String error = "Invalid ServiceNetwork subnet received";
+                throw new IllegalArgumentException(error);
             }
         }
 
-        if (json.get(SERVICE_IP) != null && !json.get(SERVICE_IP).isNull()) {
+        if (!json.path(SERVICE_IP).isMissingNode()) {
             try {
-                IpAddress.valueOf(json.get(SERVICE_IP).asText());
-            } catch (IllegalArgumentException e) {
-                final String error = "Invalid ServiceNetwork service IP address received: ";
-                throw new IllegalArgumentException(error + json.get(SERVICE_IP).asText());
+                snetBuilder.serviceIp(IpAddress.valueOf(json.path(SERVICE_IP).asText()));
+            } catch (IllegalArgumentException | NullPointerException e) {
+                final String error = "Invalid ServiceNetwork service IP address received";
+                throw new IllegalArgumentException(error);
             }
         }
 
-        if (json.get(PROVIDERS) != null && !json.get(PROVIDERS).isNull()) {
-            json.get(PROVIDERS).forEach(provider -> {
-                if (provider.get(ID) == null || provider.get(ID).isNull() ||
-                        Strings.isNullOrEmpty(provider.get(ID).asText())) {
-                    final String error = "Null or empty provider network ID received";
-                    throw new IllegalArgumentException(error);
+        if (!json.path(PROVIDERS).isMissingNode() && json.path(PROVIDERS).isNull()) {
+            snetBuilder.providers(ImmutableMap.of());
+        } else if (!json.path(PROVIDERS).isMissingNode()) {
+            Map<NetworkId, DependencyType> providers = Maps.newHashMap();
+            json.path(PROVIDERS).forEach(provider -> {
+                if (provider.path(ID).isMissingNode() ||
+                        provider.path(ID).isNull() ||
+                        Strings.isNullOrEmpty(provider.path(ID).asText()) ||
+                        provider.path(DEP_TYPE).isMissingNode() ||
+                        provider.path(DEP_TYPE).isNull()) {
+                    final String error = "Invalid provider received: ";
+                    throw new IllegalArgumentException(error + provider.asText());
                 }
 
-                if (provider.get(DEP_TYPE) == null || provider.get(DEP_TYPE).isNull()
-                        || !provider.get(DEP_TYPE).isBoolean()) {
-                    final String error = "Non-boolean bidirectional received";
-                    throw new IllegalArgumentException(error);
+                try {
+                    DependencyType type = provider.path(DEP_TYPE).asBoolean() ?
+                            BIDIRECTIONAL : UNIDIRECTIONAL;
+                    providers.put(NetworkId.of(provider.path(ID).asText()), type);
+                } catch (IllegalArgumentException e) {
+                    final String error = "Invalid provider received: ";
+                    throw new IllegalArgumentException(error + provider.asText());
                 }
             });
+            snetBuilder.providers(providers);
         }
 
-        if (json.get(PROVIDER_NETWORKS) != null && !json.get(PROVIDER_NETWORKS).isNull()) {
-            json.get(PROVIDER_NETWORKS).forEach(provider -> {
-                if (provider.get(ID) == null || provider.get(ID).isNull() ||
-                        Strings.isNullOrEmpty(provider.get(ID).asText())) {
-                    final String error = "Null or empty provider network ID received";
-                    throw new IllegalArgumentException(error);
+        if (!json.path(PROVIDER_NETWORKS).isMissingNode() &&
+                json.path(PROVIDER_NETWORKS).isNull()) {
+            snetBuilder.providers(ImmutableMap.of());
+        } else if (!json.path(PROVIDER_NETWORKS).isMissingNode()) {
+            Map<NetworkId, DependencyType> providers = Maps.newHashMap();
+            json.path(PROVIDER_NETWORKS).forEach(provider -> {
+                if (provider.path(ID).isMissingNode() ||
+                        provider.path(ID).isNull() ||
+                        Strings.isNullOrEmpty(provider.path(ID).asText()) ||
+                        provider.path(DEP_TYPE).isMissingNode() ||
+                        provider.path(DEP_TYPE).isNull()) {
+                    final String error = "Invalid provider received: ";
+                    throw new IllegalArgumentException(error + provider.asText());
                 }
 
-                if (provider.get(DEP_TYPE) == null || provider.get(DEP_TYPE).isNull()
-                        || !provider.get(DEP_TYPE).isBoolean()) {
-                    final String error = "Non-boolean bidirectional received";
-                    throw new IllegalArgumentException(error);
+                try {
+                    DependencyType type = provider.path(DEP_TYPE).asBoolean() ?
+                            BIDIRECTIONAL : UNIDIRECTIONAL;
+                    providers.put(NetworkId.of(provider.path(ID).asText()), type);
+                } catch (IllegalArgumentException e) {
+                    final String error = "Invalid provider received: ";
+                    throw new IllegalArgumentException(error + provider.asText());
                 }
             });
+            snetBuilder.providers(providers);
         }
+        return snetBuilder.build();
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java b/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java
index 5292604..01a104a 100644
--- a/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java
+++ b/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java
@@ -17,7 +17,6 @@
 
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import org.onlab.packet.IpAddress;
@@ -26,6 +25,7 @@
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.opencord.cordvtn.api.net.AddressPair;
+import org.opencord.cordvtn.api.net.NetworkId;
 import org.opencord.cordvtn.api.net.PortId;
 import org.opencord.cordvtn.api.net.ServicePort;
 import org.opencord.cordvtn.impl.DefaultServicePort;
@@ -33,6 +33,7 @@
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
 
 /**
  * Service port JSON codec.
@@ -52,13 +53,12 @@
 
     @Override
     public ObjectNode encode(ServicePort sport, CodecContext context) {
-        ObjectNode result = context.mapper().createObjectNode()
-                .put(ID, sport.id().id());
+        ObjectNode result = context.mapper().createObjectNode().put(ID, sport.id().id());
 
         if (sport.networkId() != null) {
             result.put(NETWORK_ID, sport.networkId().id());
         }
-        if (!Strings.isNullOrEmpty(sport.name())) {
+        if (!isNullOrEmpty(sport.name())) {
             result.put(NAME, sport.name());
         }
         if (sport.vlanId() != null) {
@@ -82,103 +82,86 @@
         return result;
     }
 
+    // TODO allow removing existing value when explicit null received
     @Override
     public ServicePort decode(ObjectNode json, CodecContext context) {
-        validateJson(json);
-        ServicePort.Builder sportBuilder = DefaultServicePort.builder()
-                .id(PortId.of(json.get(ID).asText()));
-
-        // TODO allow removing existing value when explicit null received
-        if (json.get(NAME) != null && !json.get(NAME).isNull()) {
-            sportBuilder.name(json.get(NAME).asText());
-        }
-        if (json.get(MAC_ADDRESS) != null && !json.get(MAC_ADDRESS).isNull()) {
-            sportBuilder.mac(MacAddress.valueOf(json.get(MAC_ADDRESS).asText()));
-        }
-        if (json.get(IP_ADDRESS) != null && !json.get(IP_ADDRESS).isNull()) {
-            sportBuilder.ip(IpAddress.valueOf(json.get(IP_ADDRESS).asText()));
-        }
-        if (json.get(VLAN_ID) != null && !json.get(VLAN_ID).isNull()) {
-            sportBuilder.vlanId(VlanId.vlanId(json.get(VLAN_ID).asText()));
-        }
-        if (json.get(FLOATING_ADDRESS_PAIRS).isNull()) {
-            sportBuilder.addressPairs(ImmutableSet.of());
-        } else if (json.get(FLOATING_ADDRESS_PAIRS) != null) {
-            Set<AddressPair> addressPairs = Sets.newHashSet();
-            json.get(FLOATING_ADDRESS_PAIRS).forEach(pair -> {
-                AddressPair addrPair = AddressPair.of(
-                        IpAddress.valueOf(pair.get(IP_ADDRESS).asText()),
-                        MacAddress.valueOf(pair.get(MAC_ADDRESS).asText()));
-                addressPairs.add(addrPair);
-            });
-            sportBuilder.addressPairs(addressPairs);
-        }
-        return sportBuilder.build();
-    }
-
-    private void validateJson(ObjectNode json) {
         checkArgument(json != null && json.isObject(), ERR_JSON);
-        checkArgument(json.get(ID) != null && !json.get(ID).isNull(), ERR_ID);
+        checkArgument(!json.path(ID).isMissingNode() && !json.path(ID).isNull(), ERR_ID);
 
-        // allow explicit null for removing the existing value
-        if (json.get(NAME) != null && !json.get(NAME).isNull()) {
-            if (Strings.isNullOrEmpty(json.get(NAME).asText())) {
-                final String error = "Null or empty ServiceNetwork name received";
+        ServicePort.Builder sportBuilder =
+                DefaultServicePort.builder().id(PortId.of(json.get(ID).asText()));
+
+        if (!json.path(NETWORK_ID).isMissingNode()) {
+            if  (json.path(NETWORK_ID).isNull() ||
+                    isNullOrEmpty(json.path(NETWORK_ID).asText())) {
+                final String error = "Null or empty ServicePort network ID received";
+                throw new IllegalArgumentException(error);
+            } else {
+                sportBuilder.networkId(NetworkId.of(json.get(NETWORK_ID).asText()));
+            }
+        }
+
+        if (!json.path(NAME).isMissingNode()) {
+           if (json.path(NAME).isNull() || isNullOrEmpty(json.path(NAME).asText())) {
+               final String error = "Null or empty ServicePort name received";
+               throw new IllegalArgumentException(error);
+           } else {
+               sportBuilder.name(json.get(NAME).asText());
+           }
+        }
+
+        if (!json.path(MAC_ADDRESS).isMissingNode()) {
+            try {
+                sportBuilder.mac(MacAddress.valueOf(json.path(MAC_ADDRESS).asText()));
+            } catch (IllegalArgumentException | NullPointerException e) {
+                final String error = "Invalid ServicePort MAC address received";
                 throw new IllegalArgumentException(error);
             }
         }
 
-        if (json.get(MAC_ADDRESS) != null && !json.get(MAC_ADDRESS).isNull()) {
+        if (!json.path(IP_ADDRESS).isMissingNode()) {
             try {
-                MacAddress.valueOf(json.get(MAC_ADDRESS).asText());
-            } catch (IllegalArgumentException e) {
-                final String error = "Invalid ServicePort MAC address received: ";
-                throw new IllegalArgumentException(error + json.get(MAC_ADDRESS).asText());
+                sportBuilder.ip(IpAddress.valueOf(json.get(IP_ADDRESS).asText()));
+            } catch (IllegalArgumentException | NullPointerException e) {
+                final String error = "Invalid ServicePort IP address received";
+                throw new IllegalArgumentException(error);
             }
         }
 
-        if (json.get(IP_ADDRESS) != null && !json.get(IP_ADDRESS).isNull()) {
+        if (!json.path(VLAN_ID).isMissingNode()) {
             try {
-                IpAddress.valueOf(json.get(IP_ADDRESS).asText());
-            } catch (IllegalArgumentException e) {
-                final String error = "Invalid ServicePort IP address received: ";
-                throw new IllegalArgumentException(error + json.get(IP_ADDRESS).asText());
+                sportBuilder.vlanId(VlanId.vlanId(json.get(VLAN_ID).asText()));
+            } catch (IllegalArgumentException | NullPointerException e) {
+                final String error = "Invalid VLAN ID is received";
+                throw new IllegalArgumentException(error);
             }
         }
 
-        if (json.get(VLAN_ID) != null && !json.get(VLAN_ID).isNull()) {
-            try {
-                VlanId.vlanId(json.get(VLAN_ID).asText());
-            } catch (IllegalArgumentException e) {
-                final String error = "Invalid VLAN ID is received: ";
-                throw new IllegalArgumentException(error + json.get(VLAN_ID).asText());
-            }
-        }
-
-        if (json.get(FLOATING_ADDRESS_PAIRS) != null &&
-                !json.get(FLOATING_ADDRESS_PAIRS).isNull()) {
-            json.get(FLOATING_ADDRESS_PAIRS).forEach(pair -> {
-                if (pair.get(IP_ADDRESS) == null ||
-                        pair.get(IP_ADDRESS).isNull() ||
-                        pair.get(MAC_ADDRESS) == null ||
-                        pair.get(MAC_ADDRESS).isNull()) {
-                    final String error = "Invalid floating address pair received";
-                    throw new IllegalArgumentException(error);
+        if (!json.path(FLOATING_ADDRESS_PAIRS).isMissingNode() &&
+                json.path(FLOATING_ADDRESS_PAIRS).isNull()) {
+            sportBuilder.addressPairs(ImmutableSet.of());
+        } else if (!json.path(FLOATING_ADDRESS_PAIRS).isMissingNode()) {
+            Set<AddressPair> addressPairs = Sets.newHashSet();
+            json.path(FLOATING_ADDRESS_PAIRS).forEach(pair -> {
+                if (pair.path(IP_ADDRESS).isMissingNode() ||
+                        pair.path(IP_ADDRESS).isNull() ||
+                        pair.path(MAC_ADDRESS).isMissingNode() ||
+                        pair.path(MAC_ADDRESS).isNull()) {
+                    final String error = "Invalid floating address pair received: ";
+                    throw new IllegalArgumentException(error + pair.asText());
                 }
                 try {
-                    IpAddress.valueOf(pair.get(IP_ADDRESS).asText());
+                    AddressPair addrPair = AddressPair.of(
+                            IpAddress.valueOf(pair.get(IP_ADDRESS).asText()),
+                            MacAddress.valueOf(pair.get(MAC_ADDRESS).asText()));
+                    addressPairs.add(addrPair);
                 } catch (IllegalArgumentException e) {
-                    final String error = "Invalid floating address pair IP: ";
-                    throw new IllegalArgumentException(error + pair.get(IP_ADDRESS).asText());
-                }
-
-                try {
-                    MacAddress.valueOf(pair.get(MAC_ADDRESS).asText());
-                } catch (IllegalArgumentException e) {
-                    final String error = "Invalid floating address pair MAC: ";
-                    throw new IllegalArgumentException(error + pair.get(MAC_ADDRESS).asText());
+                    final String error = "Invalid floating address pair received: ";
+                    throw new IllegalArgumentException(error + pair.asText());
                 }
             });
+            sportBuilder.addressPairs(addressPairs);
         }
+        return sportBuilder.build();
     }
 }
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java
index 35d9a1b..95701bd 100644
--- a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java
+++ b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkCodecTest.java
@@ -16,15 +16,16 @@
 package org.opencord.cordvtn.codec;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.NullNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.google.common.collect.ImmutableMap;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onosproject.codec.JsonCodec;
 import org.opencord.cordvtn.api.net.NetworkId;
+import org.opencord.cordvtn.api.net.SegmentId;
 import org.opencord.cordvtn.api.net.ServiceNetwork;
 import org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType;
 import org.opencord.cordvtn.impl.DefaultServiceNetwork;
@@ -38,7 +39,6 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType.BIDIRECTIONAL;
-import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.MANAGEMENT_LOCAL;
 import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.PRIVATE;
 import static org.opencord.cordvtn.codec.ServiceNetworkJsonMatcher.matchesServiceNetwork;
 
@@ -51,30 +51,35 @@
     private static final String NAME = "name";
     private static final String TYPE = "type";
     private static final String PROVIDERS = "providers";
+    private static final String SEGMENT_ID = "segment_id";
+    private static final String SUBNET = "subnet";
+    private static final String SERVICE_IP = "service_ip";
+    private static final String DEP_TYPE = "bidirectional";
 
-    private final Map<NetworkId, DependencyType> providerA =
+    private static final String NAME_1 = "network_1";
+    private static final NetworkId ID_1 = NetworkId.of("network_1");
+    private static final NetworkId PROVIDER_ID_1 = NetworkId.of("provider_1");
+    private static final SegmentId SEGMENT_ID_1 = SegmentId.of(1L);
+    private static final IpPrefix SUBNET_1 = IpPrefix.valueOf("192.168.0.0/24");
+    private static final IpAddress SERVICE_IP_1 = IpAddress.valueOf("192.168.0.1");
+
+    private static final Map<NetworkId, DependencyType> PROVIDER_1 =
             new HashMap<NetworkId, DependencyType>() {
                 {
-                    put(NetworkId.of("A"), BIDIRECTIONAL);
+                    put(NetworkId.of("provider_1"), BIDIRECTIONAL);
                 }
             };
 
-    private final ServiceNetwork networkA = DefaultServiceNetwork.builder()
-            .id(NetworkId.of("A"))
-            .name("A")
-            .type(MANAGEMENT_LOCAL)
-            .build();
-
-    private final ServiceNetwork networkB = DefaultServiceNetwork.builder()
-            .id(NetworkId.of("B"))
-            .name("B")
+    private static final ServiceNetwork NETWORK_1 = DefaultServiceNetwork.builder()
+            .id(ID_1)
+            .name(NAME_1)
             .type(PRIVATE)
-            .providers(providerA)
+            .segmentId(SEGMENT_ID_1)
+            .subnet(SUBNET_1)
+            .serviceIp(SERVICE_IP_1)
+            .providers(PROVIDER_1)
             .build();
 
-    @Rule
-    public ExpectedException exception = ExpectedException.none();
-
     private JsonCodec<ServiceNetwork> codec;
     private MockCodecContext context;
 
@@ -89,89 +94,332 @@
         assertThat(codec, notNullValue());
     }
 
+    /**
+     * Checks if encoding service network works properly.
+     */
     @Test
     public void testServiceNetworkEncode() {
-        ObjectNode networkJson = codec.encode(networkA, context);
+        ObjectNode networkJson = codec.encode(NETWORK_1, context);
         assertThat(networkJson, notNullValue());
-        assertThat(networkJson, matchesServiceNetwork(networkA));
-
-        networkJson = codec.encode(networkB, context);
-        assertThat(networkJson, notNullValue());
-        assertThat(networkJson, matchesServiceNetwork(networkB));
+        assertThat(networkJson, matchesServiceNetwork(NETWORK_1));
     }
 
+    /**
+     * Checks if decoding service network works properly.
+     */
     @Test
     public void testServiceNetworkDecode() throws IOException {
-        ServiceNetwork snet = getServiceNetwork("service-network.json");
-        assertThat(snet.id(), is(NetworkId.of("A")));
-        assertThat(snet.name(), is("A"));
-        assertThat(snet.type(), is(MANAGEMENT_LOCAL));
-        assertThat(snet.providers(), is(ImmutableMap.of()));
-
-        snet = getServiceNetwork("service-network-with-provider.json");
-        assertThat(snet.id(), is(NetworkId.of("B")));
-        assertThat(snet.name(), is("B"));
-        assertThat(snet.type(), is(PRIVATE));
-        assertThat(snet.providers(), is(providerA));
+        ServiceNetwork sNet = getServiceNetwork("service-network.json");
+        assertThat(sNet.id(), is(ID_1));
+        assertThat(sNet.name(), is(NAME_1));
+        assertThat(sNet.type(), is(PRIVATE));
+        assertThat(sNet.providers(), is(PROVIDER_1));
     }
 
-    @Test
-    public void testServiceNetworkDecodeMissingId() throws IllegalArgumentException {
+    /**
+     * Checks if decoding service network without ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeMissingId() {
         final JsonNode jsonMissingId = context.mapper().createObjectNode()
-                .put(NAME, "A")
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .put(PROVIDERS, "");
-        exception.expect(IllegalArgumentException.class);
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
         codec.decode((ObjectNode) jsonMissingId, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeEmptyId() throws IllegalArgumentException {
+    /**
+     * Checks if decoding service network with empty ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeEmptyId() {
         final JsonNode jsonEmptyId = context.mapper().createObjectNode()
                 .put(ID, "")
-                .put(NAME, "A")
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .put(PROVIDERS, "");
-        exception.expect(IllegalArgumentException.class);
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
         codec.decode((ObjectNode) jsonEmptyId, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeNullId() throws NullPointerException {
+    /**
+     * Checks if decoding service network with null ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullId() {
         final JsonNode jsonNullId = context.mapper().createObjectNode()
-                .put(NAME, "A")
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .put(PROVIDERS, "")
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
                 .set(ID, NullNode.getInstance());
-        exception.expect(IllegalArgumentException.class);
         codec.decode((ObjectNode) jsonNullId, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeWithMissingProviderId() throws NullPointerException {
-        final JsonNode jsonMissingProviderId = context.mapper().createObjectNode()
-                .put(TYPE, "B");
-        final JsonNode jsonWithProvider = context.mapper().createObjectNode()
-                .put(ID, "A")
-                .put(NAME, "A")
-                .put(TYPE, PRIVATE.name())
-                .set(PROVIDERS, jsonMissingProviderId);
-        exception.expect(IllegalArgumentException.class);
-        codec.decode((ObjectNode) jsonWithProvider, context);
+    /**
+     * Checks if decoding service network with invalid type fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidType() {
+        final JsonNode jsonInvalidType = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, "type")
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidType, context);
     }
 
-    @Test
-    public void testServiceNetworkDecodeWithWrongProviderType() throws NullPointerException {
-        final JsonNode jsonWrongProviderType = context.mapper().createObjectNode()
-                .put(ID, "B")
-                .put(TYPE, "none");
-        final JsonNode jsonWithProvider = context.mapper().createObjectNode()
-                .put(ID, "A")
-                .put(NAME, "A")
+    /**
+     * Checks if decoding service network with null type fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullType() {
+        final JsonNode jsonNullType = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(TYPE, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonNullType, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid segment ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidSegmentId() {
+        final JsonNode jsonInvalidSeg = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
                 .put(TYPE, PRIVATE.name())
-                .set(PROVIDERS, jsonWrongProviderType);
-        exception.expect(IllegalArgumentException.class);
-        codec.decode((ObjectNode) jsonWithProvider, context);
+                .put(SEGMENT_ID, "segmentId")
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidSeg, context);
+    }
+
+    /**
+     * Checks if decoding service network with 0 segment ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeZeroSegmentId() {
+        final JsonNode jsonInvalidSeg = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, 0)
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidSeg, context);
+    }
+
+    /**
+     * Checks if decoding service network with 0 segment ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullSegmentId() {
+        final JsonNode jsonInvalidSeg = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(SEGMENT_ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidSeg, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid subnet fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidSubnet() {
+        final JsonNode jsonInvalidSubnet = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, "")
+                .put(SERVICE_IP, SERVICE_IP_1.toString());
+        codec.decode((ObjectNode) jsonInvalidSubnet, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid subnet fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullSubnet() {
+        final JsonNode jsonInvalidSubnet = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(SUBNET, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidSubnet, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid service IP fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeInvalidServiceIp() {
+        final JsonNode jsonInvalidServiceIp = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, "");
+        codec.decode((ObjectNode) jsonInvalidServiceIp, context);
+    }
+
+    /**
+     * Checks if decoding service network with null service IP fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkDecodeNullServiceIp() {
+        final JsonNode jsonInvalidServiceIp = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .set(SERVICE_IP, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidServiceIp, context);
+    }
+
+    /**
+     * Checks if decoding service network with null and empty providers allowed.
+     */
+    @Test
+    public void testServiceNetworkDecodeNullAndEmptyProviders() {
+        JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+
+        jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, context.mapper().createArrayNode());
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with non-array providers value fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkNonArrayProviders() {
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(ID, PROVIDER_ID_1.id())
+                .put(DEP_TYPE, true);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkMissingProviderId() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(DEP_TYPE, true);
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkMissingProviderType() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(ID, PROVIDER_ID_1.id());
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkNullProviderId() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(DEP_TYPE, true)
+                .set(ID, NullNode.getInstance());
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
+    }
+
+    /**
+     * Checks if decoding service network with invalid provider fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServiceNetworkNullProviderType() {
+        final ArrayNode jsonProviders = context.mapper().createArrayNode();
+        final JsonNode jsonProvider = context.mapper().createObjectNode()
+                .put(ID, PROVIDER_ID_1.id())
+                .set(DEP_TYPE, NullNode.getInstance());
+        jsonProviders.add(jsonProvider);
+        final JsonNode jsonInvalidProvider = context.mapper().createObjectNode()
+                .put(ID, ID_1.id())
+                .put(NAME, NAME_1)
+                .put(TYPE, PRIVATE.name())
+                .put(SEGMENT_ID, SEGMENT_ID_1.id())
+                .put(SUBNET, SUBNET_1.toString())
+                .put(SERVICE_IP, SERVICE_IP_1.toString())
+                .set(PROVIDERS, jsonProvider);
+        codec.decode((ObjectNode) jsonInvalidProvider, context);
     }
 
     private ServiceNetwork getServiceNetwork(String resource) throws IOException {
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java
index da321e3..f5a484b 100644
--- a/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java
+++ b/src/test/java/org/opencord/cordvtn/codec/ServiceNetworkJsonMatcher.java
@@ -66,31 +66,40 @@
         }
 
         JsonNode jsonProviders = jsonNet.get("providers");
-        if (jsonProviders == null || jsonProviders == NullNode.getInstance()) {
-            description.appendText("provider networks were empty");
-            return false;
-        }
-
-        if (jsonProviders.size() != network.providers().size()) {
-            return false;
-        }
-
-        for (JsonNode provider : jsonProviders) {
-            NetworkId id = NetworkId.of(provider.get("id").asText());
-            boolean bidirectional = provider.get("bidirectional").asBoolean();
-
-            if (!network.providers().containsKey(id)) {
-                final String msg = String.format("provider id:%s couldn't find", id);
-                description.appendText(msg);
+        if (network.providers().isEmpty()) {
+            if (jsonProviders != null &&
+                    jsonProviders != NullNode.getInstance() &&
+                    jsonProviders.size() != 0) {
+                description.appendText("provider networks did not match");
                 return false;
             }
-
-            if (network.providers().get(id).equals(BIDIRECTIONAL) != bidirectional) {
-                final String msg = String.format(
-                        "mismatch provider id:%s, bidirectional: %s",
-                        id, bidirectional);
-                description.appendText(msg);
+        } else {
+            if (jsonProviders == null ||
+                    jsonProviders == NullNode.getInstance() ||
+                    jsonProviders.size() == 0) {
+                description.appendText("provider networks did not match");
                 return false;
+            } else if (jsonProviders.size() != network.providers().size()) {
+                description.appendText("provider networks did not match");
+                return false;
+            } else {
+                for (JsonNode provider : jsonProviders) {
+                    NetworkId id = NetworkId.of(provider.get("id").asText());
+                    boolean bidirectional = provider.get("bidirectional").asBoolean();
+
+                    if (!network.providers().containsKey(id)) {
+                        final String msg = String.format("provider id:%s couldn't find", id);
+                        description.appendText(msg);
+                        return false;
+                    }
+                    if (network.providers().get(id).equals(BIDIRECTIONAL) != bidirectional) {
+                        final String msg = String.format(
+                                "mismatch provider id:%s, bidirectional: %s",
+                                id, bidirectional);
+                        description.appendText(msg);
+                        return false;
+                    }
+                }
             }
         }
         return true;
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServicePortCodecTest.java b/src/test/java/org/opencord/cordvtn/codec/ServicePortCodecTest.java
new file mode 100644
index 0000000..02d0103
--- /dev/null
+++ b/src/test/java/org/opencord/cordvtn/codec/ServicePortCodecTest.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opencord.cordvtn.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.JsonCodec;
+import org.opencord.cordvtn.api.net.AddressPair;
+import org.opencord.cordvtn.api.net.NetworkId;
+import org.opencord.cordvtn.api.net.PortId;
+import org.opencord.cordvtn.api.net.ServicePort;
+import org.opencord.cordvtn.impl.DefaultServicePort;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.opencord.cordvtn.codec.ServicePortJsonMatcher.matchesServicePort;
+
+/**
+ * Unit tests for ServiceNetwork codec.
+ */
+public final class ServicePortCodecTest {
+
+    private static final String SERVICE_PORT = "servicePort";
+    private static final String ID = "id";
+    private static final String NETWORK_ID = "network_id";
+    private static final String NAME = "name";
+    private static final String IP_ADDRESS = "ip_address";
+    private static final String MAC_ADDRESS = "mac_address";
+    private static final String FLOATING_ADDRESS_PAIRS = "floating_address_pairs";
+    private static final String VLAN_ID = "vlan_id";
+
+    private static final PortId PORT_ID_1 = PortId.of("port-1");
+    private static final NetworkId NETWORK_ID_1 = NetworkId.of("network-1");
+    private static final String PORT_NAME_1 = "tap1";
+    private static final MacAddress MAC_ADDRESS_1 = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final IpAddress IP_ADDRESS_1 = IpAddress.valueOf("10.0.0.1");
+    private static final VlanId VLAN_ID_1 = VlanId.vlanId("222");
+
+    private static final AddressPair ADDRESS_PAIR_1 = AddressPair.of(
+            IpAddress.valueOf("192.168.0.1"),
+            MacAddress.valueOf("02:42:0a:06:01:01"));
+    private static final AddressPair ADDRESS_PAIR_2 = AddressPair.of(
+            IpAddress.valueOf("192.168.0.2"),
+            MacAddress.valueOf("02:42:0a:06:01:02"));
+
+    private static final Set<AddressPair> ADDRESS_PAIRS_1 =
+            new HashSet<AddressPair>() {
+                {
+                    add(ADDRESS_PAIR_1);
+                    add(ADDRESS_PAIR_2);
+                }
+            };
+
+    private static final ServicePort PORT_1 = DefaultServicePort.builder()
+            .id(PORT_ID_1)
+            .networkId(NETWORK_ID_1)
+            .name(PORT_NAME_1)
+            .ip(IP_ADDRESS_1)
+            .mac(MAC_ADDRESS_1)
+            .addressPairs(ADDRESS_PAIRS_1)
+            .vlanId(VLAN_ID_1)
+            .build();
+
+    private JsonCodec<ServicePort> codec;
+    private MockCodecContext context;
+
+    /**
+     * Creates a context and gets the servicePort codec for each test.
+     */
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        codec = context.codec(ServicePort.class);
+        assertThat(codec, notNullValue());
+    }
+
+    /**
+     * Checks if encoding service port works properly.
+     */
+    @Test
+    public void testServicePortEncode() {
+        ObjectNode jsonPort = codec.encode(PORT_1, context);
+        assertThat(jsonPort, notNullValue());
+        assertThat(jsonPort, matchesServicePort(PORT_1));
+    }
+
+    /**
+     * Checks if decoding service port works properly.
+     */
+    @Test
+    public void testServicePortDecode() throws IOException {
+        ServicePort sPort = getServicePort("service-port.json");
+        assertThat(sPort.id(), is(PORT_ID_1));
+        assertThat(sPort.networkId(), is(NETWORK_ID_1));
+        assertThat(sPort.name(), is(PORT_NAME_1));
+        assertThat(sPort.ip(), is(IP_ADDRESS_1));
+        assertThat(sPort.mac(), is(MAC_ADDRESS_1));
+        assertThat(sPort.addressPairs(), is(ADDRESS_PAIRS_1));
+        assertThat(sPort.vlanId(), is(VLAN_ID_1));
+    }
+
+    /**
+     * Checks if decoding service port without ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeMissingId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if decoding service port with empty ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeEmptyId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(ID, "")
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if decoding service port with null ID fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if empty string is not allowed for network ID.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeEmptyNetworkId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, "")
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if null is not allowed for network ID.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullNetworkId() {
+        final JsonNode jsonInvalidId = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(NETWORK_ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidId, context);
+    }
+
+    /**
+     * Checks if empty string is not allowed for port name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeEmptyName() {
+        JsonNode jsonInvalidName = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, "")
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidName, context);
+    }
+
+    /**
+     * Checks if null is not allowed for port name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullName() {
+        final JsonNode jsonInvalidName = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(NAME, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidName, context);
+    }
+
+    /**
+     * Checks if invalid IP address string is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidIpAddress() {
+        final JsonNode jsonInvalidIp = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, "ipAddress")
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidIp, context);
+    }
+
+    /**
+     * Checks if invalid IP address string is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullIpAddress() {
+        final JsonNode jsonInvalidIp = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(IP_ADDRESS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidIp, context);
+    }
+
+    /**
+     * Checks if invalid MAC address string is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidMacAddress() {
+        final JsonNode jsonInvalidMac = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, "macAddress")
+                .put(VLAN_ID, VLAN_ID_1.toShort());
+        codec.decode((ObjectNode) jsonInvalidMac, context);
+    }
+
+    /**
+     * Checks if null is not allowed for MAC address.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullMacAddress() {
+        final JsonNode jsonInvalidMac = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(MAC_ADDRESS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidMac, context);
+    }
+
+    /**
+     * Checks if invalid VLAN ID is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidVlanId() {
+        final JsonNode jsonInvalidVlan = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, "vlanId");
+        codec.decode((ObjectNode) jsonInvalidVlan, context);
+    }
+
+    /**
+     * Checks if null is not allowed for VLAN ID.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullVlanId() {
+        final JsonNode jsonInvalidVlan = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .set(VLAN_ID, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidVlan, context);
+    }
+
+    /**
+     * Checks if only array node is allowed for floating address pairs.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNonArrayAddressPairs() {
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString())
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString());
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPair);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if null and empty array node is allowed for floating address pairs.
+     */
+    @Test
+    public void testServicePortDecodeNullAndEmptyAddressPairs() {
+        JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, NullNode.getInstance());
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+
+        jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, context.mapper().createArrayNode());
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if floating address pair without IP address field fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeMissingFloatingAddrPairIp() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if floating address pair without MAC address field fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeMissingFloatingAddrPairMac() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if null IP address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullFloatingAddrPairIp() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString())
+                .set(IP_ADDRESS, NullNode.getInstance());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if null MAC address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeNullFloatingAddrPairMac() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString())
+                .set(MAC_ADDRESS, NullNode.getInstance());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if invalid IP address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidFloatingAddrPairIp() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, "ipAddress")
+                .put(MAC_ADDRESS, ADDRESS_PAIR_1.mac().toString());
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    /**
+     * Checks if invalid MAC address for floating address pair fails.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testServicePortDecodeInvalidFloatingAddrPairMac() {
+        final ArrayNode jsonAddrPairs = context.mapper().createArrayNode();
+        final JsonNode jsonAddrPair = context.mapper().createObjectNode()
+                .put(IP_ADDRESS, ADDRESS_PAIR_1.ip().toString())
+                .put(MAC_ADDRESS, "macAddress");
+        jsonAddrPairs.add(jsonAddrPair);
+        final JsonNode jsonInvalidAddrPair = context.mapper().createObjectNode()
+                .put(ID, PORT_ID_1.id())
+                .put(NETWORK_ID, NETWORK_ID_1.id())
+                .put(NAME, PORT_NAME_1)
+                .put(IP_ADDRESS, IP_ADDRESS_1.toString())
+                .put(MAC_ADDRESS, MAC_ADDRESS_1.toString())
+                .put(VLAN_ID, VLAN_ID_1.toShort())
+                .set(FLOATING_ADDRESS_PAIRS, jsonAddrPairs);
+        codec.decode((ObjectNode) jsonInvalidAddrPair, context);
+    }
+
+    private ServicePort getServicePort(String resource) throws IOException {
+        InputStream jsonStream = ServicePortCodecTest.class.getResourceAsStream(resource);
+        JsonNode jsonNode = context.mapper().readTree(jsonStream).get(SERVICE_PORT);
+        assertThat(jsonNode, notNullValue());
+
+        ServicePort sPort = codec.decode((ObjectNode) jsonNode, context);
+        assertThat(sPort, notNullValue());
+        return sPort;
+    }
+}
diff --git a/src/test/java/org/opencord/cordvtn/codec/ServicePortJsonMatcher.java b/src/test/java/org/opencord/cordvtn/codec/ServicePortJsonMatcher.java
new file mode 100644
index 0000000..08aa126
--- /dev/null
+++ b/src/test/java/org/opencord/cordvtn/codec/ServicePortJsonMatcher.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opencord.cordvtn.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.opencord.cordvtn.api.net.AddressPair;
+import org.opencord.cordvtn.api.net.ServicePort;
+
+import java.util.Objects;
+
+/**
+ * Json matcher for ServicePort.
+ */
+public final class ServicePortJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final ServicePort port;
+
+    private ServicePortJsonMatcher(ServicePort port) {
+        this.port = port;
+    }
+
+    /**
+     * Factory to allocate ServicePort matcher.
+     *
+     * @param port service port object to match
+     * @return matcher
+     */
+    public static ServicePortJsonMatcher matchesServicePort(ServicePort port) {
+        return new ServicePortJsonMatcher(port);
+    }
+
+    @Override
+    protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+        final String jsonPortId = jsonNode.get("id").asText();
+        if (!Objects.equals(jsonPortId, port.id().id())) {
+            description.appendText("Port id was " + jsonPortId);
+            return false;
+        }
+
+        final String jsonPortName = jsonNode.get("name").asText();
+        if (!Objects.equals(jsonPortName, port.name())) {
+            description.appendText("Port name was " + jsonPortName);
+            return false;
+        }
+
+        final String jsonPortNetId = jsonNode.get("network_id").asText();
+        if (!Objects.equals(jsonPortNetId, port.networkId().id())) {
+            description.appendText("Network id was " + jsonPortNetId);
+            return false;
+        }
+
+        final String jsonMacAddr = jsonNode.get("mac_address").asText();
+        if (!Objects.equals(jsonMacAddr, port.mac().toString())) {
+            description.appendText("MAC address was " + jsonMacAddr);
+            return false;
+        }
+
+        final String jsonIpAddr = jsonNode.get("ip_address").asText();
+        if (!Objects.equals(jsonIpAddr, port.ip().toString())) {
+            description.appendText("IP address was " + jsonIpAddr);
+            return false;
+        }
+
+        final String jsonVlanId = jsonNode.get("vlan_id").asText();
+        if (!Objects.equals(jsonVlanId, port.vlanId().toString())) {
+            description.appendText("VLAN id was " + jsonVlanId);
+            return false;
+        }
+
+        final JsonNode jsonAddrPairs = jsonNode.get("floating_address_pairs");
+        if (port.addressPairs().isEmpty()) {
+            if (jsonAddrPairs != null &&
+                    jsonAddrPairs != NullNode.getInstance() &&
+                    jsonAddrPairs.size() != 0) {
+                description.appendText("Floating address pairs did not match");
+                return false;
+            }
+        } else {
+            if (jsonAddrPairs == null ||
+                    jsonAddrPairs == NullNode.getInstance() ||
+                    jsonAddrPairs.size() == 0) {
+                description.appendText("Floating address pairs was empty");
+                return false;
+            } else if (jsonAddrPairs.size() != port.addressPairs().size()) {
+                description.appendText("Floating address pairs size was " +
+                        jsonAddrPairs.size());
+                return false;
+            } else {
+                for (JsonNode addrPair : jsonAddrPairs) {
+                    final AddressPair tmp = AddressPair.of(
+                            IpAddress.valueOf(addrPair.get("ip_address").asText()),
+                            MacAddress.valueOf(addrPair.get("mac_address").asText())
+                    );
+                    if (!port.addressPairs().contains(tmp)) {
+                        description.appendText("Floating address pairs did not match " + tmp);
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(port.toString());
+    }
+}
diff --git a/src/test/resources/org/opencord/cordvtn/codec/service-network-with-provider.json b/src/test/resources/org/opencord/cordvtn/codec/service-network-with-provider.json
deleted file mode 100644
index b017c07..0000000
--- a/src/test/resources/org/opencord/cordvtn/codec/service-network-with-provider.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "serviceNetwork": {
-    "id": "B",
-    "name": "B",
-    "type": "private",
-    "providers": [
-      {
-        "id": "A",
-        "bidirectional": true
-      }
-    ]
-  }
-}
\ No newline at end of file
diff --git a/src/test/resources/org/opencord/cordvtn/codec/service-network.json b/src/test/resources/org/opencord/cordvtn/codec/service-network.json
index 9235f99..d7bf5a7 100644
--- a/src/test/resources/org/opencord/cordvtn/codec/service-network.json
+++ b/src/test/resources/org/opencord/cordvtn/codec/service-network.json
@@ -1,8 +1,16 @@
 {
   "serviceNetwork": {
-    "id": "A",
-    "name": "A",
-    "type": "management_local",
-    "providerNetworks": []
+    "id": "network_1",
+    "name": "network_1",
+    "type": "private",
+    "segment_id": 1,
+    "subnet": "192.168.0.0/24",
+    "service_ip": "192.168.0.1",
+    "providers": [
+      {
+        "id": "provider_1",
+        "bidirectional": true
+      }
+    ]
   }
 }
\ No newline at end of file
diff --git a/src/test/resources/org/opencord/cordvtn/codec/service-port.json b/src/test/resources/org/opencord/cordvtn/codec/service-port.json
new file mode 100644
index 0000000..c50454e
--- /dev/null
+++ b/src/test/resources/org/opencord/cordvtn/codec/service-port.json
@@ -0,0 +1,20 @@
+{
+  "servicePort": {
+    "id": "port-1",
+    "network_id": "network-1",
+    "name": "tap1",
+    "floating_address_pairs": [
+      {
+        "ip_address": "192.168.0.1",
+        "mac_address": "02:42:0a:06:01:01"
+      },
+      {
+        "ip_address": "192.168.0.2",
+        "mac_address": "02:42:0a:06:01:02"
+      }
+    ],
+    "ip_address": "10.0.0.1",
+    "mac_address": "00:00:00:00:00:01",
+    "vlan_id": 222
+  }
+}
\ No newline at end of file