Handling of table entry messages in P4Runtime

+ synchonized method execution in P4RuntimeClient
+ support for cancellable contexts (for client shutdown)
+ logging of sent/received messages in GrpcControllerImpl
+ minor refactorings

Change-Id: I43f0fcc263579e01957a02ef3392105aed476f33
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java
index 88fc0ab..1583995 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java
@@ -25,13 +25,11 @@
 import org.onosproject.p4runtime.api.P4RuntimeController;
 import org.slf4j.Logger;
 
-import java.io.InputStream;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
-import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -67,21 +65,8 @@
 
         P4RuntimeClient client = controller.getClient(deviceId);
 
-        if (!pipeconf.extension(BMV2_JSON).isPresent()) {
-            log.warn("Missing BMv2 JSON config in pipeconf {}, aborting pipeconf deploy", pipeconf.id());
-            return false;
-        }
-
-        if (!pipeconf.extension(P4_INFO_TEXT).isPresent()) {
-            log.warn("Missing P4Info in pipeconf {}, aborting pipeconf deploy", pipeconf.id());
-            return false;
-        }
-
-        InputStream p4InfoStream = pipeconf.extension(P4_INFO_TEXT).get();
-        InputStream jsonStream = pipeconf.extension(BMV2_JSON).get();
-
         try {
-            if (!client.setPipelineConfig(p4InfoStream, jsonStream).get()) {
+            if (!client.setPipelineConfig(pipeconf, BMV2_JSON).get()) {
                 log.warn("Unable to deploy pipeconf {} to {}", pipeconf.id(), deviceId);
                 return false;
             }
diff --git a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/GrpcControllerImpl.java b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/GrpcControllerImpl.java
index 152106a..d3262f8 100644
--- a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/GrpcControllerImpl.java
+++ b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/GrpcControllerImpl.java
@@ -17,8 +17,16 @@
 package org.onosproject.grpc.ctl;
 
 import com.google.common.collect.ImmutableSet;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall;
+import io.grpc.ForwardingClientCallListener;
 import io.grpc.ManagedChannel;
 import io.grpc.ManagedChannelBuilder;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
 import io.grpc.Status;
 import io.grpc.StatusRuntimeException;
 import org.apache.felix.scr.annotations.Activate;
@@ -51,6 +59,10 @@
 @Service
 public class GrpcControllerImpl implements GrpcController {
 
+    // Hint: set to true to log all gRPC messages received/sent on all channels.
+    // TODO: make configurable at runtime
+    public static boolean enableMessageLog = false;
+
     private static final int CONNECTION_TIMEOUT_SECONDS = 20;
 
     public static final Logger log = LoggerFactory
@@ -97,6 +109,11 @@
     @Override
     public ManagedChannel connectChannel(GrpcChannelId channelId, ManagedChannelBuilder<?> channelBuilder)
             throws IOException {
+
+        if (enableMessageLog) {
+            channelBuilder.intercept(new InternalLogChannelInterceptor(channelId));
+        }
+
         ManagedChannel channel = channelBuilder.build();
 
         // Forced connection not yet implemented. Use workaround...
@@ -177,4 +194,51 @@
     public Optional<ManagedChannel> getChannel(GrpcChannelId channelId) {
         return Optional.ofNullable(channels.get(channelId));
     }
+
+    /**
+     * gRPC client interceptor that logs all messages sent and received.
+     */
+    private final class InternalLogChannelInterceptor implements ClientInterceptor {
+
+        private final GrpcChannelId channelId;
+
+        private InternalLogChannelInterceptor(GrpcChannelId channelId) {
+            this.channelId = channelId;
+        }
+
+        @Override
+        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> methodDescriptor,
+                                                                   CallOptions callOptions, Channel channel) {
+            return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(channel.newCall(
+                    methodDescriptor, callOptions.withoutWaitForReady())) {
+
+                @Override
+                public void sendMessage(ReqT message) {
+                    log.info("*** SENDING GRPC MESSAGE [{}]\n{}:\n{}", channelId, methodDescriptor.getFullMethodName(),
+                             message.toString());
+                    super.sendMessage(message);
+                }
+
+                @Override
+                public void start(Listener<RespT> responseListener, Metadata headers) {
+
+                    ClientCall.Listener<RespT> listener = new ForwardingClientCallListener<RespT>() {
+                        @Override
+                        protected Listener<RespT> delegate() {
+                            return responseListener;
+                        }
+
+                        @Override
+                        public void onMessage(RespT message) {
+                            log.info("*** RECEIVED GRPC MESSAGE [{}]\n{}:\n{}", channelId,
+                                     methodDescriptor.getFullMethodName(),
+                                     message.toString());
+                            super.onMessage(message);
+                        }
+                    };
+                    super.start(listener, headers);
+                }
+            };
+        }
+    }
 }
diff --git a/protocols/grpc/proto/dummy.proto b/protocols/grpc/proto/dummy.proto
index d02b5d8..74268df 100644
--- a/protocols/grpc/proto/dummy.proto
+++ b/protocols/grpc/proto/dummy.proto
@@ -5,7 +5,8 @@
 package dummy;
 
 service DummyService {
-  rpc SayHello (DummyMessageThatNoOneWouldReallyUse) returns (DummyMessageThatNoOneWouldReallyUse) {}
+    rpc SayHello (DummyMessageThatNoOneWouldReallyUse) returns (DummyMessageThatNoOneWouldReallyUse) {
+    }
 }
 
 message DummyMessageThatNoOneWouldReallyUse {
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
index 03e28c2..2418a78 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
@@ -22,7 +22,6 @@
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
 
-import java.io.InputStream;
 import java.util.Collection;
 import java.util.concurrent.CompletableFuture;
 
@@ -38,18 +37,20 @@
     enum WriteOperationType {
         UNSPECIFIED,
         INSERT,
-        UPDATE,
+        MODIFY,
         DELETE
     }
 
     /**
-     * Sets the pipeline configuration. This method should be called before any other method of this client.
+     * Sets the pipeline configuration defined by the given pipeconf for the given target-specific configuration
+     * extension type (e.g. {@link PiPipeconf.ExtensionType#BMV2_JSON}, or {@link PiPipeconf.ExtensionType#TOFINO_BIN}).
+     * This method should be called before any other method of this client.
      *
-     * @param p4Info       input stream of a P4Info message in text format
-     * @param targetConfig input stream of the target-specific configuration (e.g. BMv2 JSON)
+     * @param pipeconf            pipeconf
+     * @param targetConfigExtType extension type of the target-specific configuration
      * @return a completable future of a boolean, true if the operations was successful, false otherwise.
      */
-    CompletableFuture<Boolean> setPipelineConfig(InputStream p4Info, InputStream targetConfig);
+    CompletableFuture<Boolean> setPipelineConfig(PiPipeconf pipeconf, PiPipeconf.ExtensionType targetConfigExtType);
 
     /**
      * Initializes the stream channel, after which all messages received from the device will be notified using the
@@ -60,21 +61,24 @@
     CompletableFuture<Boolean> initStreamChannel();
 
     /**
-     * Performs the given write operation for the given table entries.
+     * Performs the given write operation for the given table entries and pipeconf.
      *
-     * @param entries table entries
-     * @param opType  operation type.
+     * @param entries  table entries
+     * @param opType   operation type
+     * @param pipeconf pipeconf currently deployed on the device
      * @return true if the operation was successful, false otherwise.
      */
-    boolean writeTableEntries(Collection<PiTableEntry> entries, WriteOperationType opType);
+    CompletableFuture<Boolean> writeTableEntries(Collection<PiTableEntry> entries, WriteOperationType opType,
+                                                 PiPipeconf pipeconf);
 
     /**
      * Dumps all entries currently installed in the given table.
      *
-     * @param tableId table identifier
+     * @param tableId  table identifier
+     * @param pipeconf pipeconf currently deployed on the device
      * @return completable future of a collection of table entries
      */
-    CompletableFuture<Collection<PiTableEntry>> dumpTable(PiTableId tableId);
+    CompletableFuture<Collection<PiTableEntry>> dumpTable(PiTableId tableId, PiPipeconf pipeconf);
 
     /**
      * Executes a packet-out operation.
@@ -85,7 +89,6 @@
      */
     CompletableFuture<Boolean> packetOut(PiPacketOperation packet, PiPipeconf pipeconf);
 
-
     /**
      * Shutdown the client by terminating any active RPC such as the stream channel.
      */
diff --git a/protocols/p4runtime/ctl/BUCK b/protocols/p4runtime/ctl/BUCK
index baf1645..7fb3b4c 100644
--- a/protocols/p4runtime/ctl/BUCK
+++ b/protocols/p4runtime/ctl/BUCK
@@ -31,6 +31,7 @@
 TEST_DEPS = [
     '//lib:TEST',
     '//core/api:onos-api-tests',
+    '//incubator/bmv2/model:onos-incubator-bmv2-model',
 ]
 
 BUNDLES = [
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/EncodeException.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/EncodeException.java
new file mode 100644
index 0000000..4601f32
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/EncodeException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+/**
+ * Signals that the proto message cannot be build.
+ */
+final class EncodeException extends Exception {
+
+    EncodeException(String explanation) {
+        super(explanation);
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
index d8edaff..3c27468 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
@@ -31,8 +31,11 @@
 import p4.config.P4InfoOuterClass.Preamble;
 import p4.config.P4InfoOuterClass.Table;
 
+import javax.annotation.Nullable;
 import java.util.Map;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.String.format;
 
 /**
@@ -70,7 +73,10 @@
                     String tableName = entity.getPreamble().getName();
                     EntityBrowser<MatchField> matchFieldBrowser = new EntityBrowser<>(format(
                             "match field for table '%s'", tableName));
-                    entity.getMatchFieldsList().forEach(m -> matchFieldBrowser.add(m.getName(), m.getId(), m));
+                    entity.getMatchFieldsList().forEach(m -> {
+                        String alias = extractMatchFieldSimpleName(m.getName());
+                        matchFieldBrowser.add(m.getName(), alias, m.getId(), m);
+                    });
                     matchFields.put(tableId, matchFieldBrowser);
                 });
 
@@ -82,7 +88,7 @@
                     String actionName = entity.getPreamble().getName();
                     EntityBrowser<Action.Param> paramBrowser = new EntityBrowser<>(format(
                             "param for action '%s'", actionName));
-                    entity.getParamsList().forEach(p -> paramBrowser.add(p.getName(), p.getId(), p));
+                    entity.getParamsList().forEach(p -> paramBrowser.add(p.getName(), null, p.getId(), p));
                     actionParams.put(actionId, paramBrowser);
                 });
 
@@ -105,6 +111,19 @@
                 entity -> ctrlPktMetadatas.addWithPreamble(entity.getPreamble(), entity));
     }
 
+    private String extractMatchFieldSimpleName(String name) {
+        // Removes the leading "hdr." or other scope identifier.
+        // E.g.: "hdr.ethernet.etherType" becomes "ethernet.etherType"
+        String[] pieces = name.split("\\.");
+        if (pieces.length == 3) {
+            return pieces[1] + "." + pieces[2];
+        } else if (pieces.length == 2) {
+            return name;
+        } else {
+            throw new UnsupportedOperationException("Invalid match field name: " + name);
+        }
+    }
+
     /**
      * Returns a browser for tables.
      *
@@ -220,15 +239,22 @@
         }
 
         /**
-         * Adds the given entity identified by the given name and id.
+         * Adds the given entity identified by the given name, alias and id.
          *
          * @param name   entity name
+         * @param alias  entity alias
          * @param id     entity id
          * @param entity entity message
          */
-        void add(String name, int id, T entity) {
+        void add(String name, @Nullable String alias, int id, T entity) {
+            checkNotNull(name);
+            checkArgument(!name.isEmpty(), "Name cannot be empty");
+            checkNotNull(entity);
             names.put(name, entity);
             ids.put(id, entity);
+            if (alias != null && !alias.isEmpty()) {
+                aliases.put(alias, entity);
+            }
         }
 
         /**
@@ -238,9 +264,8 @@
          * @param entity   entity message
          */
         void addWithPreamble(Preamble preamble, T entity) {
-            names.put(preamble.getName(), entity);
-            aliases.put(preamble.getName(), entity);
-            ids.put(preamble.getId(), entity);
+            checkNotNull(preamble);
+            add(preamble.getName(), preamble.getAlias(), preamble.getId(), entity);
         }
 
         /**
@@ -268,6 +293,23 @@
         }
 
         /**
+         * Returns the entity identified by the given name or alias, if present, otherwise, throws an exception.
+         *
+         * @param name entity name or alias
+         * @return entity message
+         * @throws NotFoundException if the entity cannot be found
+         */
+        T getByNameOrAlias(String name) throws NotFoundException {
+            if (hasName(name)) {
+                return names.get(name);
+            } else if (hasAlias(name)) {
+                return aliases.get(name);
+            } else {
+                throw new NotFoundException(entityName, name);
+            }
+        }
+
+        /**
          * Returns true if the P4Info defines an entity with such alias, false otherwise.
          *
          * @param alias entity alias
@@ -319,7 +361,7 @@
     /**
      * Signals tha an entity cannot be found in the P4Info.
      */
-    public static class NotFoundException extends Exception {
+    public static final class NotFoundException extends Exception {
 
         NotFoundException(String entityName, String key) {
             super(format("No such %s in P4Info with name/alias '%s'", entityName, key));
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index 3762eb1..132689a 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -17,9 +17,8 @@
 package org.onosproject.p4runtime.ctl;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.protobuf.ByteString;
-import com.google.protobuf.ExtensionRegistry;
-import com.google.protobuf.TextFormat;
 import io.grpc.Context;
 import io.grpc.ManagedChannel;
 import io.grpc.Status;
@@ -35,165 +34,130 @@
 import org.onosproject.p4runtime.api.P4RuntimeEvent;
 import org.slf4j.Logger;
 import p4.P4RuntimeGrpc;
+import p4.P4RuntimeOuterClass.Entity;
+import p4.P4RuntimeOuterClass.ForwardingPipelineConfig;
+import p4.P4RuntimeOuterClass.MasterArbitrationUpdate;
+import p4.P4RuntimeOuterClass.PacketIn;
+import p4.P4RuntimeOuterClass.ReadRequest;
+import p4.P4RuntimeOuterClass.ReadResponse;
+import p4.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest;
+import p4.P4RuntimeOuterClass.StreamMessageRequest;
+import p4.P4RuntimeOuterClass.StreamMessageResponse;
+import p4.P4RuntimeOuterClass.TableEntry;
+import p4.P4RuntimeOuterClass.Uint128;
+import p4.P4RuntimeOuterClass.Update;
+import p4.P4RuntimeOuterClass.WriteRequest;
 import p4.tmp.P4Config;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType;
 import static org.slf4j.LoggerFactory.getLogger;
-import static p4.P4RuntimeOuterClass.ForwardingPipelineConfig;
-import static p4.P4RuntimeOuterClass.MasterArbitrationUpdate;
-import static p4.P4RuntimeOuterClass.PacketIn;
-import static p4.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest;
+import static p4.P4RuntimeOuterClass.Entity.EntityCase.TABLE_ENTRY;
 import static p4.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
-import static p4.P4RuntimeOuterClass.StreamMessageRequest;
-import static p4.P4RuntimeOuterClass.StreamMessageResponse;
 import static p4.config.P4InfoOuterClass.P4Info;
 
 /**
  * Implementation of a P4Runtime client.
  */
-public class P4RuntimeClientImpl implements P4RuntimeClient {
+public final class P4RuntimeClientImpl implements P4RuntimeClient {
 
     private static final int DEADLINE_SECONDS = 15;
 
+    // FIXME: use static election ID, since mastership arbitration is not yet support on BMv2 or Tofino.
+    private static final int ELECTION_ID = 1;
+
+    private static final Map<WriteOperationType, Update.Type> UPDATE_TYPES = ImmutableMap.of(
+            WriteOperationType.UNSPECIFIED, Update.Type.UNSPECIFIED,
+            WriteOperationType.INSERT, Update.Type.INSERT,
+            WriteOperationType.MODIFY, Update.Type.MODIFY,
+            WriteOperationType.DELETE, Update.Type.DELETE
+    );
+
     private final Logger log = getLogger(getClass());
 
     private final DeviceId deviceId;
     private final int p4DeviceId;
     private final P4RuntimeControllerImpl controller;
     private final P4RuntimeGrpc.P4RuntimeBlockingStub blockingStub;
-    private final P4RuntimeGrpc.P4RuntimeStub asyncStub;
-    private ExecutorService executorService;
-    private StreamObserver<StreamMessageRequest> streamRequestObserver;
-    private Context.CancellableContext streamContext;
+    private final Context.CancellableContext cancellableContext;
+    private final ExecutorService executorService;
+    private final Executor contextExecutor;
+    private final Lock writeLock = new ReentrantLock();
+    private final StreamObserver<StreamMessageRequest> streamRequestObserver;
 
 
-    P4RuntimeClientImpl(DeviceId deviceId, int p4DeviceId, ManagedChannel channel, P4RuntimeControllerImpl controller,
-                        ExecutorService executorService) {
+    P4RuntimeClientImpl(DeviceId deviceId, int p4DeviceId, ManagedChannel channel, P4RuntimeControllerImpl controller) {
         this.deviceId = deviceId;
         this.p4DeviceId = p4DeviceId;
         this.controller = controller;
-        this.executorService = executorService;
+        this.cancellableContext = Context.current().withCancellation();
+        this.executorService = Executors.newFixedThreadPool(5, groupedThreads(
+                "onos/p4runtime-client-" + deviceId.toString(),
+                deviceId.toString() + "-%d"));
+        this.contextExecutor = this.cancellableContext.fixedContextExecutor(executorService);
         this.blockingStub = P4RuntimeGrpc.newBlockingStub(channel)
                 .withDeadlineAfter(DEADLINE_SECONDS, TimeUnit.SECONDS);
-        this.asyncStub = P4RuntimeGrpc.newStub(channel);
+        P4RuntimeGrpc.P4RuntimeStub asyncStub = P4RuntimeGrpc.newStub(channel);
+        this.streamRequestObserver = asyncStub.streamChannel(new StreamChannelResponseObserver());
+    }
+
+    /**
+     * Executes the given task (supplier) in the gRPC context executor of this client, such that if the context is
+     * cancelled (e.g. client shutdown) the RPC is automatically cancelled.
+     * <p>
+     * Important: Tasks submitted in parallel by different threads are forced executed sequentially.
+     * <p>
+     */
+    private <U> CompletableFuture<U> supplyInContext(Supplier<U> supplier) {
+        return CompletableFuture.supplyAsync(() -> {
+            // TODO: explore a more relaxed locking strategy.
+            writeLock.lock();
+            try {
+                return supplier.get();
+            } finally {
+                writeLock.unlock();
+            }
+        }, contextExecutor);
     }
 
     @Override
     public CompletableFuture<Boolean> initStreamChannel() {
-        return CompletableFuture.supplyAsync(this::doInitStreamChannel, executorService);
-    }
-
-    private boolean doInitStreamChannel() {
-        if (this.streamRequestObserver == null) {
-
-            streamContext = Context.current().withCancellation();
-            streamContext.run(
-                    () -> streamRequestObserver = asyncStub.streamChannel(new StreamChannelResponseObserver()));
-
-            // To listen for packets and other events, we need to start the RPC.
-            // Here we do it by sending a master arbitration update.
-            if (!doArbitrationUpdate()) {
-                log.warn("Unable to initialize stream channel for {}", deviceId);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean doArbitrationUpdate() {
-
-        if (streamRequestObserver == null) {
-            log.error("Null request stream observer for {}", deviceId);
-            return false;
-        }
-
-        try {
-            StreamMessageRequest initRequest = StreamMessageRequest
-                    .newBuilder()
-                    .setArbitration(MasterArbitrationUpdate
-                            .newBuilder()
-                            .setDeviceId(p4DeviceId)
-                            .build())
-                    .build();
-            streamRequestObserver.onNext(initRequest);
-            return true;
-        } catch (StatusRuntimeException e) {
-            log.warn("Arbitration update failed for {}: {}", deviceId, e);
-            return false;
-        }
+        return supplyInContext(this::doInitStreamChannel);
     }
 
     @Override
-    public CompletableFuture<Boolean> setPipelineConfig(InputStream p4info, InputStream targetConfig) {
-        return CompletableFuture.supplyAsync(() -> doSetPipelineConfig(p4info, targetConfig), executorService);
-    }
-
-    private boolean doSetPipelineConfig(InputStream p4info, InputStream targetConfig) {
-
-        log.debug("Setting pipeline config for {}", deviceId);
-
-        P4Info.Builder p4iInfoBuilder = P4Info.newBuilder();
-
-        try {
-            TextFormat.getParser().merge(new InputStreamReader(p4info),
-                    ExtensionRegistry.getEmptyRegistry(),
-                    p4iInfoBuilder);
-        } catch (IOException ex) {
-            log.warn("Unable to load p4info for {}: {}", deviceId, ex.getMessage());
-            return false;
-        }
-
-        P4Config.P4DeviceConfig deviceIdConfig;
-        try {
-            deviceIdConfig = P4Config.P4DeviceConfig
-                    .newBuilder()
-                    .setExtras(P4Config.P4DeviceConfig.Extras.getDefaultInstance())
-                    .setReassign(true)
-                    .setDeviceData(ByteString.readFrom(targetConfig))
-                    .build();
-        } catch (IOException ex) {
-            log.warn("Unable to load target-specific config for {}: {}", deviceId, ex.getMessage());
-            return false;
-        }
-
-        SetForwardingPipelineConfigRequest request = SetForwardingPipelineConfigRequest
-                .newBuilder()
-                .setAction(VERIFY_AND_COMMIT)
-                .addConfigs(ForwardingPipelineConfig
-                        .newBuilder()
-                        .setDeviceId(p4DeviceId)
-                        .setP4Info(p4iInfoBuilder.build())
-                        .setP4DeviceConfig(deviceIdConfig.toByteString())
-                        .build())
-                .build();
-        try {
-            this.blockingStub.setForwardingPipelineConfig(request);
-        } catch (StatusRuntimeException ex) {
-            log.warn("Unable to set pipeline config for {}: {}", deviceId, ex.getMessage());
-            return false;
-        }
-
-        return true;
+    public CompletableFuture<Boolean> setPipelineConfig(PiPipeconf pipeconf, ExtensionType targetConfigExtType) {
+        return supplyInContext(() -> doSetPipelineConfig(pipeconf, targetConfigExtType));
     }
 
     @Override
-    public boolean writeTableEntries(Collection<PiTableEntry> entries, WriteOperationType opType) {
-
-        throw new UnsupportedOperationException("writeTableEntries not implemented.");
+    public CompletableFuture<Boolean> writeTableEntries(Collection<PiTableEntry> piTableEntries,
+                                                        WriteOperationType opType, PiPipeconf pipeconf) {
+        return supplyInContext(() -> doWriteTableEntries(piTableEntries, opType, pipeconf));
     }
 
     @Override
-    public CompletableFuture<Collection<PiTableEntry>> dumpTable(PiTableId tableId) {
-
-        throw new UnsupportedOperationException("dumpTable not implemented.");
+    public CompletableFuture<Collection<PiTableEntry>> dumpTable(PiTableId piTableId, PiPipeconf pipeconf) {
+        return supplyInContext(() -> doDumpTable(piTableId, pipeconf));
     }
 
     @Override
@@ -227,56 +191,224 @@
         return result;
     }
 
+    /* Blocking method implementations below */
+
+    private boolean doInitStreamChannel() {
+        // To listen for packets and other events, we need to start the RPC.
+        // Here we do it by sending a master arbitration update.
+        log.info("initializing stream chanel on {}...", deviceId);
+        if (!doArbitrationUpdate()) {
+            log.warn("Unable to initialize stream channel for {}", deviceId);
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private boolean doArbitrationUpdate() {
+        log.info("Sending arbitration update to {}...", deviceId);
+        StreamMessageRequest requestMsg = StreamMessageRequest.newBuilder()
+                .setArbitration(MasterArbitrationUpdate.newBuilder()
+                                        .setDeviceId(p4DeviceId)
+                                        .build())
+                .build();
+        try {
+            streamRequestObserver.onNext(requestMsg);
+            return true;
+        } catch (StatusRuntimeException e) {
+            log.warn("Arbitration update failed for {}: {}", deviceId, e);
+            return false;
+        }
+    }
+
+    private boolean doSetPipelineConfig(PiPipeconf pipeconf, ExtensionType targetConfigExtType) {
+
+        log.info("Setting pipeline config for {} to {} using {}...", deviceId, pipeconf.id(), targetConfigExtType);
+
+        P4Info p4Info = PipeconfHelper.getP4Info(pipeconf);
+        if (p4Info == null) {
+            // Problem logged by PipeconfHelper.
+            return false;
+        }
+
+        if (!pipeconf.extension(targetConfigExtType).isPresent()) {
+            log.warn("Missing extension {} in pipeconf {}", targetConfigExtType, pipeconf.id());
+            return false;
+        }
+
+        InputStream targetConfig = pipeconf.extension(targetConfigExtType).get();
+        P4Config.P4DeviceConfig p4DeviceConfigMsg;
+        try {
+            p4DeviceConfigMsg = P4Config.P4DeviceConfig
+                    .newBuilder()
+                    .setExtras(P4Config.P4DeviceConfig.Extras.getDefaultInstance())
+                    .setReassign(true)
+                    .setDeviceData(ByteString.readFrom(targetConfig))
+                    .build();
+        } catch (IOException ex) {
+            log.warn("Unable to load target-specific config for {}: {}", deviceId, ex.getMessage());
+            return false;
+        }
+
+        SetForwardingPipelineConfigRequest request = SetForwardingPipelineConfigRequest
+                .newBuilder()
+                .setAction(VERIFY_AND_COMMIT)
+                .addConfigs(ForwardingPipelineConfig
+                                    .newBuilder()
+                                    .setDeviceId(p4DeviceId)
+                                    .setP4Info(p4Info)
+                                    .setP4DeviceConfig(p4DeviceConfigMsg.toByteString())
+                                    .build())
+                .build();
+
+        try {
+            this.blockingStub.setForwardingPipelineConfig(request);
+
+        } catch (StatusRuntimeException ex) {
+            log.warn("Unable to set pipeline config for {}: {}", deviceId, ex.getMessage());
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean doWriteTableEntries(Collection<PiTableEntry> piTableEntries, WriteOperationType opType,
+                                        PiPipeconf pipeconf) {
+
+        WriteRequest.Builder writeRequestBuilder = WriteRequest.newBuilder();
+
+        Collection<Update> updateMsgs = TableEntryEncoder.encode(piTableEntries, pipeconf)
+                .stream()
+                .map(tableEntryMsg ->
+                             Update.newBuilder()
+                                     .setEntity(Entity.newBuilder()
+                                                        .setTableEntry(tableEntryMsg)
+                                                        .build())
+                                     .setType(UPDATE_TYPES.get(opType))
+                                     .build())
+                .collect(Collectors.toList());
+
+        if (updateMsgs.size() == 0) {
+            return true;
+        }
+
+        writeRequestBuilder
+                .setDeviceId(p4DeviceId)
+                .setElectionId(Uint128.newBuilder()
+                                       .setHigh(0)
+                                       .setLow(ELECTION_ID)
+                                       .build())
+                .addAllUpdates(updateMsgs)
+                .build();
+
+        try {
+            blockingStub.write(writeRequestBuilder.build());
+            return true;
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to write table entries ({}): {}", opType, e.getMessage());
+            return false;
+        }
+    }
+
+    private Collection<PiTableEntry> doDumpTable(PiTableId piTableId, PiPipeconf pipeconf) {
+
+        log.info("Dumping table {} from {} (pipeconf {})...", piTableId, deviceId, pipeconf.id());
+
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+        int tableId;
+        try {
+            tableId = browser.tables().getByName(piTableId.id()).getPreamble().getId();
+        } catch (P4InfoBrowser.NotFoundException e) {
+            log.warn("Unable to dump table: {}", e.getMessage());
+            return Collections.emptyList();
+        }
+
+        ReadRequest requestMsg = ReadRequest.newBuilder()
+                .setDeviceId(p4DeviceId)
+                .addEntities(Entity.newBuilder()
+                                     .setTableEntry(TableEntry.newBuilder()
+                                                            .setTableId(tableId)
+                                                            .build())
+                                     .build())
+                .build();
+
+        Iterator<ReadResponse> responses;
+        try {
+            responses = blockingStub.read(requestMsg);
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to dump table: {}", e.getMessage());
+            return Collections.emptyList();
+        }
+
+        Iterable<ReadResponse> responseIterable = () -> responses;
+        List<TableEntry> tableEntryMsgs = StreamSupport
+                .stream(responseIterable.spliterator(), false)
+                .map(ReadResponse::getEntitiesList)
+                .flatMap(List::stream)
+                .filter(entity -> entity.getEntityCase() == TABLE_ENTRY)
+                .map(Entity::getTableEntry)
+                .collect(Collectors.toList());
+
+        log.info("Retrieved {} entries from table {} on {}...", tableEntryMsgs.size(), piTableId, deviceId);
+
+        return TableEntryEncoder.decode(tableEntryMsgs, pipeconf);
+    }
+
     @Override
     public void shutdown() {
 
         log.info("Shutting down client for {}...", deviceId);
 
-        if (streamRequestObserver != null) {
-            streamRequestObserver.onCompleted();
-            streamContext.cancel(null);
-            streamContext = null;
-        }
-
-        this.executorService.shutdown();
+        writeLock.lock();
         try {
-            executorService.awaitTermination(5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            log.warn("Executor service didn't shutdown in time.");
-        }
+            if (streamRequestObserver != null) {
+                streamRequestObserver.onCompleted();
+                cancellableContext.cancel(new InterruptedException("Requested client shutdown"));
+            }
 
-        // Prevent the execution of other tasks.
-        executorService = null;
+            this.executorService.shutdown();
+            try {
+                executorService.awaitTermination(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                log.warn("Executor service didn't shutdown in time.");
+            }
+        } finally {
+            writeLock.unlock();
+        }
     }
 
+    /**
+     * Handles messages received from the device on the stream channel.
+     */
     private class StreamChannelResponseObserver implements StreamObserver<StreamMessageResponse> {
 
         @Override
         public void onNext(StreamMessageResponse message) {
+            executorService.submit(() -> doNext(message));
+        }
 
-            P4RuntimeEvent event;
+        private void doNext(StreamMessageResponse message) {
+            log.info("Received message on stream channel from {}: {}", deviceId, message.getUpdateCase());
+            switch (message.getUpdateCase()) {
+                case PACKET:
+                    // Packet-in
+                    PacketIn packetIn = message.getPacket();
+                    ImmutableByteSequence data = copyFrom(packetIn.getPayload().asReadOnlyByteBuffer());
+                    ImmutableList.Builder<ImmutableByteSequence> metadataBuilder = ImmutableList.builder();
+                    packetIn.getMetadataList().stream()
+                            .map(m -> m.getValue().asReadOnlyByteBuffer())
+                            .map(ImmutableByteSequence::copyFrom)
+                            .forEach(metadataBuilder::add);
+                    P4RuntimeEvent event = new DefaultPacketInEvent(deviceId, data, metadataBuilder.build());
+                    controller.postEvent(event);
+                    return;
 
-            if (message.getPacket().isInitialized()) {
-                // Packet-in
-                PacketIn packetIn = message.getPacket();
-                ImmutableByteSequence data = copyFrom(packetIn.getPayload().asReadOnlyByteBuffer());
-                ImmutableList.Builder<ImmutableByteSequence> metadataBuilder = ImmutableList.builder();
-                packetIn.getMetadataList().stream()
-                        .map(m -> m.getValue().asReadOnlyByteBuffer())
-                        .map(ImmutableByteSequence::copyFrom)
-                        .forEach(metadataBuilder::add);
-                event = new DefaultPacketInEvent(deviceId, data, metadataBuilder.build());
+                case ARBITRATION:
+                    throw new UnsupportedOperationException("Arbitration not implemented.");
 
-            } else if (message.getArbitration().isInitialized()) {
-                // Arbitration.
-                throw new UnsupportedOperationException("Arbitration not implemented.");
-
-            } else {
-                log.warn("Unrecognized stream message from {}: {}", deviceId, message);
-                return;
+                default:
+                    log.warn("Unrecognized stream message from {}: {}", deviceId, message.getUpdateCase());
             }
-
-            controller.postEvent(event);
         }
 
         @Override
@@ -293,5 +425,4 @@
             // FIXME: same concern as before.
         }
     }
-
-}
+}
\ No newline at end of file
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java
index 06376ca..ca78652 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java
@@ -44,7 +44,6 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.String.format;
-import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -56,18 +55,16 @@
         extends AbstractListenerManager<P4RuntimeEvent, P4RuntimeEventListener>
         implements P4RuntimeController {
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected GrpcController grpcController;
-
     private final Logger log = getLogger(getClass());
-
     private final NameResolverProvider nameResolverProvider = new DnsNameResolverProvider();
     private final Map<DeviceId, P4RuntimeClient> clients = Maps.newHashMap();
     private final Map<DeviceId, GrpcChannelId> channelIds = Maps.newHashMap();
-
     // TODO: should use a cache to delete unused locks.
     private final Map<DeviceId, ReadWriteLock> deviceLocks = Maps.newConcurrentMap();
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GrpcController grpcController;
+
     @Activate
     public void activate() {
         log.info("Started");
@@ -117,8 +114,7 @@
             return false;
         }
 
-        P4RuntimeClient client = new P4RuntimeClientImpl(deviceId, p4DeviceId, channel, this,
-                                                         newSingleThreadExecutor());
+        P4RuntimeClient client = new P4RuntimeClientImpl(deviceId, p4DeviceId, channel, this);
 
         channelIds.put(deviceId, channelId);
         clients.put(deviceId, client);
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PipeconfHelper.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PipeconfHelper.java
new file mode 100644
index 0000000..8f5c408
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PipeconfHelper.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.common.collect.Maps;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.TextFormat;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.slf4j.Logger;
+import p4.config.P4InfoOuterClass.P4Info;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map;
+
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Utility class to deal with pipeconfs in the context of P4runtime.
+ */
+final class PipeconfHelper {
+
+    private static final Logger log = getLogger(PipeconfHelper.class);
+
+    // TODO: consider implementing this via a cache that expires unused browsers.
+    private static final Map<PiPipeconfId, P4InfoBrowser> BROWSERS = Maps.newConcurrentMap();
+    private static final Map<PiPipeconfId, P4Info> P4INFOS = Maps.newConcurrentMap();
+
+    private PipeconfHelper() {
+        // hide.
+    }
+
+    /**
+     * Extracts and returns a P4Info protobuf message from the given pipeconf. If the pipeconf does not define any
+     * extension of type {@link PiPipeconf.ExtensionType#P4_INFO_TEXT}, returns null;
+     *
+     * @param pipeconf pipeconf
+     * @return P4Info or null
+     */
+    static P4Info getP4Info(PiPipeconf pipeconf) {
+        return P4INFOS.computeIfAbsent(pipeconf.id(), piPipeconfId -> {
+            if (!pipeconf.extension(P4_INFO_TEXT).isPresent()) {
+                log.warn("Missing P4Info extension in pipeconf {}", pipeconf.id());
+                return null;
+            }
+
+            InputStream p4InfoStream = pipeconf.extension(P4_INFO_TEXT).get();
+            P4Info.Builder p4iInfoBuilder = P4Info.newBuilder();
+            try {
+                TextFormat.getParser().merge(new InputStreamReader(p4InfoStream), ExtensionRegistry.getEmptyRegistry(),
+                                             p4iInfoBuilder);
+            } catch (IOException ex) {
+                log.warn("Unable to parse P4Info of pipeconf {}: {}", pipeconf.id(), ex.getMessage());
+                return null;
+            }
+
+            return p4iInfoBuilder.build();
+        });
+    }
+
+    /**
+     * Returns a P4Info browser for the given pipeconf. If the pipeconf does not define any extension of type
+     * {@link PiPipeconf.ExtensionType#P4_INFO_TEXT}, returns null;
+     *
+     * @param pipeconf pipeconf
+     * @return P4Info browser or null
+     */
+    static P4InfoBrowser getP4InfoBrowser(PiPipeconf pipeconf) {
+        return BROWSERS.computeIfAbsent(pipeconf.id(), (pipeconfId) -> {
+            P4Info p4info = PipeconfHelper.getP4Info(pipeconf);
+            if (p4info == null) {
+                return null;
+            } else {
+                return new P4InfoBrowser(p4info);
+            }
+        });
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
new file mode 100644
index 0000000..68e1808
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
+import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
+import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
+import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.net.pi.runtime.PiValidFieldMatch;
+import org.slf4j.Logger;
+import p4.P4RuntimeOuterClass.Action;
+import p4.P4RuntimeOuterClass.FieldMatch;
+import p4.P4RuntimeOuterClass.TableAction;
+import p4.P4RuntimeOuterClass.TableEntry;
+import p4.config.P4InfoOuterClass;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static java.lang.String.format;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Encoder of table entries, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa.
+ */
+final class TableEntryEncoder {
+
+
+    private static final Logger log = getLogger(TableEntryEncoder.class);
+
+    private static final String HEADER_PREFIX = "hdr.";
+    private static final String VALUE_OF_PREFIX = "value of ";
+    private static final String MASK_OF_PREFIX = "mask of ";
+    private static final String HIGH_RANGE_VALUE_OF_PREFIX = "high range value of ";
+    private static final String LOW_RANGE_VALUE_OF_PREFIX = "low range value of ";
+
+    // TODO: implement cache of encoded entities.
+
+    private TableEntryEncoder() {
+        // hide.
+    }
+
+    /**
+     * Returns a collection of P4Runtime table entry protobuf messages, encoded from the given collection of PI
+     * table entries for the given pipeconf. If a PI table entry cannot be encoded, it is skipped, hence the returned
+     * collection might have different size than the input one.
+     * <p>
+     * Please check the log for an explanation of any error that might have occurred.
+     *
+     * @param piTableEntries PI table entries
+     * @param pipeconf       PI pipeconf
+     * @return collection of P4Runtime table entry protobuf messages
+     */
+    static Collection<TableEntry> encode(Collection<PiTableEntry> piTableEntries, PiPipeconf pipeconf) {
+
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        if (browser == null) {
+            log.error("Unable to get a P4Info browser for pipeconf {}, skipping encoding of all table entries");
+            return Collections.emptyList();
+        }
+
+        ImmutableList.Builder<TableEntry> tableEntryMsgListBuilder = ImmutableList.builder();
+
+        for (PiTableEntry piTableEntry : piTableEntries) {
+            try {
+                tableEntryMsgListBuilder.add(encodePiTableEntry(piTableEntry, browser));
+            } catch (P4InfoBrowser.NotFoundException | EncodeException e) {
+                log.error("Unable to encode PI table entry: {}", e.getMessage());
+            }
+        }
+
+        return tableEntryMsgListBuilder.build();
+    }
+
+    /**
+     * Returns a collection of PI table entry objects, decoded from the given collection of P4Runtime table entry
+     * messages for the given pipeconf. If a table entry message cannot be decoded, it is skipped, hence the returned
+     * collection might have different size than the input one.
+     * <p>
+     * Please check the log for an explanation of any error that might have occurred.
+     *
+     * @param tableEntryMsgs P4Runtime table entry messages
+     * @param pipeconf       PI pipeconf
+     * @return collection of PI table entry objects
+     */
+    static Collection<PiTableEntry> decode(Collection<TableEntry> tableEntryMsgs, PiPipeconf pipeconf) {
+
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        if (browser == null) {
+            log.error("Unable to get a P4Info browser for pipeconf {}, skipping decoding of all table entries");
+            return Collections.emptyList();
+        }
+
+        ImmutableList.Builder<PiTableEntry> piTableEntryListBuilder = ImmutableList.builder();
+
+        for (TableEntry tableEntryMsg : tableEntryMsgs) {
+            try {
+                piTableEntryListBuilder.add(decodeTableEntryMsg(tableEntryMsg, browser));
+            } catch (P4InfoBrowser.NotFoundException | EncodeException e) {
+                log.error("Unable to decode table entry message: {}", e.getMessage());
+            }
+        }
+
+        return piTableEntryListBuilder.build();
+    }
+
+    private static TableEntry encodePiTableEntry(PiTableEntry piTableEntry, P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        TableEntry.Builder tableEntryMsgBuilder = TableEntry.newBuilder();
+
+        P4InfoOuterClass.Table tableInfo = browser.tables().getByName(piTableEntry.table().id());
+
+        // Table id.
+        tableEntryMsgBuilder.setTableId(tableInfo.getPreamble().getId());
+
+        // Priority.
+        // FIXME: check on P4Runtime if/what is the defaulr priority.
+        int priority = piTableEntry.priority().orElse(0);
+        tableEntryMsgBuilder.setPriority(priority);
+
+        // Controller metadata (cookie)
+        tableEntryMsgBuilder.setControllerMetadata(piTableEntry.cookie());
+
+        // Timeout.
+        if (piTableEntry.timeout().isPresent()) {
+            log.warn("Found PI table entry with timeout set, not supported in P4Runtime: {}", piTableEntry);
+        }
+
+        // Table action.
+        tableEntryMsgBuilder.setAction(encodePiTableAction(piTableEntry.action(), browser));
+
+        // Field matches.
+        for (PiFieldMatch piFieldMatch : piTableEntry.fieldMatches()) {
+            tableEntryMsgBuilder.addMatch(encodePiFieldMatch(piFieldMatch, tableInfo, browser));
+        }
+
+        return tableEntryMsgBuilder.build();
+    }
+
+    private static PiTableEntry decodeTableEntryMsg(TableEntry tableEntryMsg, P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        PiTableEntry.Builder piTableEntryBuilder = PiTableEntry.builder();
+
+        P4InfoOuterClass.Table tableInfo = browser.tables().getById(tableEntryMsg.getTableId());
+
+        // Table id.
+        piTableEntryBuilder.forTable(PiTableId.of(tableInfo.getPreamble().getName()));
+
+        // Priority.
+        piTableEntryBuilder.withPriority(tableEntryMsg.getPriority());
+
+        // Controller metadata (cookie)
+        piTableEntryBuilder.withCookie(tableEntryMsg.getControllerMetadata());
+
+        // Table action.
+        piTableEntryBuilder.withAction(decodeTableActionMsg(tableEntryMsg.getAction(), browser));
+
+        // Timeout.
+        // FIXME: how to decode table entry messages with timeout, given that the timeout value is lost after encoding?
+
+        // Field matches.
+        for (FieldMatch fieldMatchMsg : tableEntryMsg.getMatchList()) {
+            piTableEntryBuilder.withFieldMatch(decodeFieldMatchMsg(fieldMatchMsg, tableInfo, browser));
+        }
+
+        return piTableEntryBuilder.build();
+    }
+
+    private static FieldMatch encodePiFieldMatch(PiFieldMatch piFieldMatch, P4InfoOuterClass.Table tableInfo,
+                                                 P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        FieldMatch.Builder fieldMatchMsgBuilder = FieldMatch.newBuilder();
+
+        // FIXME: check how field names for stacked headers are constructed in P4Runtime.
+        String fieldName = piFieldMatch.fieldId().id();
+        int tableId = tableInfo.getPreamble().getId();
+        P4InfoOuterClass.MatchField matchFieldInfo = browser.matchFields(tableId).getByNameOrAlias(fieldName);
+        String entityName = format("field match '%s' of table '%s'",
+                                   matchFieldInfo.getName(), tableInfo.getPreamble().getName());
+        int fieldId = matchFieldInfo.getId();
+        int fieldBitwidth = matchFieldInfo.getBitwidth();
+
+        fieldMatchMsgBuilder.setFieldId(fieldId);
+
+        switch (piFieldMatch.type()) {
+            case EXACT:
+                PiExactFieldMatch fieldMatch = (PiExactFieldMatch) piFieldMatch;
+                ByteString exactValue = ByteString.copyFrom(fieldMatch.value().asReadOnlyBuffer());
+                assertSize(VALUE_OF_PREFIX + entityName, exactValue, fieldBitwidth);
+                return fieldMatchMsgBuilder.setExact(
+                        FieldMatch.Exact
+                                .newBuilder()
+                                .setValue(exactValue)
+                                .build())
+                        .build();
+            case TERNARY:
+                PiTernaryFieldMatch ternaryMatch = (PiTernaryFieldMatch) piFieldMatch;
+                ByteString ternaryValue = ByteString.copyFrom(ternaryMatch.value().asReadOnlyBuffer());
+                ByteString ternaryMask = ByteString.copyFrom(ternaryMatch.mask().asReadOnlyBuffer());
+                assertSize(VALUE_OF_PREFIX + entityName, ternaryValue, fieldBitwidth);
+                assertSize(MASK_OF_PREFIX + entityName, ternaryMask, fieldBitwidth);
+                return fieldMatchMsgBuilder.setTernary(
+                        FieldMatch.Ternary
+                                .newBuilder()
+                                .setValue(ternaryValue)
+                                .setMask(ternaryMask)
+                                .build())
+                        .build();
+            case LPM:
+                PiLpmFieldMatch lpmMatch = (PiLpmFieldMatch) piFieldMatch;
+                ByteString lpmValue = ByteString.copyFrom(lpmMatch.value().asReadOnlyBuffer());
+                int lpmPrefixLen = lpmMatch.prefixLength();
+                assertSize(VALUE_OF_PREFIX + entityName, lpmValue, fieldBitwidth);
+                assertPrefixLen(entityName, lpmPrefixLen, fieldBitwidth);
+                return fieldMatchMsgBuilder.setLpm(
+                        FieldMatch.LPM.newBuilder()
+                                .setValue(lpmValue)
+                                .setPrefixLen(lpmPrefixLen)
+                                .build())
+                        .build();
+            case RANGE:
+                PiRangeFieldMatch rangeMatch = (PiRangeFieldMatch) piFieldMatch;
+                ByteString rangeHighValue = ByteString.copyFrom(rangeMatch.highValue().asReadOnlyBuffer());
+                ByteString rangeLowValue = ByteString.copyFrom(rangeMatch.lowValue().asReadOnlyBuffer());
+                assertSize(HIGH_RANGE_VALUE_OF_PREFIX + entityName, rangeHighValue, fieldBitwidth);
+                assertSize(LOW_RANGE_VALUE_OF_PREFIX + entityName, rangeLowValue, fieldBitwidth);
+                return fieldMatchMsgBuilder.setRange(
+                        FieldMatch.Range.newBuilder()
+                                .setHigh(rangeHighValue)
+                                .setLow(rangeLowValue)
+                                .build())
+                        .build();
+            case VALID:
+                PiValidFieldMatch validMatch = (PiValidFieldMatch) piFieldMatch;
+                return fieldMatchMsgBuilder.setValid(
+                        FieldMatch.Valid.newBuilder()
+                                .setValue(validMatch.isValid())
+                                .build())
+                        .build();
+            default:
+                throw new EncodeException(format(
+                        "Building of match type %s not implemented", piFieldMatch.type()));
+        }
+    }
+
+    private static PiFieldMatch decodeFieldMatchMsg(FieldMatch fieldMatchMsg, P4InfoOuterClass.Table tableInfo,
+                                                    P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        int tableId = tableInfo.getPreamble().getId();
+        String fieldMatchName = browser.matchFields(tableId).getById(fieldMatchMsg.getFieldId()).getName();
+        if (fieldMatchName.startsWith(HEADER_PREFIX)) {
+            fieldMatchName = fieldMatchName.substring(HEADER_PREFIX.length());
+        }
+
+        // FIXME: Add support for decoding of stacked header names.
+        String[] pieces = fieldMatchName.split("\\.");
+        if (pieces.length != 2) {
+            throw new EncodeException(format("unrecognized field match name '%s'", fieldMatchName));
+        }
+        PiHeaderFieldId headerFieldId = PiHeaderFieldId.of(pieces[0], pieces[1]);
+
+        FieldMatch.FieldMatchTypeCase typeCase = fieldMatchMsg.getFieldMatchTypeCase();
+
+        switch (typeCase) {
+            case EXACT:
+                FieldMatch.Exact exactFieldMatch = fieldMatchMsg.getExact();
+                ImmutableByteSequence exactValue = copyFrom(exactFieldMatch.getValue().asReadOnlyByteBuffer());
+                return new PiExactFieldMatch(headerFieldId, exactValue);
+            case TERNARY:
+                FieldMatch.Ternary ternaryFieldMatch = fieldMatchMsg.getTernary();
+                ImmutableByteSequence ternaryValue = copyFrom(ternaryFieldMatch.getValue().asReadOnlyByteBuffer());
+                ImmutableByteSequence ternaryMask = copyFrom(ternaryFieldMatch.getMask().asReadOnlyByteBuffer());
+                return new PiTernaryFieldMatch(headerFieldId, ternaryValue, ternaryMask);
+            case LPM:
+                FieldMatch.LPM lpmFieldMatch = fieldMatchMsg.getLpm();
+                ImmutableByteSequence lpmValue = copyFrom(lpmFieldMatch.getValue().asReadOnlyByteBuffer());
+                int lpmPrefixLen = lpmFieldMatch.getPrefixLen();
+                return new PiLpmFieldMatch(headerFieldId, lpmValue, lpmPrefixLen);
+            case RANGE:
+                FieldMatch.Range rangeFieldMatch = fieldMatchMsg.getRange();
+                ImmutableByteSequence rangeHighValue = copyFrom(rangeFieldMatch.getHigh().asReadOnlyByteBuffer());
+                ImmutableByteSequence rangeLowValue = copyFrom(rangeFieldMatch.getLow().asReadOnlyByteBuffer());
+                return new PiRangeFieldMatch(headerFieldId, rangeLowValue, rangeHighValue);
+            case VALID:
+                FieldMatch.Valid validFieldMatch = fieldMatchMsg.getValid();
+                return new PiValidFieldMatch(headerFieldId, validFieldMatch.getValue());
+            default:
+                throw new EncodeException(format(
+                        "Decoding of field match type '%s' not implemented", typeCase.name()));
+        }
+    }
+
+    private static TableAction encodePiTableAction(PiTableAction piTableAction, P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        TableAction.Builder tableActionMsgBuilder = TableAction.newBuilder();
+
+        switch (piTableAction.type()) {
+            case ACTION:
+                PiAction piAction = (PiAction) piTableAction;
+                int actionId = browser.actions().getByName(piAction.id().name()).getPreamble().getId();
+
+                Action.Builder actionMsgBuilder = Action.newBuilder().setActionId(actionId);
+
+                for (PiActionParam p : piAction.parameters()) {
+                    P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId).getByName(p.id().name());
+                    ByteString paramValue = ByteString.copyFrom(p.value().asReadOnlyBuffer());
+                    assertSize(format("param '%s' of action '%s'", p.id(), piAction.id()),
+                               paramValue, paramInfo.getBitwidth());
+                    actionMsgBuilder.addParams(Action.Param.newBuilder()
+                                                       .setParamId(paramInfo.getId())
+                                                       .setValue(paramValue)
+                                                       .build());
+                }
+
+                tableActionMsgBuilder.setAction(actionMsgBuilder.build());
+                break;
+
+            default:
+                throw new EncodeException(
+                        format("Building of table action type %s not implemented", piTableAction.type()));
+        }
+
+        return tableActionMsgBuilder.build();
+    }
+
+    private static PiTableAction decodeTableActionMsg(TableAction tableActionMsg, P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        TableAction.TypeCase typeCase = tableActionMsg.getTypeCase();
+
+        switch (typeCase) {
+            case ACTION:
+                PiAction.Builder piActionBuilder = PiAction.builder();
+                Action actionMsg = tableActionMsg.getAction();
+                // Action ID.
+                int actionId = actionMsg.getActionId();
+                String actionName = browser.actions().getById(actionId).getPreamble().getName();
+                piActionBuilder.withId(PiActionId.of(actionName));
+                // Params.
+                for (Action.Param paramMsg : actionMsg.getParamsList()) {
+                    String paramName = browser.actionParams(actionId).getById(paramMsg.getParamId()).getName();
+                    ImmutableByteSequence paramValue = copyFrom(paramMsg.getValue().asReadOnlyByteBuffer());
+                    piActionBuilder.withParameter(new PiActionParam(PiActionParamId.of(paramName), paramValue));
+                }
+                return piActionBuilder.build();
+
+            default:
+                throw new EncodeException(
+                        format("Decoding of table action type %s not implemented", typeCase.name()));
+        }
+    }
+
+    private static void assertSize(String entityDescr, ByteString value, int bitWidth)
+            throws EncodeException {
+
+        int byteWidth = (int) Math.ceil((float) bitWidth / 8);
+        if (value.size() != byteWidth) {
+            throw new EncodeException(format("Wrong size for %s, expected %d bytes, but found %d",
+                                             entityDescr, byteWidth, value.size()));
+        }
+    }
+
+    private static void assertPrefixLen(String entityDescr, int prefixLength, int bitWidth)
+            throws EncodeException {
+
+        if (prefixLength > bitWidth) {
+            throw new EncodeException(format(
+                    "wrong prefix length for %s, field size is %d bits, but found one is %d",
+                    entityDescr, bitWidth, prefixLength));
+        }
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
new file mode 100644
index 0000000..daeb62f
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import p4.P4RuntimeOuterClass.Action;
+import p4.P4RuntimeOuterClass.TableEntry;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Random;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onlab.util.ImmutableByteSequence.fit;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
+import static org.onosproject.p4runtime.ctl.TableEntryEncoder.decode;
+import static org.onosproject.p4runtime.ctl.TableEntryEncoder.encode;
+
+public class TableEntryEncoderTest {
+
+    private static final String TABLE_0 = "table0";
+    private static final String SET_EGRESS_PORT = "set_egress_port";
+    private static final String PORT = "port";
+    private static final String ETHERNET = "ethernet";
+    private static final String DST_ADDR = "dstAddr";
+    private static final String SRC_ADDR = "srcAddr";
+    private static final String STANDARD_METADATA = "standard_metadata";
+    private static final String INGRESS_PORT = "ingress_port";
+    private static final String ETHER_TYPE = "etherType";
+
+    private final Random rand = new Random();
+    private final URL p4InfoUrl = this.getClass().getResource("/default.p4info");
+    private final URL jsonUrl = this.getClass().getResource("/default.json");
+
+    private final PiPipeconf defaultPipeconf = DefaultPiPipeconf.builder()
+            .withId(new PiPipeconfId("mock"))
+            .withPipelineModel(Bmv2PipelineModelParser.parse(jsonUrl))
+            .addExtension(P4_INFO_TEXT, p4InfoUrl)
+            .addExtension(BMV2_JSON, jsonUrl)
+            .build();
+
+    private final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(defaultPipeconf);
+    private final ImmutableByteSequence ethAddr = fit(copyFrom(rand.nextInt()), 48);
+    private final ImmutableByteSequence portValue = copyFrom((short) rand.nextInt());
+    private final PiHeaderFieldId ethDstAddrFieldId = PiHeaderFieldId.of(ETHERNET, DST_ADDR);
+    private final PiHeaderFieldId ethSrcAddrFieldId = PiHeaderFieldId.of(ETHERNET, SRC_ADDR);
+    private final PiHeaderFieldId inPortFieldId = PiHeaderFieldId.of(STANDARD_METADATA, INGRESS_PORT);
+    private final PiHeaderFieldId ethTypeFieldId = PiHeaderFieldId.of(ETHERNET, ETHER_TYPE);
+    private final PiActionParamId portParamId = PiActionParamId.of(PORT);
+    private final PiActionId outActionId = PiActionId.of(SET_EGRESS_PORT);
+    private final PiTableId tableId = PiTableId.of(TABLE_0);
+
+    private final PiTableEntry piTableEntry = PiTableEntry
+            .builder()
+            .forTable(tableId)
+            .withFieldMatch(new PiTernaryFieldMatch(ethDstAddrFieldId, ethAddr, ImmutableByteSequence.ofOnes(6)))
+            .withFieldMatch(new PiTernaryFieldMatch(ethSrcAddrFieldId, ethAddr, ImmutableByteSequence.ofOnes(6)))
+            .withFieldMatch(new PiTernaryFieldMatch(inPortFieldId, portValue, ImmutableByteSequence.ofOnes(2)))
+            .withFieldMatch(new PiTernaryFieldMatch(ethTypeFieldId, portValue, ImmutableByteSequence.ofOnes(2)))
+            .withAction(PiAction
+                                .builder()
+                                .withId(outActionId)
+                                .withParameter(new PiActionParam(portParamId, portValue))
+                                .build())
+            .withPriority(1)
+            .withCookie(2)
+            .build();
+
+    public TableEntryEncoderTest() throws ImmutableByteSequence.ByteSequenceTrimException {
+    }
+
+    @Test
+    public void testP4InfoBrowser() throws Exception {
+
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(defaultPipeconf);
+
+        assertThat(browser.tables().hasName(TABLE_0), is(true));
+        assertThat(browser.actions().hasName(SET_EGRESS_PORT), is(true));
+
+        int tableId = browser.tables().getByName(TABLE_0).getPreamble().getId();
+        int actionId = browser.actions().getByName(SET_EGRESS_PORT).getPreamble().getId();
+
+        assertThat(browser.matchFields(tableId).hasName(STANDARD_METADATA + "." + INGRESS_PORT), is(true));
+        assertThat(browser.actionParams(actionId).hasName(PORT), is(true));
+
+        // TODO: improve, assert browsing other entities (counters, meters, etc.)
+    }
+
+    @Test
+    public void testTableEntryEncoder()
+            throws P4InfoBrowser.NotFoundException, ImmutableByteSequence.ByteSequenceTrimException {
+
+        Collection<TableEntry> result = encode(Lists.newArrayList(piTableEntry), defaultPipeconf);
+        assertThat(result, hasSize(1));
+
+        TableEntry tableEntryMsg = result.iterator().next();
+
+        Collection<PiTableEntry> decodedResults = decode(Lists.newArrayList(tableEntryMsg), defaultPipeconf);
+        PiTableEntry decodedPiTableEntry = decodedResults.iterator().next();
+
+        // Test equality for decoded entry.
+        new EqualsTester()
+                .addEqualityGroup(piTableEntry, decodedPiTableEntry)
+                .testEquals();
+
+        // Table ID.
+        int p4InfoTableId = browser.tables().getByName(tableId.id()).getPreamble().getId();
+        int encodedTableId = tableEntryMsg.getTableId();
+        assertThat(encodedTableId, is(p4InfoTableId));
+
+        // Ternary match.
+        byte[] encodedTernaryMatchValue = tableEntryMsg.getMatch(0).getTernary().getValue().toByteArray();
+        assertThat(encodedTernaryMatchValue, is(ethAddr.asArray()));
+
+        Action actionMsg = tableEntryMsg.getAction().getAction();
+
+        // Action ID.
+        int p4InfoActionId = browser.actions().getByName(outActionId.name()).getPreamble().getId();
+        int encodedActionId = actionMsg.getActionId();
+        assertThat(encodedActionId, is(p4InfoActionId));
+
+        // Action param ID.
+        int p4InfoActionParamId = browser.actionParams(p4InfoActionId).getByName(portParamId.name()).getId();
+        int encodedActionParamId = actionMsg.getParams(0).getParamId();
+        assertThat(encodedActionParamId, is(p4InfoActionParamId));
+
+        // Action param value.
+        byte[] encodedActionParam = actionMsg.getParams(0).getValue().toByteArray();
+        assertThat(encodedActionParam, is(portValue.asArray()));
+
+        // TODO: improve, assert other field match types (ternary, LPM)
+    }
+
+//    @Test
+//    public void testRuntime() throws ExecutionException, InterruptedException {
+//
+//        // FIXME: remove me.
+//
+//        P4RuntimeControllerImpl controller = new P4RuntimeControllerImpl();
+//        GrpcControllerImpl grpcController = new GrpcControllerImpl();
+//        controller.grpcController = grpcController;
+//        GrpcControllerImpl.ENABLE_MESSAGE_LOG = true;
+//        grpcController.activate();
+//        DeviceId deviceId = DeviceId.deviceId("dummy:1");
+//
+//        ManagedChannelBuilder channelBuilder = NettyChannelBuilder
+//                .forAddress("192.168.56.102", 55044)
+//                .usePlaintext(true);
+//
+//        assert (controller.createClient(deviceId, 1, channelBuilder));
+//
+//        P4RuntimeClient client = controller.getClient(deviceId);
+//
+//        assert(client.setPipelineConfig(defaultPipeconf, PiPipeconf.ExtensionType.BMV2_JSON).get());
+//
+//        assert(client.initStreamChannel().get());
+//
+//        assert(client.dumpTable(PiTableId.of(TABLE_0), defaultPipeconf).get().size() == 0);
+//
+//        assert(client.writeTableEntries(Lists.newArrayList(piTableEntry), INSERT, defaultPipeconf).get());
+//
+//        assert(client.dumpTable(PiTableId.of(TABLE_0), defaultPipeconf).get().size() == 1);
+//    }
+}
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java
deleted file mode 100644
index 17263e4..0000000
--- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.onosproject.p4runtime.ctl;
-
-import com.google.protobuf.ExtensionRegistry;
-import com.google.protobuf.TextFormat;
-import org.junit.Test;
-import p4.config.P4InfoOuterClass;
-
-import java.io.InputStream;
-import java.io.InputStreamReader;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-public class TestP4Info {
-
-    private InputStream p4InfoStream = this.getClass().getResourceAsStream("/default.p4info");
-
-    @Test
-    public void testP4InfoBrowser() throws Exception {
-
-        InputStreamReader input = new InputStreamReader(p4InfoStream);
-        ExtensionRegistry extensionRegistry = ExtensionRegistry.getEmptyRegistry();
-        P4InfoOuterClass.P4Info.Builder builder = P4InfoOuterClass.P4Info.newBuilder();
-
-        builder.clear();
-        TextFormat.getParser().merge(input, extensionRegistry, builder);
-        P4InfoOuterClass.P4Info p4Info = builder.build();
-
-        P4InfoBrowser browser = new P4InfoBrowser(p4Info);
-
-        assertThat(browser.tables().hasName("table0"), is(true));
-        assertThat(browser.actions().hasName("set_egress_port"), is(true));
-
-        int tableId = browser.tables().getByName("table0").getPreamble().getId();
-        int actionId = browser.actions().getByName("set_egress_port").getPreamble().getId();
-
-        assertThat(browser.matchFields(tableId).hasName("standard_metadata.ingress_port"), is(true));
-        assertThat(browser.actionParams(actionId).hasName("port"), is(true));
-
-        // TODO: improve, assert browsing of other entities (counters, meters, etc.)
-    }
-}
diff --git a/protocols/p4runtime/ctl/src/test/resources/default.json b/protocols/p4runtime/ctl/src/test/resources/default.json
new file mode 100644
index 0000000..3a46dcc
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/resources/default.json
@@ -0,0 +1,2697 @@
+{
+  "program" : "default.p4",
+  "__meta__" : {
+    "version" : [2, 7],
+    "compiler" : "https://github.com/p4lang/p4c"
+  },
+  "header_types" : [
+    {
+      "name" : "scalars_0",
+      "id" : 0,
+      "fields" : [
+        ["tmp", 32, false],
+        ["tmp_0", 32, false]
+      ]
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 1,
+      "fields" : [
+        ["ingress_port", 9, false],
+        ["egress_spec", 9, false],
+        ["egress_port", 9, false],
+        ["clone_spec", 32, false],
+        ["instance_type", 32, false],
+        ["drop", 1, false],
+        ["recirculate_port", 16, false],
+        ["packet_length", 32, false],
+        ["enq_timestamp", 32, false],
+        ["enq_qdepth", 19, false],
+        ["deq_timedelta", 32, false],
+        ["deq_qdepth", 19, false],
+        ["ingress_global_timestamp", 48, false],
+        ["lf_field_list", 32, false],
+        ["mcast_grp", 16, false],
+        ["resubmit_flag", 1, false],
+        ["egress_rid", 16, false],
+        ["_padding", 5, false]
+      ]
+    },
+    {
+      "name" : "ethernet_t",
+      "id" : 2,
+      "fields" : [
+        ["dstAddr", 48, false],
+        ["srcAddr", 48, false],
+        ["etherType", 16, false]
+      ]
+    },
+    {
+      "name" : "ipv4_t",
+      "id" : 3,
+      "fields" : [
+        ["version", 4, false],
+        ["ihl", 4, false],
+        ["diffserv", 8, false],
+        ["totalLen", 16, false],
+        ["identification", 16, false],
+        ["flags", 3, false],
+        ["fragOffset", 13, false],
+        ["ttl", 8, false],
+        ["protocol", 8, false],
+        ["hdrChecksum", 16, false],
+        ["srcAddr", 32, false],
+        ["dstAddr", 32, false]
+      ]
+    },
+    {
+      "name" : "tcp_t",
+      "id" : 4,
+      "fields" : [
+        ["srcPort", 16, false],
+        ["dstPort", 16, false],
+        ["seqNo", 32, false],
+        ["ackNo", 32, false],
+        ["dataOffset", 4, false],
+        ["res", 3, false],
+        ["ecn", 3, false],
+        ["ctrl", 6, false],
+        ["window", 16, false],
+        ["checksum", 16, false],
+        ["urgentPtr", 16, false]
+      ]
+    },
+    {
+      "name" : "udp_t",
+      "id" : 5,
+      "fields" : [
+        ["srcPort", 16, false],
+        ["dstPort", 16, false],
+        ["length_", 16, false],
+        ["checksum", 16, false]
+      ]
+    },
+    {
+      "name" : "ecmp_metadata_t",
+      "id" : 6,
+      "fields" : [
+        ["groupId", 16, false],
+        ["selector", 16, false]
+      ]
+    },
+    {
+      "name" : "wcmp_meta_t",
+      "id" : 7,
+      "fields" : [
+        ["groupId", 16, false],
+        ["numBits", 8, false],
+        ["selector", 64, false]
+      ]
+    },
+    {
+      "name" : "intrinsic_metadata_t",
+      "id" : 8,
+      "fields" : [
+        ["ingress_global_timestamp", 32, false],
+        ["lf_field_list", 32, false],
+        ["mcast_grp", 16, false],
+        ["egress_rid", 16, false]
+      ]
+    }
+  ],
+  "headers" : [
+    {
+      "name" : "standard_metadata_3",
+      "id" : 0,
+      "header_type" : "standard_metadata",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "standard_metadata_4",
+      "id" : 1,
+      "header_type" : "standard_metadata",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "standard_metadata_5",
+      "id" : 2,
+      "header_type" : "standard_metadata",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "scalars",
+      "id" : 3,
+      "header_type" : "scalars_0",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 4,
+      "header_type" : "standard_metadata",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ethernet",
+      "id" : 5,
+      "header_type" : "ethernet_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ipv4",
+      "id" : 6,
+      "header_type" : "ipv4_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "tcp",
+      "id" : 7,
+      "header_type" : "tcp_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "udp",
+      "id" : 8,
+      "header_type" : "udp_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ecmp_metadata",
+      "id" : 9,
+      "header_type" : "ecmp_metadata_t",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "wcmp_meta",
+      "id" : 10,
+      "header_type" : "wcmp_meta_t",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "intrinsic_metadata",
+      "id" : 11,
+      "header_type" : "intrinsic_metadata_t",
+      "metadata" : true,
+      "pi_omit" : true
+    }
+  ],
+  "header_stacks" : [],
+  "header_union_types" : [],
+  "header_unions" : [],
+  "header_union_stacks" : [],
+  "field_lists" : [],
+  "errors" : [
+    ["NoError", 1],
+    ["PacketTooShort", 2],
+    ["NoMatch", 3],
+    ["StackOutOfBounds", 4],
+    ["HeaderTooShort", 5],
+    ["ParserTimeout", 6]
+  ],
+  "enums" : [],
+  "parsers" : [
+    {
+      "name" : "parser",
+      "id" : 0,
+      "init_state" : "start",
+      "parse_states" : [
+        {
+          "name" : "parse_ipv4",
+          "id" : 0,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ipv4"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "0x06",
+              "mask" : null,
+              "next_state" : "parse_tcp"
+            },
+            {
+              "value" : "0x11",
+              "mask" : null,
+              "next_state" : "parse_udp"
+            },
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : [
+            {
+              "type" : "field",
+              "value" : ["ipv4", "protocol"]
+            }
+          ]
+        },
+        {
+          "name" : "parse_tcp",
+          "id" : 1,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "tcp"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : []
+        },
+        {
+          "name" : "parse_udp",
+          "id" : 2,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "udp"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : []
+        },
+        {
+          "name" : "start",
+          "id" : 3,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ethernet"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "0x0800",
+              "mask" : null,
+              "next_state" : "parse_ipv4"
+            },
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "etherType"]
+            }
+          ]
+        }
+      ]
+    }
+  ],
+  "deparsers" : [
+    {
+      "name" : "deparser",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "include/parsers.p4",
+        "line" : 43,
+        "column" : 8,
+        "source_fragment" : "DeparserImpl"
+      },
+      "order" : ["ethernet", "ipv4", "udp", "tcp"]
+    }
+  ],
+  "meter_arrays" : [],
+  "counter_arrays" : [
+    {
+      "name" : "table0_counter",
+      "id" : 0,
+      "is_direct" : true,
+      "binding" : "table0"
+    },
+    {
+      "name" : "port_counters_control.egress_port_counter",
+      "id" : 1,
+      "source_info" : {
+        "filename" : "include/port_counters.p4",
+        "line" : 6,
+        "column" : 38,
+        "source_fragment" : "egress_port_counter"
+      },
+      "size" : 254,
+      "is_direct" : false
+    },
+    {
+      "name" : "port_counters_control.ingress_port_counter",
+      "id" : 2,
+      "source_info" : {
+        "filename" : "include/port_counters.p4",
+        "line" : 7,
+        "column" : 38,
+        "source_fragment" : "ingress_port_counter"
+      },
+      "size" : 254,
+      "is_direct" : false
+    }
+  ],
+  "register_arrays" : [],
+  "calculations" : [],
+  "learn_lists" : [],
+  "actions" : [
+    {
+      "name" : "set_egress_port",
+      "id" : 0,
+      "runtime_data" : [
+        {
+          "name" : "port",
+          "bitwidth" : 9
+        }
+      ],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "egress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "clone_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "clone_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "instance_type"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "instance_type"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "drop"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "drop"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "recirculate_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "recirculate_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "packet_length"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "packet_length"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "enq_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "enq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "deq_timedelta"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_timedelta"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "deq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "ingress_global_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_global_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "lf_field_list"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "lf_field_list"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "mcast_grp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "mcast_grp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "resubmit_flag"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "resubmit_flag"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "egress_rid"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_rid"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "egress_spec"]
+            },
+            {
+              "type" : "runtime_data",
+              "value" : 0
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 9,
+            "column" : 4,
+            "source_fragment" : "standard_metadata.egress_spec = port"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "egress_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "clone_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "clone_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "instance_type"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "instance_type"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "drop"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "drop"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "recirculate_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "recirculate_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "packet_length"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "packet_length"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "enq_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "enq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_timedelta"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "deq_timedelta"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "deq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_global_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "ingress_global_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "lf_field_list"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "lf_field_list"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "mcast_grp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "mcast_grp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "resubmit_flag"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "resubmit_flag"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_rid"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_3", "egress_rid"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 8,
+            "column" : 49,
+            "source_fragment" : "standard_metadata, Port port) { ..."
+          }
+        }
+      ]
+    },
+    {
+      "name" : "send_to_cpu",
+      "id" : 1,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "egress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "clone_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "clone_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "instance_type"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "instance_type"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "drop"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "drop"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "recirculate_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "recirculate_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "packet_length"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "packet_length"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "enq_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "enq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "deq_timedelta"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_timedelta"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "deq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "ingress_global_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_global_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "lf_field_list"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "lf_field_list"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "mcast_grp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "mcast_grp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "resubmit_flag"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "resubmit_flag"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "egress_rid"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_rid"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "egress_spec"]
+            },
+            {
+              "type" : "hexstr",
+              "value" : "0x00ff"
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 5,
+            "column" : 4,
+            "source_fragment" : "standard_metadata.egress_spec = 9w255"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "egress_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "clone_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "clone_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "instance_type"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "instance_type"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "drop"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "drop"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "recirculate_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "recirculate_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "packet_length"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "packet_length"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "enq_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "enq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_timedelta"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "deq_timedelta"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "deq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_global_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "ingress_global_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "lf_field_list"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "lf_field_list"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "mcast_grp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "mcast_grp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "resubmit_flag"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "resubmit_flag"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_rid"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_4", "egress_rid"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 4,
+            "column" : 45,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        }
+      ]
+    },
+    {
+      "name" : "drop",
+      "id" : 2,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "egress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "clone_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "clone_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "instance_type"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "instance_type"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "drop"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "drop"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "recirculate_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "recirculate_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "packet_length"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "packet_length"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "enq_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "enq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "deq_timedelta"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_timedelta"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "deq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "ingress_global_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_global_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "lf_field_list"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "lf_field_list"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "mcast_grp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "mcast_grp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "resubmit_flag"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "resubmit_flag"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "egress_rid"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_rid"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "egress_spec"]
+            },
+            {
+              "type" : "hexstr",
+              "value" : "0x01ff"
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 13,
+            "column" : 4,
+            "source_fragment" : "standard_metadata.egress_spec = 9w511"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "egress_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "clone_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "clone_spec"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "instance_type"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "instance_type"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "drop"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "drop"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "recirculate_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "recirculate_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "packet_length"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "packet_length"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "enq_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "enq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "enq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_timedelta"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "deq_timedelta"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "deq_qdepth"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "deq_qdepth"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_global_timestamp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "ingress_global_timestamp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "lf_field_list"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "lf_field_list"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "mcast_grp"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "mcast_grp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "resubmit_flag"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "resubmit_flag"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_rid"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata_5", "egress_rid"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/actions.p4",
+            "line" : 12,
+            "column" : 38,
+            "source_fragment" : "standard_metadata) { ..."
+          }
+        }
+      ]
+    },
+    {
+      "name" : "NoAction",
+      "id" : 3,
+      "runtime_data" : [],
+      "primitives" : []
+    },
+    {
+      "name" : "act",
+      "id" : 4,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "ingress_port"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "count",
+          "parameters" : [
+            {
+              "type" : "counter_array",
+              "value" : "port_counters_control.ingress_port_counter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 11,
+            "column" : 12,
+            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "egress_spec"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "count",
+          "parameters" : [
+            {
+              "type" : "counter_array",
+              "value" : "port_counters_control.egress_port_counter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 12,
+            "column" : 12,
+            "source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
+          }
+        }
+      ]
+    }
+  ],
+  "pipelines" : [
+    {
+      "name" : "ingress",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "default.p4",
+        "line" : 10,
+        "column" : 8,
+        "source_fragment" : "ingress"
+      },
+      "init_table" : "table0",
+      "tables" : [
+        {
+          "name" : "table0",
+          "id" : 0,
+          "source_info" : {
+            "filename" : "default.p4",
+            "line" : 13,
+            "column" : 10,
+            "source_fragment" : "table0"
+          },
+          "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
+            }
+          ],
+          "match_type" : "ternary",
+          "type" : "simple",
+          "max_size" : 1024,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [0, 1, 2, 3],
+          "actions" : ["set_egress_port", "send_to_cpu", "drop", "NoAction"],
+          "base_default_next" : "node_3",
+          "next_tables" : {
+            "set_egress_port" : "node_3",
+            "send_to_cpu" : "node_3",
+            "drop" : "node_3",
+            "NoAction" : "node_3"
+          },
+          "default_entry" : {
+            "action_id" : 3,
+            "action_const" : false,
+            "action_data" : [],
+            "action_entry_const" : false
+          }
+        },
+        {
+          "name" : "tbl_act",
+          "id" : 1,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [4],
+          "actions" : ["act"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act" : null
+          },
+          "default_entry" : {
+            "action_id" : 4,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        }
+      ],
+      "action_profiles" : [],
+      "conditionals" : [
+        {
+          "name" : "node_3",
+          "id" : 0,
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 10,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.egress_spec < 254"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "<",
+              "left" : {
+                "type" : "field",
+                "value" : ["standard_metadata", "egress_spec"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x00fe"
+              }
+            }
+          },
+          "false_next" : null,
+          "true_next" : "tbl_act"
+        }
+      ]
+    },
+    {
+      "name" : "egress",
+      "id" : 1,
+      "source_info" : {
+        "filename" : "default.p4",
+        "line" : 36,
+        "column" : 8,
+        "source_fragment" : "egress"
+      },
+      "init_table" : null,
+      "tables" : [],
+      "action_profiles" : [],
+      "conditionals" : []
+    }
+  ],
+  "checksums" : [],
+  "force_arith" : [],
+  "extern_instances" : [],
+  "field_aliases" : [
+    [
+      "queueing_metadata.enq_timestamp",
+      ["standard_metadata", "enq_timestamp"]
+    ],
+    [
+      "queueing_metadata.enq_qdepth",
+      ["standard_metadata", "enq_qdepth"]
+    ],
+    [
+      "queueing_metadata.deq_timedelta",
+      ["standard_metadata", "deq_timedelta"]
+    ],
+    [
+      "queueing_metadata.deq_qdepth",
+      ["standard_metadata", "deq_qdepth"]
+    ],
+    [
+      "intrinsic_metadata.ingress_global_timestamp",
+      ["standard_metadata", "ingress_global_timestamp"]
+    ],
+    [
+      "intrinsic_metadata.lf_field_list",
+      ["standard_metadata", "lf_field_list"]
+    ],
+    [
+      "intrinsic_metadata.mcast_grp",
+      ["standard_metadata", "mcast_grp"]
+    ],
+    [
+      "intrinsic_metadata.resubmit_flag",
+      ["standard_metadata", "resubmit_flag"]
+    ],
+    [
+      "intrinsic_metadata.egress_rid",
+      ["standard_metadata", "egress_rid"]
+    ]
+  ]
+}
\ No newline at end of file