Add support for one shot action profile programming in PI

A P4 table annotated with @oneshot annotation can be programmed
only with the action profile action set. For these kind of tables
we don't issue read request for action profile groups and members.

Change-Id: I7b6a743f4f4df4190f17d958ebb4807aca5feda5
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoAnnotationUtils.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoAnnotationUtils.java
new file mode 100644
index 0000000..3540819
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoAnnotationUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.model;
+
+import p4.config.v1.P4InfoOuterClass;
+
+/**
+ * Provides utility methods for P4Info annotations.
+ */
+public final class P4InfoAnnotationUtils {
+
+    public static final String ONE_SHOT_ONLY_ANNOTATION = "oneshot";
+    public static final String MAX_GROUP_SIZE_ANNOTATION = "max_group_size";
+
+    private P4InfoAnnotationUtils() {
+    }
+
+    /**
+     * Gets the annotation value if available in the given P4Info preamble.
+     * Supports annotation in the form @my_annotation(value).
+     *
+     * @param name Annotation name
+     * @param preamble preamble of the P4Info object
+     * @return The annotation value if present, null otherwise
+     */
+    public static String getAnnotationValue(String name, P4InfoOuterClass.Preamble preamble) {
+        return preamble.getAnnotationsList().stream()
+                .filter(a -> a.startsWith("@" + name))
+                // e.g. @my_annotation(value)
+                .map(a -> a.substring(name.length() + 2, a.length() - 1))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * Checks if the given annotation name is present in the given P4Info preamble.
+     * Supports annotation in the form @my_annotation* (i.e., @oneshot, @max_group_size(10)).
+     *
+     * @param name Annotation name
+     * @param preamble preamble of the P4Info object
+     * @return True if the annotation is available, False otherwise.
+     */
+    public static boolean isAnnotationPresent(String name, P4InfoOuterClass.Preamble preamble) {
+        return preamble.getAnnotationsList().stream()
+                .anyMatch(a -> a.startsWith("@" + name));
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java
index 0bb0360..cbe1d6e 100644
--- a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java
@@ -75,6 +75,10 @@
 import java.util.stream.Collectors;
 
 import static java.lang.String.format;
+import static org.onosproject.p4runtime.model.P4InfoAnnotationUtils.MAX_GROUP_SIZE_ANNOTATION;
+import static org.onosproject.p4runtime.model.P4InfoAnnotationUtils.ONE_SHOT_ONLY_ANNOTATION;
+import static org.onosproject.p4runtime.model.P4InfoAnnotationUtils.getAnnotationValue;
+import static org.onosproject.p4runtime.model.P4InfoAnnotationUtils.isAnnotationPresent;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -87,6 +91,7 @@
     private static final String PACKET_IN = "packet_in";
     private static final String PACKET_OUT = "packet_out";
 
+
     private static final Map<CounterSpec.Unit, PiCounterModel.Unit> COUNTER_UNIT_MAP =
             new ImmutableMap.Builder<CounterSpec.Unit, PiCounterModel.Unit>()
                     .put(CounterSpec.Unit.BYTES, PiCounterModel.Unit.BYTES)
@@ -224,6 +229,8 @@
                     // Filter out missed mapping.
                     .filter(Objects::nonNull)
                     .forEach(counterModel -> tableCounterMapBuilder.put(counterModel.id(), counterModel));
+            // Check if table supports one-shot only
+            boolean oneShotOnly = isAnnotationPresent(ONE_SHOT_ONLY_ANNOTATION, tableMsg.getPreamble());
             tableImmMapBuilder.put(
                     tableId,
                     new P4TableModel(
@@ -238,8 +245,7 @@
                             tableFieldMapBuilder.build(),
                             tableActionMapBuilder.build(),
                             actionMap.get(tableMsg.getConstDefaultActionId()),
-                            tableMsg.getIsConstTable()));
-
+                            tableMsg.getIsConstTable(), oneShotOnly));
         }
 
         // Get a map with proper PI IDs for some of those maps we created at the beginning.
@@ -355,8 +361,8 @@
             // correctly interpret P4Runtime-defined max_group_size annotation:
             // https://s3-us-west-2.amazonaws.com/p4runtime/docs/master/
             // P4Runtime-Spec.html#sec-p4info-action-profile
-            final String maxSizeAnnString = findAnnotation(
-                    "max_group_size", actProfileMsg.getPreamble());
+            final String maxSizeAnnString = getAnnotationValue(
+                    MAX_GROUP_SIZE_ANNOTATION, actProfileMsg.getPreamble());
             final int maxSizeAnn = maxSizeAnnString != null
                     ? Integer.valueOf(maxSizeAnnString) : 0;
             final int maxGroupSize;
@@ -481,15 +487,6 @@
         return MATCH_TYPE_MAP.get(type);
     }
 
-    private static String findAnnotation(String name, P4InfoOuterClass.Preamble preamble) {
-        return preamble.getAnnotationsList().stream()
-                .filter(a -> a.startsWith("@" + name))
-                // e.g. @my_annotaion(value)
-                .map(a -> a.substring(name.length() + 2, a.length() - 1))
-                .findFirst()
-                .orElse(null);
-    }
-
     private static boolean isFieldString(P4Info p4info, String fieldTypeName) {
         P4Types.P4TypeInfo p4TypeInfo = p4info.getTypeInfo();
         return p4TypeInfo.containsNewTypes(fieldTypeName) &&
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
index be1b71c..ad32dab 100644
--- a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
@@ -52,6 +52,7 @@
     private final ImmutableMap<PiActionId, PiActionModel> actions;
     private final PiActionModel constDefaultAction;
     private final boolean isConstTable;
+    private final boolean oneShotOnly;
 
     P4TableModel(PiTableId id, PiTableType tableType,
                  PiActionProfileModel actionProfile, long maxSize,
@@ -60,7 +61,7 @@
                  ImmutableMap<PiMatchFieldId, PiMatchFieldModel> matchFields,
                  ImmutableMap<PiActionId, PiActionModel> actions,
                  PiActionModel constDefaultAction,
-                 boolean isConstTable) {
+                 boolean isConstTable, boolean oneShotOnly) {
         this.id = id;
         this.tableType = tableType;
         this.actionProfile = actionProfile;
@@ -72,6 +73,7 @@
         this.actions = actions;
         this.constDefaultAction = constDefaultAction;
         this.isConstTable = isConstTable;
+        this.oneShotOnly = oneShotOnly;
     }
 
     @Override
@@ -129,6 +131,10 @@
         return isConstTable;
     }
 
+    public boolean oneShotOnly() {
+        return oneShotOnly;
+    }
+
     @Override
     public Optional<PiActionModel> action(PiActionId actionId) {
         return Optional.ofNullable(actions.get(actionId));
@@ -164,7 +170,8 @@
                 && Objects.equals(this.supportAging, other.supportAging)
                 && Objects.equals(this.matchFields, other.matchFields)
                 && Objects.equals(this.actions, other.actions)
-                && Objects.equals(this.constDefaultAction, other.constDefaultAction);
+                && Objects.equals(this.constDefaultAction, other.constDefaultAction)
+                && Objects.equals(this.oneShotOnly, other.oneShotOnly);
     }
 
     @Override
diff --git a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java
index 558acb1..3785c96 100644
--- a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java
+++ b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java
@@ -83,10 +83,8 @@
         // Generate two PiPipelineModels from p4Info file
         PiPipelineModel model = P4InfoParser.parse(p4InfoUrl);
         PiPipelineModel model2 = P4InfoParser.parse(p4InfoUrl);
-
         // Check equality
         new EqualsTester().addEqualityGroup(model, model2).testEquals();
-
         // Generate a P4Info object from the file
         final P4Info p4info;
         try {
@@ -94,30 +92,33 @@
         } catch (IOException e) {
             throw new P4InfoParserException("Unable to parse protobuf " + p4InfoUrl.toString(), e);
         }
-
         List<Table> tableMsgs =  p4info.getTablesList();
         PiTableId table0Id = PiTableId.of(tableMsgs.get(0).getPreamble().getName());
         PiTableId wcmpTableId = PiTableId.of(tableMsgs.get(1).getPreamble().getName());
-
+        PiTableId wcmpTableOneShotId = PiTableId.of(tableMsgs.get(2).getPreamble().getName());
         //parse tables
         PiTableModel table0Model = model.table(table0Id).orElse(null);
         PiTableModel wcmpTableModel = model.table(wcmpTableId).orElse(null);
+        PiTableModel wcmpTableOneShotModel = model.table(wcmpTableOneShotId).orElse(null);
         PiTableModel table0Model2 = model2.table(table0Id).orElse(null);
         PiTableModel wcmpTableModel2 = model2.table(wcmpTableId).orElse(null);
-
         new EqualsTester().addEqualityGroup(table0Model, table0Model2)
                 .addEqualityGroup(wcmpTableModel, wcmpTableModel2).testEquals();
-
         // Check existence
         assertThat("model parsed value is null", table0Model, notNullValue());
         assertThat("model parsed value is null", wcmpTableModel, notNullValue());
+        assertThat("model parsed value is null", wcmpTableOneShotModel, notNullValue());
         assertThat("Incorrect size for table0 size", table0Model.maxSize(), is(equalTo(DEFAULT_MAX_TABLE_SIZE)));
         assertThat("Incorrect size for wcmp_table size", wcmpTableModel.maxSize(), is(equalTo(DEFAULT_MAX_TABLE_SIZE)));
+        assertThat("Incorrect size for wcmp_table_one_shot size", wcmpTableOneShotModel.maxSize(),
+                   is(equalTo(DEFAULT_MAX_TABLE_SIZE)));
+        // Check one-shot annotation
+        assertThat("error parsing one-shot annotation", wcmpTableModel.oneShotOnly(), is(false));
+        assertThat("error parsing one-shot annotation", wcmpTableOneShotModel.oneShotOnly(), is(true));
 
         // Check matchFields
         List<MatchField> matchFieldList = tableMsgs.get(0).getMatchFieldsList();
         List<PiMatchFieldModel> piMatchFieldList = new ArrayList<>();
-
         for (MatchField matchFieldIter : matchFieldList) {
             MatchField.MatchType matchType = matchFieldIter.getMatchType();
             PiMatchType piMatchType;
@@ -140,7 +141,6 @@
                 piMatchFieldList.get(4), piMatchFieldList.get(5),
                 piMatchFieldList.get(6), piMatchFieldList.get(7),
                 piMatchFieldList.get(8)));
-
         assertThat("Incorrect size for matchFields", wcmpTableModel.matchFields().size(), is(equalTo(1)));
 
         // check if matchFields are in order
@@ -256,8 +256,8 @@
         Collection<PiMeterModel> meterModel = model.meters();
         Collection<PiMeterModel> meterModel2 = model2.meters();
 
-        assertThat("model pased meter collaction should be empty", meterModel.isEmpty(), is(true));
-        assertThat("model pased meter collaction should be empty", meterModel2.isEmpty(), is(true));
+        assertThat("model parsed meter collection should be empty", meterModel.isEmpty(), is(true));
+        assertThat("model parsed meter collection should be empty", meterModel2.isEmpty(), is(true));
 
         //parse packet operations
         PiPacketOperationModel packetInOperationalModel =
diff --git a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java
index fdc3d2e..3da1ffb 100644
--- a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java
+++ b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java
@@ -27,8 +27,6 @@
 import org.onosproject.net.pi.model.PiActionParamModel;
 import org.onosproject.net.pi.model.PiActionProfileId;
 import org.onosproject.net.pi.model.PiActionProfileModel;
-import org.onosproject.net.pi.model.PiPacketMetadataId;
-import org.onosproject.net.pi.model.PiPacketMetadataModel;
 import org.onosproject.net.pi.model.PiCounterId;
 import org.onosproject.net.pi.model.PiCounterModel;
 import org.onosproject.net.pi.model.PiCounterType;
@@ -38,6 +36,8 @@
 import org.onosproject.net.pi.model.PiMeterId;
 import org.onosproject.net.pi.model.PiMeterModel;
 import org.onosproject.net.pi.model.PiMeterType;
+import org.onosproject.net.pi.model.PiPacketMetadataId;
+import org.onosproject.net.pi.model.PiPacketMetadataModel;
 import org.onosproject.net.pi.model.PiPacketOperationModel;
 import org.onosproject.net.pi.model.PiPacketOperationType;
 import org.onosproject.net.pi.model.PiPipelineModel;
@@ -261,6 +261,7 @@
     /* Table Models */
     private static final PiTableId PI_TABLE_ID_1 = PiTableId.of("Table1");
     private static final PiTableId PI_TABLE_ID_2 = PiTableId.of("Table2");
+    private static final PiTableId PI_TABLE_ID_3 = PiTableId.of("Table3");
 
     private static final PiTableType PI_TABLE_TYPE_1 = PiTableType.DIRECT;
     private static final PiTableType PI_TABLE_TYPE_2 = PiTableType.INDIRECT;
@@ -277,11 +278,15 @@
     private static final PiTableModel P4_TABLE_MODEL_1 =
             new P4TableModel(PI_TABLE_ID_1, PI_TABLE_TYPE_1, P4_ACTION_PROFILE_MODEL_1, MAX_SIZE_1, COUNTERS_1,
                              METERS_1, SUPPORT_AGING_1, MATCH_FIELDS_1, ACTIONS_1, P4_ACTION_MODEL_DEFAULT_1,
-                             IS_CONST_TABLE_1);
+                             IS_CONST_TABLE_1, false);
     private static final PiTableModel P4_TABLE_MODEL_2 =
             new P4TableModel(PI_TABLE_ID_2, PI_TABLE_TYPE_2, P4_ACTION_PROFILE_MODEL_2, MAX_SIZE_2, COUNTERS_2,
                              METERS_2, SUPPORT_AGING_2, MATCH_FIELDS_2, ACTIONS_2, P4_ACTION_MODEL_DEFAULT_2,
-                             IS_CONST_TABLE_2);
+                             IS_CONST_TABLE_2, false);
+    private static final PiTableModel P4_TABLE_MODEL_3 =
+            new P4TableModel(PI_TABLE_ID_2, PI_TABLE_TYPE_2, P4_ACTION_PROFILE_MODEL_2, MAX_SIZE_2, COUNTERS_2,
+                             METERS_2, SUPPORT_AGING_2, MATCH_FIELDS_2, ACTIONS_2, P4_ACTION_MODEL_DEFAULT_2,
+                             IS_CONST_TABLE_2, true);
 
     /* Packet operations */
     private static final PiPacketOperationType PI_PACKET_OPERATION_TYPE_1 = PiPacketOperationType.PACKET_IN;
@@ -309,6 +314,10 @@
             new ImmutableMap.Builder<PiTableId, PiTableModel>()
                     .put(PI_TABLE_ID_2, P4_TABLE_MODEL_2)
                     .build();
+    private static final ImmutableMap<PiTableId, PiTableModel> TABLES_3 =
+            new ImmutableMap.Builder<PiTableId, PiTableModel>()
+                    .put(PI_TABLE_ID_3, P4_TABLE_MODEL_3)
+                    .build();
 
     private static final ImmutableMap<PiActionProfileId, PiActionProfileModel> ACTION_PROFILES_1 =
             new ImmutableMap.Builder<PiActionProfileId, PiActionProfileModel>()
@@ -357,16 +366,19 @@
 
     private static final PiPipelineModel P4_PIPELINE_MODEL_1 =
             new P4PipelineModel(TABLES_1, COUNTERS_1, METERS_1, REGISTERS_1, ACTION_PROFILES_1, PACKET_OPERATIONS_1,
-                    FINGER_PRINT_1);
+                                FINGER_PRINT_1);
     private static final PiPipelineModel SAME_AS_P4_PIPELINE_MODEL_1 =
             new P4PipelineModel(TABLES_1, COUNTERS_1, METERS_1, REGISTERS_1, ACTION_PROFILES_1, PACKET_OPERATIONS_1,
-                    FINGER_PRINT_1);
+                                FINGER_PRINT_1);
     private static final PiPipelineModel P4_PIPELINE_MODEL_2 =
             new P4PipelineModel(TABLES_2, COUNTERS_2, METERS_2, REGISTERS_1, ACTION_PROFILES_2, PACKET_OPERATIONS_2,
-                    FINGER_PRINT_2);
+                                FINGER_PRINT_2);
     private static final PiPipelineModel P4_PIPELINE_MODEL_3 =
             new P4PipelineModel(TABLES_2, COUNTERS_2, METERS_2, REGISTERS_1, ACTION_PROFILES_2, PACKET_OPERATIONS_3,
                                 FINGER_PRINT_2);
+    private static final PiPipelineModel P4_PIPELINE_MODEL_4 =
+            new P4PipelineModel(TABLES_3, COUNTERS_2, METERS_2, REGISTERS_1, ACTION_PROFILES_2, PACKET_OPERATIONS_3,
+                                FINGER_PRINT_2);
 
     /**
      * Checks that the P4PipelineModel class is immutable.
@@ -385,6 +397,7 @@
                 .addEqualityGroup(P4_PIPELINE_MODEL_1, SAME_AS_P4_PIPELINE_MODEL_1)
                 .addEqualityGroup(P4_PIPELINE_MODEL_2)
                 .addEqualityGroup(P4_PIPELINE_MODEL_3)
+                .addEqualityGroup(P4_PIPELINE_MODEL_4)
                 .testEquals();
     }
 }
diff --git a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4TableModelTest.java b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4TableModelTest.java
index 13677c0..d28d984 100644
--- a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4TableModelTest.java
+++ b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4TableModelTest.java
@@ -250,15 +250,19 @@
     private static final PiTableModel P4_TABLE_MODEL_1 =
             new P4TableModel(PI_TABLE_ID_1, PI_TABLE_TYPE_1, P4_ACTION_PROFILE_MODEL_1, MAX_SIZE_1, COUNTERS_1,
                              METERS_1, SUPPORT_AGING_1, MATCH_FIELDS_1, ACTIONS_1, P4_ACTION_MODEL_DEFAULT_1,
-                             IS_CONST_TABLE_1);
+                             IS_CONST_TABLE_1, false);
     private static final PiTableModel SAME_AS_P4_TABLE_MODEL_1 =
             new P4TableModel(PI_TABLE_ID_1, PI_TABLE_TYPE_1, P4_ACTION_PROFILE_MODEL_1, MAX_SIZE_1, COUNTERS_1,
                              METERS_1, SUPPORT_AGING_1, MATCH_FIELDS_1, ACTIONS_1, P4_ACTION_MODEL_DEFAULT_1,
-                             IS_CONST_TABLE_1);
+                             IS_CONST_TABLE_1, false);
     private static final PiTableModel P4_TABLE_MODEL_2 =
             new P4TableModel(PI_TABLE_ID_2, PI_TABLE_TYPE_2, P4_ACTION_PROFILE_MODEL_2, MAX_SIZE_2, COUNTERS_2,
                              METERS_2, SUPPORT_AGING_2, MATCH_FIELDS_2, ACTIONS_2, P4_ACTION_MODEL_DEFAULT_2,
-                             IS_CONST_TABLE_2);
+                             IS_CONST_TABLE_2, false);
+    private static final PiTableModel P4_TABLE_MODEL_3 =
+            new P4TableModel(PI_TABLE_ID_2, PI_TABLE_TYPE_2, P4_ACTION_PROFILE_MODEL_2, MAX_SIZE_2, COUNTERS_2,
+                             METERS_2, SUPPORT_AGING_2, MATCH_FIELDS_2, ACTIONS_2, P4_ACTION_MODEL_DEFAULT_2,
+                             IS_CONST_TABLE_2, true);
 
     /**
      * Checks that the P4TableModel class is immutable.
@@ -276,6 +280,7 @@
         new EqualsTester()
                 .addEqualityGroup(P4_TABLE_MODEL_1, SAME_AS_P4_TABLE_MODEL_1)
                 .addEqualityGroup(P4_TABLE_MODEL_2)
+                .addEqualityGroup(P4_TABLE_MODEL_3)
                 .testEquals();
     }
 }
diff --git a/protocols/p4runtime/model/src/test/resources/org/onosproject/p4runtime/model/basic.p4info b/protocols/p4runtime/model/src/test/resources/org/onosproject/p4runtime/model/basic.p4info
index bc6ca71..f886e14 100644
--- a/protocols/p4runtime/model/src/test/resources/org/onosproject/p4runtime/model/basic.p4info
+++ b/protocols/p4runtime/model/src/test/resources/org/onosproject/p4runtime/model/basic.p4info
@@ -97,6 +97,30 @@
   direct_resource_ids: 302001091
   size: 1024
 }
+tables {
+  preamble {
+    id: 33592598
+    name: "wcmp_control.wcmp_table_oneshot"
+    alias: "wcmp_table_oneshot"
+    annotations: "@oneshot"
+  }
+  match_fields {
+    id: 1
+    name: "local_metadata.next_hop_id"
+    bitwidth: 16
+    match_type: EXACT
+  }
+  action_refs {
+    id: 16794308
+  }
+  action_refs {
+    id: 16800567
+    annotations: "@defaultonly()"
+  }
+  implementation_id: 285259294
+  direct_resource_ids: 302001091
+  size: 1024
+}
 actions {
   preamble {
     id: 16794308
diff --git a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionSetCodec.java b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionSetCodec.java
new file mode 100644
index 0000000..68ded36
--- /dev/null
+++ b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionSetCodec.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.codec;
+
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiActionSet;
+import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser;
+import p4.v1.P4RuntimeOuterClass;
+
+/**
+ * Codec for ActionSet.
+ */
+public class ActionSetCodec extends
+        AbstractCodec<PiActionSet, P4RuntimeOuterClass.ActionProfileActionSet, Object>  {
+
+    @Override
+    protected P4RuntimeOuterClass.ActionProfileActionSet encode(
+            PiActionSet piActionSet, Object ignored,
+            PiPipeconf pipeconf, P4InfoBrowser browser)
+            throws CodecException, P4InfoBrowser.NotFoundException {
+        final var actProActSetBuilder =
+                P4RuntimeOuterClass.ActionProfileActionSet.newBuilder();
+        for (PiActionSet.WeightedAction act : piActionSet.actions()) {
+            // TODO: currently we don't set "watch_port" field
+            final var actProfAct =
+                    P4RuntimeOuterClass.ActionProfileAction.newBuilder();
+            actProfAct.setAction(Codecs.CODECS.action().encode(
+                    act.action(), null, pipeconf));
+            actProfAct.setWeight(act.weight());
+            actProActSetBuilder.addActionProfileActions(actProfAct.build());
+        }
+        return actProActSetBuilder.build();
+    }
+
+    @Override
+    protected PiActionSet decode(
+            P4RuntimeOuterClass.ActionProfileActionSet message, Object ignored,
+            PiPipeconf pipeconf, P4InfoBrowser browser)
+            throws CodecException, P4InfoBrowser.NotFoundException {
+        final var builder = PiActionSet.builder();
+        for (P4RuntimeOuterClass.ActionProfileAction act : message.getActionProfileActionsList()) {
+            final var piAction = Codecs.CODECS.action().decode(
+                    act.getAction(), null, pipeconf);
+            builder.addWeightedAction(piAction, act.getWeight());
+        }
+        return builder.build();
+    }
+}
diff --git a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java
index 30e8d9d..06a2d18 100644
--- a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java
+++ b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java
@@ -40,6 +40,7 @@
     private final PacketMetadataCodec packetMetadata;
     private final PacketOutCodec packetOut;
     private final TableEntryCodec tableEntry;
+    private final ActionSetCodec actionSet;
 
     private Codecs() {
         this.action = new ActionCodec();
@@ -59,6 +60,7 @@
         this.packetMetadata = new PacketMetadataCodec();
         this.packetOut = new PacketOutCodec();
         this.tableEntry = new TableEntryCodec();
+        this.actionSet = new ActionSetCodec();
     }
 
     public EntityCodec entity() {
@@ -128,4 +130,8 @@
     DirectCounterEntryCodec directCounterEntry() {
         return directCounterEntry;
     }
+
+    ActionSetCodec actionSet() {
+        return actionSet;
+    }
 }
diff --git a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/TableEntryCodec.java b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/TableEntryCodec.java
index 513b4fa..a2455c9 100644
--- a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/TableEntryCodec.java
+++ b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/TableEntryCodec.java
@@ -21,6 +21,7 @@
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionProfileGroupId;
 import org.onosproject.net.pi.runtime.PiActionProfileMemberId;
+import org.onosproject.net.pi.runtime.PiActionSet;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiMatchKey;
 import org.onosproject.net.pi.runtime.PiTableAction;
@@ -179,6 +180,12 @@
                 tableActionMsgBuilder.setActionProfileMemberId(
                         ((PiActionProfileMemberId) piTableAction).id());
                 break;
+            case ACTION_SET:
+                P4RuntimeOuterClass.ActionProfileActionSet theActionProfileActionSet =
+                        CODECS.actionSet().encode(
+                                (PiActionSet) piTableAction, null, pipeconf);
+                tableActionMsgBuilder.setActionProfileActionSet(theActionProfileActionSet);
+                break;
             default:
                 throw new CodecException(
                         format("Building of table action type %s not implemented",
@@ -202,6 +209,9 @@
             case ACTION_PROFILE_MEMBER_ID:
                 return PiActionProfileMemberId.of(
                         tableActionMsg.getActionProfileMemberId());
+            case ACTION_PROFILE_ACTION_SET:
+                return CODECS.actionSet().decode(
+                        tableActionMsg.getActionProfileActionSet(), null, pipeconf);
             default:
                 throw new CodecException(
                         format("Decoding of table action type %s not implemented",