commit f73ff8cfce18ab7a1cdfb7045aeded6a4ab81d56 Author: Aaron Huang Date: Fri Dec 6 18:12:24 2019 +0800 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 0000000000..ffc02c3cd5 --- /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 0000000000..4a2b653429 --- /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 0000000000..59d88accc8 --- /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 The type of data to compare. + */ + public static class CompareResult { + public final List removed = new ArrayList<>(); + public final List added = new ArrayList<>(); + + public CompareResult() {} + + public CompareResult(@Nullable Collection oldItems, @Nullable Collection 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 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 leftAddresses = left.getAddresses(); + final Collection 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 leftDnses = left.getDnsServers(); + final Collection 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 leftRoutes = left.getRoutes(); + final Collection 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 0000000000..53457893ca --- /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 0000000000..4a51a8c2d3 --- /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 0000000000..cc9e09ea76 --- /dev/null +++ b/staticlibs/tests/unit/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + 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 0000000000..6e06ee877b --- /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 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 0000000000..ad63b3c9eb --- /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)); + } + } +}