[ONOS-5945] Add LispMapReferral message type with unit tests

Change-Id: I4f93140fccdbe96c4f0911e8aa325e6e3ffaee2f
diff --git a/protocols/lisp/msg/src/main/java/org/onosproject/lisp/msg/protocols/DefaultLispMapReferral.java b/protocols/lisp/msg/src/main/java/org/onosproject/lisp/msg/protocols/DefaultLispMapReferral.java
index d796e1e..2b2acdb 100644
--- a/protocols/lisp/msg/src/main/java/org/onosproject/lisp/msg/protocols/DefaultLispMapReferral.java
+++ b/protocols/lisp/msg/src/main/java/org/onosproject/lisp/msg/protocols/DefaultLispMapReferral.java
@@ -15,8 +15,214 @@
  */
 package org.onosproject.lisp.msg.protocols;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import io.netty.buffer.ByteBuf;
+import org.onlab.packet.DeserializationException;
+import org.onosproject.lisp.msg.exceptions.LispParseError;
+import org.onosproject.lisp.msg.exceptions.LispReaderException;
+import org.onosproject.lisp.msg.exceptions.LispWriterException;
+import org.onosproject.lisp.msg.protocols.DefaultLispReferralRecord.ReferralRecordReader;
+import org.onosproject.lisp.msg.protocols.DefaultLispReferralRecord.ReferralRecordWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onosproject.lisp.msg.protocols.LispType.LISP_MAP_REFERRAL;
+
 /**
  * Default LISP referral message class.
  */
-public class DefaultLispMapReferral implements LispMapReferral {
+public final class DefaultLispMapReferral extends AbstractLispMessage
+                                    implements LispMapReferral {
+
+    private static final Logger log =
+                         LoggerFactory.getLogger(DefaultLispMapReferral.class);
+
+    private final long nonce;
+    private final List<LispReferralRecord> referralRecords;
+
+    static final MapReferralWriter WRITER;
+
+    static {
+        WRITER = new MapReferralWriter();
+    }
+
+    /**
+     * A private constructor that protects object instantiation from external.
+     *
+     * @param nonce           nonce
+     * @param referralRecords a collection of referral records
+     */
+    private DefaultLispMapReferral(long nonce,
+                                   List<LispReferralRecord> referralRecords) {
+        this.nonce = nonce;
+        this.referralRecords = referralRecords;
+    }
+
+    @Override
+    public LispType getType() {
+        return LISP_MAP_REFERRAL;
+    }
+
+    @Override
+    public void writeTo(ByteBuf byteBuf) throws LispWriterException {
+        WRITER.writeTo(byteBuf, this);
+    }
+
+    @Override
+    public Builder createBuilder() {
+        return new DefaultMapReferralBuilder();
+    }
+
+    @Override
+    public int getRecordCount() {
+        return referralRecords.size();
+    }
+
+    @Override
+    public long getNonce() {
+        return nonce;
+    }
+
+    @Override
+    public List<LispReferralRecord> getReferralRecords() {
+        return ImmutableList.copyOf(referralRecords);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("type", getType())
+                .add("nonce", nonce)
+                .add("referralRecords", referralRecords)
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultLispMapReferral that = (DefaultLispMapReferral) o;
+        return Objects.equals(nonce, that.nonce);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(nonce);
+    }
+
+    public static final class DefaultMapReferralBuilder
+                                                implements MapReferralBuilder {
+
+        private long nonce;
+        private List<LispReferralRecord> referralRecords = Lists.newArrayList();
+
+        @Override
+        public LispType getType() {
+            return LISP_MAP_REFERRAL;
+        }
+
+        @Override
+        public MapReferralBuilder withNonce(long nonce) {
+            this.nonce = nonce;
+            return this;
+        }
+
+        @Override
+        public MapReferralBuilder withReferralRecords(List<LispReferralRecord> records) {
+            if (referralRecords != null) {
+                this.referralRecords = ImmutableList.copyOf(records);
+            }
+            return this;
+        }
+
+        @Override
+        public LispMapReferral build() {
+            return new DefaultLispMapReferral(nonce, referralRecords);
+        }
+    }
+
+    /**
+     * A LISP message reader for MapReferral message.
+     */
+    public static final class MapReferralReader
+                                implements LispMessageReader<LispMapReferral> {
+
+        private static final int RESERVED_SKIP_LENGTH = 3;
+
+        @Override
+        public LispMapReferral readFrom(ByteBuf byteBuf) throws LispParseError,
+                                LispReaderException, DeserializationException {
+
+            if (byteBuf.readerIndex() != 0) {
+                return null;
+            }
+
+            // let's skip the reserved field
+            byteBuf.skipBytes(RESERVED_SKIP_LENGTH);
+
+            // record count -> 8 bits
+            byte recordCount = (byte) byteBuf.readUnsignedByte();
+
+            // nonce -> 64 bits
+            long nonce = byteBuf.readLong();
+
+            List<LispReferralRecord> referralRecords = Lists.newArrayList();
+            for (int i = 0; i < recordCount; i++) {
+                referralRecords.add(new ReferralRecordReader().readFrom(byteBuf));
+            }
+
+            return new DefaultMapReferralBuilder()
+                            .withNonce(nonce)
+                            .withReferralRecords(referralRecords)
+                            .build();
+        }
+    }
+
+    /**
+     * A LISP message writer for MapReferral message.
+     */
+    public static final class MapReferralWriter
+                                implements LispMessageWriter<LispMapReferral> {
+
+        private static final int REFERRAL_SHIFT_BIT = 4;
+
+        private static final int UNUSED_ZERO = 0;
+
+        @Override
+        public void writeTo(ByteBuf byteBuf, LispMapReferral message)
+                                                    throws LispWriterException {
+
+            // specify LISP message type
+            byte msgType =
+                    (byte) (LISP_MAP_REFERRAL.getTypeCode() << REFERRAL_SHIFT_BIT);
+
+            // fill zero into reserved field
+            byteBuf.writeShort(UNUSED_ZERO);
+            byteBuf.writeByte(UNUSED_ZERO);
+
+            // record count
+            byteBuf.writeByte(message.getReferralRecords().size());
+
+            // nonce
+            byteBuf.writeLong(message.getNonce());
+
+            // serialize referral records
+            ReferralRecordWriter writer = new ReferralRecordWriter();
+            List<LispReferralRecord> records = message.getReferralRecords();
+
+            for (int i = 0; i < records.size(); i++) {
+                writer.writeTo(byteBuf, records.get(i));
+            }
+        }
+    }
 }