[ONOS-5750] Add MappingEntryBuilder with unit test

Change-Id: Ia4e2f4b6cb3a9b07a00376b12b506f43255bad11
diff --git a/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/impl/LispMappingProvider.java b/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/impl/LispMappingProvider.java
index 05e5dd7..3cf1e3b 100644
--- a/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/impl/LispMappingProvider.java
+++ b/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/impl/LispMappingProvider.java
@@ -25,12 +25,14 @@
 import org.onosproject.lisp.ctl.LispRouterId;
 import org.onosproject.lisp.ctl.LispRouterListener;
 import org.onosproject.lisp.msg.protocols.LispMapNotify;
+import org.onosproject.lisp.msg.protocols.LispMapRecord;
 import org.onosproject.lisp.msg.protocols.LispMapReply;
 import org.onosproject.lisp.msg.protocols.LispMessage;
 import org.onosproject.mapping.MappingEntry;
 import org.onosproject.mapping.MappingProvider;
 import org.onosproject.mapping.MappingProviderRegistry;
 import org.onosproject.mapping.MappingProviderService;
+import org.onosproject.mapping.MappingStore;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
@@ -38,6 +40,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+
 import static org.onosproject.mapping.MappingStore.Type.MAP_CACHE;
 import static org.onosproject.mapping.MappingStore.Type.MAP_DATABASE;
 
@@ -138,21 +142,33 @@
 
                 case LISP_MAP_REPLY:
                     LispMapReply reply = (LispMapReply) msg;
-
-                    MappingEntry replyMe = new MappingEntryBuilder(deviceId, reply).build();
-                    providerService.mappingAdded(replyMe, MAP_CACHE);
+                    processMappings(deviceId, reply.getMapRecords(), MAP_CACHE);
                     break;
 
                 case LISP_MAP_NOTIFY:
                     LispMapNotify notify = (LispMapNotify) msg;
-
-                    MappingEntry notifyMe = new MappingEntryBuilder(deviceId, notify).build();
-                    providerService.mappingAdded(notifyMe, MAP_DATABASE);
+                    processMappings(deviceId, notify.getMapRecords(), MAP_DATABASE);
                     break;
 
                 default:
                     log.warn("Unhandled message type: {}", msg.getType());
             }
         }
+
+        /**
+         * Converts map records into mapping, notifies to provider.
+         *
+         * @param deviceId device identifier
+         * @param records  a collection of map records
+         * @param type     MappingStore type
+         */
+        private void processMappings(DeviceId deviceId,
+                                     List<LispMapRecord> records,
+                                     MappingStore.Type type) {
+            records.forEach(r -> {
+                MappingEntry me = new MappingEntryBuilder(deviceId, r).build();
+                providerService.mappingAdded(me, type);
+            });
+        }
     }
 }
diff --git a/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/util/MappingEntryBuilder.java b/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/util/MappingEntryBuilder.java
index 12a58e4..90052c28 100644
--- a/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/util/MappingEntryBuilder.java
+++ b/providers/lisp/mapping/src/main/java/org/onosproject/provider/lisp/mapping/util/MappingEntryBuilder.java
@@ -15,49 +15,246 @@
  */
 package org.onosproject.provider.lisp.mapping.util;
 
-import org.onosproject.lisp.msg.protocols.LispMapNotify;
-import org.onosproject.lisp.msg.protocols.LispMapReply;
+import com.google.common.collect.Lists;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.lisp.msg.protocols.LispLocator;
+import org.onosproject.lisp.msg.protocols.LispMapRecord;
+import org.onosproject.lisp.msg.types.LispAfiAddress;
+import org.onosproject.lisp.msg.types.LispAsAddress;
+import org.onosproject.lisp.msg.types.LispDistinguishedNameAddress;
+import org.onosproject.lisp.msg.types.LispIpv4Address;
+import org.onosproject.lisp.msg.types.LispIpv6Address;
+import org.onosproject.lisp.msg.types.LispMacAddress;
+import org.onosproject.mapping.DefaultMapping;
+import org.onosproject.mapping.DefaultMappingEntry;
+import org.onosproject.mapping.DefaultMappingKey;
+import org.onosproject.mapping.DefaultMappingTreatment;
+import org.onosproject.mapping.DefaultMappingValue;
+import org.onosproject.mapping.Mapping;
 import org.onosproject.mapping.MappingEntry;
+import org.onosproject.mapping.MappingEntry.MappingEntryState;
+import org.onosproject.mapping.MappingKey;
+import org.onosproject.mapping.MappingTreatment;
+import org.onosproject.mapping.MappingValue;
+import org.onosproject.mapping.actions.MappingAction;
+import org.onosproject.mapping.actions.MappingActions;
+import org.onosproject.mapping.addresses.MappingAddress;
+import org.onosproject.mapping.addresses.MappingAddresses;
 import org.onosproject.net.DeviceId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+import java.util.UUID;
+
+import static org.onosproject.lisp.msg.types.AddressFamilyIdentifierEnum.IP4;
+import static org.onosproject.lisp.msg.types.AddressFamilyIdentifierEnum.IP6;
+
 /**
  * Mapping entry builder class.
  */
 public class MappingEntryBuilder {
-    private static final Logger log = LoggerFactory.getLogger(MappingEntryBuilder.class);
+    private static final Logger log =
+                            LoggerFactory.getLogger(MappingEntryBuilder.class);
+
+    private static final int IPV4_PREFIX_LENGTH = 32;
+    private static final int IPV6_PREFIX_LENGTH = 128;
 
     private final DeviceId deviceId;
-    private final LispMapReply mapReply;
-    private final LispMapNotify mapNotify;
+
+    private final MappingAddress address;
+    private final MappingAction action;
+    private final List<MappingTreatment> treatments;
 
     /**
      * Default constructor for MappingEntryBuilder.
      *
      * @param deviceId device identifier
-     * @param mapReply map reply message
+     * @param record   LISP map record
      */
-    public MappingEntryBuilder(DeviceId deviceId, LispMapReply mapReply) {
+    public MappingEntryBuilder(DeviceId deviceId, LispMapRecord record) {
         this.deviceId = deviceId;
-        this.mapReply = mapReply;
-        this.mapNotify = null;
+        this.address = buildAddress(record);
+        this.action = buildAction(record);
+        this.treatments = buildTreatments(record);
     }
 
     /**
-     * Default constructor for MappingEntryBuilder.
+     * Builds mapping entry from a specific LISP control message.
      *
-     * @param deviceId  device identifier
-     * @param mapNotify map notify message
+     * @return mapping entry
      */
-    public MappingEntryBuilder(DeviceId deviceId, LispMapNotify mapNotify) {
-        this.deviceId = deviceId;
-        this.mapNotify = mapNotify;
-        this.mapReply = null;
+    public MappingEntry build() {
+        Mapping.Builder builder;
+
+        // we assign leastSignificantBits of UUID as the mapping identifier for now
+        // id generation scheme can be changed later
+        UUID uuid = UUID.randomUUID();
+
+        builder = DefaultMapping.builder()
+                .withId(uuid.getLeastSignificantBits())
+                .forDevice(deviceId)
+                .withKey(buildKey())
+                .withValue(buildValue());
+
+        // TODO: we assume that the mapping entry will be always
+        // stored in routers without failure for now, which means
+        // the mapping entry state will always be ADDED rather than
+        // PENDING_ADD
+        // we will revisit this part when LISP driver is finished
+        return new DefaultMappingEntry(builder.build(), MappingEntryState.ADDED);
     }
 
-    public MappingEntry build() {
-        // TODO: provide a way to build mapping entry from input parameters
+    /**
+     * Builds mapping key.
+     *
+     * @return mapping key
+     */
+    private MappingKey buildKey() {
+
+        MappingKey.Builder builder = DefaultMappingKey.builder();
+
+        builder.withAddress(address);
+
+        return builder.build();
+    }
+
+    /**
+     * Builds mapping value.
+     *
+     * @return mapping value
+     */
+    private MappingValue buildValue() {
+
+        MappingValue.Builder builder = DefaultMappingValue.builder();
+        builder.withAction(action);
+
+        treatments.forEach(builder::add);
+
+        return builder.build();
+    }
+
+    /**
+     * Builds mapping action.
+     *
+     * @param record LISP map record
+     * @return mapping action
+     */
+    private MappingAction buildAction(LispMapRecord record) {
+
+        if (record == null) {
+            return MappingActions.noAction();
+        }
+
+        switch (record.getAction()) {
+            case NoAction:
+                return MappingActions.noAction();
+            case SendMapRequest:
+                return MappingActions.forward();
+            case NativelyForward:
+                return MappingActions.nativeForward();
+            case Drop:
+                return MappingActions.drop();
+            default:
+                log.warn("Unsupported action type {}", record.getAction());
+                return MappingActions.noAction();
+        }
+    }
+
+    /**
+     * Builds mapping address.
+     *
+     * @param record LISP map record
+     * @return mapping address
+     */
+    private MappingAddress buildAddress(LispMapRecord record) {
+
+        return record == null ? null :
+                getAddress(record.getEidPrefixAfi(), record.getMaskLength());
+    }
+
+    /**
+     * Converts LispAfiAddress into abstracted mapping address.
+     *
+     * @param address LispAfiAddress
+     * @param length  mask length
+     * @return abstracted mapping address
+     */
+    private MappingAddress getAddress(LispAfiAddress address, int length) {
+
+        if (address == null) {
+            log.warn("Address is not specified.");
+            return null;
+        }
+
+        switch (address.getAfi()) {
+            case IP4:
+                IpAddress ipv4Address = ((LispIpv4Address) address).getAddress();
+                IpPrefix ipv4Prefix = IpPrefix.valueOf(ipv4Address, length);
+                return MappingAddresses.ipv4MappingAddress(ipv4Prefix);
+            case IP6:
+                IpAddress ipv6Address = ((LispIpv6Address) address).getAddress();
+                IpPrefix ipv6Prefix = IpPrefix.valueOf(ipv6Address, length);
+                return MappingAddresses.ipv6MappingAddress(ipv6Prefix);
+            case AS:
+                int asNum = ((LispAsAddress) address).getASNum();
+                return MappingAddresses.asMappingAddress(String.valueOf(asNum));
+            case DISTINGUISHED_NAME:
+                String dn = ((LispDistinguishedNameAddress)
+                                                address).getDistinguishedName();
+                return MappingAddresses.dnMappingAddress(dn);
+            case MAC:
+                MacAddress macAddress = ((LispMacAddress) address).getAddress();
+                return MappingAddresses.ethMappingAddress(macAddress);
+            case LCAF:
+                // TODO: need to use extension address to abstract LCAF address
+                break;
+            default:
+                log.warn("Unsupported address type {}", address.getAfi());
+                break;
+        }
+
         return null;
     }
+
+    /**
+     * Builds a collection of mapping treatments.
+     *
+     * @param record LISP map record
+     * @return a collection of mapping treatments
+     */
+    private List<MappingTreatment> buildTreatments(LispMapRecord record) {
+
+        List<LispLocator> locators = record.getLocators();
+        List<MappingTreatment> treatments = Lists.newArrayList();
+        for (LispLocator locator : locators) {
+            MappingTreatment.Builder builder = DefaultMappingTreatment.builder();
+            LispAfiAddress address = locator.getLocatorAfi();
+            int addressLength = 0;
+            if (address.getAfi() == IP4) {
+                addressLength = IPV4_PREFIX_LENGTH;
+            } else if (address.getAfi() == IP6) {
+                addressLength = IPV6_PREFIX_LENGTH;
+            }
+
+            final MappingAddress mappingAddress = getAddress(address, addressLength);
+            if (mappingAddress != null) {
+                builder.withAddress(mappingAddress);
+            }
+
+            builder.setUnicastWeight(locator.getWeight())
+                    .setUnicastPriority(locator.getPriority())
+                    .setMulticastWeight(locator.getMulticastWeight())
+                    .setMulticastPriority(locator.getMulticastPriority());
+
+            // TODO: need to convert specific properties to
+            // abstracted extension properties
+
+            treatments.add(builder.build());
+        }
+
+        return treatments;
+    }
 }
diff --git a/providers/lisp/mapping/src/test/java/org/onosproject/provider/lisp/mapping/util/MappingEntryBuilderTest.java b/providers/lisp/mapping/src/test/java/org/onosproject/provider/lisp/mapping/util/MappingEntryBuilderTest.java
new file mode 100644
index 0000000..c6bf5fa
--- /dev/null
+++ b/providers/lisp/mapping/src/test/java/org/onosproject/provider/lisp/mapping/util/MappingEntryBuilderTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2017-present 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.onosproject.provider.lisp.mapping.util;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.lisp.msg.protocols.DefaultLispLocator.DefaultLocatorBuilder;
+import org.onosproject.lisp.msg.protocols.DefaultLispMapNotify.DefaultNotifyBuilder;
+import org.onosproject.lisp.msg.protocols.DefaultLispMapRecord.DefaultMapRecordBuilder;
+import org.onosproject.lisp.msg.protocols.DefaultLispMapReply.DefaultReplyBuilder;
+import org.onosproject.lisp.msg.protocols.LispLocator;
+import org.onosproject.lisp.msg.protocols.LispLocator.LocatorBuilder;
+import org.onosproject.lisp.msg.protocols.LispMapNotify;
+import org.onosproject.lisp.msg.protocols.LispMapNotify.NotifyBuilder;
+import org.onosproject.lisp.msg.protocols.LispMapRecord;
+import org.onosproject.lisp.msg.protocols.LispMapRecord.MapRecordBuilder;
+import org.onosproject.lisp.msg.protocols.LispMapReply;
+import org.onosproject.lisp.msg.protocols.LispMapReply.ReplyBuilder;
+import org.onosproject.lisp.msg.protocols.LispMapReplyAction;
+import org.onosproject.lisp.msg.types.LispIpv4Address;
+import org.onosproject.mapping.MappingEntry;
+import org.onosproject.mapping.MappingKey;
+import org.onosproject.mapping.MappingTreatment;
+import org.onosproject.mapping.MappingValue;
+import org.onosproject.mapping.actions.MappingAction;
+import org.onosproject.mapping.addresses.IPMappingAddress;
+import org.onosproject.net.DeviceId;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Mapping entry builder unit test.
+ */
+public class MappingEntryBuilderTest {
+
+    private static final String IP_RECORD_ADDRESS = "192.168.1.1";
+    private static final String IP_LOCATOR_ADDRESS = "10.1.1.1";
+    private static final int IP_RECORD_MASK_LENGTH = 24;
+    private static final int IP_LOCATOR_MASK_LENGTH = 32;
+
+    private static final String AUTH_KEY = "onos";
+
+    private static final byte UNIQUE_VALUE = (byte) 0x01;
+    private static final DeviceId DEVICE_ID = DeviceId.deviceId("lisp:10.1.1.2");
+
+    private LispMapReply mapReply;
+    private LispMapNotify mapNotify;
+
+    @Before
+    public void setUp() {
+        ReplyBuilder replyBuilder = new DefaultReplyBuilder();
+
+        List<LispMapRecord> records = ImmutableList.of(getMapRecord());
+
+        mapReply = replyBuilder
+                        .withIsEtr(true)
+                        .withIsProbe(false)
+                        .withIsSecurity(true)
+                        .withNonce(1L)
+                        .withMapRecords(records)
+                        .build();
+
+        NotifyBuilder notifyBuilder = new DefaultNotifyBuilder();
+
+        mapNotify = notifyBuilder
+                        .withKeyId((short) 1)
+                        .withAuthKey(AUTH_KEY)
+                        .withNonce(1L)
+                        .withMapRecords(records)
+                        .build();
+    }
+
+    @Test
+    public void testMapReplyConversion() {
+        List<LispMapRecord> replyRecords = mapReply.getMapRecords();
+
+        assertThat(replyRecords.size(), is(1));
+
+        testMapRecorConversion(replyRecords.get(0));
+    }
+
+    @Test
+    public void testMapNotifyConversion() {
+        List<LispMapRecord> notifyRecords = mapNotify.getMapRecords();
+
+        assertThat(notifyRecords.size(), is(1));
+
+        testMapRecorConversion(notifyRecords.get(0));
+    }
+
+    private void testMapRecorConversion(LispMapRecord record) {
+        MappingEntry mappingEntry =
+                     new MappingEntryBuilder(DEVICE_ID, record).build();
+        MappingKey key = mappingEntry.key();
+        MappingValue value = mappingEntry.value();
+
+        IPMappingAddress recordAddress = (IPMappingAddress) key.address();
+
+        assertThat(recordAddress.ip(), is(IpPrefix.valueOf(IP_RECORD_ADDRESS + "/" +
+                IP_RECORD_MASK_LENGTH)));
+
+        assertThat(value.action().type(), is(MappingAction.Type.NATIVE_FORWARD));
+
+        assertThat(value.treatments().size(), is(1));
+
+        MappingTreatment treatment = value.treatments().get(0);
+        IPMappingAddress locatorAddress = (IPMappingAddress) treatment.address();
+
+        assertThat(locatorAddress.ip(), is(IpPrefix.valueOf(IP_LOCATOR_ADDRESS + "/" +
+                IP_LOCATOR_MASK_LENGTH)));
+    }
+
+    private LispMapRecord getMapRecord() {
+        MapRecordBuilder recordBuilder = new DefaultMapRecordBuilder();
+
+        LispIpv4Address recordAddress =
+                        new LispIpv4Address(IpAddress.valueOf(IP_RECORD_ADDRESS));
+
+        LocatorBuilder locatorBuilder = new DefaultLocatorBuilder();
+
+        LispIpv4Address locatorAddress =
+                        new LispIpv4Address(IpAddress.valueOf(IP_LOCATOR_ADDRESS));
+
+        LispLocator locator1 = locatorBuilder
+                                    .withPriority(UNIQUE_VALUE)
+                                    .withWeight(UNIQUE_VALUE)
+                                    .withMulticastPriority(UNIQUE_VALUE)
+                                    .withMulticastWeight(UNIQUE_VALUE)
+                                    .withLocalLocator(true)
+                                    .withRlocProbed(false)
+                                    .withRouted(true)
+                                    .withLocatorAfi(locatorAddress)
+                                    .build();
+
+        return recordBuilder
+                .withRecordTtl(100)
+                .withIsAuthoritative(true)
+                .withMapVersionNumber((short) 1)
+                .withMaskLength((byte) IP_RECORD_MASK_LENGTH)
+                .withAction(LispMapReplyAction.NativelyForward)
+                .withEidPrefixAfi(recordAddress)
+                .withLocators(ImmutableList.of(locator1))
+                .build();
+    }
+}