Add a new static library for mainline modules

Add a network static library for common utilities. This
library could be used by all mainline modules. Initially
create LinkPropertiesUtils and MacAddressUtils.

Bug: 139268426
Bug: 135998869
Bug: 138306002
Test: build lib pass
      atest NetworkStaticLibTests
Change-Id: I8f79e4f836819ac83007acffb55103e5d69873e0
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
new file mode 100644
index 0000000..ffc02c3
--- /dev/null
+++ b/staticlibs/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// 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.
+
+// File group "net-utils-framework-common-srcs" is also compiled into android framework.
+// Add jarjar rules here so that anything outside of framework could use this library
+// directly.
+
+filegroup {
+    name: "net-utils-framework-common-srcs",
+    srcs: ["src_frameworkcommon/**/*.java"],
+    visibility: ["//frameworks/base"],
+}
+
+java_library {
+    name: "net-utils-framework-common",
+    srcs: [":net-utils-framework-common-srcs" ],
+    jarjar_rules: "jarjar-rules-shared.txt",
+    visibility: [
+        "//frameworks/base/packages/Tethering",
+        "//frameworks/opt/net/wifi",
+        "//frameworks/opt/net/ike",
+        "//frameworks/opt/telephony",
+        "//packages/modules/NetworkStack",
+        "//packages/modules/CaptivePortalLogin",
+    ]
+}
diff --git a/staticlibs/jarjar-rules-shared.txt b/staticlibs/jarjar-rules-shared.txt
new file mode 100644
index 0000000..4a2b653
--- /dev/null
+++ b/staticlibs/jarjar-rules-shared.txt
@@ -0,0 +1 @@
+rule android.net.util.** com.android.net.module.util.@1
diff --git a/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java b/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java
new file mode 100644
index 0000000..59d88ac
--- /dev/null
+++ b/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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 android.net.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Collection of link properties utilities.
+ */
+public final class LinkPropertiesUtils {
+
+    /**
+     * @param <T> The type of data to compare.
+     */
+    public static class CompareResult<T> {
+        public final List<T> removed = new ArrayList<>();
+        public final List<T> added = new ArrayList<>();
+
+        public CompareResult() {}
+
+        public CompareResult(@Nullable Collection<T> oldItems, @Nullable Collection<T> newItems) {
+            if (oldItems != null) {
+                removed.addAll(oldItems);
+            }
+            if (newItems != null) {
+                for (T newItem : newItems) {
+                    if (!removed.remove(newItem)) {
+                        added.add(newItem);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "removed=[" + TextUtils.join(",", removed)
+                    + "] added=[" + TextUtils.join(",", added)
+                    + "]";
+        }
+    }
+
+    /**
+     * Compares the addresses in {@code left} LinkProperties with {@code right}
+     * LinkProperties, examining only addresses on the base link.
+     *
+     * @param left A LinkProperties with the old list of addresses.
+     * @param right A LinkProperties with the new list of addresses.
+     * @return the differences between the addresses.
+     */
+    public static @NonNull CompareResult<LinkAddress> compareAddresses(
+            @Nullable LinkProperties left, @Nullable LinkProperties right) {
+        /*
+         * Duplicate the LinkAddresses into removed, we will be removing
+         * address which are common between mLinkAddresses and target
+         * leaving the addresses that are different. And address which
+         * are in target but not in mLinkAddresses are placed in the
+         * addedAddresses.
+         */
+        return new CompareResult<>(left != null ? left.getLinkAddresses() : null,
+                right != null ? right.getLinkAddresses() : null);
+    }
+
+   /**
+     * Compares {@code left} {@code LinkProperties} interface addresses against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalAddresses(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        final Collection<InetAddress> leftAddresses = left.getAddresses();
+        final Collection<InetAddress> rightAddresses = right.getAddresses();
+        return (leftAddresses.size() == rightAddresses.size())
+                    ? leftAddresses.containsAll(rightAddresses) : false;
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} DNS addresses against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalDnses(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        final Collection<InetAddress> leftDnses = left.getDnsServers();
+        final Collection<InetAddress> rightDnses = right.getDnsServers();
+
+        final String leftDomains = left.getDomains();
+        final String rightDomains = right.getDomains();
+        if (leftDomains == null) {
+            if (rightDomains != null) return false;
+        } else {
+            if (!leftDomains.equals(rightDomains)) return false;
+        }
+        return (leftDnses.size() == rightDnses.size())
+                ? leftDnses.containsAll(rightDnses) : false;
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} HttpProxy against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalHttpProxy(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        return Objects.equals(left.getHttpProxy(), right.getHttpProxy());
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} interface name against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalInterfaceName(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        return TextUtils.equals(left.getInterfaceName(), right.getInterfaceName());
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} Routes against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalRoutes(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        final Collection<RouteInfo> leftRoutes = left.getRoutes();
+        final Collection<RouteInfo> rightRoutes = right.getRoutes();
+        return (leftRoutes.size() == rightRoutes.size())
+                ? leftRoutes.containsAll(rightRoutes) : false;
+    }
+}
diff --git a/staticlibs/src_frameworkcommon/android/net/util/MacAddressUtils.java b/staticlibs/src_frameworkcommon/android/net/util/MacAddressUtils.java
new file mode 100644
index 0000000..5345789
--- /dev/null
+++ b/staticlibs/src_frameworkcommon/android/net/util/MacAddressUtils.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * 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 android.net.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.MacAddress;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
+
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Collection of MAC address utilities.
+ */
+public final class MacAddressUtils {
+
+    private static final long VALID_LONG_MASK = (1L << 48) - 1;
+    private static final long LOCALLY_ASSIGNED_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("2:0:0:0:0:0").toByteArray());
+    private static final long MULTICAST_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("1:0:0:0:0:0").toByteArray());
+    private static final long OUI_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("ff:ff:ff:0:0:0").toByteArray());
+    private static final long NIC_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("0:0:0:ff:ff:ff").toByteArray());
+    // Matches WifiInfo.DEFAULT_MAC_ADDRESS
+    private static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
+    private static final int ETHER_ADDR_LEN = 6;
+
+    /**
+     * @return true if this MacAddress is a multicast address.
+     */
+    public static boolean isMulticastAddress(@NonNull MacAddress address) {
+        return (longAddrFromByteAddr(address.toByteArray()) & MULTICAST_MASK) != 0;
+    }
+
+    /**
+     * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
+     * unicast bit, are randomly selected.
+     *
+     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+     *
+     * @return a random locally assigned, unicast MacAddress.
+     */
+    public static @NonNull MacAddress createRandomUnicastAddress() {
+        return createRandomUnicastAddress(null, new SecureRandom());
+    }
+
+    /**
+     * Returns a randomly generated MAC address using the given Random object and the same
+     * OUI values as the given MacAddress.
+     *
+     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+     *
+     * @param base a base MacAddress whose OUI is used for generating the random address.
+     *             If base == null then the OUI will also be randomized.
+     * @param r a standard Java Random object used for generating the random address.
+     * @return a random locally assigned MacAddress.
+     */
+    public static @NonNull MacAddress createRandomUnicastAddress(@Nullable MacAddress base,
+            @NonNull Random r) {
+        long addr;
+
+        if (base == null) {
+            addr = r.nextLong() & VALID_LONG_MASK;
+        } else {
+            addr = (longAddrFromByteAddr(base.toByteArray()) & OUI_MASK)
+                    | (NIC_MASK & r.nextLong());
+        }
+        addr |= LOCALLY_ASSIGNED_MASK;
+        addr &= ~MULTICAST_MASK;
+        MacAddress mac = MacAddress.fromBytes(byteAddrFromLongAddr(addr));
+        if (mac.equals(DEFAULT_MAC_ADDRESS)) {
+            return createRandomUnicastAddress(base, r);
+        }
+        return mac;
+    }
+
+    /**
+     * Convert a byte address to long address.
+     */
+    public static long longAddrFromByteAddr(byte[] addr) {
+        Preconditions.checkNotNull(addr);
+        if (!isMacAddress(addr)) {
+            throw new IllegalArgumentException(
+                    Arrays.toString(addr) + " was not a valid MAC address");
+        }
+        long longAddr = 0;
+        for (byte b : addr) {
+            longAddr = (longAddr << 8) + BitUtils.uint8(b);
+        }
+        return longAddr;
+    }
+
+    /**
+     * Convert a long address to byte address.
+     */
+    public static byte[] byteAddrFromLongAddr(long addr) {
+        byte[] bytes = new byte[ETHER_ADDR_LEN];
+        int index = ETHER_ADDR_LEN;
+        while (index-- > 0) {
+            bytes[index] = (byte) addr;
+            addr = addr >> 8;
+        }
+        return bytes;
+    }
+
+    /**
+     * Returns true if the given byte array is a valid MAC address.
+     * A valid byte array representation for a MacAddress is a non-null array of length 6.
+     *
+     * @param addr a byte array.
+     * @return true if the given byte array is not null and has the length of a MAC address.
+     *
+     */
+    public static boolean isMacAddress(byte[] addr) {
+        return addr != null && addr.length == ETHER_ADDR_LEN;
+    }
+}
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
new file mode 100644
index 0000000..4a51a8c
--- /dev/null
+++ b/staticlibs/tests/unit/Android.bp
@@ -0,0 +1,18 @@
+//########################################################################
+// Build NetworkStaticLibTests package
+//########################################################################
+
+android_test {
+    name: "NetworkStaticLibTests",
+    certificate: "platform",
+    srcs: ["src/**/*.java","src/**/*.kt"],
+    test_suites: ["device-tests"],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+}
+
diff --git a/staticlibs/tests/unit/AndroidManifest.xml b/staticlibs/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..cc9e09e
--- /dev/null
+++ b/staticlibs/tests/unit/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.libnet.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.libnet.tests"
+        android:label="Network Static Library Tests" />
+</manifest>
diff --git a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
new file mode 100644
index 0000000..6e06ee8
--- /dev/null
+++ b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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 android.net.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.util.LinkPropertiesUtils.CompareResult;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+
+@RunWith(AndroidJUnit4.class)
+public final class LinkPropertiesUtilsTest {
+    private static final IpPrefix PREFIX = new IpPrefix(toInetAddress("75.208.6.0"), 24);
+    private static final InetAddress V4_ADDR = toInetAddress("75.208.6.1");
+    private static final InetAddress V6_ADDR  = toInetAddress(
+            "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+    private static final InetAddress DNS1 = toInetAddress("75.208.7.1");
+    private static final InetAddress DNS2 = toInetAddress("69.78.7.1");
+
+    private static final InetAddress GATEWAY1 = toInetAddress("75.208.8.1");
+    private static final InetAddress GATEWAY2 = toInetAddress("69.78.8.1");
+
+    private static final String IF_NAME = "wlan0";
+    private static final LinkAddress V4_LINKADDR = new LinkAddress(V4_ADDR, 32);
+    private static final LinkAddress V6_LINKADDR = new LinkAddress(V6_ADDR, 128);
+    private static final RouteInfo RT_INFO1 = new RouteInfo(PREFIX, GATEWAY1, IF_NAME);
+    private static final RouteInfo RT_INFO2 = new RouteInfo(PREFIX, GATEWAY2, IF_NAME);
+    private static final String TEST_DOMAIN = "link.properties.com";
+
+    private static InetAddress toInetAddress(String addrString) {
+        return InetAddresses.parseNumericAddress(addrString);
+    }
+
+    private LinkProperties createTestObject() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(IF_NAME);
+        lp.addLinkAddress(V4_LINKADDR);
+        lp.addLinkAddress(V6_LINKADDR);
+        lp.addDnsServer(DNS1);
+        lp.addDnsServer(DNS2);
+        lp.setDomains(TEST_DOMAIN);
+        lp.addRoute(RT_INFO1);
+        lp.addRoute(RT_INFO2);
+        lp.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888));
+        return lp;
+    }
+
+    @Test
+    public void testLinkPropertiesIdenticalEqual() {
+        final LinkProperties source = createTestObject();
+        final LinkProperties target = new LinkProperties(source);
+
+        assertTrue(LinkPropertiesUtils.isIdenticalInterfaceName(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalInterfaceName(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalAddresses(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalDnses(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalRoutes(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalRoutes(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalHttpProxy(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
+
+        // Test different interface name.
+        target.setInterfaceName("lo");
+        assertFalse(LinkPropertiesUtils.isIdenticalInterfaceName(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalInterfaceName(target, source));
+        // Restore interface name
+        target.setInterfaceName(IF_NAME);
+
+        // Compare addresses.size() not equals.
+        final LinkAddress testLinkAddr = new LinkAddress(toInetAddress("75.208.6.2"), 32);
+        target.addLinkAddress(testLinkAddr);
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+
+        // Currently, target contains V4_LINKADDR, V6_LINKADDR and testLinkAddr.
+        // Compare addresses.size() equals but contains different address.
+        target.removeLinkAddress(V4_LINKADDR);
+        assertEquals(source.getAddresses().size(), target.getAddresses().size());
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+        // Restore link address
+        target.addLinkAddress(V4_LINKADDR);
+        target.removeLinkAddress(testLinkAddr);
+
+        // Compare size not equals.
+        target.addDnsServer(toInetAddress("75.208.10.1"));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source));
+
+        // Compare the same servers but target has different domains.
+        target.removeDnsServer(toInetAddress("75.208.10.1"));
+        target.setDomains("test.com");
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source));
+
+        // Test null domain.
+        target.setDomains(null);
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source));
+        // Restore domain
+        target.setDomains(TEST_DOMAIN);
+
+        // Compare size not equals.
+        final RouteInfo testRoute = new RouteInfo(toInetAddress("75.208.7.1"));
+        target.addRoute(testRoute);
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(target, source));
+
+        // Currently, target contains RT_INFO1, RT_INFO2 and testRoute.
+        // Compare size equals but different routes.
+        target.removeRoute(RT_INFO1);
+        assertEquals(source.getRoutes().size(), target.getRoutes().size());
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(target, source));
+        // Restore route
+        target.addRoute(RT_INFO1);
+        target.removeRoute(testRoute);
+
+        // Test different proxy.
+        target.setHttpProxy(ProxyInfo.buildDirectProxy("hello", 8888));
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
+
+        // Test null proxy.
+        target.setHttpProxy(null);
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
+    }
+
+    @Test
+    public void testCompareAddresses() {
+        final LinkProperties source = createTestObject();
+        final LinkProperties target = new LinkProperties(source);
+        final InetAddress addr1 = toInetAddress("75.208.6.2");
+        final LinkAddress linkAddr1 = new LinkAddress(addr1, 32);
+
+        CompareResult<LinkAddress> results = LinkPropertiesUtils.compareAddresses(source, target);
+        assertEquals(0, results.removed.size());
+        assertEquals(0, results.added.size());
+
+        source.addLinkAddress(linkAddr1);
+        results = LinkPropertiesUtils.compareAddresses(source, target);
+        assertEquals(1, results.removed.size());
+        assertEquals(linkAddr1, results.removed.get(0));
+        assertEquals(0, results.added.size());
+
+        final InetAddress addr2 = toInetAddress("75.208.6.3");
+        final LinkAddress linkAddr2 = new LinkAddress(addr2, 32);
+
+        target.addLinkAddress(linkAddr2);
+        results = LinkPropertiesUtils.compareAddresses(source, target);
+        assertEquals(linkAddr1, results.removed.get(0));
+        assertEquals(linkAddr2, results.added.get(0));
+    }
+}
diff --git a/staticlibs/tests/unit/src/android/net/util/MacAddressUtilsTest.java b/staticlibs/tests/unit/src/android/net/util/MacAddressUtilsTest.java
new file mode 100644
index 0000000..ad63b3c
--- /dev/null
+++ b/staticlibs/tests/unit/src/android/net/util/MacAddressUtilsTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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 android.net.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.MacAddress;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class MacAddressUtilsTest {
+
+    // Matches WifiInfo.DEFAULT_MAC_ADDRESS
+    private static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
+
+    @Test
+    public void testIsMulticastAddress() {
+        MacAddress[] multicastAddresses = {
+            // broadcast address
+            MacAddress.fromString("ff:ff:ff:ff:ff:ff"),
+            MacAddress.fromString("07:00:d3:56:8a:c4"),
+            MacAddress.fromString("33:33:aa:bb:cc:dd"),
+        };
+        MacAddress[] unicastAddresses = {
+            // all zero address
+            MacAddress.fromString("00:00:00:00:00:00"),
+            MacAddress.fromString("00:01:44:55:66:77"),
+            MacAddress.fromString("08:00:22:33:44:55"),
+            MacAddress.fromString("06:00:00:00:00:00"),
+        };
+
+        for (MacAddress mac : multicastAddresses) {
+            String msg = mac.toString() + " expected to be a multicast address";
+            assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
+        }
+        for (MacAddress mac : unicastAddresses) {
+            String msg = mac.toString() + " expected not to be a multicast address";
+            assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
+        }
+    }
+
+    @Test
+    public void testMacAddressRandomGeneration() {
+        final int iterations = 1000;
+
+        for (int i = 0; i < iterations; i++) {
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
+            String stringRepr = mac.toString();
+
+            assertTrue(stringRepr + " expected to be a locally assigned address",
+                    mac.isLocallyAssigned());
+            assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
+            assertFalse(mac.equals(DEFAULT_MAC_ADDRESS));
+        }
+    }
+}