Implemented a mechanism to easily add and access Neighbor Discovery protocol
packet options.

Fixes ONOS-1011

Change-Id: I94daa3f3c1297fb9a7b44901927738a29aff030a
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java
new file mode 100644
index 0000000..b8561b1
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2015 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.onlab.packet.ndp;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.IPacket;
+
+/**
+ * Neighbor Discovery Protocol packet options.
+ */
+public class NeighborDiscoveryOptions extends BasePacket {
+    public static final byte TYPE_SOURCE_LL_ADDRESS = 1;
+    public static final byte TYPE_TARGET_LL_ADDRESS = 2;
+    public static final byte TYPE_PREFIX_INFORMATION = 3;
+    public static final byte TYPE_REDIRECTED_HEADER = 4;
+    public static final byte TYPE_MTU = 5;
+
+    private final List<Option> options = new ArrayList<>();
+
+    /**
+     * Packet option.
+     */
+    public final class Option {
+        private final byte type;
+        private final byte[] data;
+
+        /**
+         * Constructor.
+         *
+         * @param type the option type
+         * @param data the option data
+         */
+        private Option(byte type, byte[] data) {
+            this.type = type;
+            this.data = Arrays.copyOfRange(data, 0, data.length);
+        }
+
+        /**
+         * Gets the option type.
+         *
+         * @return the option type
+         */
+        public byte type() {
+            return this.type;
+        }
+
+        /**
+         * Gets the option data.
+         *
+         * @return the option data
+         */
+        public byte[] data() {
+            return this.data;
+        }
+
+        /**
+         * Gets the option data length (in number of octets).
+         *
+         * @return the option data length (in number of octets)
+         */
+        public int dataLength() {
+            return data.length;
+        }
+
+        /**
+         * Gets the option length (in number of octets), including the type and
+         * length fields (one octet each).
+         *
+         * @return the option length (in number of octets), including the type
+         * and length fields
+         */
+        private int optionLength() {
+            return 2 + dataLength();
+        }
+
+        /**
+         * Gets the option length field value (in units of 8 octets).
+         *
+         * @return the option length field value (in units of 8 octets)
+         */
+        private byte optionLengthField() {
+            return (byte) ((optionLength() + 7) / 8);
+        }
+
+        /**
+         * Gets the option length on the wire (in number of octets).
+         *
+         * @return the option length on the wire (in number of octets)
+         */
+        private int optionWireLength() {
+            return 8 * optionLengthField();
+        }
+    }
+
+    /**
+     * Adds a Neighbor Discovery Protocol packet option.
+     *
+     * @param type the option type
+     * @param data the option data
+     * @return this
+     */
+    public NeighborDiscoveryOptions addOption(byte type, byte[] data) {
+        options.add(new Option(type, data));
+        return this;
+    }
+
+    /**
+     * Gets the Neighbor Discovery Protocol packet options.
+     *
+     * @return the Neighbor Discovery Protocol packet options
+     */
+    public List<NeighborDiscoveryOptions.Option> options() {
+        return this.options;
+    }
+
+    /**
+     * Checks whether any options are included.
+     *
+     * @return true if options are included, otherwise false
+     */
+    public boolean hasOptions() {
+        return !this.options.isEmpty();
+    }
+
+    @Override
+    public byte[] serialize() {
+        // Compute first the total length on the wire for all options
+
+        int wireLength = 0;
+
+        for (Option option : this.options) {
+            wireLength += option.optionWireLength();
+        }
+
+        final byte[] data = new byte[wireLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        //
+        // Serialize all options
+        //
+        for (Option option : this.options) {
+            bb.put(option.type());
+            bb.put(option.optionLengthField());
+            bb.put(option.data());
+            // Add the padding
+            int paddingLength =
+                option.optionWireLength() - option.optionLength();
+            for (int i = 0; i < paddingLength; i++) {
+                bb.put((byte) 0);
+            }
+        }
+
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+        options.clear();
+
+        //
+        // Deserialize all options
+        //
+        while (bb.hasRemaining()) {
+            byte type = bb.get();
+            if (!bb.hasRemaining()) {
+                break;
+            }
+            byte lengthField = bb.get();
+            int dataLength = lengthField * 8;   // The data length field is in
+                                                // unit of 8 octets
+
+            // Exclude the type and length fields
+            if (dataLength < 2) {
+                break;
+            }
+            dataLength -= 2;
+
+            if (bb.remaining() < dataLength) {
+                break;
+            }
+            byte[] optionData = new byte[dataLength];
+            bb.get(optionData, 0, optionData.length);
+            addOption(type, optionData);
+        }
+
+        return this;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+
+        for (Option option : this.options) {
+            result = prime * result + option.type();
+            result = prime * result + Arrays.hashCode(option.data());
+        }
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof NeighborDiscoveryOptions) {
+            NeighborDiscoveryOptions other = (NeighborDiscoveryOptions) obj;
+            return this.options.equals(other.options);
+        }
+        return false;
+    }
+}