Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d39a5035 authored by Hugo Benichi's avatar Hugo Benichi Committed by android-build-merger
Browse files

Merge "MacAddress follow-up: define the core of the class"

am: cd35c65b

Change-Id: Ic86df7e56a63d3f84ccaebefa904c7625105c581
parents 8249a5d6 cd35c65b
Loading
Loading
Loading
Loading
+226 −14
Original line number Diff line number Diff line
@@ -16,29 +16,128 @@

package android.net;

import com.android.internal.annotations.VisibleForTesting;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.util.BitUtils;

import java.util.Arrays;
import java.util.Random;
import java.util.StringJoiner;

/**
 * Represents a mac address.
 *
 * @hide
 */
public final class MacAddress {

    // TODO: add isLocallyAssigned().
    // TODO: add getRandomAddress() factory method.
public final class MacAddress implements Parcelable {

    private static final int ETHER_ADDR_LEN = 6;
    private static final byte FF = (byte) 0xff;
    @VisibleForTesting
    static final byte[] ETHER_ADDR_BROADCAST = { FF, FF, FF, FF, FF, FF };
    private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);

    /** The broadcast mac address.  */
    public static final MacAddress BROADCAST_ADDRESS = new MacAddress(ETHER_ADDR_BROADCAST);

    /** The zero mac address. */
    public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);

    /** Represents categories of mac addresses. */
    public enum MacAddressType {
        UNICAST,
        MULTICAST,
        BROADCAST;
    }

    private static final long VALID_LONG_MASK = BROADCAST_ADDRESS.mAddr;
    private static final long LOCALLY_ASSIGNED_MASK = new MacAddress("2:0:0:0:0:0").mAddr;
    private static final long MULTICAST_MASK = new MacAddress("1:0:0:0:0:0").mAddr;
    private static final long OUI_MASK = new MacAddress("ff:ff:ff:0:0:0").mAddr;
    private static final long NIC_MASK = new MacAddress("0:0:0:ff:ff:ff").mAddr;
    private static final MacAddress BASE_ANDROID_MAC = new MacAddress("da:a1:19:0:0:0");

    // Internal representation of the mac address as a single 8 byte long.
    // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
    // mac address are encoded in the 6 least significant bytes of the long, where the first
    // byte of the array is mapped to the 3rd highest logical byte of the long, the second
    // byte of the array is mapped to the 4th highest logical byte of the long, and so on.
    private final long mAddr;

    private MacAddress(long addr) {
        mAddr = addr;
    }

    /** Creates a MacAddress for the given byte representation. */
    public MacAddress(byte[] addr) {
        this(longAddrFromByteAddr(addr));
    }

    /** Creates a MacAddress for the given string representation. */
    public MacAddress(String addr) {
        this(longAddrFromByteAddr(byteAddrFromStringAddr(addr)));
    }

    /** Returns the MacAddressType of this MacAddress. */
    public MacAddressType addressType() {
        if (equals(BROADCAST_ADDRESS)) {
            return MacAddressType.BROADCAST;
        }
        if (isMulticastAddress()) {
            return MacAddressType.MULTICAST;
        }
        return MacAddressType.UNICAST;
    }

    /** Returns true if this MacAddress corresponds to a multicast address. */
    public boolean isMulticastAddress() {
        return (mAddr & MULTICAST_MASK) != 0;
    }

    /** Returns true if this MacAddress corresponds to a locally assigned address. */
    public boolean isLocallyAssigned() {
        return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
    }

    /** Returns a byte array representation of this MacAddress. */
    public byte[] toByteArray() {
        return byteAddrFromLongAddr(mAddr);
    }

    @Override
    public String toString() {
        return stringAddrFromByteAddr(byteAddrFromLongAddr(mAddr));
    }

    @Override
    public int hashCode() {
        return (int) ((mAddr >> 32) ^ mAddr);
    }

    @Override
    public boolean equals(Object o) {
        return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeLong(mAddr);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Parcelable.Creator<MacAddress> CREATOR =
            new Parcelable.Creator<MacAddress>() {
                public MacAddress createFromParcel(Parcel in) {
                    return new MacAddress(in.readLong());
                }

                public MacAddress[] newArray(int size) {
                    return new MacAddress[size];
                }
            };

    /** 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;
@@ -46,17 +145,130 @@ public final class MacAddress {

    /**
     * Return the MacAddressType of the mac address represented by the given byte array,
     * or null if the given byte array does not represent an mac address. */
     * or null if the given byte array does not represent an mac address.
     */
    public static MacAddressType macAddressType(byte[] addr) {
        if (!isMacAddress(addr)) {
            return null;
        }
        if (Arrays.equals(addr, ETHER_ADDR_BROADCAST)) {
            return MacAddressType.BROADCAST;
        return new MacAddress(addr).addressType();
    }
        if ((addr[0] & 0x01) == 1) {
            return MacAddressType.MULTICAST;

    /** DOCME */
    public static byte[] byteAddrFromStringAddr(String addr) {
        if (addr == null) {
            throw new IllegalArgumentException("cannot convert the null String");
        }
        return MacAddressType.UNICAST;
        String[] parts = addr.split(":");
        if (parts.length != ETHER_ADDR_LEN) {
            throw new IllegalArgumentException(addr + " was not a valid MAC address");
        }
        byte[] bytes = new byte[ETHER_ADDR_LEN];
        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
            int x = Integer.valueOf(parts[i], 16);
            if (x < 0 || 0xff < x) {
                throw new IllegalArgumentException(addr + "was not a valid MAC address");
            }
            bytes[i] = (byte) x;
        }
        return bytes;
    }

    /** DOCME */
    public static String stringAddrFromByteAddr(byte[] addr) {
        if (!isMacAddress(addr)) {
            return null;
        }
        StringJoiner j = new StringJoiner(":");
        for (byte b : addr) {
            j.add(Integer.toHexString(BitUtils.uint8(b)));
        }
        return j.toString();
    }

    /** @hide */
    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;
    }

    /** @hide */
    public static long longAddrFromByteAddr(byte[] 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;
    }

    /** @hide */
    public static long longAddrFromStringAddr(String addr) {
        if (addr == null) {
            throw new IllegalArgumentException("cannot convert the null String");
        }
        String[] parts = addr.split(":");
        if (parts.length != ETHER_ADDR_LEN) {
            throw new IllegalArgumentException(addr + " was not a valid MAC address");
        }
        long longAddr = 0;
        int index = ETHER_ADDR_LEN;
        while (index-- > 0) {
            int x = Integer.valueOf(parts[index], 16);
            if (x < 0 || 0xff < x) {
                throw new IllegalArgumentException(addr + "was not a valid MAC address");
            }
            longAddr = x + (longAddr << 8);
        }
        return longAddr;
    }

    /** @hide */
    public static String stringAddrFromLongAddr(long addr) {
        addr = Long.reverseBytes(addr) >> 16;
        StringJoiner j = new StringJoiner(":");
        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
            j.add(Integer.toHexString((byte) addr));
            addr = addr >> 8;
        }
        return j.toString();
    }

    /**
     * Returns a randomely generated mac address with the Android OUI value "DA-A1-19".
     * The locally assigned bit is always set to 1.
     */
    public static MacAddress getRandomAddress() {
        return getRandomAddress(BASE_ANDROID_MAC, new Random());
    }

    /**
     * Returns a randomely 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.
     */
    public static MacAddress getRandomAddress(MacAddress base, Random r) {
        long longAddr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()) | LOCALLY_ASSIGNED_MASK;
        return new MacAddress(longAddr);
    }

    // Convenience function for working around the lack of byte literals.
    private static byte[] addr(int... in) {
        if (in.length != ETHER_ADDR_LEN) {
            throw new IllegalArgumentException(Arrays.toString(in)
                    + " was not an array with length equal to " + ETHER_ADDR_LEN);
        }
        byte[] out = new byte[ETHER_ADDR_LEN];
        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
            out[i] = (byte) in[i];
        }
        return out;
    }
}
+132 −1
Original line number Diff line number Diff line
@@ -19,12 +19,14 @@ package android.net;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;

import android.net.MacAddress.MacAddressType;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import java.util.Arrays;
import java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,10 +66,139 @@ public class MacAddressTest {
            String msg = String.format("expected type of %s to be %s, but got %s",
                    Arrays.toString(t.addr), t.expected, got);
            assertEquals(msg, t.expected, got);

            if (got != null) {
                assertEquals(got, new MacAddress(t.addr).addressType());
            }
        }
    }

    @Test
    public void testIsMulticastAddress() {
        MacAddress[] multicastAddresses = {
            MacAddress.BROADCAST_ADDRESS,
            new MacAddress("07:00:d3:56:8a:c4"),
            new MacAddress("33:33:aa:bb:cc:dd"),
        };
        MacAddress[] unicastAddresses = {
            MacAddress.ALL_ZEROS_ADDRESS,
            new MacAddress("00:01:44:55:66:77"),
            new MacAddress("08:00:22:33:44:55"),
            new MacAddress("06:00:00:00:00:00"),
        };

        for (MacAddress mac : multicastAddresses) {
            String msg = mac.toString() + " expected to be a multicast address";
            assertTrue(msg, mac.isMulticastAddress());
        }
        for (MacAddress mac : unicastAddresses) {
            String msg = mac.toString() + " expected not to be a multicast address";
            assertFalse(msg, mac.isMulticastAddress());
        }
    }

    @Test
    public void testIsLocallyAssignedAddress() {
        MacAddress[] localAddresses = {
            new MacAddress("06:00:00:00:00:00"),
            new MacAddress("07:00:d3:56:8a:c4"),
            new MacAddress("33:33:aa:bb:cc:dd"),
        };
        MacAddress[] universalAddresses = {
            new MacAddress("00:01:44:55:66:77"),
            new MacAddress("08:00:22:33:44:55"),
        };

        for (MacAddress mac : localAddresses) {
            String msg = mac.toString() + " expected to be a locally assigned address";
            assertTrue(msg, mac.isLocallyAssigned());
        }
        for (MacAddress mac : universalAddresses) {
            String msg = mac.toString() + " expected not to be globally unique address";
            assertFalse(msg, mac.isLocallyAssigned());
        }
    }

    @Test
    public void testMacAddressConversions() {
        final int iterations = 10000;
        for (int i = 0; i < iterations; i++) {
            MacAddress mac = MacAddress.getRandomAddress();

            String stringRepr = mac.toString();
            byte[] bytesRepr = mac.toByteArray();

            assertEquals(mac, new MacAddress(stringRepr));
            assertEquals(mac, new MacAddress(bytesRepr));
        }
    }

    @Test
    public void testMacAddressRandomGeneration() {
        final int iterations = 1000;
        final String expectedAndroidOui = "da:a1:19";
        for (int i = 0; i < iterations; i++) {
            MacAddress mac = MacAddress.getRandomAddress();
            String stringRepr = mac.toString();

            assertTrue(stringRepr + " expected to be a locally assigned address",
                    mac.isLocallyAssigned());
            assertTrue(stringRepr + " expected to begin with " + expectedAndroidOui,
                    stringRepr.startsWith(expectedAndroidOui));
        }

        final Random r = new Random();
        final String anotherOui = "24:5f:78";
        final String expectedLocalOui = "26:5f:78";
        final MacAddress base = new MacAddress(anotherOui + ":0:0:0");
        for (int i = 0; i < iterations; i++) {
            MacAddress mac = MacAddress.getRandomAddress(base, r);
            String stringRepr = mac.toString();

            assertTrue(stringRepr + " expected to be a locally assigned address",
                    mac.isLocallyAssigned());
            assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
                    stringRepr.startsWith(expectedLocalOui));
        }
    }

    @Test
    public void testConstructorInputValidation() {
        String[] invalidStringAddresses = {
            null,
            "",
            "abcd",
            "1:2:3:4:5",
            "1:2:3:4:5:6:7",
            "10000:2:3:4:5:6",
        };

        for (String s : invalidStringAddresses) {
            try {
                MacAddress mac = new MacAddress(s);
                fail("new MacAddress(" + s + ") should have failed, but returned " + mac);
            } catch (IllegalArgumentException excepted) {
            }
        }

        byte[][] invalidBytesAddresses = {
            null,
            {},
            {1,2,3,4,5},
            {1,2,3,4,5,6,7},
        };

        for (byte[] b : invalidBytesAddresses) {
            try {
                MacAddress mac = new MacAddress(b);
                fail("new MacAddress(" + Arrays.toString(b)
                        + ") should have failed, but returned " + mac);
            } catch (IllegalArgumentException excepted) {
            }
        }
    }

    static byte[] toByteArray(int[] in) {
    static byte[] toByteArray(int... in) {
        byte[] out = new byte[in.length];
        for (int i = 0; i < in.length; i++) {
            out[i] = (byte) in[i];