ONOS-6605 PI flow rule translator implementation

Change-Id: Icac66f17677c494152207f4b52355ad647e1227b
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 c274e6a..1db1d66 100644
--- a/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
+++ b/utils/misc/src/main/java/org/onlab/util/ImmutableByteSequence.java
@@ -23,6 +23,8 @@
 import java.util.Arrays;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
 import static org.apache.commons.lang3.ArrayUtils.reverse;
 
 /**
@@ -241,4 +243,71 @@
     public String toString() {
         return HexString.toHexString(value.array());
     }
+
+    /**
+     * Trims or expands the given byte sequence so to fit a given bit-width. When trimming, the
+     * operations is deemed to be safe only if the trimmed bits are zero, otherwise an exception
+     * will be thrown. When expanding, the sequence will be padded with zeros. The returned byte
+     * sequence will have minimum size to contain the given bit-width.
+     *
+     * @param original a byte sequence
+     * @param bitWidth a non-zero positive integer
+     * @return a new byte sequence
+     * @throws ByteSequenceTrimException if the byte sequence cannot be fitted
+     */
+    public static ImmutableByteSequence fit(ImmutableByteSequence original, int bitWidth)
+            throws ByteSequenceTrimException {
+
+        checkNotNull(original, "byte sequence cannot be null");
+        checkArgument(bitWidth > 0, "bit-width must be a non-zero positive integer");
+
+        int newByteWidth = (int) Math.ceil((double) bitWidth / 8);
+
+        byte[] originalBytes = original.asArray();
+
+        if (newByteWidth > original.size()) {
+            // pad missing bytes with zeros
+            return ImmutableByteSequence.copyFrom(Arrays.copyOf(originalBytes, newByteWidth));
+        }
+
+        byte[] newBytes = new byte[newByteWidth];
+        // ImmutableByteSequence is always big-endian, hence check the array in reverse order
+        int diff = originalBytes.length - newByteWidth;
+        for (int i = originalBytes.length - 1; i >= 0; i--) {
+            byte ob = originalBytes[i]; // original byte
+            byte nb; // new byte
+            if (i > diff) {
+                // no need to truncate, copy as is
+                nb = ob;
+            } else if (i == diff) {
+                // truncate this byte, check if we're loosing something
+                byte mask = (byte) ((1 >> ((bitWidth % 8) + 1)) - 1);
+                if ((ob & ~mask) != 0) {
+                    throw new ByteSequenceTrimException(originalBytes, bitWidth);
+                } else {
+                    nb = (byte) (ob & mask);
+                }
+            } else {
+                // drop this byte, check if we're loosing something
+                if (originalBytes[i] != 0) {
+                    throw new ByteSequenceTrimException(originalBytes, bitWidth);
+                } else {
+                    continue;
+                }
+            }
+            newBytes[i - diff] = nb;
+        }
+
+        return ImmutableByteSequence.copyFrom(newBytes);
+    }
+
+    /**
+     * Signals that a byte sequence cannot be trimmed.
+     */
+    public static class ByteSequenceTrimException extends Exception {
+        ByteSequenceTrimException(byte[] bytes, int bitWidth) {
+            super(format("cannot trim %s into a %d long bits value",
+                         HexString.toHexString(bytes), bitWidth));
+        }
+    }
 }