Allows to specify matches, action parameters as strings in PI

Some PI elements can encode in their value a string (e.g., when
a P4Runtime translation is used), for this reason we allow users
to specify matches and action parameters as strings.
From southbound, during decode, we interpret the elements as
string if the P4 model suggests that.

Change-Id: I5884de1500437ab647abc200d65de442e23bd1a8
diff --git a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
index 3970e76..43a5bdf 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
@@ -195,6 +195,18 @@
         }
 
         /**
+         * Adds an exact field match for the given fieldId and value.
+         *
+         * @param fieldId protocol-independent header field Id
+         * @param value   exact match value
+         * @return this
+         */
+        public Builder matchExact(PiMatchFieldId fieldId, String value) {
+            fieldMatchMapBuilder.put(fieldId, new PiExactFieldMatch(fieldId, copyFrom(value)));
+            return this;
+        }
+
+        /**
          * Adds a ternary field match for the given fieldId, value and mask.
          *
          * @param fieldId protocol-independent header field Id
@@ -399,6 +411,18 @@
         }
 
         /**
+         * Adds an optional field match for the given fieldId and value.
+         *
+         * @param fieldId protocol-independent header field Id
+         * @param value   optional match value
+         * @return this
+         */
+        public Builder matchOptional(PiMatchFieldId fieldId, String value) {
+            fieldMatchMapBuilder.put(fieldId, new PiOptionalFieldMatch(fieldId, copyFrom(value)));
+            return this;
+        }
+
+        /**
          * Builds a PiCriterion.
          *
          * @return PiCriterion
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java
index a8d565c..91f6e04 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java
@@ -104,6 +104,17 @@
     }
 
     /**
+     * Creates an action's runtime parameter for the given param ID and string
+     * value.
+     *
+     * @param id    parameter identifier
+     * @param value value
+     */
+    public PiActionParam(PiActionParamId id, String value) {
+        this(id, copyFrom(value));
+    }
+
+    /**
      * Returns the identifier of this parameter.
      *
      * @return parameter identifier
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/codec/TableEntryEncoderTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/codec/TableEntryEncoderTest.java
index 8614527..29f2936 100644
--- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/codec/TableEntryEncoderTest.java
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/codec/TableEntryEncoderTest.java
@@ -100,10 +100,10 @@
     private final P4InfoBrowser browser2 = PipeconfHelper.getP4InfoBrowser(defaultPipeconf2);
     private final ImmutableByteSequence ethAddr = copyFrom(rand.nextInt()).fit(48);
     private final ImmutableByteSequence ethAddrString = ImmutableByteSequence.copyFrom(
-            "00:11:22:33:44:55:66".getBytes());
+            "00:11:22:33:44:55:66");
     private final ImmutableByteSequence portValue = copyFrom((short) rand.nextInt());
     private final ImmutableByteSequence portValueString = ImmutableByteSequence.copyFrom(
-            String.format("Ethernet%d", rand.nextInt()).getBytes());
+            String.format("Ethernet%d", rand.nextInt()));
     private final ImmutableByteSequence portValue32Bit = copyFrom((short) rand.nextInt()).fit(32);
     private final PiMatchFieldId ethDstAddrFieldId = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + DST_ADDR);
     private final PiMatchFieldId ethSrcAddrFieldId = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + SRC_ADDR);
diff --git a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionCodec.java b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionCodec.java
index 69b31a8..d0a285e 100644
--- a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionCodec.java
+++ b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionCodec.java
@@ -74,10 +74,14 @@
         final PiAction.Builder builder = PiAction.builder()
                 .withId(PiActionId.of(actionName));
         for (P4RuntimeOuterClass.Action.Param p : message.getParamsList()) {
-            final String paramName = paramInfo.getById(p.getParamId()).getName();
-            final ImmutableByteSequence value = ImmutableByteSequence.copyFrom(
-                    p.getValue().toByteArray());
-            builder.withParameter(new PiActionParam(PiActionParamId.of(paramName), value));
+            final P4InfoOuterClass.Action.Param actionParam = paramInfo.getById(p.getParamId());
+            final ImmutableByteSequence value;
+            if (browser.isTypeString(actionParam.getTypeName())) {
+                value = ImmutableByteSequence.copyFrom(new String(p.getValue().toByteArray()));
+            } else {
+                value = ImmutableByteSequence.copyFrom(p.getValue().toByteArray());
+            }
+            builder.withParameter(new PiActionParam(PiActionParamId.of(actionParam.getName()), value));
         }
         return builder.build();
     }
diff --git a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/FieldMatchCodec.java b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/FieldMatchCodec.java
index d42e6b9..6b01546 100644
--- a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/FieldMatchCodec.java
+++ b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/FieldMatchCodec.java
@@ -152,16 +152,23 @@
             PiPipeconf pipeconf, P4InfoBrowser browser)
             throws CodecException, P4InfoBrowser.NotFoundException {
 
-        String fieldMatchName = browser.matchFields(tablePreamble.getId())
-                .getById(message.getFieldId()).getName();
-        PiMatchFieldId headerFieldId = PiMatchFieldId.of(fieldMatchName);
+        final P4InfoOuterClass.MatchField matchField =
+                browser.matchFields(tablePreamble.getId())
+                        .getById(message.getFieldId());
+        final PiMatchFieldId headerFieldId = PiMatchFieldId.of(matchField.getName());
+        final boolean isSdnString = browser.isTypeString(matchField.getTypeName());
 
         P4RuntimeOuterClass.FieldMatch.FieldMatchTypeCase typeCase = message.getFieldMatchTypeCase();
 
         switch (typeCase) {
             case EXACT:
                 P4RuntimeOuterClass.FieldMatch.Exact exactFieldMatch = message.getExact();
-                ImmutableByteSequence exactValue = copyFrom(exactFieldMatch.getValue().asReadOnlyByteBuffer());
+                ImmutableByteSequence exactValue;
+                if (isSdnString) {
+                    exactValue = copyFrom(new String(exactFieldMatch.getValue().toByteArray()));
+                } else {
+                    exactValue = copyFrom(exactFieldMatch.getValue().asReadOnlyByteBuffer());
+                }
                 return new PiExactFieldMatch(headerFieldId, exactValue);
             case TERNARY:
                 P4RuntimeOuterClass.FieldMatch.Ternary ternaryFieldMatch = message.getTernary();
@@ -180,7 +187,12 @@
                 return new PiRangeFieldMatch(headerFieldId, rangeLowValue, rangeHighValue);
             case OPTIONAL:
                 P4RuntimeOuterClass.FieldMatch.Optional optionalFieldMatch = message.getOptional();
-                ImmutableByteSequence optionalValue = copyFrom(optionalFieldMatch.getValue().asReadOnlyByteBuffer());
+                ImmutableByteSequence optionalValue;
+                if (isSdnString) {
+                    optionalValue = copyFrom(new String(optionalFieldMatch.getValue().toByteArray()));
+                } else {
+                    optionalValue = copyFrom(optionalFieldMatch.getValue().asReadOnlyByteBuffer());
+                }
                 return new PiOptionalFieldMatch(headerFieldId, optionalValue);
             default:
                 throw new CodecException(format(
diff --git a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketMetadataCodec.java b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketMetadataCodec.java
index 3f78085..876f84c 100644
--- a/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketMetadataCodec.java
+++ b/protocols/p4runtime/utils/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketMetadataCodec.java
@@ -17,6 +17,7 @@
 package org.onosproject.p4runtime.ctl.codec;
 
 import com.google.protobuf.ByteString;
+import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.pi.model.PiPacketMetadataId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiPacketMetadata;
@@ -54,14 +55,18 @@
             P4InfoOuterClass.Preamble ctrlPktMetaPreamble,
             PiPipeconf pipeconf, P4InfoBrowser browser)
             throws P4InfoBrowser.NotFoundException {
-        final String packetMetadataName = browser
-                .packetMetadatas(ctrlPktMetaPreamble.getId())
-                .getById(message.getMetadataId()).getName();
-        final PiPacketMetadataId metadataId = PiPacketMetadataId
-                .of(packetMetadataName);
+        final P4InfoOuterClass.ControllerPacketMetadata.Metadata packetMetadata =
+                browser.packetMetadatas(ctrlPktMetaPreamble.getId())
+                .getById(message.getMetadataId());
+        final ImmutableByteSequence value;
+        if (browser.isTypeString(packetMetadata.getTypeName())) {
+            value = copyFrom(new String(message.getValue().toByteArray()));
+        } else {
+            value = copyFrom(message.getValue().asReadOnlyByteBuffer());
+        }
         return PiPacketMetadata.builder()
-                .withId(metadataId)
-                .withValue(copyFrom(message.getValue().asReadOnlyByteBuffer()))
+                .withId(PiPacketMetadataId.of(packetMetadata.getName()))
+                .withValue(value)
                 .build();
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java b/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
index 013f3c5..1710d29 100644
--- a/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
+++ b/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
@@ -51,6 +51,7 @@
     The order of a newly-created byte buffer is always BIG_ENDIAN.
      */
     private ByteBuffer value;
+    private boolean isAscii = false;
 
     /**
      * Private constructor. Creates a new byte sequence object backed by the
@@ -65,6 +66,11 @@
         this.value.rewind();
     }
 
+    private ImmutableByteSequence(ByteBuffer value, boolean isAscii) {
+        this(value);
+        this.isAscii = isAscii;
+    }
+
     /**
      * Creates a new immutable byte sequence with the same content and order of
      * the passed byte array.
@@ -126,6 +132,20 @@
     }
 
     /**
+     * Creates a new immutable byte sequence from the given string.
+     *
+     * @param original a string
+     * @return a new byte buffer object
+     */
+    public static ImmutableByteSequence copyFrom(String original) {
+        checkArgument(original != null && original.length() > 0,
+                      "Cannot copy from an empty or null string");
+        return new ImmutableByteSequence(ByteBuffer.allocate(original.length())
+                                                 .put(original.getBytes()),
+                                         true);
+    }
+
+    /**
      * Creates a new byte sequence of 8 bytes containing the given long value.
      *
      * @param original a long value
@@ -394,19 +414,34 @@
     }
 
     /**
-     * Returns a hexadecimal representation of this byte sequence, e.g.
-     * 0xbeef. The length of the returned string is not representative of the
-     * length of the byte sequence, as all padding zeros are removed.
+     * Returns the ASCII representation of the byte sequence if the content can
+     * be interpreted as an ASCII string, otherwise returns the hexadecimal
+     * representation of this byte sequence, e.g.0xbeef. The length of the
+     * returned string is not representative of the length of the byte sequence,
+     * as all padding zeros are removed.
      *
      * @return hexadecimal representation
      */
     @Override
     public String toString() {
-        final String hexValue = HexString
-                .toHexString(value.array(), "")
-                // Remove leading zeros, but leave one if string is all zeros.
-                .replaceFirst("^0+(?!$)", "");
-        return "0x" + hexValue;
+        if (this.isAscii()) {
+            return new String(value.array());
+        } else {
+            return "0x" + HexString
+                    .toHexString(value.array(), "")
+                    // Remove leading zeros, but leave one if string is all zeros.
+                    .replaceFirst("^0+(?!$)", "");
+        }
+    }
+
+    /**
+     * Checks if the content can be interpreted as an ASCII printable string.
+     *
+     * @return True if the content can be interpreted as an ASCII printable
+     *  string, false otherwise
+     */
+    public boolean isAscii() {
+        return isAscii;
     }
 
     /**