Major refactoring of the BMv2 protocol module (onos1.6 cherry-pick)

- Created 3 separate sub-modules: API (doesn't depend on
    Thrift), CTL (depends on Thrift), THRIFT-API (to generate Thrift
    sources)
- Implemented 2 new services (for device configuration swapping and
    table entry management) needed to distribute BMv2-specific state
    among ONOS instances.
- Implemented a BMv2 controller (previously other modules where
    using separately a Thrift client and a server)
- Added a default BMv2 JSON configuration (default.json) and interpreter
    to be used for devices that connect for the first time to ONOS.
    This allows for basic services to work (i.e. LLDP link discovery,
    ARP proxy. etc.).
- Changed behavior of the flow rule translator and extension selector,
    now it allows extension to specify only some of the match parameters
    (before extension selectors were expected to describe the whole
    match key, i.e. all fields)
- Various renaming to better represent the API
- Various java doc fixes / improvements

Change-Id: Ida4b5e546b0def97c3552a6c05f7bce76fd32c28
diff --git a/core/api/src/main/java/org/onosproject/net/flow/criteria/ExtensionSelectorType.java b/core/api/src/main/java/org/onosproject/net/flow/criteria/ExtensionSelectorType.java
index 7f87ce9..6952224 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/criteria/ExtensionSelectorType.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/criteria/ExtensionSelectorType.java
@@ -39,7 +39,7 @@
         NICIRA_MATCH_NSH_CH3(4),
         NICIRA_MATCH_NSH_CH4(5),
         OFDPA_MATCH_VLAN_VID(16),
-        P4_BMV2_MATCH_KEY(128);
+        BMV2_MATCH_PARAMS(128);
 
         private ExtensionSelectorType type;
 
diff --git a/core/api/src/main/java/org/onosproject/net/flow/instructions/ExtensionTreatmentType.java b/core/api/src/main/java/org/onosproject/net/flow/instructions/ExtensionTreatmentType.java
index 648a397..f87274e 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/instructions/ExtensionTreatmentType.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/instructions/ExtensionTreatmentType.java
@@ -46,7 +46,7 @@
         NICIRA_SET_NSH_CH3(36),
         NICIRA_SET_NSH_CH4(37),
         OFDPA_SET_VLAN_ID(64),
-        P4_BMV2_ACTION(128);
+        BMV2_ACTION(128);
 
         private ExtensionTreatmentType type;
 
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 79461b7..91982d0 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -33,6 +33,7 @@
 import org.onlab.util.Bandwidth;
 import org.onlab.util.ClosedOpenRange;
 import org.onlab.util.Frequency;
+import org.onlab.util.ImmutableByteSequence;
 import org.onlab.util.KryoNamespace;
 import org.onlab.util.Match;
 import org.onosproject.app.ApplicationState;
@@ -535,6 +536,7 @@
             )
             .register(ClosedOpenRange.class)
             .register(DiscreteResourceCodec.class)
+            .register(ImmutableByteSequence.class)
             .build("API");
 
 
diff --git a/drivers/pom.xml b/drivers/pom.xml
index 99db6e1..26e2ff7 100644
--- a/drivers/pom.xml
+++ b/drivers/pom.xml
@@ -41,7 +41,7 @@
         <module>utilities</module>
         <module>lumentum</module>
         <module>bti</module>
-        <module>bmv2</module>
+        <!--<module>bmv2</module>-->
         <module>corsa</module>
         <module>optical</module>
     </modules>
diff --git a/protocols/bmv2/api/pom.xml b/protocols/bmv2/api/pom.xml
new file mode 100644
index 0000000..13ad693
--- /dev/null
+++ b/protocols/bmv2/api/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onos-bmv2-protocol</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.6.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <packaging>bundle</packaging>
+
+    <artifactId>onos-bmv2-protocol-api</artifactId>
+
+   <dependencies>
+       <dependency>
+           <groupId>org.onosproject</groupId>
+           <artifactId>onos-api</artifactId>
+           <version>${project.version}</version>
+       </dependency>
+       <dependency>
+           <groupId>org.apache.felix</groupId>
+           <artifactId>org.apache.felix.scr.annotations</artifactId>
+       </dependency>
+   </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelAction.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2ActionModel.java
similarity index 80%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelAction.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2ActionModel.java
index f53806b..ea70b65 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelAction.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2ActionModel.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
+import com.google.common.annotations.Beta;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 
@@ -26,22 +27,23 @@
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * BMv2 model action.
+ * A BMv2 action model.
  */
-public final class Bmv2ModelAction {
+@Beta
+public final class Bmv2ActionModel {
 
     private final String name;
     private final int id;
-    private final LinkedHashMap<String, Bmv2ModelRuntimeData> runtimeDatas = Maps.newLinkedHashMap();
+    private final LinkedHashMap<String, Bmv2RuntimeDataModel> runtimeDatas = Maps.newLinkedHashMap();
 
     /**
-     * Creates a new action object.
+     * Creates a new action model.
      *
      * @param name         name
      * @param id           id
      * @param runtimeDatas list of runtime data
      */
-    protected Bmv2ModelAction(String name, int id, List<Bmv2ModelRuntimeData> runtimeDatas) {
+    protected Bmv2ActionModel(String name, int id, List<Bmv2RuntimeDataModel> runtimeDatas) {
         this.name = name;
         this.id = id;
         runtimeDatas.forEach(r -> this.runtimeDatas.put(r.name(), r));
@@ -66,22 +68,22 @@
     }
 
     /**
-     * Returns this action's runtime data defined by the passed name, null
+     * Returns this action's runtime data defined by the given name, null
      * if not present.
      *
      * @return runtime data or null
      */
-    public Bmv2ModelRuntimeData runtimeData(String name) {
+    public Bmv2RuntimeDataModel runtimeData(String name) {
         return runtimeDatas.get(name);
     }
 
     /**
      * Returns an immutable list of runtime data for this action.
-     * The list is ordered according to the values defined in the model.
+     * The list is ordered according to the values defined in the configuration.
      *
      * @return list of runtime data.
      */
-    public List<Bmv2ModelRuntimeData> runtimeDatas() {
+    public List<Bmv2RuntimeDataModel> runtimeDatas() {
         return ImmutableList.copyOf(runtimeDatas.values());
     }
 
@@ -98,7 +100,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelAction other = (Bmv2ModelAction) obj;
+        final Bmv2ActionModel other = (Bmv2ActionModel) obj;
         return Objects.equals(this.name, other.name)
                 && Objects.equals(this.id, other.id)
                 && Objects.equals(this.runtimeDatas, other.runtimeDatas);
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2Configuration.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2Configuration.java
new file mode 100644
index 0000000..1cbb146
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2Configuration.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.context;
+
+import com.eclipsesource.json.JsonObject;
+import com.google.common.annotations.Beta;
+
+import java.util.List;
+
+/**
+ * BMv2 packet processing configuration. Such a configuration is used to define the way BMv2 should process packets
+ * (i.e. it defines the device ingress/egress pipelines, parser, tables, actions, etc.). It must be noted that this
+ * class exposes only a subset of the configuration properties of a BMv2 device (only those that are needed for the
+ * purpose of translating ONOS structures to BMv2 structures). Such a configuration is backed by a JSON object.
+ * BMv2 JSON configuration files are usually generated using a P4 frontend compiler such as p4c-bmv2.
+ */
+@Beta
+public interface Bmv2Configuration {
+
+    /**
+     * Return an unmodifiable view of the JSON backing this configuration.
+     *
+     * @return a JSON object.
+     */
+    JsonObject json();
+
+    /**
+     * Returns the header type associated with the given numeric ID, null if there's no such an ID in the configuration.
+     *
+     * @param id integer value
+     * @return header type object or null
+     */
+    Bmv2HeaderTypeModel headerType(int id);
+
+    /**
+     * Returns the header type associated with the given name, null if there's no such a name in the configuration.
+     *
+     * @param name string value
+     * @return header type object or null
+     */
+    Bmv2HeaderTypeModel headerType(String name);
+
+    /**
+     * Returns the list of all the header types defined by in this configuration. Values returned are sorted in
+     * ascending order based on the numeric ID.
+     *
+     * @return list of header types
+     */
+    List<Bmv2HeaderTypeModel> headerTypes();
+
+    /**
+     * Returns the header associated with the given numeric ID, null if there's no such an ID in the configuration.
+     *
+     * @param id integer value
+     * @return header object or null
+     */
+    Bmv2HeaderModel header(int id);
+
+    /**
+     * Returns the header associated with the given name, null if there's no such a name in the configuration.
+     *
+     * @param name string value
+     * @return header object or null
+     */
+    Bmv2HeaderModel header(String name);
+
+    /**
+     * Returns the list of all the header instances defined in this configuration. Values returned are sorted in
+     * ascending order based on the numeric ID.
+     *
+     * @return list of header types
+     */
+    List<Bmv2HeaderModel> headers();
+
+    /**
+     * Returns the action associated with the given numeric ID, null if there's no such an ID in the configuration.
+     *
+     * @param id integer value
+     * @return action object or null
+     */
+    Bmv2ActionModel action(int id);
+
+    /**
+     * Returns the action associated with the given name, null if there's no such a name in the configuration.
+     *
+     * @param name string value
+     * @return action object or null
+     */
+    Bmv2ActionModel action(String name);
+
+    /**
+     * Returns the list of all the actions defined by in this configuration. Values returned are sorted in ascending
+     * order based on the numeric ID.
+     *
+     * @return list of actions
+     */
+    List<Bmv2ActionModel> actions();
+
+    /**
+     * Returns the table associated with the given numeric ID, null if there's no such an ID in the configuration.
+     *
+     * @param id integer value
+     * @return table object or null
+     */
+    Bmv2TableModel table(int id);
+
+    /**
+     * Returns the table associated with the given name, null if there's no such a name in the configuration.
+     *
+     * @param name string value
+     * @return table object or null
+     */
+    Bmv2TableModel table(String name);
+
+    /**
+     * Returns the list of all the tables defined by in this configuration. Values returned are sorted in ascending
+     * order based on the numeric ID.
+     *
+     * @return list of actions
+     */
+    List<Bmv2TableModel> tables();
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2Model.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2DefaultConfiguration.java
similarity index 61%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2Model.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2DefaultConfiguration.java
index 7a5e8a0..53a31b9 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2Model.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2DefaultConfiguration.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
 import com.eclipsesource.json.JsonArray;
 import com.eclipsesource.json.JsonObject;
@@ -34,187 +34,104 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 /**
- * Partial representation of a packet processing model for BMv2. Such a model is
- * used to define the way BMv2 should process packets (i.e. it defines the
- * device ingress/egress pipelines, parser, tables, actions, etc.) and can be
- * generated (i.e. JSON) by compiling a P4 program using p4c-bm.
- * <p>
- * It must be noted that this class exposes only a subset of the full model
- * properties (only those that are needed for the purpose of mapping ONOS types
- * to BMv2 types.
- *
- * @see <a href="https://github.com/p4lang/p4c-bm">
- * P4 front-end compiler for BMv2 (p4c-bm)</a>
+ * Default implementation of a BMv2 configuration backed by a JSON object.
  */
-public final class Bmv2Model {
+public final class Bmv2DefaultConfiguration implements Bmv2Configuration {
 
     private final JsonObject json;
-    private final DualKeySortedMap<Bmv2ModelHeaderType> headerTypes = new DualKeySortedMap<>();
-    private final DualKeySortedMap<Bmv2ModelHeader> headers = new DualKeySortedMap<>();
-    private final DualKeySortedMap<Bmv2ModelAction> actions = new DualKeySortedMap<>();
-    private final DualKeySortedMap<Bmv2ModelTable> tables = new DualKeySortedMap<>();
+    private final DualKeySortedMap<Bmv2HeaderTypeModel> headerTypes = new DualKeySortedMap<>();
+    private final DualKeySortedMap<Bmv2HeaderModel> headers = new DualKeySortedMap<>();
+    private final DualKeySortedMap<Bmv2ActionModel> actions = new DualKeySortedMap<>();
+    private final DualKeySortedMap<Bmv2TableModel> tables = new DualKeySortedMap<>();
 
-    private Bmv2Model(JsonObject json) {
+    private Bmv2DefaultConfiguration(JsonObject json) {
         this.json = JsonObject.unmodifiableObject(json);
     }
 
     /**
-     * Returns a new BMv2 model object by parsing the passed JSON.
+     * Returns a new BMv2 configuration object by parsing the passed JSON.
      *
      * @param json json
      * @return a new BMv2 configuration object
-     * @see <a href="https://github.com/p4lang/behavioral-model/blob/master/docs/JSON_format.md">
+     * @see <a href="https://github.com/p4lang/behavioral-configuration/blob/master/docs/JSON_format.md">
      * BMv2 JSON specification</a>
      */
-    public static Bmv2Model parse(JsonObject json) {
+    public static Bmv2DefaultConfiguration parse(JsonObject json) {
         checkArgument(json != null, "json cannot be null");
-        // TODO: implement caching, no need to parse a json if we already have the model
-        Bmv2Model model = new Bmv2Model(json);
-        model.doParse();
-        return model;
+        // TODO: implement caching, no need to parse a json if we already have the configuration
+        Bmv2DefaultConfiguration configuration = new Bmv2DefaultConfiguration(json);
+        configuration.doParse();
+        return configuration;
     }
 
-    /**
-     * Returns the header type associated with the passed numeric id,
-     * null if there's no such an id in the model.
-     *
-     * @param id integer value
-     * @return header type object or null
-     */
-    public Bmv2ModelHeaderType headerType(int id) {
+    @Override
+    public Bmv2HeaderTypeModel headerType(int id) {
         return headerTypes.get(id);
     }
 
-    /**
-     * Returns the header type associated with the passed name,
-     * null if there's no such a name in the model.
-     *
-     * @param name string value
-     * @return header type object or null
-     */
-    public Bmv2ModelHeaderType headerType(String name) {
+    @Override
+    public Bmv2HeaderTypeModel headerType(String name) {
         return headerTypes.get(name);
     }
 
-    /**
-     * Returns the list of all the header types defined by in this model.
-     * Values returned are sorted in ascending order based on the numeric id.
-     *
-     * @return list of header types
-     */
-    public List<Bmv2ModelHeaderType> headerTypes() {
+    @Override
+    public List<Bmv2HeaderTypeModel> headerTypes() {
         return ImmutableList.copyOf(headerTypes.sortedMap().values());
     }
 
-    /**
-     * Returns the header associated with the passed numeric id,
-     * null if there's no such an id in the model.
-     *
-     * @param id integer value
-     * @return header object or null
-     */
-    public Bmv2ModelHeader header(int id) {
+    @Override
+    public Bmv2HeaderModel header(int id) {
         return headers.get(id);
     }
 
-    /**
-     * Returns the header associated with the passed name,
-     * null if there's no such a name in the model.
-     *
-     * @param name string value
-     * @return header object or null
-     */
-    public Bmv2ModelHeader header(String name) {
+    @Override
+    public Bmv2HeaderModel header(String name) {
         return headers.get(name);
     }
 
-    /**
-     * Returns the list of all the header instances defined in this model.
-     * Values returned are sorted in ascending order based on the numeric id.
-     *
-     * @return list of header types
-     */
-    public List<Bmv2ModelHeader> headers() {
+    @Override
+    public List<Bmv2HeaderModel> headers() {
         return ImmutableList.copyOf(headers.sortedMap().values());
     }
 
-    /**
-     * Returns the action associated with the passed numeric id,
-     * null if there's no such an id in the model.
-     *
-     * @param id integer value
-     * @return action object or null
-     */
-    public Bmv2ModelAction action(int id) {
+    @Override
+    public Bmv2ActionModel action(int id) {
         return actions.get(id);
     }
 
-    /**
-     * Returns the action associated with the passed name,
-     * null if there's no such a name in the model.
-     *
-     * @param name string value
-     * @return action object or null
-     */
-    public Bmv2ModelAction action(String name) {
+    @Override
+    public Bmv2ActionModel action(String name) {
         return actions.get(name);
     }
 
-    /**
-     * Returns the list of all the actions defined by in this model.
-     * Values returned are sorted in ascending order based on the numeric id.
-     *
-     * @return list of actions
-     */
-    public List<Bmv2ModelAction> actions() {
+    @Override
+    public List<Bmv2ActionModel> actions() {
         return ImmutableList.copyOf(actions.sortedMap().values());
     }
 
-    /**
-     * Returns the table associated with the passed numeric id,
-     * null if there's no such an id in the model.
-     *
-     * @param id integer value
-     * @return table object or null
-     */
-    public Bmv2ModelTable table(int id) {
+    @Override
+    public Bmv2TableModel table(int id) {
         return tables.get(id);
     }
 
-    /**
-     * Returns the table associated with the passed name,
-     * null if there's no such a name in the model.
-     *
-     * @param name string value
-     * @return table object or null
-     */
-    public Bmv2ModelTable table(String name) {
+    @Override
+    public Bmv2TableModel table(String name) {
         return tables.get(name);
     }
 
-    /**
-     * Returns the list of all the tables defined by in this model.
-     * Values returned are sorted in ascending order based on the numeric id.
-     *
-     * @return list of actions
-     */
-    public List<Bmv2ModelTable> tables() {
+    @Override
+    public List<Bmv2TableModel> tables() {
         return ImmutableList.copyOf(tables.sortedMap().values());
     }
 
-    /**
-     * Return an unmodifiable view of the low-level JSON representation of this
-     * model.
-     *
-     * @return a JSON object.
-     */
+    @Override
     public JsonObject json() {
         return this.json;
     }
 
     /**
-     * Generates a hash code for this BMv2 model. The hash function is based
-     * solely on the low-level JSON representation.
+     * Generates a hash code for this BMv2 configuration. The hash function is based solely on the JSON backing this
+     * configuration.
      */
     @Override
     public int hashCode() {
@@ -222,7 +139,7 @@
     }
 
     /**
-     * Indicates whether some other BMv2 model is equal to this one.
+     * Indicates whether some other BMv2 configuration is equal to this one.
      * Equality is based solely on the low-level JSON representation.
      *
      * @param obj other object
@@ -236,7 +153,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2Model other = (Bmv2Model) obj;
+        final Bmv2DefaultConfiguration other = (Bmv2DefaultConfiguration) obj;
         return Objects.equal(this.json, other.json);
     }
 
@@ -257,10 +174,10 @@
             JsonObject jHeaderType = val.asObject();
 
             // populate fields list
-            List<Bmv2ModelFieldType> fieldTypes = Lists.newArrayList();
+            List<Bmv2FieldTypeModel> fieldTypes = Lists.newArrayList();
 
             jHeaderType.get("fields").asArray().forEach(x -> fieldTypes.add(
-                    new Bmv2ModelFieldType(
+                    new Bmv2FieldTypeModel(
                             x.asArray().get(0).asString(),
                             x.asArray().get(1).asInt())));
 
@@ -268,7 +185,7 @@
             String name = jHeaderType.get("name").asString();
             int id = jHeaderType.get("id").asInt();
 
-            Bmv2ModelHeaderType headerType = new Bmv2ModelHeaderType(name,
+            Bmv2HeaderTypeModel headerType = new Bmv2HeaderTypeModel(name,
                                                                      id,
                                                                      fieldTypes);
 
@@ -284,7 +201,7 @@
             int id = jHeader.get("id").asInt();
             String typeName = jHeader.get("header_type").asString();
 
-            Bmv2ModelHeader header = new Bmv2ModelHeader(name,
+            Bmv2HeaderModel header = new Bmv2HeaderModel(name,
                                                          id,
                                                          headerTypes.get(typeName),
                                                          jHeader.get("metadata").asBoolean());
@@ -299,10 +216,10 @@
             JsonObject jAction = val.asObject();
 
             // populate runtime data list
-            List<Bmv2ModelRuntimeData> runtimeDatas = Lists.newArrayList();
+            List<Bmv2RuntimeDataModel> runtimeDatas = Lists.newArrayList();
 
             jAction.get("runtime_data").asArray().forEach(jData -> runtimeDatas.add(
-                    new Bmv2ModelRuntimeData(
+                    new Bmv2RuntimeDataModel(
                             jData.asObject().get("name").asString(),
                             jData.asObject().get("bitwidth").asInt()
                     )));
@@ -311,7 +228,7 @@
             String name = jAction.get("name").asString();
             int id = jAction.get("id").asInt();
 
-            Bmv2ModelAction action = new Bmv2ModelAction(name,
+            Bmv2ActionModel action = new Bmv2ActionModel(name,
                                                          id,
                                                          runtimeDatas);
 
@@ -326,15 +243,15 @@
                 JsonObject jTable = val.asObject();
 
                 // populate keys
-                List<Bmv2ModelTableKey> keys = Lists.newArrayList();
+                List<Bmv2TableKeyModel> keys = Lists.newArrayList();
 
                 jTable.get("key").asArray().forEach(jKey -> {
                     JsonArray target = jKey.asObject().get("target").asArray();
 
-                    Bmv2ModelHeader header = header(target.get(0).asString());
+                    Bmv2HeaderModel header = header(target.get(0).asString());
                     String typeName = target.get(1).asString();
 
-                    Bmv2ModelField field = new Bmv2ModelField(
+                    Bmv2FieldModel field = new Bmv2FieldModel(
                             header, header.type().field(typeName));
 
                     String matchTypeStr = jKey.asObject().get("match_type").asString();
@@ -359,11 +276,11 @@
                                     "Unable to parse match type: " + matchTypeStr);
                     }
 
-                    keys.add(new Bmv2ModelTableKey(matchType, field));
+                    keys.add(new Bmv2TableKeyModel(matchType, field));
                 });
 
                 // populate actions set
-                Set<Bmv2ModelAction> actionzz = Sets.newHashSet();
+                Set<Bmv2ActionModel> actionzz = Sets.newHashSet();
                 jTable.get("actions").asArray().forEach(
                         jAction -> actionzz.add(action(jAction.asString())));
 
@@ -371,7 +288,7 @@
                 String name = jTable.get("name").asString();
                 int id = jTable.get("id").asInt();
 
-                Bmv2ModelTable table = new Bmv2ModelTable(name,
+                Bmv2TableModel table = new Bmv2TableModel(name,
                                                           id,
                                                           jTable.get("match_type").asString(),
                                                           jTable.get("type").asString(),
@@ -405,7 +322,7 @@
         }
 
         private T get(String name) {
-            return get(strToIntMap.get(name));
+            return strToIntMap.get(name) == null ? null : get(strToIntMap.get(name));
         }
 
         private SortedMap<Integer, T> sortedMap() {
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2DeviceContext.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2DeviceContext.java
new file mode 100644
index 0000000..f54cc1d
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2DeviceContext.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.context;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A BMv2 device context, defined by a configuration and an interpreter.
+ */
+@Beta
+public final class Bmv2DeviceContext {
+
+    private final Bmv2Configuration configuration;
+    private final Bmv2Interpreter interpreter;
+
+    /**
+     * Creates a new BMv2 device context.
+     *
+     * @param configuration a configuration
+     * @param interpreter   an interpreter
+     */
+    public Bmv2DeviceContext(Bmv2Configuration configuration, Bmv2Interpreter interpreter) {
+        this.configuration = checkNotNull(configuration, "configuration cannot be null");
+        this.interpreter = checkNotNull(interpreter, "interpreter cannot be null");
+    }
+
+    /**
+     * Returns the BMv2 configuration of this context.
+     *
+     * @return a configuration
+     */
+    public Bmv2Configuration configuration() {
+        return configuration;
+    }
+
+    /**
+     * Returns the BMv2 interpreter of this context.
+     *
+     * @return an interpreter
+     */
+    public Bmv2Interpreter interpreter() {
+        return interpreter;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(configuration, interpreter);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Bmv2DeviceContext other = (Bmv2DeviceContext) obj;
+        return Objects.equal(this.configuration, other.configuration)
+                && Objects.equal(this.interpreter, other.interpreter);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("configuration", configuration)
+                .add("interpreter", interpreter)
+                .toString();
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelField.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FieldModel.java
similarity index 78%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelField.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FieldModel.java
index f20ae3b..b1b5ce8 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelField.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FieldModel.java
@@ -14,21 +14,23 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * Representation of a BMv2 model's header field instance.
+ * A BMv2 header field model.
  */
-public final class Bmv2ModelField {
+@Beta
+public final class Bmv2FieldModel {
 
-    private final Bmv2ModelHeader header;
-    private final Bmv2ModelFieldType type;
+    private final Bmv2HeaderModel header;
+    private final Bmv2FieldTypeModel type;
 
-    protected Bmv2ModelField(Bmv2ModelHeader header, Bmv2ModelFieldType type) {
+    protected Bmv2FieldModel(Bmv2HeaderModel header, Bmv2FieldTypeModel type) {
         this.header = header;
         this.type = type;
     }
@@ -38,7 +40,7 @@
      *
      * @return a header instance
      */
-    public Bmv2ModelHeader header() {
+    public Bmv2HeaderModel header() {
         return header;
     }
 
@@ -47,7 +49,7 @@
      *
      * @return a field type value
      */
-    public Bmv2ModelFieldType type() {
+    public Bmv2FieldTypeModel type() {
         return type;
     }
 
@@ -64,7 +66,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelField other = (Bmv2ModelField) obj;
+        final Bmv2FieldModel other = (Bmv2FieldModel) obj;
         return Objects.equal(this.header, other.header)
                 && Objects.equal(this.type, other.type);
     }
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelFieldType.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FieldTypeModel.java
similarity index 85%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelFieldType.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FieldTypeModel.java
index c0147bbd..5b944cc 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelFieldType.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FieldTypeModel.java
@@ -14,21 +14,23 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * BMv2 model header type field.
+ * A BMv2 header type field model.
  */
-public final class Bmv2ModelFieldType {
+@Beta
+public final class Bmv2FieldTypeModel {
 
     private final String name;
     private final int bitWidth;
 
-    protected Bmv2ModelFieldType(String name, int bitWidth) {
+    protected Bmv2FieldTypeModel(String name, int bitWidth) {
         this.name = name;
         this.bitWidth = bitWidth;
     }
@@ -64,7 +66,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelFieldType other = (Bmv2ModelFieldType) obj;
+        final Bmv2FieldTypeModel other = (Bmv2FieldTypeModel) obj;
         return Objects.equal(this.name, other.name)
                 && Objects.equal(this.bitWidth, other.bitWidth);
     }
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FlowRuleTranslator.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FlowRuleTranslator.java
new file mode 100644
index 0000000..3fb5b11
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FlowRuleTranslator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.context;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
+import org.onosproject.net.flow.FlowRule;
+
+/**
+ * Translator of ONOS flow rules to BMv2 table entries.
+ */
+@Beta
+public interface Bmv2FlowRuleTranslator {
+
+    /**
+     * Returns a BMv2 table entry equivalent to the given flow rule for the given context.
+     * <p>
+     * Translation is performed according to the following logic:
+     * <ul>
+     *  <li> table name: obtained from the context interpreter {@link Bmv2Interpreter#tableIdMap() table ID map}.
+     *  <li> match key: is built using both the context interpreter {@link Bmv2Interpreter#criterionTypeMap() criterion
+     *  map} and all {@link org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector extension selectors} (if any).
+     *  <li> action: is built using the context interpreter
+     *          {@link Bmv2Interpreter#mapTreatment(org.onosproject.net.flow.TrafficTreatment, Bmv2Configuration)
+     *          treatment mapping function} or the flow rule
+     *          {@link org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment} extension treatment} (if any).
+     *  <li> timeout: if the table supports timeout, use the same as the flow rule, otherwise none (i.e. permanent
+     *          entry).
+     * </ul>
+     *
+     * @param rule    a flow rule
+     * @param context a context
+     * @return a BMv2 table entry
+     * @throws Bmv2FlowRuleTranslatorException if the flow rule cannot be translated
+     */
+    Bmv2TableEntry translate(FlowRule rule, Bmv2DeviceContext context) throws Bmv2FlowRuleTranslatorException;
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FlowRuleTranslatorException.java
similarity index 72%
copy from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
copy to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FlowRuleTranslatorException.java
index 8c11944..49f999d 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2FlowRuleTranslatorException.java
@@ -14,7 +14,14 @@
  * limitations under the License.
  */
 
+package org.onosproject.bmv2.api.context;
+
 /**
- * Bmv2 runtime APIs.
+ * BMv2 flow rule translator exception.
  */
-package org.onosproject.bmv2.api.runtime;
\ No newline at end of file
+public class Bmv2FlowRuleTranslatorException extends Exception {
+
+    public Bmv2FlowRuleTranslatorException(String msg) {
+        super(msg);
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelHeader.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2HeaderModel.java
similarity index 84%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelHeader.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2HeaderModel.java
index abab008..5c1f4ff 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelHeader.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2HeaderModel.java
@@ -14,31 +14,33 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * Representation of a BMv2 model header instance.
+ * BMv2 header instance model.
  */
-public final class Bmv2ModelHeader {
+@Beta
+public final class Bmv2HeaderModel {
 
     private final String name;
     private final int id;
-    private final Bmv2ModelHeaderType type;
+    private final Bmv2HeaderTypeModel type;
     private final boolean isMetadata;
 
     /**
-     * Creates a new header instance.
+     * Creates a new header instance model.
      *
      * @param name     name
      * @param id       id
      * @param type     header type
      * @param metadata if is metadata
      */
-    protected Bmv2ModelHeader(String name, int id, Bmv2ModelHeaderType type, boolean metadata) {
+    protected Bmv2HeaderModel(String name, int id, Bmv2HeaderTypeModel type, boolean metadata) {
         this.name = name;
         this.id = id;
         this.type = type;
@@ -68,7 +70,7 @@
      *
      * @return a header type value
      */
-    public Bmv2ModelHeaderType type() {
+    public Bmv2HeaderTypeModel type() {
         return type;
     }
 
@@ -94,7 +96,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelHeader other = (Bmv2ModelHeader) obj;
+        final Bmv2HeaderModel other = (Bmv2HeaderModel) obj;
         return Objects.equal(this.name, other.name)
                 && Objects.equal(this.id, other.id)
                 && Objects.equal(this.type, other.type)
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelHeaderType.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2HeaderTypeModel.java
similarity index 82%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelHeaderType.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2HeaderTypeModel.java
index 3865871..5350340 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelHeaderType.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2HeaderTypeModel.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
@@ -26,22 +27,23 @@
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * BMv2 model header type.
+ * BMv2 header type model.
  */
-public final class Bmv2ModelHeaderType {
+@Beta
+public final class Bmv2HeaderTypeModel {
 
     private final String name;
     private final int id;
-    private final LinkedHashMap<String, Bmv2ModelFieldType> fields = Maps.newLinkedHashMap();
+    private final LinkedHashMap<String, Bmv2FieldTypeModel> fields = Maps.newLinkedHashMap();
 
     /**
-     * Creates a new header type instance.
+     * Creates a new header type model.
      *
      * @param name       name
      * @param id         id
      * @param fieldTypes fields
      */
-    protected Bmv2ModelHeaderType(String name, int id, List<Bmv2ModelFieldType> fieldTypes) {
+    protected Bmv2HeaderTypeModel(String name, int id, List<Bmv2FieldTypeModel> fieldTypes) {
         this.name = name;
         this.id = id;
         fieldTypes.forEach(f -> this.fields.put(f.name(), f));
@@ -72,7 +74,7 @@
      * @param fieldName field name
      * @return field or null
      */
-    public Bmv2ModelFieldType field(String fieldName) {
+    public Bmv2FieldTypeModel field(String fieldName) {
         return fields.get(fieldName);
     }
 
@@ -83,7 +85,7 @@
      *
      * @return list of fields
      */
-    public List<Bmv2ModelFieldType> fields() {
+    public List<Bmv2FieldTypeModel> fields() {
         return ImmutableList.copyOf(fields.values());
     }
 
@@ -100,7 +102,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelHeaderType other = (Bmv2ModelHeaderType) obj;
+        final Bmv2HeaderTypeModel other = (Bmv2HeaderTypeModel) obj;
         return Objects.equal(this.name, other.name)
                 && Objects.equal(this.id, other.id)
                 && Objects.equal(this.fields, other.fields);
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2Interpreter.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2Interpreter.java
new file mode 100644
index 0000000..8d44494
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2Interpreter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.context;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableBiMap;
+import org.onosproject.bmv2.api.runtime.Bmv2Action;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+
+/**
+ * A BMv2 configuration interpreter.
+ */
+@Beta
+public interface Bmv2Interpreter {
+
+    /**
+     * Returns a bi-map describing a one-to-one relationship between ONOS flow rule table IDs and BMv2 table names.
+     *
+     * @return a {@link com.google.common.collect.BiMap} where the key is a ONOS flow rule table id and
+     * the value is a BMv2 table names
+     */
+    ImmutableBiMap<Integer, String> tableIdMap();
+
+    /**
+     * Returns a bi-map describing a one-to-one relationship between ONOS criterion types and BMv2 header field names.
+     * Header field names are formatted using the notation {@code header_name.field_member_name}.
+     *
+     * @return a {@link com.google.common.collect.BiMap} where the keys are ONOS criterion types and the values are
+     * BMv2 header field names
+     */
+    ImmutableBiMap<Criterion.Type, String> criterionTypeMap();
+
+    /**
+     * Return a BMv2 action that is functionally equivalent to the given ONOS traffic treatment for the given
+     * configuration.
+     *
+     * @param treatment     a ONOS traffic treatment
+     * @param configuration a BMv2 configuration
+     * @return a BMv2 action object
+     * @throws Bmv2InterpreterException if the treatment cannot be mapped to any BMv2 action
+     */
+    Bmv2Action mapTreatment(TrafficTreatment treatment, Bmv2Configuration configuration)
+            throws Bmv2InterpreterException;
+
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2InterpreterException.java
similarity index 73%
copy from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
copy to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2InterpreterException.java
index 8c11944..5e774ad 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2InterpreterException.java
@@ -14,7 +14,14 @@
  * limitations under the License.
  */
 
+package org.onosproject.bmv2.api.context;
+
 /**
- * Bmv2 runtime APIs.
+ * A BMv2 interpreter exception.
  */
-package org.onosproject.bmv2.api.runtime;
\ No newline at end of file
+public class Bmv2InterpreterException extends Exception {
+
+    public Bmv2InterpreterException(String message) {
+        super(message);
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelRuntimeData.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2RuntimeDataModel.java
similarity index 84%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelRuntimeData.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2RuntimeDataModel.java
index 388c137..e97d2bb 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelRuntimeData.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2RuntimeDataModel.java
@@ -14,27 +14,30 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
+
+import com.google.common.annotations.Beta;
 
 import java.util.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * BMv2 model action runtime data.
+ * A BMv2 action runtime data model.
  */
-public final class Bmv2ModelRuntimeData {
+@Beta
+public final class Bmv2RuntimeDataModel {
 
     private final String name;
     private final int bitWidth;
 
     /**
-     * Creates a new runtime data.
+     * Creates a new runtime data model.
      *
      * @param name     name
      * @param bitWidth bitwidth
      */
-    protected Bmv2ModelRuntimeData(String name, int bitWidth) {
+    protected Bmv2RuntimeDataModel(String name, int bitWidth) {
         this.name = name;
         this.bitWidth = bitWidth;
     }
@@ -70,7 +73,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelRuntimeData other = (Bmv2ModelRuntimeData) obj;
+        final Bmv2RuntimeDataModel other = (Bmv2RuntimeDataModel) obj;
         return Objects.equals(this.name, other.name)
                 && Objects.equals(this.bitWidth, other.bitWidth);
     }
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTableKey.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2TableKeyModel.java
similarity index 81%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTableKey.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2TableKeyModel.java
index a98a7ec..8f2f0222 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTableKey.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2TableKeyModel.java
@@ -14,28 +14,30 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onosproject.bmv2.api.runtime.Bmv2MatchParam;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * Representation of a table key.
+ * A BMv2 table key model.
  */
-public final class Bmv2ModelTableKey {
+@Beta
+public final class Bmv2TableKeyModel {
 
     private final Bmv2MatchParam.Type matchType;
-    private final Bmv2ModelField field;
+    private final Bmv2FieldModel field;
 
     /**
-     * Creates a new table key.
+     * Creates a new table key model.
      *
      * @param matchType match type
      * @param field     field instance
      */
-    protected Bmv2ModelTableKey(Bmv2MatchParam.Type matchType, Bmv2ModelField field) {
+    protected Bmv2TableKeyModel(Bmv2MatchParam.Type matchType, Bmv2FieldModel field) {
         this.matchType = matchType;
         this.field = field;
     }
@@ -55,7 +57,7 @@
      *
      * @return a header field value
      */
-    public Bmv2ModelField field() {
+    public Bmv2FieldModel field() {
         return field;
     }
 
@@ -72,7 +74,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelTableKey other = (Bmv2ModelTableKey) obj;
+        final Bmv2TableKeyModel other = (Bmv2TableKeyModel) obj;
         return Objects.equal(this.matchType, other.matchType)
                 && Objects.equal(this.field, other.field);
     }
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTable.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2TableModel.java
similarity index 88%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTable.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2TableModel.java
index 618a49e..8f6fa55 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTable.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/Bmv2TableModel.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 
 import java.util.List;
@@ -24,9 +25,10 @@
 import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
- * BMv2 model table representation.
+ * A BMv2 table model.
  */
-public final class Bmv2ModelTable {
+@Beta
+public final class Bmv2TableModel {
 
     private final String name;
     private final int id;
@@ -35,11 +37,11 @@
     private final int maxSize;
     private final boolean hasCounters;
     private final boolean hasTimeouts;
-    private final List<Bmv2ModelTableKey> keys;
-    private final Set<Bmv2ModelAction> actions;
+    private final List<Bmv2TableKeyModel> keys;
+    private final Set<Bmv2ActionModel> actions;
 
     /**
-     * Creates a new table.
+     * Creates a new table model.
      *
      * @param name           name
      * @param id             id
@@ -51,9 +53,9 @@
      * @param keys           list of match keys
      * @param actions        list of actions
      */
-    protected Bmv2ModelTable(String name, int id, String matchType, String type,
+    protected Bmv2TableModel(String name, int id, String matchType, String type,
                              int maxSize, boolean withCounters, boolean supportTimeout,
-                             List<Bmv2ModelTableKey> keys, Set<Bmv2ModelAction> actions) {
+                             List<Bmv2TableKeyModel> keys, Set<Bmv2ActionModel> actions) {
         this.name = name;
         this.id = id;
         this.matchType = matchType;
@@ -134,7 +136,7 @@
      *
      * @return a list of match keys
      */
-    public List<Bmv2ModelTableKey> keys() {
+    public List<Bmv2TableKeyModel> keys() {
         return keys;
     }
 
@@ -143,7 +145,7 @@
      *
      * @return a list of actions
      */
-    public Set<Bmv2ModelAction> actions() {
+    public Set<Bmv2ActionModel> actions() {
         return actions;
     }
 
@@ -161,7 +163,7 @@
         if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
-        final Bmv2ModelTable other = (Bmv2ModelTable) obj;
+        final Bmv2TableModel other = (Bmv2TableModel) obj;
         return Objects.equal(this.name, other.name)
                 && Objects.equal(this.id, other.id)
                 && Objects.equal(this.matchType, other.matchType)
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/package-info.java
similarity index 89%
copy from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
copy to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/package-info.java
index 8c11944..7489aa4 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/context/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Bmv2 runtime APIs.
+ * BMv2 device context API.
  */
-package org.onosproject.bmv2.api.runtime;
\ No newline at end of file
+package org.onosproject.bmv2.api.context;
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/package-info.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/package-info.java
similarity index 89%
copy from protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/package-info.java
copy to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/package-info.java
index 9e79ae9..d7e4475 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/package-info.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * BMv2 Thrift client implementation.
+ * BMv2 protocol API.
  */
-package org.onosproject.bmv2.ctl;
\ No newline at end of file
+package org.onosproject.bmv2.api;
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
similarity index 95%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
index 9b2a209..0f1983f 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Action.java
@@ -28,7 +28,7 @@
 import static com.google.common.base.Preconditions.checkState;
 
 /**
- * Bmv2 action representation.
+ * An action of a BMv2 match-action table entry.
  */
 public final class Bmv2Action {
 
@@ -94,7 +94,7 @@
     }
 
     /**
-     * A Bmv2 action builder.
+     * A BMv2 action builder.
      */
     public static final class Builder {
 
@@ -128,9 +128,9 @@
         }
 
         /**
-         * Builds a Bmv2 action object.
+         * Builds a BMv2 action object.
          *
-         * @return a Bmv2 action
+         * @return a BMv2 action
          */
         public Bmv2Action build() {
             checkState(name != null, "action name not set");
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Device.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Device.java
similarity index 66%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Device.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Device.java
index e289615..703ec59 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Device.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Device.java
@@ -21,6 +21,8 @@
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -29,20 +31,25 @@
  */
 public final class Bmv2Device {
 
+    public static final String N_A = "n/a";
+
     public static final String SCHEME = "bmv2";
+    public static final String PROTOCOL = "bmv2-thrift";
     public static final String MANUFACTURER = "p4.org";
     public static final String HW_VERSION = "bmv2";
+    public static final String SW_VERSION = N_A;
+    public static final String SERIAL_NUMBER = N_A;
 
     private final String thriftServerHost;
     private final int thriftServerPort;
     private final int internalDeviceId;
 
     /**
-     * Creates a new Bmv2 device object.
+     * Creates a new BMv2 device object.
      *
-     * @param thriftServerHost the host of the Thrift runtime server running inside the device
-     * @param thriftServerPort the port of the Thrift runtime server running inside the device
-     * @param internalDeviceId the internal device id
+     * @param thriftServerHost the hostname / IP address of the Thrift runtime server running on the device
+     * @param thriftServerPort the port of the Thrift runtime server running on the device
+     * @param internalDeviceId the internal device ID number
      */
     public Bmv2Device(String thriftServerHost, int thriftServerPort, int internalDeviceId) {
         this.thriftServerHost = checkNotNull(thriftServerHost, "host cannot be null");
@@ -51,7 +58,17 @@
     }
 
     /**
-     * Returns the hostname (or IP address) of the Thrift runtime server running inside the device.
+     * Returns a Bmv2Device representing the given deviceId.
+     *
+     * @param deviceId a deviceId
+     * @return
+     */
+    public static Bmv2Device of(DeviceId deviceId) {
+        return DeviceIdParser.parse(checkNotNull(deviceId, "deviceId cannot be null"));
+    }
+
+    /**
+     * Returns the hostname (or IP address) of the Thrift runtime server running on the device.
      *
      * @return a string value
      */
@@ -60,7 +77,7 @@
     }
 
     /**
-     * Returns the port of the Thrift runtime server running inside the device.
+     * Returns the port of the Thrift runtime server running on the device.
      *
      * @return an integer value
      */
@@ -86,7 +103,8 @@
     public DeviceId asDeviceId() {
         try {
             // TODO: include internalDeviceId number in the deviceId URI
-            return DeviceId.deviceId(new URI(SCHEME, this.thriftServerHost + ":" + this.thriftServerPort, null));
+            return DeviceId.deviceId(new URI(SCHEME, this.thriftServerHost + ":" + this.thriftServerPort,
+                                             String.valueOf(this.internalDeviceId)));
         } catch (URISyntaxException e) {
             throw new IllegalArgumentException("Unable to build deviceID for device " + this.toString(), e);
         }
@@ -113,6 +131,23 @@
 
     @Override
     public String toString() {
-        return thriftServerHost + ":" + thriftServerPort + "/" + internalDeviceId;
+        return asDeviceId().toString();
+    }
+
+    private static class DeviceIdParser {
+
+        private static final Pattern REGEX = Pattern.compile(SCHEME + ":(.+):(\\d+)#(\\d+)");
+
+        public static Bmv2Device parse(DeviceId deviceId) {
+            Matcher matcher = REGEX.matcher(deviceId.toString());
+            if (matcher.find()) {
+                String host = matcher.group(1);
+                int port = Integer.valueOf(matcher.group(2));
+                int internalDeviceId = Integer.valueOf(matcher.group(3));
+                return new Bmv2Device(host, port, internalDeviceId);
+            } else {
+                throw new RuntimeException("Unable to parse bmv2 device id string: " + deviceId.toString());
+            }
+        }
     }
 }
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Client.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2DeviceAgent.java
similarity index 62%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Client.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2DeviceAgent.java
index 9dcc3ce..ff02ea3 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Client.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2DeviceAgent.java
@@ -18,14 +18,29 @@
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
 
 import java.util.Collection;
-import java.util.List;
 
 /**
- * RPC client to control a BMv2 device.
+ * An agent to control a BMv2 device.
  */
-public interface Bmv2Client {
+public interface Bmv2DeviceAgent {
+
+    /**
+     * Returns the device ID of this agent.
+     *
+     * @return a device id
+     */
+    DeviceId deviceId();
+
+    /**
+     * Pings the device, returns true if the device is reachable, false otherwise.
+     *
+     * @return true if reachable, false otherwise
+     */
+    boolean ping();
+
     /**
      * Adds a new table entry.
      *
@@ -36,16 +51,14 @@
     long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException;
 
     /**
-     * Modifies a currently installed entry by updating its action.
+     * Modifies an existing table entry by updating its action.
      *
      * @param tableName string value of table name
      * @param entryId   long value of entry ID
      * @param action    an action value
      * @throws Bmv2RuntimeException if any error occurs
      */
-    void modifyTableEntry(String tableName,
-                          long entryId, Bmv2Action action)
-            throws Bmv2RuntimeException;
+    void modifyTableEntry(String tableName, long entryId, Bmv2Action action) throws Bmv2RuntimeException;
 
     /**
      * Deletes currently installed entry.
@@ -54,8 +67,7 @@
      * @param entryId   long value of entry ID
      * @throws Bmv2RuntimeException if any error occurs
      */
-    void deleteTableEntry(String tableName,
-                          long entryId) throws Bmv2RuntimeException;
+    void deleteTableEntry(String tableName, long entryId) throws Bmv2RuntimeException;
 
     /**
      * Sets table default action.
@@ -64,11 +76,10 @@
      * @param action    an action value
      * @throws Bmv2RuntimeException if any error occurs
      */
-    void setTableDefaultAction(String tableName, Bmv2Action action)
-            throws Bmv2RuntimeException;
+    void setTableDefaultAction(String tableName, Bmv2Action action) throws Bmv2RuntimeException;
 
     /**
-     * Returns information of the ports currently configured in the switch.
+     * Returns information on the ports currently configured in the switch.
      *
      * @return collection of port information
      * @throws Bmv2RuntimeException if any error occurs
@@ -76,7 +87,7 @@
     Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException;
 
     /**
-     * Return a string representation of a table content.
+     * Return a string representation of the given table content.
      *
      * @param tableName string value of table name
      * @return table string dump
@@ -85,24 +96,6 @@
     String dumpTable(String tableName) throws Bmv2RuntimeException;
 
     /**
-     * Returns a list of ids for the entries installed in the given table.
-     *
-     * @param tableName string value of table name
-     * @return a list of entry ids
-     * @throws Bmv2RuntimeException if any error occurs
-     */
-    List<Long> getInstalledEntryIds(String tableName) throws Bmv2RuntimeException;
-
-    /**
-     * Removes all entries installed in the given table.
-     *
-     * @param tableName string value of table name
-     * @return the number of entries removed
-     * @throws Bmv2RuntimeException if any error occurs
-     */
-    int cleanupTable(String tableName) throws Bmv2RuntimeException;
-
-    /**
      * Requests the device to transmit a given byte sequence over the given port.
      *
      * @param portNumber a port number
@@ -112,14 +105,14 @@
     void transmitPacket(int portNumber, ImmutableByteSequence packet) throws Bmv2RuntimeException;
 
     /**
-     * Reset the state of the switch (e.g. delete all entries, etc.).
+     * Resets the state of the switch (e.g. delete all entries, etc.).
      *
      * @throws Bmv2RuntimeException if any error occurs
      */
     void resetState() throws Bmv2RuntimeException;
 
     /**
-     * Returns the JSON-formatted model configuration currently used to process packets.
+     * Returns the JSON configuration currently used to process packets.
      *
      * @return a JSON-formatted string value
      * @throws Bmv2RuntimeException if any error occurs
@@ -127,7 +120,7 @@
     String dumpJsonConfig() throws Bmv2RuntimeException;
 
     /**
-     * Returns the md5 hash of the JSON-formatted model configuration currently used to process packets.
+     * Returns the md5 sum of the JSON-formatted model configuration currently used to process packets.
      *
      * @return a string value
      * @throws Bmv2RuntimeException if any error occurs
@@ -143,4 +136,39 @@
      * @throws Bmv2RuntimeException if any error occurs
      */
     Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException;
+
+    /**
+     * Returns the counter values for a given counter and index.
+     *
+     * @param counterName a counter name
+     * @param index       an integer value
+     * @return a pair of long values, where the left value is the number of bytes and the right value is the number of
+     * packets
+     * @throws Bmv2RuntimeException if any error occurs
+     */
+    Pair<Long, Long> readCounter(String counterName, int index) throws Bmv2RuntimeException;
+
+    /**
+     * Returns the ID of the current BMv2 process instance (used to distinguish between different executions of the
+     * same BMv2 device).
+     *
+     * @return an integer value
+     * @throws Bmv2RuntimeException if any error occurs
+     */
+    int getProcessInstanceId() throws Bmv2RuntimeException;
+
+    /**
+     * Uploads a new JSON configuration on the device.
+     *
+     * @param jsonString a string value
+     * @throws Bmv2RuntimeException if any error occurs
+     */
+    void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException;
+
+    /**
+     * Triggers a configuration swap on the device.
+     *
+     * @throws Bmv2RuntimeException
+     */
+    void swapJsonConfig() throws Bmv2RuntimeException;
 }
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExactMatchParam.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExactMatchParam.java
similarity index 94%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExactMatchParam.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExactMatchParam.java
index d23bfc6..d02dd2d 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExactMatchParam.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExactMatchParam.java
@@ -23,9 +23,9 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Representation of a Bmv2 exact match parameter.
+ * Representation of a BMv2 exact match parameter.
  */
-public class Bmv2ExactMatchParam implements Bmv2MatchParam {
+public final class Bmv2ExactMatchParam implements Bmv2MatchParam {
 
     private final ImmutableByteSequence value;
 
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
new file mode 100644
index 0000000..10bc9dc
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.runtime;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.flow.AbstractExtension;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.net.flow.criteria.ExtensionSelectorType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Extension selector for BMv2 used as a wrapper for multiple BMv2 match parameters.
+ */
+public final class Bmv2ExtensionSelector extends AbstractExtension implements ExtensionSelector {
+
+    private final KryoNamespace appKryo = new KryoNamespace.Builder()
+            .register(HashMap.class)
+            .register(Bmv2MatchParam.class)
+            .register(Bmv2ExactMatchParam.class)
+            .register(Bmv2TernaryMatchParam.class)
+            .register(Bmv2LpmMatchParam.class)
+            .register(Bmv2ValidMatchParam.class)
+            .build();
+
+    private Map<String, Bmv2MatchParam> parameterMap;
+
+    /**
+     * Creates a new BMv2 extension selector for the given match parameters map, where the keys are expected to be field
+     * names formatted as headerName.fieldMemberName (e.g. ethernet.dstAddr).
+     *
+     * @param paramMap a map
+     */
+    public Bmv2ExtensionSelector(Map<String, Bmv2MatchParam> paramMap) {
+        this.parameterMap = checkNotNull(paramMap, "param map cannot be null");
+    }
+
+    /**
+     * Returns the match parameters map of this selector.
+     *
+     * @return a match parameter map
+     */
+    public Map<String, Bmv2MatchParam> parameterMap() {
+        return parameterMap;
+    }
+
+
+    @Override
+    public ExtensionSelectorType type() {
+        return ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS.type();
+    }
+
+    @Override
+    public byte[] serialize() {
+        return appKryo.serialize(parameterMap);
+    }
+
+    @Override
+    public void deserialize(byte[] data) {
+        this.parameterMap = appKryo.deserialize(data);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(parameterMap);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Bmv2ExtensionSelector other = (Bmv2ExtensionSelector) obj;
+        return Objects.equal(this.parameterMap, other.parameterMap);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("parameterMap", parameterMap)
+                .toString();
+    }
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
new file mode 100644
index 0000000..b4c9790
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.runtime;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.flow.AbstractExtension;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
+
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.BMV2_ACTION;
+
+/**
+ * Extension treatment for BMv2 used as a wrapper for a {@link Bmv2Action}.
+ */
+public final class Bmv2ExtensionTreatment extends AbstractExtension implements ExtensionTreatment {
+
+    private final KryoNamespace appKryo = new KryoNamespace.Builder().build();
+    private Bmv2Action action;
+
+    public Bmv2ExtensionTreatment(Bmv2Action action) {
+        this.action = action;
+    }
+
+    public Bmv2Action getAction() {
+        return action;
+    }
+
+    @Override
+    public ExtensionTreatmentType type() {
+        return BMV2_ACTION.type();
+    }
+
+    @Override
+    public byte[] serialize() {
+        return appKryo.serialize(action);
+    }
+
+    @Override
+    public void deserialize(byte[] data) {
+        action = appKryo.deserialize(data);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(action);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Bmv2ExtensionTreatment other = (Bmv2ExtensionTreatment) obj;
+        return Objects.equal(this.action, other.action);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("action", action)
+                .toString();
+    }
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2FlowRuleWrapper.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2FlowRuleWrapper.java
new file mode 100644
index 0000000..a7b5b46
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2FlowRuleWrapper.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.runtime;
+
+import com.google.common.base.Objects;
+import org.onosproject.net.flow.FlowRule;
+
+import java.util.Date;
+
+/**
+ * A wrapper class for a ONOS flow rule installed on a BMv2 device.
+ */
+public class Bmv2FlowRuleWrapper {
+
+    private final FlowRule rule;
+    private final long entryId;
+    private final Date creationDate;
+
+    /**
+     * Creates a new flow rule wrapper.
+     *
+     * @param rule         a flow rule
+     * @param entryId      a BMv2 table entry ID
+     * @param creationDate the creation date of the flow rule
+     */
+    public Bmv2FlowRuleWrapper(FlowRule rule, long entryId, Date creationDate) {
+        this.rule = rule;
+        this.entryId = entryId;
+        this.creationDate = new Date();
+    }
+
+    /**
+     * Returns the flow rule contained by this wrapper.
+     *
+     * @return a flow rule
+     */
+    public FlowRule rule() {
+        return rule;
+    }
+
+    /**
+     * Return the seconds since when this flow rule was installed on the device.
+     *
+     * @return an integer value
+     */
+    public long lifeInSeconds() {
+        return (new Date().getTime() - creationDate.getTime()) / 1000;
+    }
+
+    /**
+     * Returns the creation date of this flow rule.
+     *
+     * @return a date
+     */
+    public Date creationDate() {
+        return creationDate;
+    }
+
+    /**
+     * Returns the BMv2 entry ID of this flow rule.
+     *
+     * @return a long value
+     */
+    public long entryId() {
+        return entryId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(rule, entryId, creationDate);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Bmv2FlowRuleWrapper other = (Bmv2FlowRuleWrapper) obj;
+        return Objects.equal(this.rule, other.rule)
+                && Objects.equal(this.entryId, other.entryId)
+                && Objects.equal(this.creationDate, other.creationDate);
+    }
+
+    @Override
+    public String toString() {
+        return creationDate + "-" + rule.hashCode();
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2LpmMatchParam.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2LpmMatchParam.java
similarity index 95%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2LpmMatchParam.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2LpmMatchParam.java
index 29d5279..ee75d3b 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2LpmMatchParam.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2LpmMatchParam.java
@@ -24,9 +24,9 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Representation of a Bmv2 longest prefix match (LPM) parameter.
+ * Representation of a BMv2 longest prefix match (LPM) parameter.
  */
-public class Bmv2LpmMatchParam implements Bmv2MatchParam {
+public final class Bmv2LpmMatchParam implements Bmv2MatchParam {
 
     private final ImmutableByteSequence value;
     private final int prefixLength;
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchKey.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchKey.java
similarity index 88%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchKey.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchKey.java
index cdcfcd9..c0c6b7c 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchKey.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchKey.java
@@ -28,7 +28,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Bmv2 match key representation.
+ * A match key of a BMv2 match-action table entry.
  */
 public final class Bmv2MatchKey {
 
@@ -39,13 +39,17 @@
         this.matchParams = matchParams;
     }
 
+    /**
+     * Returns a new match key builder.
+     *
+     * @return a match key builder
+     */
     public static Builder builder() {
         return new Builder();
     }
 
     /**
-     * Returns an immutable view of the ordered list of match parameters of this
-     * match key.
+     * Returns the list of match parameters of this match key.
      *
      * @return list match parameters
      */
@@ -79,7 +83,7 @@
     }
 
     /**
-     * Builder of a Bmv2 match key.
+     * Builder of a BMv2 match key.
      */
     public static final class Builder {
 
@@ -89,6 +93,12 @@
             this.matchParams = Lists.newArrayList();
         }
 
+        /**
+         * Adds a match parameter to the match key.
+         *
+         * @param param a match parameter
+         * @return this
+         */
         public Builder add(Bmv2MatchParam param) {
             this.matchParams.add(checkNotNull(param));
             return this;
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchParam.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchParam.java
similarity index 94%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchParam.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchParam.java
index 35bac79..3b7142f 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchParam.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2MatchParam.java
@@ -17,7 +17,7 @@
 package org.onosproject.bmv2.api.runtime;
 
 /**
- * Representation of a Bmv2 match parameter.
+ * Representation of a BMv2 match parameter.
  */
 public interface Bmv2MatchParam {
 
@@ -29,7 +29,7 @@
     Type type();
 
     /**
-     * Bmv2 match types.
+     * BMv2 match types.
      */
     enum Type {
         /**
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ParsedTableEntry.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ParsedTableEntry.java
new file mode 100644
index 0000000..9b4435f
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ParsedTableEntry.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.runtime;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Representation of a table entry obtained by parsing a BMv2 table dump.
+ */
+public final class Bmv2ParsedTableEntry {
+    private final long entryId;
+    private final Bmv2MatchKey matchKey;
+    private final Bmv2Action action;
+
+    /**
+     * Creates a new parsed table entry.
+     *
+     * @param entryId  an entry ID
+     * @param matchKey a match key
+     * @param action   an action
+     */
+    public Bmv2ParsedTableEntry(long entryId, Bmv2MatchKey matchKey, Bmv2Action action) {
+        this.entryId = entryId;
+        this.matchKey = matchKey;
+        this.action = action;
+    }
+
+    /**
+     * Returns the entry ID.
+     *
+     * @return a long value
+     */
+    public long entryId() {
+        return entryId;
+    }
+
+    /**
+     * Returns the match key.
+     *
+     * @return a match key object
+     */
+    public Bmv2MatchKey matchKey() {
+        return matchKey;
+    }
+
+    /**
+     * Returns the action.
+     *
+     * @return an action object
+     */
+    public Bmv2Action action() {
+        return action;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(entryId, matchKey, action);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Bmv2ParsedTableEntry other = (Bmv2ParsedTableEntry) obj;
+        return Objects.equal(this.entryId, other.entryId)
+                && Objects.equal(this.matchKey, other.matchKey)
+                && Objects.equal(this.action, other.action);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("entryId", entryId)
+                .add("matchKey", matchKey)
+                .add("action", action)
+                .toString();
+    }
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2PortInfo.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2PortInfo.java
new file mode 100644
index 0000000..f53a436
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2PortInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.runtime;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Information of a port of a BMv2 device.
+ */
+public final class Bmv2PortInfo {
+
+    private final String ifaceName;
+    private final int number;
+    private final boolean isUp;
+
+    /**
+     * Creates a new port description.
+     *
+     * @param ifaceName the common name of the network interface
+     * @param number    a port number
+     * @param isUp      interface status
+     */
+    public Bmv2PortInfo(String ifaceName, int number, boolean isUp) {
+        this.ifaceName = ifaceName;
+        this.number = number;
+        this.isUp = isUp;
+    }
+
+    /**
+     * Returns the common name the network interface used by this port.
+     *
+     * @return a string value
+     */
+    public String ifaceName() {
+        return ifaceName;
+    }
+
+    /**
+     * Returns the number of this port.
+     *
+     * @return an integer value
+     */
+    public int number() {
+        return number;
+    }
+
+    /**
+     * Returns true if the port is up, false otherwise.
+     *
+     * @return a boolean value
+     */
+    public boolean isUp() {
+        return isUp;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(ifaceName, number, isUp);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Bmv2PortInfo other = (Bmv2PortInfo) obj;
+        return Objects.equal(this.ifaceName, other.ifaceName)
+                && Objects.equal(this.number, other.number)
+                && Objects.equal(this.isUp, other.isUp);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("ifaceName", ifaceName)
+                .add("number", number)
+                .add("isUp", isUp)
+                .toString();
+    }
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2RuntimeException.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2RuntimeException.java
new file mode 100644
index 0000000..697c8d2
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2RuntimeException.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.runtime;
+
+/**
+ * General exception of the BMv2 runtime APIs.
+ */
+public final class Bmv2RuntimeException extends Exception {
+
+    private final Code code;
+    private String codeString;
+
+    public Bmv2RuntimeException(String message) {
+        super(message);
+        this.code = Code.OTHER;
+        this.codeString = message;
+    }
+
+    public Bmv2RuntimeException(Throwable cause) {
+        super(cause);
+        this.code = Code.OTHER;
+        this.codeString = cause.toString();
+    }
+
+    public Bmv2RuntimeException(Code code) {
+        super(code.name());
+        this.code = code;
+    }
+
+    public Code getCode() {
+        return this.code;
+    }
+
+    public String explain() {
+        return (codeString == null) ? code.name() : code.name() + " " + codeString;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " " + explain();
+    }
+
+    public enum Code {
+        TABLE_FULL,
+        TABLE_INVALID_HANDLE,
+        TABLE_EXPIRED_HANDLE,
+        TABLE_COUNTERS_DISABLED,
+        TABLE_METERS_DISABLED,
+        TABLE_AGEING_DISABLED,
+        TABLE_INVALID_TABLE_NAME,
+        TABLE_INVALID_ACTION_NAME,
+        TABLE_WRONG_TABLE_TYPE,
+        TABLE_INVALID_MBR_HANDLE,
+        TABLE_MBR_STILL_USED,
+        TABLE_MBR_ALREADY_IN_GRP,
+        TABLE_MBR_NOT_IN_GRP,
+        TABLE_INVALID_GRP_HANDLE,
+        TABLE_GRP_STILL_USED,
+        TABLE_EMPTY_GRP,
+        TABLE_DUPLICATE_ENTRY,
+        TABLE_BAD_MATCH_KEY,
+        TABLE_INVALID_METER_OPERATION,
+        TABLE_DEFAULT_ACTION_IS_CONST,
+        TABLE_DEFAULT_ENTRY_IS_CONST,
+        TABLE_GENERAL_ERROR,
+        TABLE_UNKNOWN_ERROR,
+
+        DEV_MGR_ERROR_GENERAL,
+        DEV_MGR_UNKNOWN,
+
+        COUNTER_INVALID_NAME,
+        COUNTER_INVALID_INDEX,
+        COUNTER_ERROR_GENERAL,
+        COUNTER_ERROR_UNKNOWN,
+
+        SWAP_CONFIG_DISABLED,
+        SWAP_ONGOING,
+        SWAP_NO_ONGOING,
+        SWAP_ERROR_UKNOWN,
+
+        // TODO: add other codes based on autogenerated Thrift APIs
+
+        OTHER
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntry.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntry.java
similarity index 91%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntry.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntry.java
index 96adadd..ce6bd83 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntry.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntry.java
@@ -22,7 +22,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Bmv2 representation of a table entry.
+ * An entry of a match-action table in a BMv2 device.
  */
 public final class Bmv2TableEntry {
 
@@ -45,7 +45,7 @@
     }
 
     /**
-     * Returns a new Bmv2 table entry builder.
+     * Returns a new BMv2 table entry builder.
      *
      * @return a new builder.
      */
@@ -63,7 +63,7 @@
     }
 
     /**
-     * Returns the match key.
+     * Returns the match key of this table entry.
      *
      * @return match key
      */
@@ -72,7 +72,7 @@
     }
 
     /**
-     * Returns the action.
+     * Returns the action of this table entry.
      *
      * @return action
      */
@@ -81,7 +81,7 @@
     }
 
     /**
-     * Returns true is the entry has a valid priority value set.
+     * Returns true is the entry has a valid priority.
      *
      * @return true if priority is set, false elsewhere
      */
@@ -90,7 +90,7 @@
     }
 
     /**
-     * Return the entry priority.
+     * Return the priority of this table entry.
      *
      * @return priority
      */
@@ -99,7 +99,7 @@
     }
 
     /**
-     * Returns true is the entry has a valid timeout value set.
+     * Returns true is this table entry has a valid timeout.
      *
      * @return true if timeout is set, false elsewhere
      */
@@ -108,9 +108,9 @@
     }
 
     /**
-     * Returns the entry timeout in fractional seconds.
+     * Returns the timeout (in fractional seconds) of this table entry.
      *
-     * @return timeout
+     * @return a timeout vale (in fractional seconds)
      */
     public final double timeout() {
         return timeout;
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntryReference.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntryReference.java
new file mode 100644
index 0000000..fd59fb9
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TableEntryReference.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.runtime;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onosproject.net.DeviceId;
+
+/**
+ * A reference to a table entry installed on a BMv2 device.
+ */
+public final class Bmv2TableEntryReference {
+
+
+    private final DeviceId deviceId;
+    private final String tableName;
+    private final Bmv2MatchKey matchKey;
+
+    /**
+     * Creates a new table entry reference.
+     *
+     * @param deviceId a device ID
+     * @param tableName a table name
+     * @param matchKey a match key
+     */
+    public Bmv2TableEntryReference(DeviceId deviceId, String tableName, Bmv2MatchKey matchKey) {
+        this.deviceId = deviceId;
+        this.tableName = tableName;
+        this.matchKey = matchKey;
+    }
+
+    /**
+     * Returns the device ID of this table entry reference.
+     *
+     * @return a device ID
+     */
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    /**
+     * Returns the name of the table of this table entry reference.
+     *
+     * @return a table name
+     */
+    public String tableName() {
+        return tableName;
+    }
+
+    /**
+     * Returns the match key of this table entry reference.
+     *
+     * @return a match key
+     */
+    public Bmv2MatchKey matchKey() {
+        return matchKey;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(deviceId, tableName, matchKey);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Bmv2TableEntryReference other = (Bmv2TableEntryReference) obj;
+        return Objects.equal(this.deviceId, other.deviceId)
+                && Objects.equal(this.tableName, other.tableName)
+                && Objects.equal(this.matchKey, other.matchKey);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("deviceId", deviceId)
+                .add("tableName", tableName)
+                .add("matchKey", matchKey)
+                .toString();
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TernaryMatchParam.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TernaryMatchParam.java
similarity index 95%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TernaryMatchParam.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TernaryMatchParam.java
index 76d42c3..60b38f0 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TernaryMatchParam.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2TernaryMatchParam.java
@@ -24,9 +24,9 @@
 import static com.google.common.base.Preconditions.checkState;
 
 /**
- * Representation of a Bmv2 ternary match parameter.
+ * Representation of a BMv2 ternary match parameter.
  */
-public class Bmv2TernaryMatchParam implements Bmv2MatchParam {
+public final class Bmv2TernaryMatchParam implements Bmv2MatchParam {
 
     private final ImmutableByteSequence value;
     private final ImmutableByteSequence mask;
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ValidMatchParam.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ValidMatchParam.java
similarity index 93%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ValidMatchParam.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ValidMatchParam.java
index 31080d5..38b8c2a 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ValidMatchParam.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ValidMatchParam.java
@@ -22,9 +22,9 @@
 import java.util.Objects;
 
 /**
- * Representation of a Bmv2 valid match parameter.
+ * Representation of a BMv2 valid match parameter.
  */
-public class Bmv2ValidMatchParam implements Bmv2MatchParam {
+public final class Bmv2ValidMatchParam implements Bmv2MatchParam {
 
     private final boolean flag;
 
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
similarity index 96%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
rename to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
index 8c11944..d4b1279 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Bmv2 runtime APIs.
+ * BMv2 runtime API.
  */
 package org.onosproject.bmv2.api.runtime;
\ No newline at end of file
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2Controller.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2Controller.java
new file mode 100644
index 0000000..62b1f55
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2Controller.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.service;
+
+import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
+import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
+import org.onosproject.net.DeviceId;
+
+/**
+ * A controller of BMv2 devices.
+ */
+public interface Bmv2Controller {
+
+    /**
+     * Default port.
+     */
+    int DEFAULT_PORT = 40123;
+
+    /**
+     * Return an agent to operate on the given device.
+     *
+     * @param deviceId a device ID
+     * @return a BMv2 agent
+     * @throws Bmv2RuntimeException if the agent is not available
+     */
+    Bmv2DeviceAgent getAgent(DeviceId deviceId) throws Bmv2RuntimeException;
+
+    /**
+     * Returns true if the given device is reachable from this controller, false otherwise.
+     *
+     * @param deviceId a device ID
+     * @return a boolean value
+     */
+    boolean isReacheable(DeviceId deviceId);
+
+    /**
+     * Register the given device listener.
+     *
+     * @param listener a device listener
+     */
+    void addDeviceListener(Bmv2DeviceListener listener);
+
+    /**
+     * Unregister the given device listener.
+     *
+     * @param listener a device listener
+     */
+    void removeDeviceListener(Bmv2DeviceListener listener);
+
+    /**
+     * Register the given packet listener.
+     *
+     * @param listener a packet listener
+     */
+    void addPacketListener(Bmv2PacketListener listener);
+
+    /**
+     * Unregister the given packet listener.
+     *
+     * @param listener a packet listener
+     */
+    void removePacketListener(Bmv2PacketListener listener);
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2DeviceContextService.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2DeviceContextService.java
new file mode 100644
index 0000000..ca0a0e5
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2DeviceContextService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.service;
+
+import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
+import org.onosproject.bmv2.api.context.Bmv2Interpreter;
+import org.onosproject.net.DeviceId;
+
+/**
+ * A service for managing BMv2 device contexts.
+ */
+public interface Bmv2DeviceContextService {
+
+    // TODO: handle the potential configuration states (e.g. RUNNING, SWAP_REQUESTED, etc.)
+
+    /**
+     * Returns the context of a given device. The context returned is the last one for which a configuration swap was
+     * triggered, hence there's no guarantees that the device is enforcing the returned context's  configuration at the
+     * time of the call.
+     *
+     * @param deviceId a device ID
+     * @return a BMv2 device context
+     */
+    Bmv2DeviceContext getContext(DeviceId deviceId);
+
+    /**
+     * Triggers a configuration swap on a given device.
+     *
+     * @param deviceId a device ID
+     * @param context  a BMv2 device context
+     */
+    void triggerConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context);
+
+    /**
+     * Binds the given interpreter with the given class loader so that other ONOS instances in the cluster can properly
+     * load the interpreter.
+     *
+     * @param interpreterClass an interpreter class
+     * @param loader           a class loader
+     */
+    void registerInterpreterClassLoader(Class<? extends Bmv2Interpreter> interpreterClass, ClassLoader loader);
+
+    /**
+     * Notifies this service that a given device has been updated, meaning a potential context change.
+     * It returns true if the device configuration is the same as the last for which a swap was triggered, false
+     * otherwise. In the last case, the service will asynchronously trigger a swap to the last
+     * configuration stored by this service. If no swap has already been triggered then a default configuration will be
+     * applied.
+     *
+     * @param deviceId a device ID
+     * @return a boolean value
+     */
+    boolean notifyDeviceChange(DeviceId deviceId);
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2DeviceListener.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2DeviceListener.java
new file mode 100644
index 0000000..dbd6d65
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2DeviceListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.service;
+
+import org.onosproject.bmv2.api.runtime.Bmv2Device;
+
+/**
+ * A listener of BMv2 device events.
+ */
+public interface Bmv2DeviceListener {
+
+    /**
+     * Handles a hello message.
+     *
+     * @param device        the BMv2 device that originated the message
+     * @param instanceId    the ID of the BMv2 process instance
+     * @param jsonConfigMd5 the MD5 sum of the JSON configuration currently running on the device
+     */
+    void handleHello(Bmv2Device device, int instanceId, String jsonConfigMd5);
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2PacketListener.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2PacketListener.java
new file mode 100644
index 0000000..19365c9
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2PacketListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.service;
+
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.api.runtime.Bmv2Device;
+
+/**
+ * A listener of BMv2 packet events.
+ */
+public interface Bmv2PacketListener {
+
+    /**
+     * Handles a packet-in message.
+     *
+     * @param device    the BMv2 device that originated the message
+     * @param inputPort the device port where the packet was received
+     * @param reason    a reason code
+     * @param tableId   the ID of table that originated this packet-in
+     * @param contextId the ID of the BMv2 context where the packet-in was originated
+     * @param packet    the packet raw data
+     */
+    void handlePacketIn(Bmv2Device device, int inputPort, long reason, int tableId, int contextId,
+                        ImmutableByteSequence packet);
+}
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2TableEntryService.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2TableEntryService.java
new file mode 100644
index 0000000..5b55764
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/Bmv2TableEntryService.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.service;
+
+
+import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
+import org.onosproject.bmv2.api.runtime.Bmv2FlowRuleWrapper;
+import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntryReference;
+import org.onosproject.net.DeviceId;
+
+import java.util.List;
+
+/**
+ * A service for managing BMv2 table entries.
+ */
+public interface Bmv2TableEntryService {
+
+    /**
+     * Returns a flow rule translator.
+     *
+     * @return a flow rule translator
+     */
+    Bmv2FlowRuleTranslator getFlowRuleTranslator();
+
+    /**
+     * Returns a list of table entries installed in the given device and table. The table entries returned are the
+     * result of a table dump parse.
+     *
+     * @param deviceId  a device id
+     * @param tableName a table name
+     * @return a list of parsed table entries
+     */
+    List<Bmv2ParsedTableEntry> getTableEntries(DeviceId deviceId, String tableName);
+
+    /**
+     * Binds the given ONOS flow rule with a BMv2 table entry reference.
+     *
+     * @param entryRef a table entry reference
+     * @param rule     a BMv2 flow rule wrapper
+     */
+    void bindEntryReference(Bmv2TableEntryReference entryRef, Bmv2FlowRuleWrapper rule);
+
+    /**
+     * Returns the ONOS flow rule associated with the given BMv2 table entry reference, or null if there's no such a
+     * mapping.
+     *
+     * @param entryRef a table entry reference
+     * @return a BMv2 flow rule wrapper
+     */
+    Bmv2FlowRuleWrapper lookupEntryReference(Bmv2TableEntryReference entryRef);
+
+    /**
+     * Removes any flow rule previously bound with a given BMv2 table entry reference.
+     *
+     * @param entryRef a table entry reference
+     */
+    void unbindEntryReference(Bmv2TableEntryReference entryRef);
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/package-info.java
similarity index 90%
copy from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
copy to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/package-info.java
index 8c11944..07bb1a0 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/service/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Bmv2 runtime APIs.
+ * BMv2 service API.
  */
-package org.onosproject.bmv2.api.runtime;
\ No newline at end of file
+package org.onosproject.bmv2.api.service;
\ No newline at end of file
diff --git a/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/utils/Bmv2TranslatorUtils.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/utils/Bmv2TranslatorUtils.java
new file mode 100644
index 0000000..8d8966f
--- /dev/null
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/utils/Bmv2TranslatorUtils.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.api.utils;
+
+import org.onlab.util.HexString;
+import org.onlab.util.ImmutableByteSequence;
+
+import java.util.Arrays;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Collection of util methods to deal with flow rule translation.
+ */
+public final class Bmv2TranslatorUtils {
+
+    private Bmv2TranslatorUtils() {
+        // Ban constructor.
+    }
+
+    /**
+     * Returns the number of bytes necessary to contain the given bit-width.
+     *
+     * @param bitWidth an integer value
+     * @return an integer value
+     */
+    public static int roundToBytes(int bitWidth) {
+        return (int) Math.ceil((double) bitWidth / 8);
+    }
+
+    /**
+     * Trims or expands the given byte sequence so to fit a given bit-width.
+     *
+     * @param original a byte sequence
+     * @param bitWidth an integer value
+     * @return a new byte sequence
+     * @throws ByteSequenceFitException if the byte sequence cannot be fitted in the given bit-width
+     */
+    public static ImmutableByteSequence fitByteSequence(ImmutableByteSequence original, int bitWidth)
+            throws ByteSequenceFitException {
+
+        checkNotNull(original, "byte sequence cannot be null");
+        checkArgument(bitWidth > 0, "byte width must a non-zero positive integer");
+
+        int newByteWidth = roundToBytes(bitWidth);
+
+        if (original.size() == newByteWidth) {
+            // nothing to do
+            return original;
+        }
+
+        byte[] originalBytes = original.asArray();
+
+        if (newByteWidth > original.size()) {
+            // pad missing bytes with zeros
+            return ImmutableByteSequence.copyFrom(Arrays.copyOf(originalBytes, newByteWidth));
+        }
+
+        byte[] newBytes = new byte[newByteWidth];
+        // ImmutableByteSequence is always big-endian, hence check the array in reverse order
+        int diff = originalBytes.length - newByteWidth;
+        for (int i = originalBytes.length - 1; i > 0; i--) {
+            byte ob = originalBytes[i]; // original byte
+            byte nb; // new byte
+            if (i > diff) {
+                // no need to truncate, copy as is
+                nb = ob;
+            } else if (i == diff) {
+                // truncate this byte, check if we're loosing something
+                byte mask = (byte) ((1 >> ((bitWidth % 8) + 1)) - 1);
+                if ((ob & ~mask) != 0) {
+                    throw new ByteSequenceFitException(originalBytes, bitWidth);
+                } else {
+                    nb = (byte) (ob & mask);
+                }
+            } else {
+                // drop this byte, check if we're loosing something
+                if (originalBytes[i] != 0) {
+                    throw new ByteSequenceFitException(originalBytes, bitWidth);
+                } else {
+                    continue;
+                }
+            }
+            newBytes[i - diff] = nb;
+        }
+
+        return ImmutableByteSequence.copyFrom(newBytes);
+    }
+
+    /**
+     * A byte sequence fit exception.
+     */
+    public static class ByteSequenceFitException extends Exception {
+        public ByteSequenceFitException(byte[] bytes, int bitWidth) {
+            super("cannot fit " + HexString.toHexString(bytes) + " into a " + bitWidth + " bits value");
+        }
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/utils/package-info.java
similarity index 90%
copy from protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
copy to protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/utils/package-info.java
index 8c11944..bdae039 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/package-info.java
+++ b/protocols/bmv2/api/src/main/java/org/onosproject/bmv2/api/utils/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Bmv2 runtime APIs.
+ * BMv2 utils.
  */
-package org.onosproject.bmv2.api.runtime;
\ No newline at end of file
+package org.onosproject.bmv2.api.utils;
\ No newline at end of file
diff --git a/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2ModelTest.java b/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/context/Bmv2ConfigurationTest.java
similarity index 75%
rename from protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2ModelTest.java
rename to protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/context/Bmv2ConfigurationTest.java
index 1c51b7e..bc1659d 100644
--- a/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2ModelTest.java
+++ b/protocols/bmv2/api/src/test/java/org/onosproject/bmv2/api/context/Bmv2ConfigurationTest.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package org.onosproject.bmv2.api.model;
+package org.onosproject.bmv2.api.context;
 
 import com.eclipsesource.json.Json;
 import com.eclipsesource.json.JsonObject;
 import com.google.common.testing.EqualsTester;
+import org.hamcrest.collection.IsIterableContainingInOrder;
 import org.junit.Before;
 import org.junit.Test;
 import org.onosproject.bmv2.api.runtime.Bmv2MatchParam;
@@ -30,13 +31,12 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
 import static org.hamcrest.core.IsEqual.equalTo;
 
 /**
- * BMv2 model parser test suite.
+ * BMv2 JSON configuration parser test.
  */
-public class Bmv2ModelTest {
+public class Bmv2ConfigurationTest {
 
     private JsonObject json;
     private JsonObject json2;
@@ -44,28 +44,28 @@
     @Before
     public void setUp() throws Exception {
         json = Json.parse(new BufferedReader(new InputStreamReader(
-                this.getClass().getResourceAsStream("/simple_pipeline.json")))).asObject();
+                this.getClass().getResourceAsStream("/simple.json")))).asObject();
         json2 = Json.parse(new BufferedReader(new InputStreamReader(
-                this.getClass().getResourceAsStream("/simple_pipeline.json")))).asObject();
+                this.getClass().getResourceAsStream("/simple.json")))).asObject();
     }
 
     @Test
     public void testParse() throws Exception {
-        Bmv2Model model = Bmv2Model.parse(json);
-        Bmv2Model model2 = Bmv2Model.parse(json2);
+        Bmv2Configuration config = Bmv2DefaultConfiguration.parse(json);
+        Bmv2Configuration config2 = Bmv2DefaultConfiguration.parse(json2);
 
         new EqualsTester()
-                .addEqualityGroup(model, model2)
+                .addEqualityGroup(config, config2)
                 .testEquals();
 
         /* Check header types */
-        Bmv2ModelHeaderType stdMetaT = model.headerType("standard_metadata_t");
-        Bmv2ModelHeaderType ethernetT = model.headerType("ethernet_t");
-        Bmv2ModelHeaderType intrinsicMetaT = model.headerType("intrinsic_metadata_t");
+        Bmv2HeaderTypeModel stdMetaT = config.headerType("standard_metadata_t");
+        Bmv2HeaderTypeModel ethernetT = config.headerType("ethernet_t");
+        Bmv2HeaderTypeModel intrinsicMetaT = config.headerType("intrinsic_metadata_t");
 
-        Bmv2ModelHeaderType stdMetaT2 = model2.headerType("standard_metadata_t");
-        Bmv2ModelHeaderType ethernetT2 = model2.headerType("ethernet_t");
-        Bmv2ModelHeaderType intrinsicMetaT2 = model2.headerType("intrinsic_metadata_t");
+        Bmv2HeaderTypeModel stdMetaT2 = config2.headerType("standard_metadata_t");
+        Bmv2HeaderTypeModel ethernetT2 = config2.headerType("ethernet_t");
+        Bmv2HeaderTypeModel intrinsicMetaT2 = config2.headerType("intrinsic_metadata_t");
 
         new EqualsTester()
                 .addEqualityGroup(stdMetaT, stdMetaT2)
@@ -88,7 +88,7 @@
 
         // check that fields are in order
         assertThat("Incorrect order for header type fields",
-                   stdMetaT.fields(), contains(
+                   stdMetaT.fields(), IsIterableContainingInOrder.contains(
                         stdMetaT.field("ingress_port"),
                         stdMetaT.field("packet_length"),
                         stdMetaT.field("egress_spec"),
@@ -99,13 +99,13 @@
                         stdMetaT.field("_padding")));
 
         /* Check actions */
-        Bmv2ModelAction floodAction = model.action("flood");
-        Bmv2ModelAction dropAction = model.action("_drop");
-        Bmv2ModelAction fwdAction = model.action("fwd");
+        Bmv2ActionModel floodAction = config.action("flood");
+        Bmv2ActionModel dropAction = config.action("_drop");
+        Bmv2ActionModel fwdAction = config.action("set_egress_port");
 
-        Bmv2ModelAction floodAction2 = model2.action("flood");
-        Bmv2ModelAction dropAction2 = model2.action("_drop");
-        Bmv2ModelAction fwdAction2 = model2.action("fwd");
+        Bmv2ActionModel floodAction2 = config2.action("flood");
+        Bmv2ActionModel dropAction2 = config2.action("_drop");
+        Bmv2ActionModel fwdAction2 = config2.action("set_egress_port");
 
         new EqualsTester()
                 .addEqualityGroup(floodAction, floodAction2)
@@ -133,8 +133,8 @@
                    fwdAction.runtimeData("port").bitWidth(), is(equalTo(9)));
 
         /* Check tables */
-        Bmv2ModelTable table0 = model.table(0);
-        Bmv2ModelTable table02 = model2.table(0);
+        Bmv2TableModel table0 = config.table(0);
+        Bmv2TableModel table02 = config2.table(0);
 
         new EqualsTester()
                 .addEqualityGroup(table0, table02)
diff --git a/protocols/bmv2/api/src/test/resources/simple.json b/protocols/bmv2/api/src/test/resources/simple.json
new file mode 100644
index 0000000..9be7b89
--- /dev/null
+++ b/protocols/bmv2/api/src/test/resources/simple.json
@@ -0,0 +1,397 @@
+{
+    "header_types": [
+        {
+            "name": "standard_metadata_t",
+            "id": 0,
+            "fields": [
+                [
+                    "ingress_port",
+                    9
+                ],
+                [
+                    "packet_length",
+                    32
+                ],
+                [
+                    "egress_spec",
+                    9
+                ],
+                [
+                    "egress_port",
+                    9
+                ],
+                [
+                    "egress_instance",
+                    32
+                ],
+                [
+                    "instance_type",
+                    32
+                ],
+                [
+                    "clone_spec",
+                    32
+                ],
+                [
+                    "_padding",
+                    5
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        },
+        {
+            "name": "ethernet_t",
+            "id": 1,
+            "fields": [
+                [
+                    "dstAddr",
+                    48
+                ],
+                [
+                    "srcAddr",
+                    48
+                ],
+                [
+                    "etherType",
+                    16
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        },
+        {
+            "name": "intrinsic_metadata_t",
+            "id": 2,
+            "fields": [
+                [
+                    "ingress_global_timestamp",
+                    32
+                ],
+                [
+                    "lf_field_list",
+                    32
+                ],
+                [
+                    "mcast_grp",
+                    16
+                ],
+                [
+                    "egress_rid",
+                    16
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        }
+    ],
+    "headers": [
+        {
+            "name": "standard_metadata",
+            "id": 0,
+            "header_type": "standard_metadata_t",
+            "metadata": true
+        },
+        {
+            "name": "ethernet",
+            "id": 1,
+            "header_type": "ethernet_t",
+            "metadata": false
+        },
+        {
+            "name": "intrinsic_metadata",
+            "id": 2,
+            "header_type": "intrinsic_metadata_t",
+            "metadata": true
+        }
+    ],
+    "header_stacks": [],
+    "parsers": [
+        {
+            "name": "parser",
+            "id": 0,
+            "init_state": "start",
+            "parse_states": [
+                {
+                    "name": "start",
+                    "id": 0,
+                    "parser_ops": [],
+                    "transition_key": [],
+                    "transitions": [
+                        {
+                            "value": "default",
+                            "mask": null,
+                            "next_state": "parse_ethernet"
+                        }
+                    ]
+                },
+                {
+                    "name": "parse_ethernet",
+                    "id": 1,
+                    "parser_ops": [
+                        {
+                            "op": "extract",
+                            "parameters": [
+                                {
+                                    "type": "regular",
+                                    "value": "ethernet"
+                                }
+                            ]
+                        }
+                    ],
+                    "transition_key": [],
+                    "transitions": [
+                        {
+                            "value": "default",
+                            "mask": null,
+                            "next_state": null
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "deparsers": [
+        {
+            "name": "deparser",
+            "id": 0,
+            "order": [
+                "ethernet"
+            ]
+        }
+    ],
+    "meter_arrays": [],
+    "actions": [
+        {
+            "name": "set_egress_port",
+            "id": 0,
+            "runtime_data": [
+                {
+                    "name": "port",
+                    "bitwidth": 9
+                }
+            ],
+            "primitives": [
+                {
+                    "op": "modify_field",
+                    "parameters": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "egress_spec"
+                            ]
+                        },
+                        {
+                            "type": "runtime_data",
+                            "value": 0
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "_drop",
+            "id": 1,
+            "runtime_data": [],
+            "primitives": [
+                {
+                    "op": "modify_field",
+                    "parameters": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "egress_spec"
+                            ]
+                        },
+                        {
+                            "type": "hexstr",
+                            "value": "0x1ff"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "flood",
+            "id": 2,
+            "runtime_data": [],
+            "primitives": [
+                {
+                    "op": "modify_field",
+                    "parameters": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "intrinsic_metadata",
+                                "mcast_grp"
+                            ]
+                        },
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "ingress_port"
+                            ]
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "send_to_cpu",
+            "id": 3,
+            "runtime_data": [],
+            "primitives": [
+                {
+                    "op": "modify_field",
+                    "parameters": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "egress_spec"
+                            ]
+                        },
+                        {
+                            "type": "hexstr",
+                            "value": "0xff"
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "pipelines": [
+        {
+            "name": "ingress",
+            "id": 0,
+            "init_table": "table0",
+            "tables": [
+                {
+                    "name": "table0",
+                    "id": 0,
+                    "match_type": "ternary",
+                    "type": "simple",
+                    "max_size": 16384,
+                    "with_counters": false,
+                    "direct_meters": null,
+                    "support_timeout": false,
+                    "key": [
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "standard_metadata",
+                                "ingress_port"
+                            ],
+                            "mask": null
+                        },
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "ethernet",
+                                "dstAddr"
+                            ],
+                            "mask": null
+                        },
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "ethernet",
+                                "srcAddr"
+                            ],
+                            "mask": null
+                        },
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "ethernet",
+                                "etherType"
+                            ],
+                            "mask": null
+                        }
+                    ],
+                    "actions": [
+                        "set_egress_port",
+                        "flood",
+                        "send_to_cpu",
+                        "_drop"
+                    ],
+                    "next_tables": {
+                        "set_egress_port": null,
+                        "flood": null,
+                        "send_to_cpu": null,
+                        "_drop": null
+                    },
+                    "default_action": null,
+                    "base_default_next": null
+                }
+            ],
+            "conditionals": []
+        },
+        {
+            "name": "egress",
+            "id": 1,
+            "init_table": null,
+            "tables": [],
+            "conditionals": []
+        }
+    ],
+    "calculations": [],
+    "checksums": [],
+    "learn_lists": [],
+    "field_lists": [],
+    "counter_arrays": [],
+    "register_arrays": [],
+    "force_arith": [
+        [
+            "standard_metadata",
+            "ingress_port"
+        ],
+        [
+            "standard_metadata",
+            "packet_length"
+        ],
+        [
+            "standard_metadata",
+            "egress_spec"
+        ],
+        [
+            "standard_metadata",
+            "egress_port"
+        ],
+        [
+            "standard_metadata",
+            "egress_instance"
+        ],
+        [
+            "standard_metadata",
+            "instance_type"
+        ],
+        [
+            "standard_metadata",
+            "clone_spec"
+        ],
+        [
+            "standard_metadata",
+            "_padding"
+        ],
+        [
+            "intrinsic_metadata",
+            "ingress_global_timestamp"
+        ],
+        [
+            "intrinsic_metadata",
+            "lf_field_list"
+        ],
+        [
+            "intrinsic_metadata",
+            "mcast_grp"
+        ],
+        [
+            "intrinsic_metadata",
+            "egress_rid"
+        ]
+    ]
+}
\ No newline at end of file
diff --git a/protocols/bmv2/ctl/pom.xml b/protocols/bmv2/ctl/pom.xml
new file mode 100644
index 0000000..5e8ae37
--- /dev/null
+++ b/protocols/bmv2/ctl/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onos-bmv2-protocol</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.6.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>onos-bmv2-protocol-ctl</artifactId>
+
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.thrift</groupId>
+            <artifactId>libthrift</artifactId>
+            <version>0.9.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-bmv2-protocol-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-bmv2-protocol-thrift-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-serializers</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+
+</project>
\ No newline at end of file
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2ControllerImpl.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2ControllerImpl.java
new file mode 100644
index 0000000..caee250
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2ControllerImpl.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.thrift.TException;
+import org.apache.thrift.TProcessor;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TMultiplexedProtocol;
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.server.TThreadPoolServer;
+import org.apache.thrift.transport.TServerSocket;
+import org.apache.thrift.transport.TServerTransport;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.api.service.Bmv2Controller;
+import org.onosproject.bmv2.api.runtime.Bmv2Device;
+import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
+import org.onosproject.bmv2.api.service.Bmv2DeviceListener;
+import org.onosproject.bmv2.api.service.Bmv2PacketListener;
+import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
+import org.onosproject.bmv2.thriftapi.ControlPlaneService;
+import org.onosproject.bmv2.thriftapi.SimpleSwitch;
+import org.onosproject.bmv2.thriftapi.Standard;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Default implementation of a BMv2 controller.
+ */
+@Component(immediate = true)
+@Service
+public class Bmv2ControllerImpl implements Bmv2Controller {
+
+    private static final String APP_ID = "org.onosproject.bmv2";
+
+    // Seconds after a client is expired (and connection closed) in the cache.
+    private static final int CLIENT_CACHE_TIMEOUT = 60;
+    // Number of connection retries after a network error.
+    private static final int NUM_CONNECTION_RETRIES = 2;
+    // Time between retries in milliseconds.
+    private static final int TIME_BETWEEN_RETRIES = 10;
+
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    // Cache where clients are removed after a predefined timeout.
+    private final LoadingCache<DeviceId, Pair<TTransport, Bmv2DeviceThriftClient>> agentCache =
+            CacheBuilder.newBuilder()
+                    .expireAfterAccess(CLIENT_CACHE_TIMEOUT, TimeUnit.SECONDS)
+                    .removalListener(new ClientRemovalListener())
+                    .build(new ClientLoader());
+
+    private final InternalTrackingProcessor trackingProcessor = new InternalTrackingProcessor();
+
+    private final ExecutorService executorService = Executors
+            .newFixedThreadPool(16, groupedThreads("onos/bmv2", "controller", log));
+
+    private final Set<Bmv2DeviceListener> deviceListeners = new CopyOnWriteArraySet<>();
+    private final Set<Bmv2PacketListener> packetListeners = new CopyOnWriteArraySet<>();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    private TThreadPoolServer thriftServer;
+
+    // TODO: make it configurable trough component config
+    private int serverPort = DEFAULT_PORT;
+
+    @Activate
+    public void activate() {
+        coreService.registerApplication(APP_ID);
+        startServer(serverPort);
+        log.info("Activated");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        stopServer();
+        log.info("Deactivated");
+    }
+
+    private void startServer(int port) {
+        try {
+            TServerTransport transport = new TServerSocket(port);
+            log.info("Starting server on port {}...", port);
+            this.thriftServer = new TThreadPoolServer(new TThreadPoolServer.Args(transport)
+                                                              .processor(trackingProcessor)
+                                                              .executorService(executorService));
+            executorService.execute(thriftServer::serve);
+        } catch (TTransportException e) {
+            log.error("Unable to start server", e);
+        }
+    }
+
+    private void stopServer() {
+        // Stop the server if running...
+        if (thriftServer != null && !thriftServer.isServing()) {
+            thriftServer.stop();
+        }
+        try {
+            executorService.shutdown();
+            executorService.awaitTermination(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            List<Runnable> runningTasks = executorService.shutdownNow();
+            log.warn("Unable to stop server threads: {}", runningTasks);
+        }
+    }
+
+    @Override
+    public Bmv2DeviceAgent getAgent(DeviceId deviceId) throws Bmv2RuntimeException {
+        try {
+            checkNotNull(deviceId, "deviceId cannot be null");
+            return agentCache.get(deviceId).getRight();
+        } catch (ExecutionException e) {
+            throw new Bmv2RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean isReacheable(DeviceId deviceId) {
+        try {
+            return getAgent(deviceId).ping();
+        } catch (Bmv2RuntimeException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public void addDeviceListener(Bmv2DeviceListener listener) {
+        if (!deviceListeners.contains(listener)) {
+            deviceListeners.add(listener);
+        }
+    }
+
+    @Override
+    public void removeDeviceListener(Bmv2DeviceListener listener) {
+        deviceListeners.remove(listener);
+    }
+
+    @Override
+    public void addPacketListener(Bmv2PacketListener listener) {
+        if (!packetListeners.contains(listener)) {
+            packetListeners.add(listener);
+        }
+    }
+
+    @Override
+    public void removePacketListener(Bmv2PacketListener listener) {
+        packetListeners.remove(listener);
+    }
+
+    /**
+     * Client cache removal listener. Close the connection on cache removal.
+     */
+    private static class ClientRemovalListener implements
+            RemovalListener<DeviceId, Pair<TTransport, Bmv2DeviceThriftClient>> {
+
+        @Override
+        public void onRemoval(RemovalNotification<DeviceId, Pair<TTransport, Bmv2DeviceThriftClient>> notification) {
+            // close the transport connection
+            Bmv2DeviceThriftClient client = notification.getValue().getRight();
+            TTransport transport = notification.getValue().getLeft();
+            // Locking here is ugly, but needed (see SafeThriftClient).
+            synchronized (transport) {
+                if (transport.isOpen()) {
+                    transport.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles Thrift calls from BMv2 devices using registered listeners.
+     */
+    private final class InternalServiceHandler implements ControlPlaneService.Iface {
+
+        private final TSocket socket;
+        private Bmv2Device remoteDevice;
+
+        private InternalServiceHandler(TSocket socket) {
+            this.socket = socket;
+        }
+
+        @Override
+        public boolean ping() {
+            return true;
+        }
+
+        @Override
+        public void hello(int thriftServerPort, int deviceId, int instanceId, String jsonConfigMd5) {
+            // Locally note the remote device for future uses.
+            String host = socket.getSocket().getInetAddress().getHostAddress();
+            remoteDevice = new Bmv2Device(host, thriftServerPort, deviceId);
+
+            if (deviceListeners.size() == 0) {
+                log.debug("Received hello, but there's no listener registered.");
+            } else {
+                deviceListeners.forEach(
+                        l -> executorService.execute(() -> l.handleHello(remoteDevice, instanceId, jsonConfigMd5)));
+            }
+        }
+
+        @Override
+        public void packetIn(int port, long reason, int tableId, int contextId, ByteBuffer packet) {
+            if (remoteDevice == null) {
+                log.debug("Received packet-in, but the remote device is still unknown. Need a hello first...");
+                return;
+            }
+
+            if (packetListeners.size() == 0) {
+                log.debug("Received packet-in, but there's no listener registered.");
+            } else {
+                packetListeners.forEach(
+                        l -> executorService.execute(() -> l.handlePacketIn(remoteDevice,
+                                                                            port,
+                                                                            reason,
+                                                                            tableId,
+                                                                            contextId,
+                                                                            ImmutableByteSequence.copyFrom(packet))));
+            }
+        }
+    }
+
+    /**
+     * Thrift Processor decorator. This class is needed in order to have access to the socket when handling a call.
+     * Socket is needed to get the IP address of the client originating the call (see InternalServiceHandler.hello())
+     */
+    private final class InternalTrackingProcessor implements TProcessor {
+
+        // Map sockets to processors.
+        // TODO: implement it as a cache so unused sockets are expired automatically
+        private final ConcurrentMap<TSocket, ControlPlaneService.Processor<InternalServiceHandler>> processors =
+                Maps.newConcurrentMap();
+
+        @Override
+        public boolean process(final TProtocol in, final TProtocol out) throws TException {
+            // Get the socket for this request.
+            TSocket socket = (TSocket) in.getTransport();
+            // Get or create a processor for this socket
+            ControlPlaneService.Processor<InternalServiceHandler> processor = processors.computeIfAbsent(socket, s -> {
+                InternalServiceHandler handler = new InternalServiceHandler(s);
+                return new ControlPlaneService.Processor<>(handler);
+            });
+            // Delegate to the processor we are decorating.
+            return processor.process(in, out);
+        }
+    }
+
+    /**
+     * Transport/client cache loader.
+     */
+    private class ClientLoader extends CacheLoader<DeviceId, Pair<TTransport, Bmv2DeviceThriftClient>> {
+
+        private final SafeThriftClient.Options options = new SafeThriftClient.Options(NUM_CONNECTION_RETRIES,
+                                                                                      TIME_BETWEEN_RETRIES);
+
+        @Override
+        public Pair<TTransport, Bmv2DeviceThriftClient> load(DeviceId deviceId)
+                throws TTransportException {
+            log.debug("Instantiating new client... > deviceId={}", deviceId);
+            // Make the expensive call
+            Bmv2Device device = Bmv2Device.of(deviceId);
+            TTransport transport = new TSocket(device.thriftServerHost(), device.thriftServerPort());
+            TProtocol protocol = new TBinaryProtocol(transport);
+            // Our BMv2 device implements multiple Thrift services, create a client for each one on the same transport.
+            Standard.Client standardClient = new Standard.Client(
+                    new TMultiplexedProtocol(protocol, "standard"));
+            SimpleSwitch.Client simpleSwitch = new SimpleSwitch.Client(
+                    new TMultiplexedProtocol(protocol, "simple_switch"));
+            // Wrap clients so to automatically have synchronization and resiliency to connectivity errors
+            Standard.Iface safeStandardClient = SafeThriftClient.wrap(standardClient,
+                                                                      Standard.Iface.class,
+                                                                      options);
+            SimpleSwitch.Iface safeSimpleSwitchClient = SafeThriftClient.wrap(simpleSwitch,
+                                                                              SimpleSwitch.Iface.class,
+                                                                              options);
+            Bmv2DeviceThriftClient client = new Bmv2DeviceThriftClient(deviceId,
+                                                                       transport,
+                                                                       safeStandardClient,
+                                                                       safeSimpleSwitchClient);
+            return Pair.of(transport, client);
+        }
+    }
+}
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DefaultInterpreterImpl.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DefaultInterpreterImpl.java
new file mode 100644
index 0000000..f5e95df
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DefaultInterpreterImpl.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import com.google.common.collect.ImmutableBiMap;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2Interpreter;
+import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
+import org.onosproject.bmv2.api.runtime.Bmv2Action;
+import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+
+import static org.onosproject.net.PortNumber.CONTROLLER;
+import static org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+
+/**
+ * Implementation of a BMv2 interpreter for the BMv2 default.json configuration.
+ */
+public final class Bmv2DefaultInterpreterImpl implements Bmv2Interpreter {
+
+    public static final String TABLE0 = "table0";
+    public static final String SEND_TO_CPU = "send_to_cpu";
+    public static final String DROP = "_drop";
+    public static final String SET_EGRESS_PORT = "set_egress_port";
+
+    // Lazily populate field map.
+    private static final ImmutableBiMap<Criterion.Type, String> CRITERION_MAP = ImmutableBiMap.of(
+            Criterion.Type.IN_PORT, "standard_metadata.ingress_port",
+            Criterion.Type.ETH_DST, "ethernet.dstAddr",
+            Criterion.Type.ETH_SRC, "ethernet.srcAddr",
+            Criterion.Type.ETH_TYPE, "ethernet.etherType");
+
+    private static final ImmutableBiMap<Integer, String> TABLE_MAP = ImmutableBiMap.of(
+            0, TABLE0);
+
+    @Override
+    public ImmutableBiMap<Criterion.Type, String> criterionTypeMap() {
+        return CRITERION_MAP;
+    }
+
+    @Override
+    public ImmutableBiMap<Integer, String> tableIdMap() {
+        return TABLE_MAP;
+    }
+
+    @Override
+    public Bmv2Action mapTreatment(TrafficTreatment treatment, Bmv2Configuration configuration)
+            throws Bmv2InterpreterException {
+
+
+        if (treatment.allInstructions().size() == 0) {
+            // No instructions means drop for us.
+            return Bmv2Action.builder().withName(DROP).build();
+        } else if (treatment.allInstructions().size() > 1) {
+            // Otherwise, we understand treatments with only 1 instruction.
+            throw new Bmv2InterpreterException("Treatment has multiple instructions");
+        }
+
+        Instruction instruction = treatment.allInstructions().get(0);
+
+        switch (instruction.type()) {
+            case OUTPUT:
+                OutputInstruction outInstruction = (OutputInstruction) instruction;
+                if (outInstruction.port() == CONTROLLER) {
+                    return Bmv2Action.builder().withName(SEND_TO_CPU).build();
+                } else {
+                    return buildEgressAction(outInstruction, configuration);
+                }
+            case NOACTION:
+                return Bmv2Action.builder().withName(DROP).build();
+            default:
+                throw new Bmv2InterpreterException("Instruction type not supported: " + instruction.type().name());
+        }
+    }
+
+
+    /**
+     * Builds an egress action equivalent to the given output instruction for the given configuration.
+     *
+     * @param instruction   an output instruction
+     * @param configuration a configuration
+     * @return a BMv2 action
+     * @throws Bmv2InterpreterException if the instruction cannot be translated to a BMv2 action
+     */
+    private Bmv2Action buildEgressAction(OutputInstruction instruction, Bmv2Configuration configuration)
+            throws Bmv2InterpreterException {
+
+        Bmv2Action.Builder actionBuilder = Bmv2Action.builder();
+
+        actionBuilder.withName(SET_EGRESS_PORT);
+
+        if (instruction.port().isLogical()) {
+            throw new Bmv2InterpreterException("Output on logic port not supported: " + instruction);
+        }
+
+        // Get the byte sequence for the out port with the right length
+        long portNumber = instruction.port().toLong();
+        int bitWidth = configuration.action(SET_EGRESS_PORT).runtimeData("port").bitWidth();
+        try {
+            ImmutableByteSequence outPort = Bmv2TranslatorUtils.fitByteSequence(
+                    ImmutableByteSequence.copyFrom(portNumber), bitWidth);
+            return Bmv2Action.builder().withName(SET_EGRESS_PORT).addParameter(outPort).build();
+        } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+            throw new Bmv2InterpreterException(e.getMessage());
+        }
+    }
+}
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DeviceContextServiceImpl.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DeviceContextServiceImpl.java
new file mode 100644
index 0000000..bcdb939
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DeviceContextServiceImpl.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonObject;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.SharedExecutors;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
+import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
+import org.onosproject.bmv2.api.context.Bmv2Interpreter;
+import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
+import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
+import org.onosproject.bmv2.api.service.Bmv2Controller;
+import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Component(immediate = true)
+@Service
+public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService {
+
+    private static final String JSON_DEFAULT_CONFIG_PATH = "/default.json";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private Bmv2Controller controller;
+
+    private final ExecutorService executorService = SharedExecutors.getPoolThreadExecutor();
+
+    private ConsistentMap<DeviceId, Bmv2DeviceContext> contexts;
+    private Map<DeviceId, Bmv2DeviceContext> contextsMap;
+
+    private Map<String, ClassLoader> interpreterClassLoaders;
+
+    private Bmv2DeviceContext defaultContext;
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Activate
+    public void activate() {
+        KryoNamespace kryo = new KryoNamespace.Builder()
+                .register(KryoNamespaces.API)
+                .register(new BmvDeviceContextSerializer(), Bmv2DeviceContext.class)
+                .build();
+
+        this.contexts = storageService.<DeviceId, Bmv2DeviceContext>consistentMapBuilder()
+                .withSerializer(Serializer.using(kryo))
+                .withName("onos-bmv2-contexts")
+                .build();
+        contextsMap = contexts.asJavaMap();
+
+        interpreterClassLoaders = Maps.newConcurrentMap();
+
+        Bmv2Configuration defaultConfiguration = loadDefaultConfiguration();
+        Bmv2Interpreter defaultInterpreter = new Bmv2DefaultInterpreterImpl();
+        defaultContext = new Bmv2DeviceContext(defaultConfiguration, defaultInterpreter);
+
+        interpreterClassLoaders.put(defaultInterpreter.getClass().getName(), this.getClass().getClassLoader());
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public Bmv2DeviceContext getContext(DeviceId deviceId) {
+        checkNotNull(deviceId, "device id cannot be null");
+        return contextsMap.get(deviceId);
+    }
+
+    @Override
+    public void triggerConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context) {
+        checkNotNull(deviceId, "device id cannot be null");
+        checkNotNull(context, "context cannot be null");
+        if (!interpreterClassLoaders.containsKey(context.interpreter().getClass().getName())) {
+            log.error("Unable to trigger configuration swap, missing class loader for context interpreter. " +
+                              "Please register it with registerInterpreterClassLoader()");
+        } else {
+            executorService.execute(() -> executeConfigurationSwap(deviceId, context));
+        }
+    }
+
+    @Override
+    public void registerInterpreterClassLoader(Class<? extends Bmv2Interpreter> interpreterClass, ClassLoader loader) {
+        interpreterClassLoaders.put(interpreterClass.getName(), loader);
+    }
+
+    private void executeConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context) {
+        contexts.compute(deviceId, (key, existingValue) -> {
+            if (context.equals(existingValue)) {
+                log.info("Dropping swap request as one has already been triggered for the given context.");
+                return existingValue;
+            }
+            try {
+                Bmv2DeviceAgent agent = controller.getAgent(deviceId);
+                String jsonString = context.configuration().json().toString();
+                agent.loadNewJsonConfig(jsonString);
+                agent.swapJsonConfig();
+                return context;
+            } catch (Bmv2RuntimeException e) {
+                log.error("Unable to swap configuration on {}: {}", deviceId, e.explain());
+                return existingValue;
+            }
+        });
+    }
+
+    @Override
+    public boolean notifyDeviceChange(DeviceId deviceId) {
+        checkNotNull(deviceId, "device id cannot be null");
+
+        Bmv2DeviceContext storedContext = getContext(deviceId);
+
+        if (storedContext == null) {
+            log.info("No context previously stored for {}, swapping to DEFAULT_CONTEXT.", deviceId);
+            triggerConfigurationSwap(deviceId, defaultContext);
+            // Device can be accepted.
+            return false;
+        } else {
+            Bmv2Configuration deviceConfiguration = loadDeviceConfiguration(deviceId);
+            if (deviceConfiguration == null) {
+                log.warn("Unable to load configuration from device {}", deviceId);
+                return false;
+            }
+            if (storedContext.configuration().equals(deviceConfiguration)) {
+                return true;
+            } else {
+                log.info("Device context is different from the stored one, triggering configuration swap for {}...",
+                         deviceId);
+                triggerConfigurationSwap(deviceId, storedContext);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Load and parse a BMv2 JSON configuration from the given device.
+     *
+     * @param deviceId a device id
+     * @return a BMv2 configuration
+     */
+    private Bmv2Configuration loadDeviceConfiguration(DeviceId deviceId) {
+        try {
+            String jsonString = controller.getAgent(deviceId).dumpJsonConfig();
+            return Bmv2DefaultConfiguration.parse(Json.parse(jsonString).asObject());
+        } catch (Bmv2RuntimeException e) {
+            log.warn("Unable to load JSON configuration from {}: {}", deviceId, e.explain());
+            return null;
+        }
+    }
+
+    /**
+     * Loads default configuration from file.
+     *
+     * @return a BMv2 configuration
+     */
+    protected static Bmv2DefaultConfiguration loadDefaultConfiguration() {
+        try {
+            JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
+                    Bmv2DeviceContextServiceImpl.class.getResourceAsStream(JSON_DEFAULT_CONFIG_PATH)))).asObject();
+            return Bmv2DefaultConfiguration.parse(json);
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to load default configuration", e);
+        }
+    }
+
+    /**
+     * Internal BMv2 context serializer.
+     */
+    private class BmvDeviceContextSerializer extends com.esotericsoftware.kryo.Serializer<Bmv2DeviceContext> {
+
+        @Override
+        public void write(Kryo kryo, Output output, Bmv2DeviceContext context) {
+            kryo.writeObject(output, context.configuration().json().toString());
+            kryo.writeObject(output, context.interpreter().getClass().getName());
+        }
+
+        @Override
+        public Bmv2DeviceContext read(Kryo kryo, Input input, Class<Bmv2DeviceContext> type) {
+            String jsonStr = kryo.readObject(input, String.class);
+            String interpreterClassName = kryo.readObject(input, String.class);
+            Bmv2Configuration configuration = Bmv2DefaultConfiguration.parse(Json.parse(jsonStr).asObject());
+            ClassLoader loader = interpreterClassLoaders.get(interpreterClassName);
+            if (loader == null) {
+                throw new IllegalStateException("No class loader registered for interpreter: " + interpreterClassName);
+            }
+            try {
+                Bmv2Interpreter interpreter = (Bmv2Interpreter) loader.loadClass(interpreterClassName).newInstance();
+                return new Bmv2DeviceContext(configuration, interpreter);
+            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+                throw new RuntimeException("Unable to load interpreter class", e);
+            }
+        }
+    }
+}
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DeviceThriftClient.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DeviceThriftClient.java
new file mode 100644
index 0000000..ae852c3
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2DeviceThriftClient.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.thrift.TException;
+import org.apache.thrift.transport.TTransport;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.api.runtime.Bmv2Action;
+import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
+import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
+import org.onosproject.bmv2.api.runtime.Bmv2PortInfo;
+import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
+import org.onosproject.bmv2.thriftapi.BmAddEntryOptions;
+import org.onosproject.bmv2.thriftapi.BmCounterValue;
+import org.onosproject.bmv2.thriftapi.BmMatchParam;
+import org.onosproject.bmv2.thriftapi.BmMatchParamExact;
+import org.onosproject.bmv2.thriftapi.BmMatchParamLPM;
+import org.onosproject.bmv2.thriftapi.BmMatchParamTernary;
+import org.onosproject.bmv2.thriftapi.BmMatchParamType;
+import org.onosproject.bmv2.thriftapi.BmMatchParamValid;
+import org.onosproject.bmv2.thriftapi.SimpleSwitch;
+import org.onosproject.bmv2.thriftapi.Standard;
+import org.onosproject.net.DeviceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.onosproject.bmv2.ctl.Bmv2TExceptionParser.parseTException;
+
+/**
+ * Implementation of a Thrift client to control a BMv2 device.
+ */
+public final class Bmv2DeviceThriftClient implements Bmv2DeviceAgent {
+
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    // FIXME: make context_id arbitrary for each call
+    // See: https://github.com/p4lang/behavioral-model/blob/master/modules/bm_sim/include/bm_sim/context.h
+    private static final int CONTEXT_ID = 0;
+
+    private final Standard.Iface standardClient;
+    private final SimpleSwitch.Iface simpleSwitchClient;
+    private final TTransport transport;
+    private final DeviceId deviceId;
+
+    // ban constructor
+    protected Bmv2DeviceThriftClient(DeviceId deviceId, TTransport transport, Standard.Iface standardClient,
+                                     SimpleSwitch.Iface simpleSwitchClient) {
+        this.deviceId = deviceId;
+        this.transport = transport;
+        this.standardClient = standardClient;
+        this.simpleSwitchClient = simpleSwitchClient;
+    }
+
+    @Override
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    @Override
+    public boolean ping() {
+        try {
+            return this.simpleSwitchClient.ping();
+        } catch (TException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException {
+
+        log.debug("Adding table entry... > deviceId={}, entry={}", deviceId, entry);
+
+        long entryId = -1;
+
+        try {
+            BmAddEntryOptions options = new BmAddEntryOptions();
+
+            if (entry.hasPriority()) {
+                options.setPriority(entry.priority());
+            }
+
+            entryId = standardClient.bm_mt_add_entry(
+                    CONTEXT_ID,
+                    entry.tableName(),
+                    buildMatchParamsList(entry.matchKey()),
+                    entry.action().name(),
+                    buildActionParamsList(entry.action()),
+                    options);
+
+            if (entry.hasTimeout()) {
+                /* bmv2 accepts timeouts in milliseconds */
+                int msTimeout = (int) Math.round(entry.timeout() * 1_000);
+                standardClient.bm_mt_set_entry_ttl(
+                        CONTEXT_ID, entry.tableName(), entryId, msTimeout);
+            }
+
+            log.debug("Table entry added! > deviceId={}, entryId={}/{}", deviceId, entry.tableName(), entryId);
+
+            return entryId;
+
+        } catch (TException e) {
+            log.debug("Exception while adding table entry: {} > deviceId={}, tableName={}",
+                      e, deviceId, entry.tableName());
+            if (entryId != -1) {
+                // entry is in inconsistent state (unable to add timeout), remove it
+                try {
+                    deleteTableEntry(entry.tableName(), entryId);
+                } catch (Bmv2RuntimeException e1) {
+                    log.debug("Unable to remove failed table entry: {} > deviceId={}, tableName={}",
+                              e1, deviceId, entry.tableName());
+                }
+            }
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public final void modifyTableEntry(String tableName,
+                                       long entryId, Bmv2Action action)
+            throws Bmv2RuntimeException {
+
+        log.debug("Modifying table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
+
+        try {
+            standardClient.bm_mt_modify_entry(
+                    CONTEXT_ID,
+                    tableName,
+                    entryId,
+                    action.name(),
+                    buildActionParamsList(action));
+            log.debug("Table entry modified! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
+        } catch (TException e) {
+            log.debug("Exception while modifying table entry: {} > deviceId={}, entryId={}/{}",
+                      e, deviceId, tableName, entryId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public final void deleteTableEntry(String tableName,
+                                       long entryId) throws Bmv2RuntimeException {
+
+        log.debug("Deleting table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
+
+        try {
+            standardClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
+            log.debug("Table entry deleted! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
+        } catch (TException e) {
+            log.debug("Exception while deleting table entry: {} > deviceId={}, entryId={}/{}",
+                      e, deviceId, tableName, entryId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public final void setTableDefaultAction(String tableName, Bmv2Action action)
+            throws Bmv2RuntimeException {
+
+        log.debug("Setting table default... > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
+
+        try {
+            standardClient.bm_mt_set_default_action(
+                    CONTEXT_ID,
+                    tableName,
+                    action.name(),
+                    buildActionParamsList(action));
+            log.debug("Table default set! > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
+        } catch (TException e) {
+            log.debug("Exception while setting table default : {} > deviceId={}, tableName={}, action={}",
+                      e, deviceId, tableName, action);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException {
+
+        log.debug("Retrieving port info... > deviceId={}", deviceId);
+
+        try {
+            return standardClient.bm_dev_mgr_show_ports().stream()
+                    .map(p -> new Bmv2PortInfo(p.getIface_name(), p.getPort_num(), p.isIs_up()))
+                    .collect(Collectors.toList());
+        } catch (TException e) {
+            log.debug("Exception while retrieving port info: {} > deviceId={}", e, deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public String dumpTable(String tableName) throws Bmv2RuntimeException {
+
+        log.debug("Retrieving table dump... > deviceId={}, tableName={}", deviceId, tableName);
+
+        try {
+            String dump = standardClient.bm_dump_table(CONTEXT_ID, tableName);
+            log.debug("Table dump retrieved! > deviceId={}, tableName={}", deviceId, tableName);
+            return dump;
+        } catch (TException e) {
+            log.debug("Exception while retrieving table dump: {} > deviceId={}, tableName={}",
+                      e, deviceId, tableName);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public void transmitPacket(int portNumber, ImmutableByteSequence packet) throws Bmv2RuntimeException {
+
+        log.debug("Requesting packet transmission... > portNumber={}, packetSize={}", portNumber, packet.size());
+
+        try {
+
+            simpleSwitchClient.push_packet(portNumber, ByteBuffer.wrap(packet.asArray()));
+            log.debug("Packet transmission requested! > portNumber={}, packetSize={}", portNumber, packet.size());
+        } catch (TException e) {
+            log.debug("Exception while requesting packet transmission: {} > portNumber={}, packetSize={}",
+                      e, portNumber, packet.size());
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public void resetState() throws Bmv2RuntimeException {
+
+        log.debug("Resetting device state... > deviceId={}", deviceId);
+
+        try {
+            standardClient.bm_reset_state();
+            log.debug("Device state reset! > deviceId={}", deviceId);
+        } catch (TException e) {
+            log.debug("Exception while resetting device state: {} > deviceId={}", e, deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public String dumpJsonConfig() throws Bmv2RuntimeException {
+
+        log.debug("Dumping device config... > deviceId={}", deviceId);
+
+        try {
+            String config = standardClient.bm_get_config();
+            log.debug("Device config dumped! > deviceId={}, configLength={}", deviceId, config.length());
+            return config;
+        } catch (TException e) {
+            log.debug("Exception while dumping device config: {} > deviceId={}", e, deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException {
+
+        log.debug("Reading table entry counters... > deviceId={}, tableName={}, entryId={}",
+                  deviceId, tableName, entryId);
+
+        try {
+            BmCounterValue counterValue = standardClient.bm_mt_read_counter(CONTEXT_ID, tableName, entryId);
+            log.debug("Table entry counters retrieved! > deviceId={}, tableName={}, entryId={}, bytes={}, packets={}",
+                      deviceId, tableName, entryId, counterValue.bytes, counterValue.packets);
+            return Pair.of(counterValue.bytes, counterValue.packets);
+        } catch (TException e) {
+            log.debug("Exception while reading table counters: {} > deviceId={}, tableName={}, entryId={}",
+                      e.toString(), deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public Pair<Long, Long> readCounter(String counterName, int index) throws Bmv2RuntimeException {
+
+        log.debug("Reading table entry counters... > deviceId={}, counterName={}, index={}",
+                  deviceId, counterName, index);
+
+        try {
+            BmCounterValue counterValue = standardClient.bm_counter_read(CONTEXT_ID, counterName, index);
+            log.debug("Table entry counters retrieved! >deviceId={}, counterName={}, index={}, bytes={}, packets={}",
+                      deviceId, counterName, index, counterValue.bytes, counterValue.packets);
+            return Pair.of(counterValue.bytes, counterValue.packets);
+        } catch (TException e) {
+            log.debug("Exception while reading table counters: {} > deviceId={}, counterName={}, index={}",
+                      e.toString(), deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public int getProcessInstanceId() throws Bmv2RuntimeException {
+        log.debug("Getting process instance ID... > deviceId={}", deviceId);
+        try {
+            int instanceId = simpleSwitchClient.get_process_instance_id();
+            log.debug("TProcess instance ID retrieved! > deviceId={}, instanceId={}",
+                      deviceId, instanceId);
+            return instanceId;
+        } catch (TException e) {
+            log.debug("Exception while getting process instance ID: {} > deviceId={}", e.toString(), deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public String getJsonConfigMd5() throws Bmv2RuntimeException {
+
+        log.debug("Getting device config md5... > deviceId={}", deviceId);
+
+        try {
+            String md5 = standardClient.bm_get_config_md5();
+            log.debug("Device config md5 received! > deviceId={}, configMd5={}", deviceId, md5);
+            return md5;
+        } catch (TException e) {
+            log.debug("Exception while getting device config md5: {} > deviceId={}", e, deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException {
+
+        log.debug("Loading new JSON config on device... > deviceId={}, jsonStringLength={}",
+                  deviceId, jsonString.length());
+
+        try {
+            standardClient.bm_load_new_config(jsonString);
+            log.debug("JSON config loaded! > deviceId={}", deviceId);
+        } catch (TException e) {
+            log.debug("Exception while loading JSON config: {} > deviceId={}", e, deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    @Override
+    public void swapJsonConfig() throws Bmv2RuntimeException {
+
+        log.debug("Swapping JSON config on device... > deviceId={}", deviceId);
+
+        try {
+            standardClient.bm_swap_configs();
+            log.debug("JSON config swapped! > deviceId={}", deviceId);
+        } catch (TException e) {
+            log.debug("Exception while swapping JSON config: {} > deviceId={}", e, deviceId);
+            throw parseTException(e);
+        }
+    }
+
+    /**
+     * Builds a list of Bmv2/Thrift compatible match parameters.
+     *
+     * @param matchKey a bmv2 matchKey
+     * @return list of thrift-compatible bm match parameters
+     */
+    private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) {
+        List<BmMatchParam> paramsList = Lists.newArrayList();
+        matchKey.matchParams().forEach(x -> {
+            ByteBuffer value;
+            ByteBuffer mask;
+            switch (x.type()) {
+                case EXACT:
+                    value = ByteBuffer.wrap(((Bmv2ExactMatchParam) x).value().asArray());
+                    paramsList.add(
+                            new BmMatchParam(BmMatchParamType.EXACT)
+                                    .setExact(new BmMatchParamExact(value)));
+                    break;
+                case TERNARY:
+                    value = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).value().asArray());
+                    mask = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).mask().asArray());
+                    paramsList.add(
+                            new BmMatchParam(BmMatchParamType.TERNARY)
+                                    .setTernary(new BmMatchParamTernary(value, mask)));
+                    break;
+                case LPM:
+                    value = ByteBuffer.wrap(((Bmv2LpmMatchParam) x).value().asArray());
+                    int prefixLength = ((Bmv2LpmMatchParam) x).prefixLength();
+                    paramsList.add(
+                            new BmMatchParam(BmMatchParamType.LPM)
+                                    .setLpm(new BmMatchParamLPM(value, prefixLength)));
+                    break;
+                case VALID:
+                    boolean flag = ((Bmv2ValidMatchParam) x).flag();
+                    paramsList.add(
+                            new BmMatchParam(BmMatchParamType.VALID)
+                                    .setValid(new BmMatchParamValid(flag)));
+                    break;
+                default:
+                    // should never be here
+                    throw new RuntimeException("Unknown match param type " + x.type().name());
+            }
+        });
+        return paramsList;
+    }
+
+    /**
+     * Build a list of Bmv2/Thrift compatible action parameters.
+     *
+     * @param action an action object
+     * @return list of ByteBuffers
+     */
+    private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) {
+        List<ByteBuffer> buffers = Lists.newArrayList();
+        action.parameters().forEach(p -> buffers.add(ByteBuffer.wrap(p.asArray())));
+        return buffers;
+    }
+}
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImpl.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImpl.java
new file mode 100644
index 0000000..b3c1e8a
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImpl.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Sets;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.api.context.Bmv2ActionModel;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
+import org.onosproject.bmv2.api.context.Bmv2FieldModel;
+import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
+import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException;
+import org.onosproject.bmv2.api.context.Bmv2Interpreter;
+import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
+import org.onosproject.bmv2.api.context.Bmv2RuntimeDataModel;
+import org.onosproject.bmv2.api.context.Bmv2TableKeyModel;
+import org.onosproject.bmv2.api.context.Bmv2TableModel;
+import org.onosproject.bmv2.api.runtime.Bmv2Action;
+import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
+import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
+import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.ExtensionCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.Instructions.ExtensionInstructionWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
+import static org.onosproject.net.flow.criteria.Criterion.Type.EXTENSION;
+import static org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS;
+
+/**
+ * Default implementation of a BMv2 flow rule translator.
+ */
+@Beta
+public class Bmv2FlowRuleTranslatorImpl implements Bmv2FlowRuleTranslator {
+
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    @Override
+    public Bmv2TableEntry translate(FlowRule rule, Bmv2DeviceContext context)
+            throws Bmv2FlowRuleTranslatorException {
+
+        Bmv2Configuration configuration = context.configuration();
+        Bmv2Interpreter interpreter = context.interpreter();
+
+        int tableId = rule.tableId();
+        String tableName = interpreter.tableIdMap().get(tableId);
+
+        Bmv2TableModel table = (tableName == null) ? configuration.table(tableId) : configuration.table(tableName);
+
+        if (table == null) {
+            throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId);
+        }
+
+        /* Translate selector */
+        Bmv2MatchKey bmv2MatchKey = buildMatchKey(interpreter, rule.selector(), table);
+
+        /* Translate treatment */
+        TrafficTreatment treatment = rule.treatment();
+        Bmv2Action bmv2Action = null;
+        // If treatment has only 1 instruction of type extension, use that
+        for (Instruction inst : treatment.allInstructions()) {
+            if (inst.type() == Instruction.Type.EXTENSION) {
+                if (treatment.allInstructions().size() == 1) {
+                    bmv2Action = getActionFromExtension((ExtensionInstructionWrapper) inst);
+                } else {
+                    throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic treatment, found multiple " +
+                                                                      "instructions of which one is an extension: " +
+                                                                      treatment.toString());
+                }
+            }
+        }
+
+        if (bmv2Action == null) {
+            // No extension, use interpreter to build action.
+            try {
+                bmv2Action = interpreter.mapTreatment(treatment, configuration);
+            } catch (Bmv2InterpreterException e) {
+                throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment. " + e.toString());
+            }
+        }
+
+        if (bmv2Action == null) {
+            // Interpreter returned null.
+            throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment");
+        }
+
+        // Check action
+        Bmv2ActionModel actionModel = configuration.action(bmv2Action.name());
+        if (actionModel == null) {
+            throw new Bmv2FlowRuleTranslatorException("Unknown action " + bmv2Action.name());
+        }
+        if (!table.actions().contains(actionModel)) {
+            throw new Bmv2FlowRuleTranslatorException("Action " + bmv2Action.name()
+                                                              + " is not defined for table " + tableName);
+        }
+        if (actionModel.runtimeDatas().size() != bmv2Action.parameters().size()) {
+            throw new Bmv2FlowRuleTranslatorException("Wrong number of parameters for action "
+                                                              + actionModel.name() + ", expected "
+                                                              + actionModel.runtimeDatas().size() + ", but found "
+                                                              + bmv2Action.parameters().size());
+        }
+        for (int i = 0; i < actionModel.runtimeDatas().size(); i++) {
+            Bmv2RuntimeDataModel data = actionModel.runtimeDatas().get(i);
+            ImmutableByteSequence param = bmv2Action.parameters().get(i);
+            if (param.size() != roundToBytes(data.bitWidth())) {
+                throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for parameter " + data.name()
+                                                                  + " of action " + actionModel.name()
+                                                                  + ", expected " + roundToBytes(data.bitWidth())
+                                                                  + " bytes, but found " + param.size());
+            }
+        }
+
+        Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder();
+
+        // In BMv2 0 is the highest priority.
+        int newPriority = Integer.MAX_VALUE - rule.priority();
+
+        tableEntryBuilder
+                .withTableName(table.name())
+                .withPriority(newPriority)
+                .withMatchKey(bmv2MatchKey)
+                .withAction(bmv2Action);
+
+        if (!rule.isPermanent()) {
+            if (table.hasTimeouts()) {
+                tableEntryBuilder.withTimeout((double) rule.timeout());
+            } else {
+                log.warn("Flow rule is temporary but table {} doesn't support timeouts, translating to permanent",
+                         table.name());
+            }
+
+        }
+
+        return tableEntryBuilder.build();
+    }
+
+    private Bmv2TernaryMatchParam buildTernaryParam(Bmv2FieldModel field, Criterion criterion, int bitWidth)
+            throws Bmv2FlowRuleTranslatorException {
+
+        // Value and mask will be filled according to criterion type
+        ImmutableByteSequence value;
+        ImmutableByteSequence mask = null;
+
+        int byteWidth = roundToBytes(bitWidth);
+
+        switch (criterion.type()) {
+            case IN_PORT:
+                long port = ((PortCriterion) criterion).port().toLong();
+                value = ImmutableByteSequence.copyFrom(port);
+                break;
+            case ETH_DST:
+                EthCriterion c = (EthCriterion) criterion;
+                value = ImmutableByteSequence.copyFrom(c.mac().toBytes());
+                if (c.mask() != null) {
+                    mask = ImmutableByteSequence.copyFrom(c.mask().toBytes());
+                }
+                break;
+            case ETH_SRC:
+                EthCriterion c2 = (EthCriterion) criterion;
+                value = ImmutableByteSequence.copyFrom(c2.mac().toBytes());
+                if (c2.mask() != null) {
+                    mask = ImmutableByteSequence.copyFrom(c2.mask().toBytes());
+                }
+                break;
+            case ETH_TYPE:
+                short ethType = ((EthTypeCriterion) criterion).ethType().toShort();
+                value = ImmutableByteSequence.copyFrom(ethType);
+                break;
+            // TODO: implement building for other criterion types (easy with DefaultCriterion of ONOS-4034)
+            default:
+                throw new Bmv2FlowRuleTranslatorException("Feature not implemented, ternary builder for criterion" +
+                                                                  "type: " + criterion.type().name());
+        }
+
+        // Fit byte sequence in field model bit-width.
+        try {
+            value = Bmv2TranslatorUtils.fitByteSequence(value, bitWidth);
+        } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+            throw new Bmv2FlowRuleTranslatorException(
+                    "Fit exception for criterion " + criterion.type().name() + " value, " + e.getMessage());
+        }
+
+        if (mask == null) {
+            // no mask, all ones
+            mask = ImmutableByteSequence.ofOnes(byteWidth);
+        } else {
+            try {
+                mask = Bmv2TranslatorUtils.fitByteSequence(mask, bitWidth);
+            } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+                throw new Bmv2FlowRuleTranslatorException(
+                        "Fit exception for criterion " + criterion.type().name() + " mask, " + e.getMessage());
+            }
+        }
+
+        return new Bmv2TernaryMatchParam(value, mask);
+    }
+
+    private Bmv2Action getActionFromExtension(Instructions.ExtensionInstructionWrapper inst)
+            throws Bmv2FlowRuleTranslatorException {
+
+        ExtensionTreatment extTreatment = inst.extensionInstruction();
+
+        if (extTreatment.type() == ExtensionTreatmentTypes.BMV2_ACTION.type()) {
+            if (extTreatment instanceof Bmv2ExtensionTreatment) {
+                return ((Bmv2ExtensionTreatment) extTreatment).getAction();
+            } else {
+                throw new Bmv2FlowRuleTranslatorException("Unable to decode treatment extension: " + extTreatment);
+            }
+        } else {
+            throw new Bmv2FlowRuleTranslatorException("Unsupported treatment extension type: " + extTreatment.type());
+        }
+    }
+
+    private Bmv2MatchKey buildMatchKey(Bmv2Interpreter interpreter, TrafficSelector selector, Bmv2TableModel tableModel)
+            throws Bmv2FlowRuleTranslatorException {
+
+        // Find a bmv2 extension selector (if any) and get the parameter map.
+        Optional<Bmv2ExtensionSelector> extSelector = selector.criteria().stream()
+                .filter(c -> c.type().equals(EXTENSION))
+                .map(c -> (ExtensionCriterion) c)
+                .map(ExtensionCriterion::extensionSelector)
+                .filter(c -> c.type().equals(BMV2_MATCH_PARAMS.type()))
+                .map(c -> (Bmv2ExtensionSelector) c)
+                .findFirst();
+        Map<String, Bmv2MatchParam> extParamMap =
+                (extSelector.isPresent()) ? extSelector.get().parameterMap() : Collections.emptyMap();
+
+        Set<Criterion> translatedCriteria = Sets.newHashSet();
+        Set<String> usedExtParams = Sets.newHashSet();
+
+        Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder();
+
+        keysLoop:
+        for (Bmv2TableKeyModel keyModel : tableModel.keys()) {
+
+            // use fieldName dot notation (e.g. ethernet.dstAddr)
+            String fieldName = keyModel.field().header().name() + "." + keyModel.field().type().name();
+
+            int bitWidth = keyModel.field().type().bitWidth();
+            int byteWidth = roundToBytes(bitWidth);
+
+            Criterion.Type criterionType = interpreter.criterionTypeMap().inverse().get(fieldName);
+
+            if (!extParamMap.containsKey(fieldName) &&
+                    (criterionType == null || selector.getCriterion(criterionType) == null)) {
+                // Neither an extension nor a mapping / criterion is available for this field.
+                switch (keyModel.matchType()) {
+                    case TERNARY:
+                        // Wildcard field
+                        matchKeyBuilder.withWildcard(byteWidth);
+                        break;
+                    case LPM:
+                        // LPM with prefix 0
+                        matchKeyBuilder.add(new Bmv2LpmMatchParam(ImmutableByteSequence.ofZeros(byteWidth), 0));
+                        break;
+                    default:
+                        throw new Bmv2FlowRuleTranslatorException("No value found for required match field "
+                                                                          + fieldName);
+                }
+                // Next key
+                continue keysLoop;
+            }
+
+            Bmv2MatchParam matchParam;
+
+            if (extParamMap.containsKey(fieldName)) {
+                // Parameter found in extension
+                if (criterionType != null && selector.getCriterion(criterionType) != null) {
+                    // Found also a criterion that can be mapped. This is bad.
+                    throw new Bmv2FlowRuleTranslatorException("Both an extension and a criterion mapping are defined " +
+                                                                      "for match field " + fieldName);
+                }
+
+                matchParam = extParamMap.get(fieldName);
+                usedExtParams.add(fieldName);
+
+                // Check parameter type and size
+                if (!keyModel.matchType().equals(matchParam.type())) {
+                    throw new Bmv2FlowRuleTranslatorException("Wrong match type for parameter " + fieldName
+                                                                      + ", expected " + keyModel.matchType().name()
+                                                                      + ", but found " + matchParam.type().name());
+                }
+                int foundByteWidth;
+                switch (keyModel.matchType()) {
+                    case EXACT:
+                        Bmv2ExactMatchParam m1 = (Bmv2ExactMatchParam) matchParam;
+                        foundByteWidth = m1.value().size();
+                        break;
+                    case TERNARY:
+                        Bmv2TernaryMatchParam m2 = (Bmv2TernaryMatchParam) matchParam;
+                        foundByteWidth = m2.value().size();
+                        break;
+                    case LPM:
+                        Bmv2LpmMatchParam m3 = (Bmv2LpmMatchParam) matchParam;
+                        foundByteWidth = m3.value().size();
+                        break;
+                    case VALID:
+                        foundByteWidth = -1;
+                        break;
+                    default:
+                        // should never be her
+                        throw new RuntimeException("Unrecognized match type " + keyModel.matchType().name());
+                }
+                if (foundByteWidth != -1 && foundByteWidth != byteWidth) {
+                    throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for match parameter " + fieldName
+                                                                      + ", expected " + byteWidth + ", but found "
+                                                                      + foundByteWidth);
+                }
+
+            } else {
+                // A criterion mapping is available for this key
+                Criterion criterion = selector.getCriterion(criterionType);
+                translatedCriteria.add(criterion);
+                switch (keyModel.matchType()) {
+                    case TERNARY:
+                        matchParam = buildTernaryParam(keyModel.field(), criterion, bitWidth);
+                        break;
+                    default:
+                        // TODO: implement other match param builders (exact, LPM, etc.)
+                        throw new Bmv2FlowRuleTranslatorException("Feature not yet implemented, match param builder: "
+                                                                          + keyModel.matchType().name());
+                }
+            }
+
+            matchKeyBuilder.add(matchParam);
+        }
+
+        // Check if all criteria have been translated
+        Set<Criterion> ignoredCriteria = selector.criteria()
+                .stream()
+                .filter(c -> !c.type().equals(EXTENSION))
+                .filter(c -> !translatedCriteria.contains(c))
+                .collect(Collectors.toSet());
+
+        if (ignoredCriteria.size() > 0) {
+            throw new Bmv2FlowRuleTranslatorException("The following criteria cannot be translated for table "
+                                                              + tableModel.name() + ": " + ignoredCriteria.toString());
+        }
+
+        // Check is all extension parameters have been used
+        Set<String> ignoredExtParams = extParamMap.keySet()
+                .stream()
+                .filter(k -> !usedExtParams.contains(k))
+                .collect(Collectors.toSet());
+
+        if (ignoredExtParams.size() > 0) {
+            throw new Bmv2FlowRuleTranslatorException("The following extension match parameters cannot be used for " +
+                                                              "table " + tableModel.name() + ": "
+                                                              + ignoredExtParams.toString());
+        }
+
+        return matchKeyBuilder.build();
+    }
+
+}
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TExceptionParser.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TExceptionParser.java
new file mode 100644
index 0000000..6ce3fbd
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TExceptionParser.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+
+import org.apache.thrift.TException;
+import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
+import org.onosproject.bmv2.thriftapi.InvalidCounterOperation;
+import org.onosproject.bmv2.thriftapi.InvalidDevMgrOperation;
+import org.onosproject.bmv2.thriftapi.InvalidLearnOperation;
+import org.onosproject.bmv2.thriftapi.InvalidMcOperation;
+import org.onosproject.bmv2.thriftapi.InvalidMeterOperation;
+import org.onosproject.bmv2.thriftapi.InvalidRegisterOperation;
+import org.onosproject.bmv2.thriftapi.InvalidSwapOperation;
+import org.onosproject.bmv2.thriftapi.InvalidTableOperation;
+
+import static org.onosproject.bmv2.api.runtime.Bmv2RuntimeException.Code;
+
+/**
+ * Utility class to translate a Thrift exception into a Bmv2RuntimeException.
+ */
+final class Bmv2TExceptionParser {
+
+    private Bmv2TExceptionParser() {
+        // ban constructor.
+    }
+
+    static Bmv2RuntimeException parseTException(TException cause) {
+        try {
+            return new Bmv2RuntimeException(getCode(cause));
+        } catch (ParserException e) {
+            return new Bmv2RuntimeException(e.codeString);
+        }
+    }
+
+    private static Code getCode(TException e) throws ParserException {
+        if (e instanceof InvalidTableOperation) {
+            InvalidTableOperation t = (InvalidTableOperation) e;
+            switch (t.getCode()) {
+                case TABLE_FULL:
+                    return Code.TABLE_FULL;
+                case INVALID_HANDLE:
+                    return Code.TABLE_INVALID_HANDLE;
+                case EXPIRED_HANDLE:
+                    return Code.TABLE_EXPIRED_HANDLE;
+                case COUNTERS_DISABLED:
+                    return Code.TABLE_COUNTERS_DISABLED;
+                case METERS_DISABLED:
+                    return Code.TABLE_METERS_DISABLED;
+                case AGEING_DISABLED:
+                    return Code.TABLE_AGEING_DISABLED;
+                case INVALID_TABLE_NAME:
+                    return Code.TABLE_INVALID_TABLE_NAME;
+                case INVALID_ACTION_NAME:
+                    return Code.TABLE_INVALID_ACTION_NAME;
+                case WRONG_TABLE_TYPE:
+                    return Code.TABLE_WRONG_TABLE_TYPE;
+                case INVALID_MBR_HANDLE:
+                    return Code.TABLE_INVALID_MBR_HANDLE;
+                case MBR_STILL_USED:
+                    return Code.TABLE_MBR_STILL_USED;
+                case MBR_ALREADY_IN_GRP:
+                    return Code.TABLE_MBR_ALREADY_IN_GRP;
+                case MBR_NOT_IN_GRP:
+                    return Code.TABLE_MBR_NOT_IN_GRP;
+                case INVALID_GRP_HANDLE:
+                    return Code.TABLE_INVALID_GRP_HANDLE;
+                case GRP_STILL_USED:
+                    return Code.TABLE_GRP_STILL_USED;
+                case EMPTY_GRP:
+                    return Code.TABLE_EMPTY_GRP;
+                case DUPLICATE_ENTRY:
+                    return Code.TABLE_DUPLICATE_ENTRY;
+                case BAD_MATCH_KEY:
+                    return Code.TABLE_BAD_MATCH_KEY;
+                case INVALID_METER_OPERATION:
+                    return Code.TABLE_INVALID_METER_OPERATION;
+                case DEFAULT_ACTION_IS_CONST:
+                    return Code.TABLE_DEFAULT_ACTION_IS_CONST;
+                case DEFAULT_ENTRY_IS_CONST:
+                    return Code.TABLE_DEFAULT_ENTRY_IS_CONST;
+                case ERROR:
+                    return Code.TABLE_GENERAL_ERROR;
+                default:
+                    return Code.TABLE_UNKNOWN_ERROR;
+            }
+        } else if (e instanceof InvalidCounterOperation) {
+            InvalidCounterOperation t = (InvalidCounterOperation) e;
+            switch (t.getCode()) {
+                case INVALID_COUNTER_NAME:
+                    return Code.COUNTER_INVALID_NAME;
+                case INVALID_INDEX:
+                    return Code.COUNTER_INVALID_INDEX;
+                case ERROR:
+                    return Code.COUNTER_ERROR_GENERAL;
+                default:
+                    return Code.COUNTER_ERROR_UNKNOWN;
+            }
+        } else if (e instanceof InvalidDevMgrOperation) {
+            InvalidDevMgrOperation t = (InvalidDevMgrOperation) e;
+            switch (t.getCode()) {
+                case ERROR:
+                    return Code.DEV_MGR_ERROR_GENERAL;
+                default:
+                    return Code.DEV_MGR_UNKNOWN;
+            }
+        } else if (e instanceof InvalidSwapOperation) {
+            InvalidSwapOperation t = (InvalidSwapOperation) e;
+            switch (t.getCode()) {
+                case CONFIG_SWAP_DISABLED:
+                    return Code.SWAP_CONFIG_DISABLED;
+                case ONGOING_SWAP:
+                    return Code.SWAP_ONGOING;
+                case NO_ONGOING_SWAP:
+                    return Code.SWAP_NO_ONGOING;
+                default:
+                    return Code.SWAP_ERROR_UKNOWN;
+            }
+        } else if (e instanceof InvalidMeterOperation) {
+            // TODO
+            throw new ParserException(e.toString());
+        } else if (e instanceof InvalidRegisterOperation) {
+            // TODO
+            throw new ParserException(e.toString());
+        } else if (e instanceof InvalidLearnOperation) {
+            // TODO
+            throw new ParserException(e.toString());
+        } else if (e instanceof InvalidMcOperation) {
+            // TODO
+            throw new ParserException(e.toString());
+        } else {
+            throw new ParserException(e.toString());
+        }
+    }
+
+    private static class ParserException extends Exception {
+
+        private String codeString;
+
+        public ParserException(String codeString) {
+            this.codeString = codeString;
+        }
+    }
+}
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParser.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParser.java
new file mode 100644
index 0000000..04e37e5
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParser.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.CacheStats;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.util.HexString;
+import org.onlab.util.ImmutableByteSequence;
+import org.onlab.util.SharedScheduledExecutors;
+import org.onosproject.bmv2.api.context.Bmv2ActionModel;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.runtime.Bmv2Action;
+import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
+import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.ByteSequenceFitException;
+
+/**
+ * BMv2 table dump parser.
+ */
+public final class Bmv2TableDumpParser {
+
+    // Examples of a BMv2 table dump can be found in Bmv2TableDumpParserTest
+
+    // 1: entry id, 2: match string, 3: action string
+    private static final String ENTRY_PATTERN_REGEX = "(\\d+): (.*) => (.*)";
+    // 1: match values, 2: masks
+    private static final String MATCH_TERNARY_PATTERN_REGEX = "([0-9a-fA-F ]+) &&& ([0-9a-fA-F ]+)";
+    // 1: match values, 2: masks
+    private static final String MATCH_LPM_PATTERN_REGEX = "([0-9a-fA-F ]+) / ([0-9a-fA-F ]+)";
+    // 1: match values
+    private static final String MATCH_EXACT_PATTERN_REGEX = "([0-9a-fA-F ]+)";
+    // 1: action name, 2: action params
+    private static final String ACTION_PATTERN_REGEX = "(.+) - ?([0-9a-fA-F ,]*)";
+
+    private static final Pattern ENTRY_PATTERN = Pattern.compile(ENTRY_PATTERN_REGEX);
+    private static final Pattern MATCH_TERNARY_PATTERN = Pattern.compile(MATCH_TERNARY_PATTERN_REGEX);
+    private static final Pattern MATCH_LPM_PATTERN = Pattern.compile(MATCH_LPM_PATTERN_REGEX);
+    private static final Pattern MATCH_EXACT_PATTERN = Pattern.compile(MATCH_EXACT_PATTERN_REGEX);
+    private static final Pattern ACTION_PATTERN = Pattern.compile(ACTION_PATTERN_REGEX);
+
+    // Cache to avoid re-parsing known lines.
+    // The assumption here is that entries are not updated too frequently, so that the entry id doesn't change often.
+    // Otherwise, we should cache only the match and action strings...
+    private static final LoadingCache<Pair<String, Bmv2Configuration>, Optional<Bmv2ParsedTableEntry>> ENTRY_CACHE =
+            CacheBuilder.newBuilder()
+            .expireAfterAccess(60, TimeUnit.SECONDS)
+            .recordStats()
+            .build(new CacheLoader<Pair<String, Bmv2Configuration>, Optional<Bmv2ParsedTableEntry>>() {
+                @Override
+                public Optional<Bmv2ParsedTableEntry> load(Pair<String, Bmv2Configuration> key) throws Exception {
+                    // Very expensive call.
+                    return Optional.ofNullable(parseLine(key.getLeft(), key.getRight()));
+                }
+            });
+
+    private static final Logger log = LoggerFactory.getLogger(Bmv2TableDumpParser.class);
+
+    private static final long STATS_LOG_FREQUENCY = 3; // minutes
+
+    static {
+        SharedScheduledExecutors.getSingleThreadExecutor().scheduleAtFixedRate(
+                () -> reportStats(), 0, STATS_LOG_FREQUENCY, TimeUnit.MINUTES);
+    }
+
+    private Bmv2TableDumpParser() {
+        // Ban constructor.
+    }
+
+    /**
+     * Parse the given BMv2 table dump.
+     *
+     * @param tableDump a string value
+     * @return a list of {@link Bmv2ParsedTableEntry}
+     */
+    public static List<Bmv2ParsedTableEntry> parse(String tableDump, Bmv2Configuration configuration) {
+        checkNotNull(tableDump, "tableDump cannot be null");
+        // Parse all lines
+        List<Bmv2ParsedTableEntry> result = Arrays.stream(tableDump.split("\n"))
+                .map(line -> Pair.of(line, configuration))
+                .map(Bmv2TableDumpParser::loadFromCache)
+                .filter(Optional::isPresent)
+                .map(Optional::get)
+                .collect(Collectors.toList());
+        return result;
+    }
+
+    private static Optional<Bmv2ParsedTableEntry> loadFromCache(Pair<String, Bmv2Configuration> key) {
+        try {
+            return ENTRY_CACHE.get(key);
+        } catch (ExecutionException e) {
+            Throwable t = e.getCause();
+            if (t instanceof Bmv2TableDumpParserException) {
+                Bmv2TableDumpParserException parserException = (Bmv2TableDumpParserException) t;
+                log.warn("{}", parserException.getMessage());
+            } else {
+                log.error("Exception while parsing table dump line", e);
+            }
+            return Optional.empty();
+        }
+    }
+
+    private static void reportStats() {
+        CacheStats stats = ENTRY_CACHE.stats();
+        log.info("Cache stats: requestCount={}, hitRate={}, exceptionsCount={}, avgLoadPenalty={}",
+                 stats.requestCount(), stats.hitRate(), stats.loadExceptionCount(), stats.averageLoadPenalty());
+    }
+
+    private static Bmv2ParsedTableEntry parseLine(String line, Bmv2Configuration configuration)
+            throws Bmv2TableDumpParserException {
+        Matcher matcher = ENTRY_PATTERN.matcher(line);
+        if (matcher.find()) {
+            long entryId = parseEntryId(matcher, 1);
+            String matchString = parseMatchString(matcher, 2);
+            String actionString = parseActionString(matcher, 3);
+            Bmv2MatchKey matchKey = parseMatchKey(matchString);
+            Bmv2Action action = parseAction(actionString, configuration);
+            return new Bmv2ParsedTableEntry(entryId, matchKey, action);
+        } else {
+            // Not a table entry
+            return null;
+        }
+    }
+
+    private static Long parseEntryId(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
+        String str = matcher.group(groupIdx);
+        if (str == null) {
+            throw new Bmv2TableDumpParserException("Unable to find entry ID: " + matcher.group());
+        }
+        long entryId;
+        try {
+            entryId = Long.valueOf(str.trim());
+        } catch (NumberFormatException e) {
+            throw new Bmv2TableDumpParserException("Unable to parse entry id for string: " + matcher.group());
+        }
+        return entryId;
+    }
+
+    private static String parseMatchString(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
+        String str = matcher.group(groupIdx);
+        if (str == null) {
+            throw new Bmv2TableDumpParserException("Unable to find match string: " + matcher.group());
+        }
+        return str.trim();
+    }
+
+    private static String parseActionString(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
+        String str = matcher.group(groupIdx);
+        if (str == null) {
+            throw new Bmv2TableDumpParserException("Unable to find action string: " + matcher.group());
+        }
+        return str.trim();
+    }
+
+    private static Bmv2MatchKey parseMatchKey(String str) throws Bmv2TableDumpParserException {
+
+        Bmv2MatchKey.Builder builder = Bmv2MatchKey.builder();
+
+        // Try with ternary...
+        Matcher matcher = MATCH_TERNARY_PATTERN.matcher(str);
+        if (matcher.find()) {
+            // Ternary Match.
+            List<ImmutableByteSequence> values = parseMatchValues(matcher, 1);
+            List<ImmutableByteSequence> masks = parseMatchMasks(matcher, 2, values);
+            for (int i = 0; i < values.size(); i++) {
+                builder.add(new Bmv2TernaryMatchParam(values.get(i), masks.get(i)));
+            }
+            return builder.build();
+        }
+
+        // FIXME: LPM match parsing broken if table key contains also a ternary match
+        // Also it assumes the lpm parameter is the last one, which is wrong.
+        // Try with LPM...
+        matcher = MATCH_LPM_PATTERN.matcher(str);
+        if (matcher.find()) {
+            // Lpm Match.
+            List<ImmutableByteSequence> values = parseMatchValues(matcher, 1);
+            int prefixLength = parseLpmPrefix(matcher, 2);
+            for (int i = 0; i < values.size() - 1; i++) {
+                builder.add(new Bmv2ExactMatchParam(values.get(i)));
+            }
+            builder.add(new Bmv2LpmMatchParam(values.get(values.size() - 1), prefixLength));
+            return builder.build();
+        }
+
+        // Try with exact...
+        matcher = MATCH_EXACT_PATTERN.matcher(str);
+        if (matcher.find()) {
+            // Exact match.
+            parseMatchValues(matcher, 1)
+                    .stream()
+                    .map(Bmv2ExactMatchParam::new)
+                    .forEach(builder::add);
+            return builder.build();
+        }
+
+        throw new Bmv2TableDumpParserException("Unable to parse match string: " + str);
+    }
+
+    private static List<ImmutableByteSequence> parseMatchValues(Matcher matcher, int groupIdx)
+            throws Bmv2TableDumpParserException {
+        String matchString = matcher.group(groupIdx);
+        if (matchString == null) {
+            throw new Bmv2TableDumpParserException("Unable to find match params for string: " + matcher.group());
+        }
+        List<ImmutableByteSequence> result = Lists.newArrayList();
+        for (String paramString : matchString.split(" ")) {
+            byte[] bytes = HexString.fromHexString(paramString, null);
+            result.add(ImmutableByteSequence.copyFrom(bytes));
+        }
+        return result;
+    }
+
+    private static List<ImmutableByteSequence> parseMatchMasks(Matcher matcher, int groupIdx,
+                                                               List<ImmutableByteSequence> matchParams)
+            throws Bmv2TableDumpParserException {
+        String maskString = matcher.group(groupIdx);
+        if (maskString == null) {
+            throw new Bmv2TableDumpParserException("Unable to find mask for string: " + matcher.group());
+        }
+        List<ImmutableByteSequence> result = Lists.newArrayList();
+        /*
+        Mask here is a hex string with no spaces, hence individual mask params can be derived according
+        to given matchParam sizes.
+         */
+        byte[] maskBytes = HexString.fromHexString(maskString, null);
+        int startPosition = 0;
+        for (ImmutableByteSequence bs : matchParams) {
+            if (startPosition + bs.size() > maskBytes.length) {
+                throw new Bmv2TableDumpParserException("Invalid length for mask in string: " + matcher.group());
+            }
+            ImmutableByteSequence maskParam = ImmutableByteSequence.copyFrom(maskBytes,
+                                                                             startPosition,
+                                                                             startPosition + bs.size() - 1);
+            result.add(maskParam);
+            startPosition += bs.size();
+        }
+        return result;
+    }
+
+    private static int parseLpmPrefix(Matcher matcher, int groupIdx)
+            throws Bmv2TableDumpParserException {
+        String str = matcher.group(groupIdx);
+        if (str == null) {
+            throw new Bmv2TableDumpParserException("Unable to find LPM prefix for string: " + matcher.group());
+        }
+        // For some reason the dumped prefix has 16 bits more than the one programmed
+        try {
+            return Integer.valueOf(str.trim()) - 16;
+        } catch (NumberFormatException e) {
+            throw new Bmv2TableDumpParserException("Unable to parse LPM prefix from string: " + matcher.group());
+        }
+    }
+
+    private static Bmv2Action parseAction(String str, Bmv2Configuration configuration)
+            throws Bmv2TableDumpParserException {
+        Matcher matcher = ACTION_PATTERN.matcher(str);
+        if (matcher.find()) {
+            String actionName = parseActionName(matcher, 1);
+            Bmv2ActionModel actionModel = configuration.action(actionName);
+            if (actionModel == null) {
+                throw new Bmv2TableDumpParserException("Not such an action in configuration: " + actionName);
+            }
+            Bmv2Action.Builder builder = Bmv2Action.builder().withName(actionName);
+            List<ImmutableByteSequence> actionParams = parseActionParams(matcher, 2);
+            if (actionParams.size() != actionModel.runtimeDatas().size()) {
+                throw new Bmv2TableDumpParserException("Invalid number of parameters for action: " + actionName);
+            }
+            for (int i = 0; i < actionModel.runtimeDatas().size(); i++) {
+                try {
+                    // fit param byte-width according to configuration.
+                    builder.addParameter(fitByteSequence(actionParams.get(i),
+                                                         actionModel.runtimeDatas().get(i).bitWidth()));
+                } catch (ByteSequenceFitException e) {
+                    throw new Bmv2TableDumpParserException("Unable to parse action param: " + e.toString());
+                }
+            }
+            return builder.build();
+        }
+        throw new Bmv2TableDumpParserException("Unable to parse action string: " + str.trim());
+    }
+
+    private static String parseActionName(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
+        String actionName = matcher.group(groupIdx);
+        if (actionName == null) {
+            throw new Bmv2TableDumpParserException("Unable to find action name for string: " + matcher.group());
+        }
+        return actionName.trim();
+    }
+
+    private static List<ImmutableByteSequence> parseActionParams(Matcher matcher, int groupIdx)
+            throws Bmv2TableDumpParserException {
+        String paramsString = matcher.group(groupIdx);
+        if (paramsString == null) {
+            throw new Bmv2TableDumpParserException("Unable to find action params for string: " + matcher.group());
+        }
+        if (paramsString.length() == 0) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(paramsString.split(","))
+                .map(String::trim)
+                .map(s -> HexString.fromHexString(s, null))
+                .map(ImmutableByteSequence::copyFrom)
+                .collect(Collectors.toList());
+    }
+
+    public static class Bmv2TableDumpParserException extends Exception {
+        public Bmv2TableDumpParserException(String msg) {
+            super(msg);
+        }
+    }
+}
\ No newline at end of file
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableEntryServiceImpl.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableEntryServiceImpl.java
new file mode 100644
index 0000000..8258334
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableEntryServiceImpl.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
+import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
+import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
+import org.onosproject.bmv2.api.service.Bmv2Controller;
+import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
+import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2FlowRuleWrapper;
+import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
+import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntryReference;
+import org.onosproject.bmv2.api.service.Bmv2TableEntryService;
+import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
+import org.onosproject.net.DeviceId;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of the Bmv2TableEntryService.
+ */
+@Component(immediate = true)
+@Service
+public class Bmv2TableEntryServiceImpl implements Bmv2TableEntryService {
+
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    private final Bmv2FlowRuleTranslator translator = new Bmv2FlowRuleTranslatorImpl();
+
+    private EventuallyConsistentMap<Bmv2TableEntryReference, Bmv2FlowRuleWrapper> flowRules;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected Bmv2Controller controller;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected Bmv2DeviceContextService contextService;
+
+
+    @Activate
+    public void activate() {
+        KryoNamespace kryo = new KryoNamespace.Builder()
+                .register(KryoNamespaces.API)
+                .register(Bmv2TableEntryReference.class)
+                .register(Bmv2MatchKey.class)
+                .register(Bmv2ExactMatchParam.class)
+                .register(Bmv2TernaryMatchParam.class)
+                .register(Bmv2LpmMatchParam.class)
+                .register(Bmv2ValidMatchParam.class)
+                .register(Bmv2FlowRuleWrapper.class)
+                .build();
+
+        flowRules = storageService.<Bmv2TableEntryReference, Bmv2FlowRuleWrapper>eventuallyConsistentMapBuilder()
+                .withSerializer(kryo)
+                .withTimestampProvider((k, v) -> new WallClockTimestamp())
+                .withName("onos-bmv2-flowrules")
+                .build();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public Bmv2FlowRuleTranslator getFlowRuleTranslator() {
+        return translator;
+    }
+
+    @Override
+    public List<Bmv2ParsedTableEntry> getTableEntries(DeviceId deviceId, String tableName) {
+        try {
+            Bmv2DeviceContext context = contextService.getContext(deviceId);
+            if (context == null) {
+                log.warn("Unable to get table entries, found null context for {}", deviceId);
+                return Collections.emptyList();
+            }
+            Bmv2DeviceAgent agent = controller.getAgent(deviceId);
+            String tableDump = agent.dumpTable(tableName);
+            return Bmv2TableDumpParser.parse(tableDump, context.configuration());
+        } catch (Bmv2RuntimeException e) {
+            log.warn("Unable to get table entries for {}: {}", deviceId, e.explain());
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public Bmv2FlowRuleWrapper lookupEntryReference(Bmv2TableEntryReference entryRef) {
+        checkNotNull(entryRef, "table entry reference cannot be null");
+        return flowRules.get(entryRef);
+    }
+
+    @Override
+    public void bindEntryReference(Bmv2TableEntryReference entryRef, Bmv2FlowRuleWrapper rule) {
+        checkNotNull(entryRef, "table entry reference cannot be null");
+        checkNotNull(rule, "bmv2 flow rule cannot be null");
+        flowRules.put(entryRef, rule);
+    }
+
+    @Override
+    public void unbindEntryReference(Bmv2TableEntryReference entryRef) {
+        checkNotNull(entryRef, "table entry reference cannot be null");
+        flowRules.remove(entryRef);
+    }
+}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/SafeThriftClient.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/SafeThriftClient.java
similarity index 94%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/SafeThriftClient.java
rename to protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/SafeThriftClient.java
index 98813f9..f6e4813 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/SafeThriftClient.java
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/SafeThriftClient.java
@@ -218,8 +218,8 @@
             // Thrift transport layer is not thread-safe (it's a wrapper on a socket), hence we need locking.
             synchronized (transport) {
 
-                LOG.debug("Invoking client method... > method={}, fromThread={}",
-                          method.getName(), Thread.currentThread().getId());
+                LOG.debug("Invoking method... > fromThread={}, method={}, args={}",
+                          Thread.currentThread().getId(), method.getName(), args);
 
                 try {
 
@@ -235,15 +235,13 @@
                                 // If here, transport has been successfully open, hence new exceptions will be thrown.
                                 return method.invoke(baseClient, args);
                             } catch (InvocationTargetException e1) {
-                                LOG.debug("Exception while invoking client method: {} > method={}, fromThread={}",
-                                          e1, method.getName(), Thread.currentThread().getId());
+                                LOG.debug("Exception: {}", e1.getTargetException());
                                 throw e1.getTargetException();
                             }
                         }
                     }
                     // Target exception is neither a TTransportException nor a restartable cause.
-                    LOG.debug("Exception while invoking client method: {} > method={}, fromThread={}",
-                              e, method.getName(), Thread.currentThread().getId());
+                    LOG.debug("Exception: {}", e.getTargetException());
                     throw e.getTargetException();
                 }
             }
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/package-info.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/package-info.java
similarity index 92%
rename from protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/package-info.java
rename to protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/package-info.java
index 9e79ae9..dcd9a28 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/package-info.java
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2016 Open Networking Laboratory
+ * Copyright 2016-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.
diff --git a/protocols/bmv2/ctl/src/main/resources/default.json b/protocols/bmv2/ctl/src/main/resources/default.json
new file mode 100644
index 0000000..abc0452
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/resources/default.json
@@ -0,0 +1,730 @@
+{
+    "header_types": [
+        {
+            "name": "standard_metadata_t",
+            "id": 0,
+            "fields": [
+                [
+                    "ingress_port",
+                    9
+                ],
+                [
+                    "packet_length",
+                    32
+                ],
+                [
+                    "egress_spec",
+                    9
+                ],
+                [
+                    "egress_port",
+                    9
+                ],
+                [
+                    "egress_instance",
+                    32
+                ],
+                [
+                    "instance_type",
+                    32
+                ],
+                [
+                    "clone_spec",
+                    32
+                ],
+                [
+                    "_padding",
+                    5
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        },
+        {
+            "name": "intrinsic_metadata_t",
+            "id": 1,
+            "fields": [
+                [
+                    "ingress_global_timestamp",
+                    32
+                ],
+                [
+                    "lf_field_list",
+                    32
+                ],
+                [
+                    "mcast_grp",
+                    16
+                ],
+                [
+                    "egress_rid",
+                    16
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        },
+        {
+            "name": "ethernet_t",
+            "id": 2,
+            "fields": [
+                [
+                    "dstAddr",
+                    48
+                ],
+                [
+                    "srcAddr",
+                    48
+                ],
+                [
+                    "etherType",
+                    16
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        },
+        {
+            "name": "ipv4_t",
+            "id": 3,
+            "fields": [
+                [
+                    "version",
+                    4
+                ],
+                [
+                    "ihl",
+                    4
+                ],
+                [
+                    "diffserv",
+                    8
+                ],
+                [
+                    "totalLen",
+                    16
+                ],
+                [
+                    "identification",
+                    16
+                ],
+                [
+                    "flags",
+                    3
+                ],
+                [
+                    "fragOffset",
+                    13
+                ],
+                [
+                    "ttl",
+                    8
+                ],
+                [
+                    "protocol",
+                    8
+                ],
+                [
+                    "hdrChecksum",
+                    16
+                ],
+                [
+                    "srcAddr",
+                    32
+                ],
+                [
+                    "dstAddr",
+                    32
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        },
+        {
+            "name": "tcp_t",
+            "id": 4,
+            "fields": [
+                [
+                    "srcPort",
+                    16
+                ],
+                [
+                    "dstPort",
+                    16
+                ],
+                [
+                    "seqNo",
+                    32
+                ],
+                [
+                    "ackNo",
+                    32
+                ],
+                [
+                    "dataOffset",
+                    4
+                ],
+                [
+                    "res",
+                    3
+                ],
+                [
+                    "ecn",
+                    3
+                ],
+                [
+                    "ctrl",
+                    6
+                ],
+                [
+                    "window",
+                    16
+                ],
+                [
+                    "checksum",
+                    16
+                ],
+                [
+                    "urgentPtr",
+                    16
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        },
+        {
+            "name": "udp_t",
+            "id": 5,
+            "fields": [
+                [
+                    "srcPort",
+                    16
+                ],
+                [
+                    "dstPort",
+                    16
+                ],
+                [
+                    "length_",
+                    16
+                ],
+                [
+                    "checksum",
+                    16
+                ]
+            ],
+            "length_exp": null,
+            "max_length": null
+        }
+    ],
+    "headers": [
+        {
+            "name": "standard_metadata",
+            "id": 0,
+            "header_type": "standard_metadata_t",
+            "metadata": true
+        },
+        {
+            "name": "intrinsic_metadata",
+            "id": 1,
+            "header_type": "intrinsic_metadata_t",
+            "metadata": true
+        },
+        {
+            "name": "ethernet",
+            "id": 2,
+            "header_type": "ethernet_t",
+            "metadata": false
+        },
+        {
+            "name": "ipv4",
+            "id": 3,
+            "header_type": "ipv4_t",
+            "metadata": false
+        },
+        {
+            "name": "tcp",
+            "id": 4,
+            "header_type": "tcp_t",
+            "metadata": false
+        },
+        {
+            "name": "udp",
+            "id": 5,
+            "header_type": "udp_t",
+            "metadata": false
+        }
+    ],
+    "header_stacks": [],
+    "parsers": [
+        {
+            "name": "parser",
+            "id": 0,
+            "init_state": "start",
+            "parse_states": [
+                {
+                    "name": "start",
+                    "id": 0,
+                    "parser_ops": [],
+                    "transition_key": [],
+                    "transitions": [
+                        {
+                            "value": "default",
+                            "mask": null,
+                            "next_state": "parse_ethernet"
+                        }
+                    ]
+                },
+                {
+                    "name": "parse_ethernet",
+                    "id": 1,
+                    "parser_ops": [
+                        {
+                            "op": "extract",
+                            "parameters": [
+                                {
+                                    "type": "regular",
+                                    "value": "ethernet"
+                                }
+                            ]
+                        }
+                    ],
+                    "transition_key": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "ethernet",
+                                "etherType"
+                            ]
+                        }
+                    ],
+                    "transitions": [
+                        {
+                            "value": "0x0800",
+                            "mask": null,
+                            "next_state": "parse_ipv4"
+                        },
+                        {
+                            "value": "default",
+                            "mask": null,
+                            "next_state": null
+                        }
+                    ]
+                },
+                {
+                    "name": "parse_ipv4",
+                    "id": 2,
+                    "parser_ops": [
+                        {
+                            "op": "extract",
+                            "parameters": [
+                                {
+                                    "type": "regular",
+                                    "value": "ipv4"
+                                }
+                            ]
+                        }
+                    ],
+                    "transition_key": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "ipv4",
+                                "fragOffset"
+                            ]
+                        },
+                        {
+                            "type": "field",
+                            "value": [
+                                "ipv4",
+                                "protocol"
+                            ]
+                        }
+                    ],
+                    "transitions": [
+                        {
+                            "value": "0x000006",
+                            "mask": null,
+                            "next_state": "parse_tcp"
+                        },
+                        {
+                            "value": "0x000011",
+                            "mask": null,
+                            "next_state": "parse_udp"
+                        },
+                        {
+                            "value": "default",
+                            "mask": null,
+                            "next_state": null
+                        }
+                    ]
+                },
+                {
+                    "name": "parse_tcp",
+                    "id": 3,
+                    "parser_ops": [
+                        {
+                            "op": "extract",
+                            "parameters": [
+                                {
+                                    "type": "regular",
+                                    "value": "tcp"
+                                }
+                            ]
+                        }
+                    ],
+                    "transition_key": [],
+                    "transitions": [
+                        {
+                            "value": "default",
+                            "mask": null,
+                            "next_state": null
+                        }
+                    ]
+                },
+                {
+                    "name": "parse_udp",
+                    "id": 4,
+                    "parser_ops": [
+                        {
+                            "op": "extract",
+                            "parameters": [
+                                {
+                                    "type": "regular",
+                                    "value": "udp"
+                                }
+                            ]
+                        }
+                    ],
+                    "transition_key": [],
+                    "transitions": [
+                        {
+                            "value": "default",
+                            "mask": null,
+                            "next_state": null
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "deparsers": [
+        {
+            "name": "deparser",
+            "id": 0,
+            "order": [
+                "ethernet",
+                "ipv4",
+                "udp",
+                "tcp"
+            ]
+        }
+    ],
+    "meter_arrays": [],
+    "actions": [
+        {
+            "name": "_drop",
+            "id": 0,
+            "runtime_data": [],
+            "primitives": [
+                {
+                    "op": "modify_field",
+                    "parameters": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "egress_spec"
+                            ]
+                        },
+                        {
+                            "type": "hexstr",
+                            "value": "0x1ff"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "count_packet",
+            "id": 1,
+            "runtime_data": [],
+            "primitives": [
+                {
+                    "op": "count",
+                    "parameters": [
+                        {
+                            "type": "counter_array",
+                            "value": "ingress_port_counter"
+                        },
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "ingress_port"
+                            ]
+                        }
+                    ]
+                },
+                {
+                    "op": "count",
+                    "parameters": [
+                        {
+                            "type": "counter_array",
+                            "value": "egress_port_counter"
+                        },
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "egress_spec"
+                            ]
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "send_to_cpu",
+            "id": 2,
+            "runtime_data": [],
+            "primitives": [
+                {
+                    "op": "modify_field",
+                    "parameters": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "egress_spec"
+                            ]
+                        },
+                        {
+                            "type": "hexstr",
+                            "value": "0xff"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "set_egress_port",
+            "id": 3,
+            "runtime_data": [
+                {
+                    "name": "port",
+                    "bitwidth": 9
+                }
+            ],
+            "primitives": [
+                {
+                    "op": "modify_field",
+                    "parameters": [
+                        {
+                            "type": "field",
+                            "value": [
+                                "standard_metadata",
+                                "egress_spec"
+                            ]
+                        },
+                        {
+                            "type": "runtime_data",
+                            "value": 0
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "pipelines": [
+        {
+            "name": "ingress",
+            "id": 0,
+            "init_table": "table0",
+            "tables": [
+                {
+                    "name": "port_count_table",
+                    "id": 0,
+                    "match_type": "exact",
+                    "type": "simple",
+                    "max_size": 16384,
+                    "with_counters": false,
+                    "direct_meters": null,
+                    "support_timeout": false,
+                    "key": [],
+                    "actions": [
+                        "count_packet"
+                    ],
+                    "next_tables": {
+                        "count_packet": null
+                    },
+                    "default_action": null,
+                    "base_default_next": null
+                },
+                {
+                    "name": "table0",
+                    "id": 1,
+                    "match_type": "ternary",
+                    "type": "simple",
+                    "max_size": 16384,
+                    "with_counters": true,
+                    "direct_meters": null,
+                    "support_timeout": true,
+                    "key": [
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "standard_metadata",
+                                "ingress_port"
+                            ],
+                            "mask": null
+                        },
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "ethernet",
+                                "dstAddr"
+                            ],
+                            "mask": null
+                        },
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "ethernet",
+                                "srcAddr"
+                            ],
+                            "mask": null
+                        },
+                        {
+                            "match_type": "ternary",
+                            "target": [
+                                "ethernet",
+                                "etherType"
+                            ],
+                            "mask": null
+                        }
+                    ],
+                    "actions": [
+                        "set_egress_port",
+                        "send_to_cpu",
+                        "_drop"
+                    ],
+                    "next_tables": {
+                        "set_egress_port": "_condition_0",
+                        "send_to_cpu": "_condition_0",
+                        "_drop": "_condition_0"
+                    },
+                    "default_action": null,
+                    "base_default_next": "_condition_0"
+                }
+            ],
+            "conditionals": [
+                {
+                    "name": "_condition_0",
+                    "id": 0,
+                    "expression": {
+                        "type": "expression",
+                        "value": {
+                            "op": "<",
+                            "left": {
+                                "type": "field",
+                                "value": [
+                                    "standard_metadata",
+                                    "egress_spec"
+                                ]
+                            },
+                            "right": {
+                                "type": "hexstr",
+                                "value": "0xfe"
+                            }
+                        }
+                    },
+                    "true_next": "port_count_table",
+                    "false_next": null
+                }
+            ]
+        },
+        {
+            "name": "egress",
+            "id": 1,
+            "init_table": null,
+            "tables": [],
+            "conditionals": []
+        }
+    ],
+    "calculations": [],
+    "checksums": [],
+    "learn_lists": [],
+    "field_lists": [],
+    "counter_arrays": [
+        {
+            "name": "ingress_port_counter",
+            "id": 0,
+            "is_direct": false,
+            "size": 254
+        },
+        {
+            "name": "egress_port_counter",
+            "id": 1,
+            "is_direct": false,
+            "size": 254
+        },
+        {
+            "name": "table0_counter",
+            "id": 2,
+            "is_direct": true,
+            "binding": "table0"
+        }
+    ],
+    "register_arrays": [],
+    "force_arith": [
+        [
+            "standard_metadata",
+            "ingress_port"
+        ],
+        [
+            "standard_metadata",
+            "packet_length"
+        ],
+        [
+            "standard_metadata",
+            "egress_spec"
+        ],
+        [
+            "standard_metadata",
+            "egress_port"
+        ],
+        [
+            "standard_metadata",
+            "egress_instance"
+        ],
+        [
+            "standard_metadata",
+            "instance_type"
+        ],
+        [
+            "standard_metadata",
+            "clone_spec"
+        ],
+        [
+            "standard_metadata",
+            "_padding"
+        ],
+        [
+            "intrinsic_metadata",
+            "ingress_global_timestamp"
+        ],
+        [
+            "intrinsic_metadata",
+            "lf_field_list"
+        ],
+        [
+            "intrinsic_metadata",
+            "mcast_grp"
+        ],
+        [
+            "intrinsic_metadata",
+            "egress_rid"
+        ]
+    ]
+}
\ No newline at end of file
diff --git a/protocols/bmv2/ctl/src/test/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImplTest.java b/protocols/bmv2/ctl/src/test/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImplTest.java
new file mode 100644
index 0000000..41a774f
--- /dev/null
+++ b/protocols/bmv2/ctl/src/test/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImplTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
+import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
+import org.onosproject.bmv2.api.context.Bmv2Interpreter;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.Random;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onosproject.bmv2.ctl.Bmv2DefaultInterpreterImpl.TABLE0;
+
+/**
+ * Tests for {@link Bmv2FlowRuleTranslatorImpl}.
+ */
+public class Bmv2FlowRuleTranslatorImplTest {
+
+    private Random random = new Random();
+    private Bmv2Configuration configuration = Bmv2DeviceContextServiceImpl.loadDefaultConfiguration();
+    private Bmv2Interpreter interpreter = new Bmv2DefaultInterpreterImpl();
+    private Bmv2DeviceContext context = new Bmv2DeviceContext(configuration, interpreter);
+    private Bmv2FlowRuleTranslator translator = new Bmv2FlowRuleTranslatorImpl();
+
+    @Test
+    public void testTranslate() throws Exception {
+
+        DeviceId deviceId = DeviceId.NONE;
+        ApplicationId appId = new DefaultApplicationId(1, "test");
+        int tableId = 0;
+        MacAddress ethDstMac = MacAddress.valueOf(random.nextLong());
+        MacAddress ethSrcMac = MacAddress.valueOf(random.nextLong());
+        short ethType = (short) (0x0000FFFF & random.nextInt());
+        short outPort = (short) random.nextInt(65);
+        short inPort = (short) random.nextInt(65);
+        int timeout = random.nextInt(100);
+        int priority = random.nextInt(100);
+
+        TrafficSelector matchInPort1 = DefaultTrafficSelector
+                .builder()
+                .matchInPort(PortNumber.portNumber(inPort))
+                .matchEthDst(ethDstMac)
+                .matchEthSrc(ethSrcMac)
+                .matchEthType(ethType)
+                .build();
+
+        TrafficTreatment outPort2 = DefaultTrafficTreatment
+                .builder()
+                .setOutput(PortNumber.portNumber(outPort))
+                .build();
+
+        FlowRule rule1 = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .forTable(tableId)
+                .fromApp(appId)
+                .withSelector(matchInPort1)
+                .withTreatment(outPort2)
+                .makeTemporary(timeout)
+                .withPriority(priority)
+                .build();
+
+        FlowRule rule2 = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .forTable(tableId)
+                .fromApp(appId)
+                .withSelector(matchInPort1)
+                .withTreatment(outPort2)
+                .makeTemporary(timeout)
+                .withPriority(priority)
+                .build();
+
+        Bmv2TableEntry entry1 = translator.translate(rule1, context);
+        Bmv2TableEntry entry2 = translator.translate(rule1, context);
+
+        // check equality, i.e. same rules must produce same entries
+        new EqualsTester()
+                .addEqualityGroup(rule1, rule2)
+                .addEqualityGroup(entry1, entry2)
+                .testEquals();
+
+        int numMatchParams = configuration.table(TABLE0).keys().size();
+        // parse values stored in entry1
+        Bmv2TernaryMatchParam inPortParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(0);
+        Bmv2TernaryMatchParam ethDstParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(1);
+        Bmv2TernaryMatchParam ethSrcParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(2);
+        Bmv2TernaryMatchParam ethTypeParam = (Bmv2TernaryMatchParam) entry1.matchKey().matchParams().get(3);
+        double expectedTimeout = (double) (configuration.table(TABLE0).hasTimeouts() ? rule1.timeout() : -1);
+
+        // check that the number of parameters in the entry is the same as the number of table keys
+        assertThat("Incorrect number of match parameters",
+                   entry1.matchKey().matchParams().size(), is(equalTo(numMatchParams)));
+
+        // check that values stored in entry are the same used for the flow rule
+        assertThat("Incorrect inPort match param value",
+                   inPortParam.value().asReadOnlyBuffer().getShort(), is(equalTo(inPort)));
+        assertThat("Incorrect ethDestMac match param value",
+                   ethDstParam.value().asArray(), is(equalTo(ethDstMac.toBytes())));
+        assertThat("Incorrect ethSrcMac match param value",
+                   ethSrcParam.value().asArray(), is(equalTo(ethSrcMac.toBytes())));
+        assertThat("Incorrect ethType match param value",
+                   ethTypeParam.value().asReadOnlyBuffer().getShort(), is(equalTo(ethType)));
+        assertThat("Incorrect priority value",
+                   entry1.priority(), is(equalTo(Integer.MAX_VALUE - rule1.priority())));
+        assertThat("Incorrect timeout value",
+                   entry1.timeout(), is(equalTo(expectedTimeout)));
+
+    }
+}
\ No newline at end of file
diff --git a/protocols/bmv2/ctl/src/test/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParserTest.java b/protocols/bmv2/ctl/src/test/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParserTest.java
new file mode 100644
index 0000000..973fcd5
--- /dev/null
+++ b/protocols/bmv2/ctl/src/test/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParserTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-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.onosproject.bmv2.ctl;
+
+import org.junit.Test;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+public class Bmv2TableDumpParserTest {
+
+    private Bmv2Configuration configuration = Bmv2DeviceContextServiceImpl.loadDefaultConfiguration();
+
+    @Test
+    public void testParse() throws Exception {
+        String text = readFile();
+        List<Bmv2ParsedTableEntry> result = Bmv2TableDumpParser.parse(text, configuration);
+        assertThat(result.size(), equalTo(10));
+    }
+
+    private String readFile()
+            throws IOException, URISyntaxException {
+        byte[] encoded = Files.readAllBytes(Paths.get(this.getClass().getResource("/tabledump.txt").toURI()));
+        return new String(encoded, Charset.defaultCharset());
+    }
+}
diff --git a/protocols/bmv2/ctl/src/test/resources/tabledump.txt b/protocols/bmv2/ctl/src/test/resources/tabledump.txt
new file mode 100644
index 0000000..392ceb8
--- /dev/null
+++ b/protocols/bmv2/ctl/src/test/resources/tabledump.txt
@@ -0,0 +1,10 @@
+0: 0000 000000000000 000000000000 0806 &&& 0000000000000000000000000000ffff => send_to_cpu -
+1: 0000 000000000000 000000000000 0800 &&& 0000000000000000000000000000ffff => send_to_cpu -
+2: 0000 000000000000 000000000000 88cc &&& 0000000000000000000000000000ffff => send_to_cpu -
+3: 0000 000000000000 000000000000 8942 &&& 0000000000000000000000000000ffff => send_to_cpu -
+4: 0001 000400000001 000400000000 0000 &&& ffffffffffffffffffffffffffff0000 => set_egress_port - 2,
+5: 0002 000400000000 000400000001 0000 &&& ffffffffffffffffffffffffffff0000 => set_egress_port - 1,
+51539607552: 0001 0000 => set_egress_port - 1,
+51539607553: 0001 0002 => set_egress_port - 3,
+51539607554: 0001 0001 => set_egress_port - 2,
+51539607555: 0001 0003 => set_egress_port - 4,
\ No newline at end of file
diff --git a/protocols/bmv2/pom.xml b/protocols/bmv2/pom.xml
index 5bd8f5f..ffa8623 100644
--- a/protocols/bmv2/pom.xml
+++ b/protocols/bmv2/pom.xml
@@ -26,243 +26,15 @@
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>onos-bmv2-protocol</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
 
-    <packaging>bundle</packaging>
+    <modules>
+        <module>api</module>
+        <module>ctl</module>
+        <module>thrift-api</module>
+    </modules>
+
+    <packaging>pom</packaging>
 
     <description>BMv2 protocol subsystem</description>
 
-    <properties>
-        <!-- BMv2 Commit ID and Thrift version -->
-        <bmv2.commit>4421bafd6d26740b0bbf802c2e9f9f54c1211b13</bmv2.commit>
-        <bmv2.thrift.version>0.9.3</bmv2.thrift.version>
-        <!-- Do not change below -->
-        <bmv2.baseurl>
-            https://raw.githubusercontent.com/ccascone/behavioral-model/${bmv2.commit}
-        </bmv2.baseurl>
-        <bmv2.thrift.srcdir>${project.basedir}/src/main/thrift</bmv2.thrift.srcdir>
-        <thrift.path>${project.build.directory}/thrift-compiler/</thrift.path>
-        <thrift.filename>thrift-${os.detected.classifier}.exe</thrift.filename>
-    </properties>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.thrift</groupId>
-            <artifactId>libthrift</artifactId>
-            <version>${bmv2.thrift.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.onosproject</groupId>
-            <artifactId>onos-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.scr.annotations</artifactId>
-        </dependency>
-    </dependencies>
-
-    <repositories>
-        <!-- Needed for thrift-compiler, which is hosted on GitHub -->
-        <repository>
-            <id>jitpack.io</id>
-            <url>https://jitpack.io</url>
-        </repository>
-    </repositories>
-
-    <build>
-        <extensions>
-            <extension>
-                <groupId>kr.motd.maven</groupId>
-                <artifactId>os-maven-plugin</artifactId>
-                <version>1.4.0.Final</version>
-            </extension>
-        </extensions>
-
-        <plugins>
-            <!-- Download Thrift source files from BMv2 Github repo -->
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>wagon-maven-plugin</artifactId>
-                <version>1.0</version>
-                <executions>
-                    <execution>
-                        <id>download-bmv2-thrift-standard</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>download-single</goal>
-                        </goals>
-                        <configuration>
-                            <url>${bmv2.baseurl}</url>
-                            <fromFile>thrift_src/standard.thrift</fromFile>
-                            <toDir>${bmv2.thrift.srcdir}</toDir>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>download-bmv2-thrift-simple_pre</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>download-single</goal>
-                        </goals>
-                        <configuration>
-                            <url>${bmv2.baseurl}</url>
-                            <fromFile>thrift_src/simple_pre.thrift</fromFile>
-                            <toDir>${bmv2.thrift.srcdir}</toDir>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>download-bmv2-thrift-simple_pre_lag</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>download-single</goal>
-                        </goals>
-                        <configuration>
-                            <url>${bmv2.baseurl}</url>
-                            <fromFile>thrift_src/simple_pre_lag.thrift
-                            </fromFile>
-                            <toDir>${bmv2.thrift.srcdir}</toDir>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>download-bmv2-thrift-simple_switch</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>download-single</goal>
-                        </goals>
-                        <configuration>
-                            <url>${bmv2.baseurl}</url>
-                            <fromFile>
-                                targets/simple_switch/thrift/simple_switch.thrift
-                            </fromFile>
-                            <toDir>${bmv2.thrift.srcdir}</toDir>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>download-bmv2-thrift-simple_switch-cpservice</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>download-single</goal>
-                        </goals>
-                        <configuration>
-                            <url>${bmv2.baseurl}</url>
-                            <fromFile>
-                                targets/simple_switch/thrift/control_plane.thrift
-                            </fromFile>
-                            <toDir>${bmv2.thrift.srcdir}</toDir>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <!-- Extract Thrift compiler -->
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-dependency-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>unpack</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>unpack</goal>
-                        </goals>
-                        <configuration>
-                            <artifactItems>
-                                <artifactItem>
-                                    <groupId>com.github.ccascone</groupId>
-                                    <artifactId>mvn-thrift-compiler</artifactId>
-                                    <version>1.1_${bmv2.thrift.version}</version>
-                                    <type>jar</type>
-                                    <includes>${thrift.filename}</includes>
-                                    <outputDirectory>${project.build.directory}/thrift-compiler</outputDirectory>
-                                </artifactItem>
-                            </artifactItems>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <!-- Add missing java namespace to Thrift files -->
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>exec-maven-plugin</artifactId>
-                <version>1.4.0</version>
-                <executions>
-                    <execution>
-                        <id>add-bmv2-thrift-java-namespace</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>exec</goal>
-                        </goals>
-                        <configuration>
-                            <executable>${bmv2.thrift.srcdir}/patch.sh
-                            </executable>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>set-thrift-compiler-permissions</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>exec</goal>
-                        </goals>
-                        <configuration>
-                            <executable>chmod</executable>
-                            <arguments>
-                                <argument>+x</argument>
-                                <argument>${thrift.path}/${thrift.filename}</argument>
-                            </arguments>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <!-- Compile Thrift files -->
-            <plugin>
-                <groupId>org.apache.thrift.tools</groupId>
-                <artifactId>maven-thrift-plugin</artifactId>
-                <version>0.1.11</version>
-                <configuration>
-                    <thriftExecutable>${thrift.path}/${thrift.filename}</thriftExecutable>
-                    <outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
-                </configuration>
-                <executions>
-                    <execution>
-                        <id>thrift-sources</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>compile</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <!-- Make generated sources visible -->
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>build-helper-maven-plugin</artifactId>
-                <version>1.4</version>
-                <executions>
-                    <execution>
-                        <id>add-thrift-sources-to-path</id>
-                        <phase>generate-sources</phase>
-                        <goals>
-                            <goal>add-source</goal>
-                        </goals>
-                        <configuration>
-                            <sources>
-                                <source>
-                                    ${project.build.directory}/generated-sources
-                                </source>
-                            </sources>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <!-- OSGi -->
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-scr-plugin</artifactId>
-            </plugin>
-            <plugin>
-                <groupId>org.onosproject</groupId>
-                <artifactId>onos-maven-plugin</artifactId>
-            </plugin>
-        </plugins>
-    </build>
-
 </project>
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/package-info.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/package-info.java
deleted file mode 100644
index adadced..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2016-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.
- */
-
-/**
- * BMv2 configuration model classes.
- */
-package org.onosproject.bmv2.api.model;
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/package-info.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/package-info.java
deleted file mode 100644
index 4d982ed..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/package-info.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2014-2016 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.
- */
-
-/**
- * Bmv2 API abstractions.
- * <p>
- * Bmv2 APIs are divided in two sub-packages, runtime and model.
- * Runtime APIs are used to represent operations that can be performed at runtime
- * on a Bmv2 device, while model APIs are used to describe the Bmv2 packet
- * processing model.
- */
-package org.onosproject.bmv2.api;
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ControlPlaneServer.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ControlPlaneServer.java
deleted file mode 100644
index 672529e..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ControlPlaneServer.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.api.runtime;
-
-import org.onlab.util.ImmutableByteSequence;
-
-/**
- * A server that listens for requests from a BMv2 device.
- */
-public interface Bmv2ControlPlaneServer {
-    /**
-     * Default listening port.
-     */
-    int DEFAULT_PORT = 40123;
-
-    /**
-     * Register the given hello listener, to be called each time a hello message is received from a BMv2 device.
-     *
-     * @param listener a hello listener
-     */
-    void addHelloListener(HelloListener listener);
-
-    /**
-     * Unregister the given hello listener.
-     *
-     * @param listener a hello listener
-     */
-    void removeHelloListener(HelloListener listener);
-
-    /**
-     * Register the given packet listener, to be called each time a packet-in message is received from a BMv2 device.
-     *
-     * @param listener a packet listener
-     */
-    void addPacketListener(PacketListener listener);
-
-    /**
-     * Unregister the given packet listener.
-     *
-     * @param listener a packet listener
-     */
-    void removePacketListener(PacketListener listener);
-
-    interface HelloListener {
-
-        /**
-         * Handles a hello message.
-         *
-         * @param device the BMv2 device that originated the message
-         */
-        void handleHello(Bmv2Device device);
-    }
-
-    interface PacketListener {
-
-        /**
-         * Handles a packet-in message.
-         *
-         * @param device    the BMv2 device that originated the message
-         * @param inputPort the device port where the packet was received
-         * @param reason    a reason code
-         * @param tableId   the table id that originated this packet-in
-         * @param contextId the context id where the packet-in was originated
-         * @param packet    the packet body
-         */
-        void handlePacketIn(Bmv2Device device, int inputPort, long reason, int tableId, int contextId,
-                            ImmutableByteSequence packet);
-    }
-}
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
deleted file mode 100644
index bf44a0e..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionSelector.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.api.runtime;
-
-import org.onlab.util.KryoNamespace;
-import org.onosproject.net.flow.AbstractExtension;
-import org.onosproject.net.flow.criteria.ExtensionSelector;
-import org.onosproject.net.flow.criteria.ExtensionSelectorType;
-
-/**
- * Extension selector for Bmv2 used as a wrapper for a {@link Bmv2MatchKey}.
- */
-public class Bmv2ExtensionSelector extends AbstractExtension implements ExtensionSelector {
-
-    private final KryoNamespace appKryo = new KryoNamespace.Builder().build();
-    private Bmv2MatchKey matchKey;
-
-    public Bmv2ExtensionSelector(Bmv2MatchKey matchKey) {
-        this.matchKey = matchKey;
-    }
-
-    public Bmv2MatchKey matchKey() {
-        return matchKey;
-    }
-
-    @Override
-    public ExtensionSelectorType type() {
-        return ExtensionSelectorType.ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type();
-    }
-
-    @Override
-    public byte[] serialize() {
-        return appKryo.serialize(matchKey);
-    }
-
-    @Override
-    public void deserialize(byte[] data) {
-        matchKey = appKryo.deserialize(data);
-    }
-}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
deleted file mode 100644
index 12ab1e3..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2ExtensionTreatment.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.api.runtime;
-
-import org.onlab.util.KryoNamespace;
-import org.onosproject.net.flow.AbstractExtension;
-import org.onosproject.net.flow.instructions.ExtensionTreatment;
-import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
-
-/**
- * Extension treatment for Bmv2 used as a wrapper for a {@link Bmv2Action}.
- */
-public class Bmv2ExtensionTreatment extends AbstractExtension implements ExtensionTreatment {
-
-    private final KryoNamespace appKryo = new KryoNamespace.Builder().build();
-    private Bmv2Action action;
-
-    public Bmv2ExtensionTreatment(Bmv2Action action) {
-        this.action = action;
-    }
-
-    public Bmv2Action getAction() {
-        return action;
-    }
-
-    @Override
-    public ExtensionTreatmentType type() {
-        return ExtensionTreatmentType.ExtensionTreatmentTypes.P4_BMV2_ACTION.type();
-    }
-
-    @Override
-    public byte[] serialize() {
-        return appKryo.serialize(action);
-    }
-
-    @Override
-    public void deserialize(byte[] data) {
-        action = appKryo.deserialize(data);
-    }
-}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2PortInfo.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2PortInfo.java
deleted file mode 100644
index c6be426..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2PortInfo.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.api.runtime;
-
-import com.google.common.base.MoreObjects;
-import org.p4.bmv2.thrift.DevMgrPortInfo;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Bmv2 representation of a switch port information.
- */
-public final class Bmv2PortInfo {
-
-    private final DevMgrPortInfo portInfo;
-
-    public Bmv2PortInfo(DevMgrPortInfo portInfo) {
-        this.portInfo = portInfo;
-    }
-
-    public final String ifaceName() {
-        return portInfo.getIface_name();
-    }
-
-    public final int portNumber() {
-        return portInfo.getPort_num();
-    }
-
-    public final boolean isUp() {
-        return portInfo.isIs_up();
-    }
-
-    public final Map<String, String> getExtraProperties() {
-        return Collections.unmodifiableMap(portInfo.getExtra());
-    }
-
-    @Override
-    public final String toString() {
-        return MoreObjects.toStringHelper(this)
-                .addValue(portInfo)
-                .toString();
-    }
-}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2RuntimeException.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2RuntimeException.java
deleted file mode 100644
index f7153c5..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2RuntimeException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.api.runtime;
-
-/**
- * General exception of the Bmv2 runtime APIs.
- */
-public class Bmv2RuntimeException extends Exception {
-
-    public Bmv2RuntimeException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public Bmv2RuntimeException(String message) {
-        super(message);
-    }
-}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ControlPlaneThriftServer.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ControlPlaneThriftServer.java
deleted file mode 100644
index 80ce0c2..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ControlPlaneThriftServer.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.ctl;
-
-import com.google.common.collect.Maps;
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.thrift.TException;
-import org.apache.thrift.TProcessor;
-import org.apache.thrift.protocol.TProtocol;
-import org.apache.thrift.server.TThreadPoolServer;
-import org.apache.thrift.transport.TServerSocket;
-import org.apache.thrift.transport.TServerTransport;
-import org.apache.thrift.transport.TSocket;
-import org.apache.thrift.transport.TTransportException;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.bmv2.api.runtime.Bmv2ControlPlaneServer;
-import org.onosproject.bmv2.api.runtime.Bmv2Device;
-import org.onosproject.core.CoreService;
-import org.p4.bmv2.thrift.ControlPlaneService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.nio.ByteBuffer;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import static org.onlab.util.Tools.groupedThreads;
-import static org.p4.bmv2.thrift.ControlPlaneService.Processor;
-
-@Component(immediate = true)
-@Service
-public class Bmv2ControlPlaneThriftServer implements Bmv2ControlPlaneServer {
-
-    private static final String APP_ID = "org.onosproject.bmv2";
-    private static final Logger LOG = LoggerFactory.getLogger(Bmv2ControlPlaneThriftServer.class);
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected CoreService coreService;
-
-    private final InternalTrackingProcessor trackingProcessor = new InternalTrackingProcessor();
-    private final ExecutorService executorService = Executors
-            .newFixedThreadPool(16, groupedThreads("onos/bmv2", "control-plane-server", LOG));
-
-    private final Set<HelloListener> helloListeners = new CopyOnWriteArraySet<>();
-    private final Set<PacketListener> packetListeners = new CopyOnWriteArraySet<>();
-
-    private TThreadPoolServer thriftServer;
-    private int serverPort = DEFAULT_PORT;
-
-    @Activate
-    public void activate() {
-        coreService.registerApplication(APP_ID);
-        try {
-            TServerTransport transport = new TServerSocket(serverPort);
-            LOG.info("Starting server on port {}...", serverPort);
-            this.thriftServer = new TThreadPoolServer(new TThreadPoolServer.Args(transport)
-                                                              .processor(trackingProcessor)
-                                                              .executorService(executorService));
-            executorService.execute(thriftServer::serve);
-        } catch (TTransportException e) {
-            LOG.error("Unable to start server", e);
-        }
-        LOG.info("Activated");
-    }
-
-    @Deactivate
-    public void deactivate() {
-        // Stop the server if running...
-        if (thriftServer != null && !thriftServer.isServing()) {
-            thriftServer.stop();
-        }
-        try {
-            executorService.awaitTermination(1, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            LOG.error("Server threads did not terminate");
-        }
-        executorService.shutdownNow();
-        LOG.info("Deactivated");
-    }
-
-    @Override
-    public void addHelloListener(HelloListener listener) {
-        if (!helloListeners.contains(listener)) {
-            helloListeners.add(listener);
-        }
-    }
-
-    @Override
-    public void removeHelloListener(HelloListener listener) {
-        helloListeners.remove(listener);
-    }
-
-    @Override
-    public void addPacketListener(PacketListener listener) {
-        if (!packetListeners.contains(listener)) {
-            packetListeners.add(listener);
-        }
-    }
-
-    @Override
-    public void removePacketListener(PacketListener listener) {
-        packetListeners.remove(listener);
-    }
-
-    /**
-     * Handles service calls using registered listeners.
-     */
-    private final class InternalServiceHandler implements ControlPlaneService.Iface {
-
-        private final TSocket socket;
-        private Bmv2Device remoteDevice;
-
-        private InternalServiceHandler(TSocket socket) {
-            this.socket = socket;
-        }
-
-        @Override
-        public boolean ping() {
-            return true;
-        }
-
-        @Override
-        public void hello(int thriftServerPort, int deviceId) {
-            // Locally note the remote device for future uses.
-            String host = socket.getSocket().getInetAddress().getHostAddress();
-            remoteDevice = new Bmv2Device(host, thriftServerPort, deviceId);
-
-            if (helloListeners.size() == 0) {
-                LOG.debug("Received hello, but there's no listener registered.");
-            } else {
-                helloListeners.forEach(listener -> listener.handleHello(remoteDevice));
-            }
-        }
-
-        @Override
-        public void packetIn(int port, long reason, int tableId, int contextId, ByteBuffer packet) {
-            if (remoteDevice == null) {
-                LOG.debug("Received packet-in, but the remote device is still unknown. Need a hello first...");
-                return;
-            }
-
-            if (packetListeners.size() == 0) {
-                LOG.debug("Received packet-in, but there's no listener registered.");
-            } else {
-                packetListeners.forEach(listener -> listener.handlePacketIn(remoteDevice,
-                                                                            port,
-                                                                            reason,
-                                                                            tableId,
-                                                                            contextId,
-                                                                            ImmutableByteSequence.copyFrom(packet)));
-            }
-        }
-    }
-
-    /**
-     * Thrift Processor decorator. This class is needed in order to have access to the socket when handling a call.
-     * Socket is needed to get the IP address of the client originating the call (see InternalServiceHandler.hello())
-     */
-    private final class InternalTrackingProcessor implements TProcessor {
-
-        // Map sockets to processors.
-        // TODO: implement it as a cache so unused sockets are expired automatically
-        private final ConcurrentMap<TSocket, Processor<InternalServiceHandler>> processors = Maps.newConcurrentMap();
-
-        @Override
-        public boolean process(final TProtocol in, final TProtocol out) throws TException {
-            // Get the socket for this request.
-            TSocket socket = (TSocket) in.getTransport();
-            // Get or create a processor for this socket
-            Processor<InternalServiceHandler> processor = processors.computeIfAbsent(socket, s -> {
-                InternalServiceHandler handler = new InternalServiceHandler(s);
-                return new Processor<>(handler);
-            });
-            // Delegate to the processor we are decorating.
-            return processor.process(in, out);
-        }
-    }
-}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParser.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParser.java
deleted file mode 100644
index 0d413da..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2TableDumpParser.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.ctl;
-
-import com.google.common.collect.Lists;
-import org.apache.commons.lang3.tuple.Pair;
-
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * String parser for the BMv2 table dump.
- */
-public class Bmv2TableDumpParser {
-
-    /*
-    Example of BMv2 table dump:
-    0: 0000 000000000000 000000000000 0806 &&& 0000000000000000000000000000ffff => send_to_cpu -
-
-    For each entry, we want to match the id and all the rest.
-     */
-    private static final String ENTRY_PATTERN_STRING = "(\\d+):(.+)";
-    private static final Pattern ENTRY_PATTERN = Pattern.compile(ENTRY_PATTERN_STRING);
-
-    /**
-     * Returns a list of entry Ids for the given table dump.
-     *
-     * @param tableDump a string value
-     * @return a list of long values
-     * @throws Bmv2TableDumpParserException if dump can't be parsed
-     */
-    public List<Long> getEntryIds(String tableDump) throws Bmv2TableDumpParserException {
-        return parse(tableDump).stream().map(Pair::getKey).collect(Collectors.toList());
-    }
-
-    private List<Pair<Long, String>> parse(String tableDump) throws Bmv2TableDumpParserException {
-        checkNotNull(tableDump, "tableDump cannot be null");
-
-        List<Pair<Long, String>> results = Lists.newArrayList();
-
-        // TODO: consider caching parser results for speed.
-
-        Matcher matcher = ENTRY_PATTERN.matcher(tableDump);
-
-        while (matcher.find()) {
-            String entryString = matcher.group(1);
-            if (entryString == null) {
-                throw new Bmv2TableDumpParserException("Unable to parse entry for string: " + matcher.group());
-            }
-            Long entryId = -1L;
-            try {
-                entryId = Long.valueOf(entryString.trim());
-            } catch (NumberFormatException e) {
-                throw new Bmv2TableDumpParserException("Unable to parse entry id for string: " + matcher.group());
-            }
-            String allTheRest = matcher.group(2);
-            if (allTheRest == null) {
-                throw new Bmv2TableDumpParserException("Unable to parse entry for string: " + matcher.group());
-            }
-            results.add(Pair.of(entryId, allTheRest));
-        }
-
-        return results;
-    }
-
-    public class Bmv2TableDumpParserException extends Throwable {
-        public Bmv2TableDumpParserException(String msg) {
-            super(msg);
-        }
-    }
-}
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ThriftClient.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ThriftClient.java
deleted file mode 100644
index 28d88e9..0000000
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ThriftClient.java
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * Copyright 2014-2016 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.onosproject.bmv2.ctl;
-
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.cache.RemovalListener;
-import com.google.common.cache.RemovalNotification;
-import com.google.common.collect.Lists;
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.thrift.TException;
-import org.apache.thrift.protocol.TBinaryProtocol;
-import org.apache.thrift.protocol.TMultiplexedProtocol;
-import org.apache.thrift.protocol.TProtocol;
-import org.apache.thrift.transport.TSocket;
-import org.apache.thrift.transport.TTransport;
-import org.apache.thrift.transport.TTransportException;
-import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.bmv2.api.runtime.Bmv2Action;
-import org.onosproject.bmv2.api.runtime.Bmv2Client;
-import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
-import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
-import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
-import org.onosproject.bmv2.api.runtime.Bmv2PortInfo;
-import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
-import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
-import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
-import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
-import org.onosproject.net.DeviceId;
-import org.p4.bmv2.thrift.BmAddEntryOptions;
-import org.p4.bmv2.thrift.BmCounterValue;
-import org.p4.bmv2.thrift.BmMatchParam;
-import org.p4.bmv2.thrift.BmMatchParamExact;
-import org.p4.bmv2.thrift.BmMatchParamLPM;
-import org.p4.bmv2.thrift.BmMatchParamTernary;
-import org.p4.bmv2.thrift.BmMatchParamType;
-import org.p4.bmv2.thrift.BmMatchParamValid;
-import org.p4.bmv2.thrift.DevMgrPortInfo;
-import org.p4.bmv2.thrift.SimpleSwitch;
-import org.p4.bmv2.thrift.Standard;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.nio.ByteBuffer;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onosproject.bmv2.ctl.SafeThriftClient.Options;
-
-/**
- * Implementation of a Thrift client to control the Bmv2 switch.
- */
-public final class Bmv2ThriftClient implements Bmv2Client {
-
-    private static final Logger LOG =
-            LoggerFactory.getLogger(Bmv2ThriftClient.class);
-
-    // FIXME: make context_id arbitrary for each call
-    // See: https://github.com/p4lang/behavioral-model/blob/master/modules/bm_sim/include/bm_sim/context.h
-    private static final int CONTEXT_ID = 0;
-    // Seconds after a client is expired (and connection closed) in the cache.
-    private static final int CLIENT_CACHE_TIMEOUT = 60;
-    // Number of connection retries after a network error.
-    private static final int NUM_CONNECTION_RETRIES = 3;
-    // Time between retries in milliseconds.
-    private static final int TIME_BETWEEN_RETRIES = 300;
-
-    // Static client cache where clients are removed after a predefined timeout.
-    private static final LoadingCache<DeviceId, Bmv2ThriftClient>
-            CLIENT_CACHE = CacheBuilder.newBuilder()
-            .expireAfterAccess(CLIENT_CACHE_TIMEOUT, TimeUnit.SECONDS)
-            .removalListener(new ClientRemovalListener())
-            .build(new ClientLoader());
-
-    private static final Bmv2TableDumpParser TABLE_DUMP_PARSER = new Bmv2TableDumpParser();
-
-    private final Standard.Iface standardClient;
-    private final SimpleSwitch.Iface simpleSwitchClient;
-    private final TTransport transport;
-    private final DeviceId deviceId;
-
-    // ban constructor
-    private Bmv2ThriftClient(DeviceId deviceId, TTransport transport, Standard.Iface standardClient,
-                             SimpleSwitch.Iface simpleSwitchClient) {
-        this.deviceId = deviceId;
-        this.transport = transport;
-        this.standardClient = standardClient;
-        this.simpleSwitchClient = simpleSwitchClient;
-
-        LOG.debug("New client created! > deviceId={}", deviceId);
-    }
-
-    /**
-     * Returns a client object to control the passed device.
-     *
-     * @param deviceId device id
-     * @return bmv2 client object
-     * @throws Bmv2RuntimeException if a connection to the device cannot be established
-     */
-    public static Bmv2ThriftClient of(DeviceId deviceId) throws Bmv2RuntimeException {
-        try {
-            checkNotNull(deviceId, "deviceId cannot be null");
-            LOG.debug("Getting a client from cache... > deviceId{}", deviceId);
-            return CLIENT_CACHE.get(deviceId);
-        } catch (ExecutionException e) {
-            LOG.debug("Exception while getting a client from cache: {} > ", e, deviceId);
-            throw new Bmv2RuntimeException(e.getMessage(), e.getCause());
-        }
-    }
-
-    /**
-     * Force a close of the transport session (if one is open) with the given device.
-     *
-     * @param deviceId device id
-     */
-    public static void forceDisconnectOf(DeviceId deviceId) {
-        CLIENT_CACHE.invalidate(deviceId);
-    }
-
-    /**
-     * Pings the device. Returns true if the device is reachable,
-     * false otherwise.
-     *
-     * @param deviceId device id
-     * @return true if reachable, false otherwise
-     */
-    public static boolean ping(DeviceId deviceId) {
-        // poll ports status as workaround to assess device reachability
-        try {
-            LOG.debug("Pinging device... > deviceId={}", deviceId);
-            Bmv2ThriftClient client = of(deviceId);
-            boolean result = client.simpleSwitchClient.ping();
-            LOG.debug("Device pinged! > deviceId={}, state={}", deviceId, result);
-            return result;
-        } catch (TException | Bmv2RuntimeException e) {
-            LOG.debug("Device NOT reachable! > deviceId={}", deviceId);
-            return false;
-        }
-    }
-
-    /**
-     * Parse device ID into host and port.
-     *
-     * @param did device ID
-     * @return a pair of host and port
-     */
-    private static Pair<String, Integer> parseDeviceId(DeviceId did) {
-        String[] info = did.toString().split(":");
-        if (info.length == 3) {
-            String host = info[1];
-            int port = Integer.parseInt(info[2]);
-            return ImmutablePair.of(host, port);
-        } else {
-            throw new IllegalArgumentException(
-                    "Unable to parse BMv2 device ID "
-                            + did.toString()
-                            + ", expected format is scheme:host:port");
-        }
-    }
-
-    /**
-     * Builds a list of Bmv2/Thrift compatible match parameters.
-     *
-     * @param matchKey a bmv2 matchKey
-     * @return list of thrift-compatible bm match parameters
-     */
-    private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) {
-        List<BmMatchParam> paramsList = Lists.newArrayList();
-        matchKey.matchParams().forEach(x -> {
-            ByteBuffer value;
-            ByteBuffer mask;
-            switch (x.type()) {
-                case EXACT:
-                    value = ByteBuffer.wrap(((Bmv2ExactMatchParam) x).value().asArray());
-                    paramsList.add(
-                            new BmMatchParam(BmMatchParamType.EXACT)
-                                    .setExact(new BmMatchParamExact(value)));
-                    break;
-                case TERNARY:
-                    value = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).value().asArray());
-                    mask = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).mask().asArray());
-                    paramsList.add(
-                            new BmMatchParam(BmMatchParamType.TERNARY)
-                                    .setTernary(new BmMatchParamTernary(value, mask)));
-                    break;
-                case LPM:
-                    value = ByteBuffer.wrap(((Bmv2LpmMatchParam) x).value().asArray());
-                    int prefixLength = ((Bmv2LpmMatchParam) x).prefixLength();
-                    paramsList.add(
-                            new BmMatchParam(BmMatchParamType.LPM)
-                                    .setLpm(new BmMatchParamLPM(value, prefixLength)));
-                    break;
-                case VALID:
-                    boolean flag = ((Bmv2ValidMatchParam) x).flag();
-                    paramsList.add(
-                            new BmMatchParam(BmMatchParamType.VALID)
-                                    .setValid(new BmMatchParamValid(flag)));
-                    break;
-                default:
-                    // should never be here
-                    throw new RuntimeException("Unknown match param type " + x.type().name());
-            }
-        });
-        return paramsList;
-    }
-
-    /**
-     * Build a list of Bmv2/Thrift compatible action parameters.
-     *
-     * @param action an action object
-     * @return list of ByteBuffers
-     */
-    private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) {
-        List<ByteBuffer> buffers = Lists.newArrayList();
-        action.parameters().forEach(p -> buffers.add(ByteBuffer.wrap(p.asArray())));
-        return buffers;
-    }
-
-    @Override
-    public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException {
-
-        LOG.debug("Adding table entry... > deviceId={}, entry={}", deviceId, entry);
-
-        long entryId = -1;
-
-        try {
-            BmAddEntryOptions options = new BmAddEntryOptions();
-
-            if (entry.hasPriority()) {
-                options.setPriority(entry.priority());
-            }
-
-            entryId = standardClient.bm_mt_add_entry(
-                    CONTEXT_ID,
-                    entry.tableName(),
-                    buildMatchParamsList(entry.matchKey()),
-                    entry.action().name(),
-                    buildActionParamsList(entry.action()),
-                    options);
-
-            if (entry.hasTimeout()) {
-                /* bmv2 accepts timeouts in milliseconds */
-                int msTimeout = (int) Math.round(entry.timeout() * 1_000);
-                standardClient.bm_mt_set_entry_ttl(
-                        CONTEXT_ID, entry.tableName(), entryId, msTimeout);
-            }
-
-            LOG.debug("Table entry added! > deviceId={}, entryId={}/{}", deviceId, entry.tableName(), entryId);
-
-            return entryId;
-
-        } catch (TException e) {
-            LOG.debug("Exception while adding table entry: {} > deviceId={}, tableName={}",
-                      e, deviceId, entry.tableName());
-            if (entryId != -1) {
-                // entry is in inconsistent state (unable to add timeout), remove it
-                try {
-                    deleteTableEntry(entry.tableName(), entryId);
-                } catch (Bmv2RuntimeException e1) {
-                    LOG.debug("Unable to remove failed table entry: {} > deviceId={}, tableName={}",
-                              e1, deviceId, entry.tableName());
-                }
-            }
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public final void modifyTableEntry(String tableName,
-                                       long entryId, Bmv2Action action)
-            throws Bmv2RuntimeException {
-
-        LOG.debug("Modifying table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
-
-        try {
-            standardClient.bm_mt_modify_entry(
-                    CONTEXT_ID,
-                    tableName,
-                    entryId,
-                    action.name(),
-                    buildActionParamsList(action));
-            LOG.debug("Table entry modified! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
-        } catch (TException e) {
-            LOG.debug("Exception while modifying table entry: {} > deviceId={}, entryId={}/{}",
-                      e, deviceId, tableName, entryId);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public final void deleteTableEntry(String tableName,
-                                       long entryId) throws Bmv2RuntimeException {
-
-        LOG.debug("Deleting table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
-
-        try {
-            standardClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
-            LOG.debug("Table entry deleted! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
-        } catch (TException e) {
-            LOG.debug("Exception while deleting table entry: {} > deviceId={}, entryId={}/{}",
-                      e, deviceId, tableName, entryId);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public final void setTableDefaultAction(String tableName, Bmv2Action action)
-            throws Bmv2RuntimeException {
-
-        LOG.debug("Setting table default... > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
-
-        try {
-            standardClient.bm_mt_set_default_action(
-                    CONTEXT_ID,
-                    tableName,
-                    action.name(),
-                    buildActionParamsList(action));
-            LOG.debug("Table default set! > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
-        } catch (TException e) {
-            LOG.debug("Exception while setting table default : {} > deviceId={}, tableName={}, action={}",
-                      e, deviceId, tableName, action);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException {
-
-        LOG.debug("Retrieving port info... > deviceId={}", deviceId);
-
-        try {
-            List<DevMgrPortInfo> portInfos = standardClient.bm_dev_mgr_show_ports();
-
-            Collection<Bmv2PortInfo> bmv2PortInfos = Lists.newArrayList();
-
-            bmv2PortInfos.addAll(
-                    portInfos.stream()
-                            .map(Bmv2PortInfo::new)
-                            .collect(Collectors.toList()));
-
-            LOG.debug("Port info retrieved! > deviceId={}, portInfos={}", deviceId, bmv2PortInfos);
-
-            return bmv2PortInfos;
-
-        } catch (TException e) {
-            LOG.debug("Exception while retrieving port info: {} > deviceId={}", e, deviceId);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public String dumpTable(String tableName) throws Bmv2RuntimeException {
-
-        LOG.debug("Retrieving table dump... > deviceId={}, tableName={}", deviceId, tableName);
-
-        try {
-            String dump = standardClient.bm_dump_table(CONTEXT_ID, tableName);
-            LOG.debug("Table dump retrieved! > deviceId={}, tableName={}", deviceId, tableName);
-            return dump;
-        } catch (TException e) {
-            LOG.debug("Exception while retrieving table dump: {} > deviceId={}, tableName={}",
-                      e, deviceId, tableName);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public List<Long> getInstalledEntryIds(String tableName) throws Bmv2RuntimeException {
-
-        LOG.debug("Getting entry ids... > deviceId={}, tableName={}", deviceId, tableName);
-
-        try {
-            List<Long> entryIds = TABLE_DUMP_PARSER.getEntryIds(dumpTable(tableName));
-            LOG.debug("Entry ids retrieved! > deviceId={}, tableName={}, entryIdsCount={}",
-                      deviceId, tableName, entryIds.size());
-            return entryIds;
-        } catch (Bmv2TableDumpParser.Bmv2TableDumpParserException e) {
-            LOG.debug("Exception while retrieving entry ids: {} > deviceId={}, tableName={}",
-                      e, deviceId, tableName);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public int cleanupTable(String tableName) throws Bmv2RuntimeException {
-
-        LOG.debug("Starting table cleanup... > deviceId={}, tableName={}", deviceId, tableName);
-
-        List<Long> entryIds = getInstalledEntryIds(tableName);
-
-        int count = 0;
-        for (Long entryId : entryIds) {
-            try {
-                standardClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
-                count++;
-            } catch (TException e) {
-                LOG.warn("Exception while deleting entry: {} > deviceId={}, tableName={}, entryId={}",
-                         e.toString(), deviceId, tableName, entryId);
-            }
-        }
-
-        return count;
-    }
-
-    @Override
-    public void transmitPacket(int portNumber, ImmutableByteSequence packet) throws Bmv2RuntimeException {
-
-        LOG.debug("Requesting packet transmission... > portNumber={}, packet={}", portNumber, packet);
-
-        try {
-
-            simpleSwitchClient.push_packet(portNumber, ByteBuffer.wrap(packet.asArray()));
-            LOG.debug("Packet transmission requested! > portNumber={}, packet={}", portNumber, packet);
-        } catch (TException e) {
-            LOG.debug("Exception while requesting packet transmission: {} > portNumber={}, packet={}",
-                      e, portNumber, packet);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public void resetState() throws Bmv2RuntimeException {
-
-        LOG.debug("Resetting device state... > deviceId={}", deviceId);
-
-        try {
-            standardClient.bm_reset_state();
-            LOG.debug("Device state reset! > deviceId={}", deviceId);
-        } catch (TException e) {
-            LOG.debug("Exception while resetting device state: {} > deviceId={}", e, deviceId);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public String dumpJsonConfig() throws Bmv2RuntimeException {
-
-        LOG.debug("Dumping device config... > deviceId={}", deviceId);
-
-        try {
-            String config = standardClient.bm_get_config();
-            LOG.debug("Device config dumped! > deviceId={}, configLength={}", deviceId, config.length());
-            return config;
-        } catch (TException e) {
-            LOG.debug("Exception while dumping device config: {} > deviceId={}", e, deviceId);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException {
-
-        LOG.debug("Reading table entry counters... > deviceId={}, tableName={}, entryId={}",
-                  deviceId, tableName, entryId);
-
-        try {
-            BmCounterValue counterValue = standardClient.bm_mt_read_counter(CONTEXT_ID, tableName, entryId);
-            LOG.debug("Table entry counters retrieved! > deviceId={}, tableName={}, entryId={}, bytes={}, packets={}",
-                      deviceId, tableName, entryId, counterValue.bytes, counterValue.packets);
-            return Pair.of(counterValue.bytes, counterValue.packets);
-        } catch (TException e) {
-            LOG.debug("Exception while reading table counters: {} > deviceId={}, tableName={}, entryId={}",
-                      e.toString(), deviceId);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public String getJsonConfigMd5() throws Bmv2RuntimeException {
-
-        LOG.debug("Getting device config md5... > deviceId={}", deviceId);
-
-        try {
-            String md5 = standardClient.bm_get_config_md5();
-            LOG.debug("Device config md5 received! > deviceId={}, configMd5={}", deviceId, md5);
-            return md5;
-        } catch (TException e) {
-            LOG.debug("Exception while getting device config md5: {} > deviceId={}", e, deviceId);
-            throw new Bmv2RuntimeException(e.getMessage(), e);
-        }
-    }
-
-    /**
-     * Transport/client cache loader.
-     */
-    private static class ClientLoader
-            extends CacheLoader<DeviceId, Bmv2ThriftClient> {
-
-        private static final Options RECONN_OPTIONS = new Options(NUM_CONNECTION_RETRIES, TIME_BETWEEN_RETRIES);
-
-        @Override
-        public Bmv2ThriftClient load(DeviceId deviceId)
-                throws TTransportException {
-            LOG.debug("Creating new client in cache... > deviceId={}", deviceId);
-            Pair<String, Integer> info = parseDeviceId(deviceId);
-            //make the expensive call
-            TTransport transport = new TSocket(
-                    info.getLeft(), info.getRight());
-            TProtocol protocol = new TBinaryProtocol(transport);
-            // Our BMv2 device implements multiple Thrift services, create a client for each one on the same transport.
-            Standard.Client standardClient = new Standard.Client(
-                    new TMultiplexedProtocol(protocol, "standard"));
-            SimpleSwitch.Client simpleSwitch = new SimpleSwitch.Client(
-                    new TMultiplexedProtocol(protocol, "simple_switch"));
-            // Wrap clients so to automatically have synchronization and resiliency to connectivity errors
-            Standard.Iface safeStandardClient = SafeThriftClient.wrap(standardClient,
-                                                                      Standard.Iface.class,
-                                                                      RECONN_OPTIONS);
-            SimpleSwitch.Iface safeSimpleSwitchClient = SafeThriftClient.wrap(simpleSwitch,
-                                                                              SimpleSwitch.Iface.class,
-                                                                              RECONN_OPTIONS);
-
-            return new Bmv2ThriftClient(deviceId, transport, safeStandardClient, safeSimpleSwitchClient);
-        }
-    }
-
-    /**
-     * Client cache removal listener. Close the connection on cache removal.
-     */
-    private static class ClientRemovalListener implements
-            RemovalListener<DeviceId, Bmv2ThriftClient> {
-
-        @Override
-        public void onRemoval(RemovalNotification<DeviceId, Bmv2ThriftClient> notification) {
-            // close the transport connection
-            Bmv2ThriftClient client = notification.getValue();
-            // Locking here is ugly, but needed (see SafeThriftClient).
-            synchronized (client.transport) {
-                LOG.debug("Closing transport session... > deviceId={}", client.deviceId);
-                if (client.transport.isOpen()) {
-                    client.transport.close();
-                    LOG.debug("Transport session closed! > deviceId={}", client.deviceId);
-                } else {
-                    LOG.debug("Transport session was already closed! deviceId={}", client.deviceId);
-                }
-            }
-            LOG.debug("Removing client from cache... > deviceId={}", client.deviceId);
-        }
-    }
-}
diff --git a/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2TableDumpParserTest.java b/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2TableDumpParserTest.java
deleted file mode 100644
index 87ae453..0000000
--- a/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2TableDumpParserTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2016-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.onosproject.bmv2.api.model;
-
-import org.junit.Test;
-import org.onosproject.bmv2.ctl.Bmv2TableDumpParser;
-
-import java.util.List;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsEqual.equalTo;
-
-public class Bmv2TableDumpParserTest {
-
-    @Test
-    public void testParse() throws Exception, Bmv2TableDumpParser.Bmv2TableDumpParserException {
-
-        String text =
-                "0: 0000 000000000000 000000000000 &&& 0000000000000000000000000000 => send_to_cpu -\n" +
-                        "1: 0000 000000000000 000000000000 &&& 0000000000000000000000000000 => send_to_cpu -\n" +
-                        "2: 0000 000000000000 000000000000 &&& 0000000000000000000000000000 => send_to_cpu -\n" +
-                        "3: 0000 000000000000 000000000000 &&& 0000000000000000000000000000 => send_to_cpu -";
-
-        Bmv2TableDumpParser parser = new Bmv2TableDumpParser();
-
-        List<Long> result = parser.getEntryIds(text);
-
-        assertThat("invalid parsed values", result.get(0), is(equalTo(0L)));
-        assertThat("invalid parsed values", result.get(1), is(equalTo(1L)));
-        assertThat("invalid parsed values", result.get(2), is(equalTo(2L)));
-        assertThat("invalid parsed values", result.get(3), is(equalTo(3L)));
-    }
-}
diff --git a/protocols/bmv2/src/test/resources/simple_pipeline.json b/protocols/bmv2/src/test/resources/simple_pipeline.json
deleted file mode 100644
index 372bdcd..0000000
--- a/protocols/bmv2/src/test/resources/simple_pipeline.json
+++ /dev/null
@@ -1,505 +0,0 @@
-{
-  "header_types": [
-    {
-      "name": "standard_metadata_t",
-      "id": 0,
-      "fields": [
-        [
-          "ingress_port",
-          9
-        ],
-        [
-          "packet_length",
-          32
-        ],
-        [
-          "egress_spec",
-          9
-        ],
-        [
-          "egress_port",
-          9
-        ],
-        [
-          "egress_instance",
-          32
-        ],
-        [
-          "instance_type",
-          32
-        ],
-        [
-          "clone_spec",
-          32
-        ],
-        [
-          "_padding",
-          5
-        ]
-      ],
-      "length_exp": null,
-      "max_length": null
-    },
-    {
-      "name": "ethernet_t",
-      "id": 1,
-      "fields": [
-        [
-          "dstAddr",
-          48
-        ],
-        [
-          "srcAddr",
-          48
-        ],
-        [
-          "etherType",
-          16
-        ]
-      ],
-      "length_exp": null,
-      "max_length": null
-    },
-    {
-      "name": "intrinsic_metadata_t",
-      "id": 2,
-      "fields": [
-        [
-          "ingress_global_timestamp",
-          32
-        ],
-        [
-          "lf_field_list",
-          32
-        ],
-        [
-          "mcast_grp",
-          16
-        ],
-        [
-          "egress_rid",
-          16
-        ]
-      ],
-      "length_exp": null,
-      "max_length": null
-    },
-    {
-      "name": "cpu_header_t",
-      "id": 3,
-      "fields": [
-        [
-          "device",
-          8
-        ],
-        [
-          "reason",
-          8
-        ]
-      ],
-      "length_exp": null,
-      "max_length": null
-    }
-  ],
-  "headers": [
-    {
-      "name": "standard_metadata",
-      "id": 0,
-      "header_type": "standard_metadata_t",
-      "metadata": true
-    },
-    {
-      "name": "ethernet",
-      "id": 1,
-      "header_type": "ethernet_t",
-      "metadata": false
-    },
-    {
-      "name": "intrinsic_metadata",
-      "id": 2,
-      "header_type": "intrinsic_metadata_t",
-      "metadata": true
-    },
-    {
-      "name": "cpu_header",
-      "id": 3,
-      "header_type": "cpu_header_t",
-      "metadata": false
-    }
-  ],
-  "header_stacks": [],
-  "parsers": [
-    {
-      "name": "parser",
-      "id": 0,
-      "init_state": "start",
-      "parse_states": [
-        {
-          "name": "start",
-          "id": 0,
-          "parser_ops": [],
-          "transition_key": [
-            {
-              "type": "lookahead",
-              "value": [
-                0,
-                64
-              ]
-            }
-          ],
-          "transitions": [
-            {
-              "value": "0x0000000000000000",
-              "mask": null,
-              "next_state": "parse_cpu_header"
-            },
-            {
-              "value": "default",
-              "mask": null,
-              "next_state": "parse_ethernet"
-            }
-          ]
-        },
-        {
-          "name": "parse_cpu_header",
-          "id": 1,
-          "parser_ops": [
-            {
-              "op": "extract",
-              "parameters": [
-                {
-                  "type": "regular",
-                  "value": "cpu_header"
-                }
-              ]
-            }
-          ],
-          "transition_key": [],
-          "transitions": [
-            {
-              "value": "default",
-              "mask": null,
-              "next_state": "parse_ethernet"
-            }
-          ]
-        },
-        {
-          "name": "parse_ethernet",
-          "id": 2,
-          "parser_ops": [
-            {
-              "op": "extract",
-              "parameters": [
-                {
-                  "type": "regular",
-                  "value": "ethernet"
-                }
-              ]
-            }
-          ],
-          "transition_key": [],
-          "transitions": [
-            {
-              "value": "default",
-              "mask": null,
-              "next_state": null
-            }
-          ]
-        }
-      ]
-    }
-  ],
-  "deparsers": [
-    {
-      "name": "deparser",
-      "id": 0,
-      "order": [
-        "cpu_header",
-        "ethernet"
-      ]
-    }
-  ],
-  "meter_arrays": [],
-  "actions": [
-    {
-      "name": "flood",
-      "id": 0,
-      "runtime_data": [],
-      "primitives": [
-        {
-          "op": "modify_field",
-          "parameters": [
-            {
-              "type": "field",
-              "value": [
-                "intrinsic_metadata",
-                "mcast_grp"
-              ]
-            },
-            {
-              "type": "field",
-              "value": [
-                "standard_metadata",
-                "ingress_port"
-              ]
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "name": "_drop",
-      "id": 1,
-      "runtime_data": [],
-      "primitives": [
-        {
-          "op": "modify_field",
-          "parameters": [
-            {
-              "type": "field",
-              "value": [
-                "standard_metadata",
-                "egress_spec"
-              ]
-            },
-            {
-              "type": "hexstr",
-              "value": "0x1ff"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "name": "fwd",
-      "id": 2,
-      "runtime_data": [
-        {
-          "name": "port",
-          "bitwidth": 9
-        }
-      ],
-      "primitives": [
-        {
-          "op": "modify_field",
-          "parameters": [
-            {
-              "type": "field",
-              "value": [
-                "standard_metadata",
-                "egress_spec"
-              ]
-            },
-            {
-              "type": "runtime_data",
-              "value": 0
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "name": "send_to_cpu",
-      "id": 3,
-      "runtime_data": [
-        {
-          "name": "device",
-          "bitwidth": 8
-        },
-        {
-          "name": "reason",
-          "bitwidth": 8
-        }
-      ],
-      "primitives": [
-        {
-          "op": "add_header",
-          "parameters": [
-            {
-              "type": "header",
-              "value": "cpu_header"
-            }
-          ]
-        },
-        {
-          "op": "modify_field",
-          "parameters": [
-            {
-              "type": "field",
-              "value": [
-                "cpu_header",
-                "device"
-              ]
-            },
-            {
-              "type": "runtime_data",
-              "value": 0
-            }
-          ]
-        },
-        {
-          "op": "modify_field",
-          "parameters": [
-            {
-              "type": "field",
-              "value": [
-                "cpu_header",
-                "reason"
-              ]
-            },
-            {
-              "type": "runtime_data",
-              "value": 1
-            }
-          ]
-        },
-        {
-          "op": "modify_field",
-          "parameters": [
-            {
-              "type": "field",
-              "value": [
-                "standard_metadata",
-                "egress_spec"
-              ]
-            },
-            {
-              "type": "hexstr",
-              "value": "0xfa"
-            }
-          ]
-        }
-      ]
-    }
-  ],
-  "pipelines": [
-    {
-      "name": "ingress",
-      "id": 0,
-      "init_table": "table0",
-      "tables": [
-        {
-          "name": "table0",
-          "id": 0,
-          "match_type": "ternary",
-          "type": "simple",
-          "max_size": 16384,
-          "with_counters": false,
-          "direct_meters": null,
-          "support_timeout": false,
-          "key": [
-            {
-              "match_type": "ternary",
-              "target": [
-                "standard_metadata",
-                "ingress_port"
-              ],
-              "mask": null
-            },
-            {
-              "match_type": "ternary",
-              "target": [
-                "ethernet",
-                "dstAddr"
-              ],
-              "mask": null
-            },
-            {
-              "match_type": "ternary",
-              "target": [
-                "ethernet",
-                "srcAddr"
-              ],
-              "mask": null
-            },
-            {
-              "match_type": "ternary",
-              "target": [
-                "ethernet",
-                "etherType"
-              ],
-              "mask": null
-            }
-          ],
-          "actions": [
-            "fwd",
-            "flood",
-            "send_to_cpu",
-            "_drop"
-          ],
-          "next_tables": {
-            "fwd": null,
-            "flood": null,
-            "send_to_cpu": null,
-            "_drop": null
-          },
-          "default_action": null
-        }
-      ],
-      "conditionals": []
-    },
-    {
-      "name": "egress",
-      "id": 1,
-      "init_table": null,
-      "tables": [],
-      "conditionals": []
-    }
-  ],
-  "calculations": [],
-  "checksums": [],
-  "learn_lists": [],
-  "field_lists": [],
-  "counter_arrays": [],
-  "register_arrays": [],
-  "force_arith": [
-    [
-      "standard_metadata",
-      "ingress_port"
-    ],
-    [
-      "standard_metadata",
-      "packet_length"
-    ],
-    [
-      "standard_metadata",
-      "egress_spec"
-    ],
-    [
-      "standard_metadata",
-      "egress_port"
-    ],
-    [
-      "standard_metadata",
-      "egress_instance"
-    ],
-    [
-      "standard_metadata",
-      "instance_type"
-    ],
-    [
-      "standard_metadata",
-      "clone_spec"
-    ],
-    [
-      "standard_metadata",
-      "_padding"
-    ],
-    [
-      "intrinsic_metadata",
-      "ingress_global_timestamp"
-    ],
-    [
-      "intrinsic_metadata",
-      "lf_field_list"
-    ],
-    [
-      "intrinsic_metadata",
-      "mcast_grp"
-    ],
-    [
-      "intrinsic_metadata",
-      "egress_rid"
-    ]
-  ]
-}
\ No newline at end of file
diff --git a/protocols/bmv2/thrift-api/pom.xml b/protocols/bmv2/thrift-api/pom.xml
new file mode 100644
index 0000000..655b84b
--- /dev/null
+++ b/protocols/bmv2/thrift-api/pom.xml
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onos-bmv2-protocol</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.6.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>onos-bmv2-protocol-thrift-api</artifactId>
+
+    <packaging>bundle</packaging>
+
+    <properties>
+        <!-- BMv2 Commit ID and Thrift version -->
+        <bmv2.commit>e55f9cdaee5e3d729f839e7ec6c322dd66c1a1a0</bmv2.commit>
+        <bmv2.thrift.version>0.9.3</bmv2.thrift.version>
+        <!-- Do not change below -->
+        <bmv2.baseurl>
+            https://raw.githubusercontent.com/ccascone/behavioral-model/${bmv2.commit}
+        </bmv2.baseurl>
+        <bmv2.thrift.srcdir>${project.basedir}/src/main/thrift</bmv2.thrift.srcdir>
+        <thrift.path>${project.build.directory}/thrift-compiler/</thrift.path>
+        <thrift.filename>thrift-${os.detected.classifier}.exe</thrift.filename>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.thrift</groupId>
+            <artifactId>libthrift</artifactId>
+            <version>0.9.3</version>
+        </dependency>
+    </dependencies>
+
+    <repositories>
+        <!-- Needed for thrift-compiler, which is hosted on GitHub -->
+        <repository>
+            <id>jitpack.io</id>
+            <url>https://jitpack.io</url>
+        </repository>
+    </repositories>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>1.4.0.Final</version>
+            </extension>
+        </extensions>
+
+        <plugins>
+            <!-- Download Thrift source files from BMv2 Github repo -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>wagon-maven-plugin</artifactId>
+                <version>1.0</version>
+                <executions>
+                    <execution>
+                        <id>download-bmv2-thrift-standard</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>download-single</goal>
+                        </goals>
+                        <configuration>
+                            <url>${bmv2.baseurl}</url>
+                            <fromFile>thrift_src/standard.thrift</fromFile>
+                            <toDir>${bmv2.thrift.srcdir}</toDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>download-bmv2-thrift-simple_pre</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>download-single</goal>
+                        </goals>
+                        <configuration>
+                            <url>${bmv2.baseurl}</url>
+                            <fromFile>thrift_src/simple_pre.thrift</fromFile>
+                            <toDir>${bmv2.thrift.srcdir}</toDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>download-bmv2-thrift-simple_pre_lag</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>download-single</goal>
+                        </goals>
+                        <configuration>
+                            <url>${bmv2.baseurl}</url>
+                            <fromFile>thrift_src/simple_pre_lag.thrift
+                            </fromFile>
+                            <toDir>${bmv2.thrift.srcdir}</toDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>download-bmv2-thrift-simple_switch</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>download-single</goal>
+                        </goals>
+                        <configuration>
+                            <url>${bmv2.baseurl}</url>
+                            <fromFile>
+                                targets/simple_switch/thrift/simple_switch.thrift
+                            </fromFile>
+                            <toDir>${bmv2.thrift.srcdir}</toDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>download-bmv2-thrift-simple_switch-cpservice</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>download-single</goal>
+                        </goals>
+                        <configuration>
+                            <url>${bmv2.baseurl}</url>
+                            <fromFile>
+                                targets/simple_switch/thrift/control_plane.thrift
+                            </fromFile>
+                            <toDir>${bmv2.thrift.srcdir}</toDir>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- Extract Thrift compiler -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>unpack</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>com.github.ccascone</groupId>
+                                    <artifactId>mvn-thrift-compiler</artifactId>
+                                    <version>1.1_${bmv2.thrift.version}</version>
+                                    <type>jar</type>
+                                    <includes>${thrift.filename}</includes>
+                                    <outputDirectory>${project.build.directory}/thrift-compiler</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- Add missing java namespace to Thrift files -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>1.4.0</version>
+                <executions>
+                    <execution>
+                        <id>add-bmv2-thrift-java-namespace</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>exec</goal>
+                        </goals>
+                        <configuration>
+                            <executable>${bmv2.thrift.srcdir}/patch.sh
+                            </executable>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>set-thrift-compiler-permissions</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>exec</goal>
+                        </goals>
+                        <configuration>
+                            <executable>chmod</executable>
+                            <arguments>
+                                <argument>+x</argument>
+                                <argument>${thrift.path}/${thrift.filename}</argument>
+                            </arguments>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- Compile Thrift files -->
+            <plugin>
+                <groupId>org.apache.thrift.tools</groupId>
+                <artifactId>maven-thrift-plugin</artifactId>
+                <version>0.1.11</version>
+                <configuration>
+                    <thriftExecutable>${thrift.path}/${thrift.filename}</thriftExecutable>
+                    <outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>thrift-sources</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- Make generated sources visible -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <version>1.4</version>
+                <executions>
+                    <execution>
+                        <id>add-thrift-sources-to-path</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>add-source</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>
+                                    ${project.build.directory}/generated-sources
+                                </source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/thrift/.gitignore b/protocols/bmv2/thrift-api/src/main/thrift/.gitignore
similarity index 100%
rename from protocols/bmv2/src/main/thrift/.gitignore
rename to protocols/bmv2/thrift-api/src/main/thrift/.gitignore
diff --git a/protocols/bmv2/src/main/thrift/patch.sh b/protocols/bmv2/thrift-api/src/main/thrift/patch.sh
similarity index 95%
rename from protocols/bmv2/src/main/thrift/patch.sh
rename to protocols/bmv2/thrift-api/src/main/thrift/patch.sh
index b3fc40f..1b77aca8 100755
--- a/protocols/bmv2/src/main/thrift/patch.sh
+++ b/protocols/bmv2/thrift-api/src/main/thrift/patch.sh
@@ -18,7 +18,7 @@
 set -e
 
 basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-ns="org.p4.bmv2.thrift"
+ns="org.onosproject.bmv2.thriftapi"
 
 # add java namespace at beginning of file
 for f in ${basedir}/*.thrift
diff --git a/providers/pom.xml b/providers/pom.xml
index e2f915d..726fd95 100644
--- a/providers/pom.xml
+++ b/providers/pom.xml
@@ -46,7 +46,7 @@
         <module>lldpcommon</module>
         <module>lldp</module>
         <module>netcfglinks</module>
-        <module>bmv2</module>
+        <!--<module>bmv2</module>-->
         <module>isis</module>
     </modules>
 
diff --git a/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java b/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
index 57171de..f5f9a59 100644
--- a/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
+++ b/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
@@ -73,6 +73,24 @@
     }
 
     /**
+     * Creates a new immutable byte sequence with the same content and order of
+     * the passed byte array, from/to the given indexes (inclusive).
+     *
+     * @param original a byte array value
+     * @return a new immutable byte sequence
+     */
+    public static ImmutableByteSequence copyFrom(byte[] original, int fromIdx, int toIdx) {
+        checkArgument(original != null && original.length > 0,
+                      "Cannot copy from an empty or null array");
+        checkArgument(toIdx >= fromIdx && toIdx < original.length, "invalid indexes");
+        ByteBuffer buffer = ByteBuffer.allocate((toIdx - fromIdx) + 1);
+        for (int i = fromIdx; i <= toIdx; i++) {
+            buffer.put(original[i]);
+        }
+        return new ImmutableByteSequence(buffer);
+    }
+
+    /**
      * Creates a new immutable byte sequence copying bytes from the given
      * ByteBuffer {@link ByteBuffer}. If the byte buffer order is not big-endian
      * bytes will be copied in reverse order.