diff options
-rw-r--r-- | res/values/config.xml | 3 | ||||
-rw-r--r-- | res/values/overlayable.xml | 10 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpClient.java | 33 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpPacket.java | 35 | ||||
-rw-r--r-- | src/android/net/util/HostnameTransliterator.java | 103 | ||||
-rw-r--r-- | tests/integration/src/android/net/ip/IpClientIntegrationTest.java | 111 | ||||
-rw-r--r-- | tests/unit/src/android/net/dhcp/DhcpPacketTest.java | 26 | ||||
-rw-r--r-- | tests/unit/src/android/net/util/HostnameTransliteratorTest.java | 137 |
8 files changed, 432 insertions, 26 deletions
diff --git a/res/values/config.xml b/res/values/config.xml index 13ab04e..37a6976 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -46,4 +46,7 @@ <!-- Set to true if NetworkMonitor needs to load the resource by neighbor mcc when device doesn't have a SIM card inserted. --> <bool name="config_no_sim_card_uses_neighbor_mcc">false</bool> + + <!-- Configuration for including DHCP client hostname option --> + <bool name="config_dhcp_client_hostname">false</bool> </resources> diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml index 4727215..57c9e7a 100644 --- a/res/values/overlayable.xml +++ b/res/values/overlayable.xml @@ -24,6 +24,16 @@ <item type="bool" name="config_no_sim_card_uses_neighbor_mcc"/> <!-- Configuration value for DhcpResults --> <item type="array" name="config_default_dns_servers"/> + <!-- Configuration for including DHCP client hostname option. + If this option is true, client hostname set in Settings.Global.DEVICE_NAME will be + included in DHCPDISCOVER/DHCPREQUEST, otherwise, the DHCP hostname option will not + be sent. RFC952 and RFC1123 stipulates an valid hostname should be only comprised of + 'a-z', 'A-Z' and '-', and the length should be up to 63 octets or less (RFC1035#2.3.4), + platform will perform best-effort transliteration for other characters. Anything that + could be used to identify the device uniquely is not recommended, e.g. user's name, + random number and etc. + --> + <item type="bool" name="config_dhcp_client_hostname"/> </policy> </overlayable> </resources> diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java index 662a640..8aa739a 100644 --- a/src/android/net/dhcp/DhcpClient.java +++ b/src/android/net/dhcp/DhcpClient.java @@ -68,6 +68,7 @@ import android.net.ipmemorystore.OnStatusListener; import android.net.metrics.DhcpClientEvent; import android.net.metrics.DhcpErrorEvent; import android.net.metrics.IpConnectivityLog; +import android.net.util.HostnameTransliterator; import android.net.util.InterfaceParams; import android.net.util.NetworkStackUtils; import android.net.util.PacketReader; @@ -76,6 +77,7 @@ import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; +import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.util.EventLog; @@ -312,6 +314,8 @@ public class DhcpClient extends StateMachine { private final NetworkStackIpMemoryStore mIpMemoryStore; @Nullable private DhcpPacketHandler mDhcpPacketHandler; + @Nullable + private final String mHostname; // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. private long mLastInitEnterTime; @@ -356,6 +360,22 @@ public class DhcpClient extends StateMachine { } /** + * Get the configuration from RRO to check whether or not to send hostname option in + * DHCPDISCOVER/DHCPREQUEST message. + */ + public boolean getSendHostnameOption(final Context context) { + return context.getResources().getBoolean(R.bool.config_dhcp_client_hostname); + } + + /** + * Get the device name from system settings. + */ + public String getDeviceName(final Context context) { + return Settings.Global.getString(context.getContentResolver(), + Settings.Global.DEVICE_NAME); + } + + /** * Get a IpMemoryStore instance. */ public NetworkStackIpMemoryStore getIpMemoryStore() { @@ -433,6 +453,11 @@ public class DhcpClient extends StateMachine { mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); + + // Transliterate hostname read from system settings if RRO option is enabled. + final boolean sendHostname = deps.getSendHostnameOption(context); + mHostname = sendHostname ? new HostnameTransliterator().transliterate( + deps.getDeviceName(mContext)) : null; } public void registerForPreDhcpNotification() { @@ -624,7 +649,7 @@ public class DhcpClient extends StateMachine { private boolean sendDiscoverPacket() { final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS, isDhcpRapidCommitEnabled()); + DO_UNICAST, REQUESTED_PARAMS, isDhcpRapidCommitEnabled(), mHostname); return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); } @@ -638,7 +663,7 @@ public class DhcpClient extends StateMachine { final ByteBuffer packet = DhcpPacket.buildRequestPacket( encap, mTransactionId, getSecs(), clientAddress, DO_UNICAST, mHwAddr, requestedAddress, - serverAddress, REQUESTED_PARAMS, null); + serverAddress, REQUESTED_PARAMS, mHostname); String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + " request=" + requestedAddress.getHostAddress() + @@ -727,7 +752,7 @@ public class DhcpClient extends StateMachine { mDhcpLease = results; if (mDhcpLease.dnsServers.isEmpty()) { // supplement customized dns servers - String[] dnsServersList = + final String[] dnsServersList = mContext.getResources().getStringArray(R.array.config_default_dns_servers); for (final String dnsServer : dnsServersList) { try { @@ -1242,7 +1267,7 @@ public class DhcpClient extends StateMachine { final Layer2PacketParcelable l2Packet = new Layer2PacketParcelable(); final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS, true /* rapid commit */); + DO_UNICAST, REQUESTED_PARAMS, true /* rapid commit */, mHostname); l2Packet.dstMacAddress = MacAddress.fromBytes(DhcpPacket.ETHER_BROADCAST); l2Packet.payload = packet.array(); diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java index c5700b3..9eca0b5 100644 --- a/src/android/net/dhcp/DhcpPacket.java +++ b/src/android/net/dhcp/DhcpPacket.java @@ -1,3 +1,19 @@ +/* + * 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.dhcp; import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; @@ -15,6 +31,8 @@ import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.networkstack.apishim.ShimUtils; + import java.io.UnsupportedEncodingException; import java.net.Inet4Address; import java.net.UnknownHostException; @@ -350,7 +368,6 @@ public abstract class DhcpPacket { // Set in unit tests, to ensure that the test does not break when run on different devices and // on different releases. static String testOverrideVendorId = null; - static String testOverrideHostname = null; protected DhcpPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp, Inet4Address nextIp, Inet4Address relayIp, @@ -724,9 +741,16 @@ public abstract class DhcpPacket { return "android-dhcp-" + Build.VERSION.RELEASE; } - private String getHostname() { - if (testOverrideHostname != null) return testOverrideHostname; - return SystemProperties.get("net.hostname"); + /** + * Get the DHCP client hostname after transliteration. + */ + @VisibleForTesting + public String getHostname() { + if (mHostName == null + && !ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { + return SystemProperties.get("net.hostname"); + } + return mHostName; } /** @@ -1328,10 +1352,11 @@ public abstract class DhcpPacket { */ public static ByteBuffer buildDiscoverPacket(int encap, int transactionId, short secs, byte[] clientMac, boolean broadcast, byte[] expectedParams, - boolean rapidCommit) { + boolean rapidCommit, String hostname) { DhcpPacket pkt = new DhcpDiscoverPacket(transactionId, secs, INADDR_ANY /* relayIp */, clientMac, broadcast, INADDR_ANY /* srcIp */, rapidCommit); pkt.mRequestedParams = expectedParams; + pkt.mHostName = hostname; return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); } diff --git a/src/android/net/util/HostnameTransliterator.java b/src/android/net/util/HostnameTransliterator.java new file mode 100644 index 0000000..cf126d1 --- /dev/null +++ b/src/android/net/util/HostnameTransliterator.java @@ -0,0 +1,103 @@ +/* + * 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.icu.text.Transliterator; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +/** + * Transliterator to display a human-readable DHCP client hostname. + */ +public class HostnameTransliterator { + private static final String TAG = "HostnameTransliterator"; + + // Maximum length of hostname to be encoded in the DHCP message. Following RFC1035#2.3.4 + // and this transliterator converts the device name to a single label, so the label length + // limit applies to the whole hostname. + private static final int MAX_DNS_LABEL_LENGTH = 63; + + @Nullable + private final Transliterator mTransliterator; + + public HostnameTransliterator() { + final Enumeration<String> availableIDs = Transliterator.getAvailableIDs(); + final Set<String> actualIds = new HashSet<>(Collections.list(availableIDs)); + final StringBuilder rules = new StringBuilder(); + if (actualIds.contains("Any-ASCII")) { + rules.append(":: Any-ASCII; "); + } else if (actualIds.contains("Any-Latin") && actualIds.contains("Latin-ASCII")) { + rules.append(":: Any-Latin; :: Latin-ASCII; "); + } else { + Log.e(TAG, "ICU Transliterator doesn't include supported ID"); + mTransliterator = null; + return; + } + mTransliterator = Transliterator.createFromRules("", rules.toString(), + Transliterator.FORWARD); + } + + @VisibleForTesting + public HostnameTransliterator(Transliterator transliterator) { + mTransliterator = transliterator; + } + + // RFC952 and RFC1123 stipulates an valid hostname should be: + // 1. Only contain the alphabet (A-Z, a-z), digits (0-9), minus sign (-). + // 2. No blank or space characters are permitted as part of a name. + // 3. The first character must be an alpha character or digit. + // 4. The last character must not be a minus sign (-). + private String maybeRemoveRedundantSymbols(@NonNull String string) { + String result = string.replaceAll("[^a-zA-Z0-9-]", "-"); + result = result.replaceAll("-+", "-"); + if (result.startsWith("-")) { + result = result.replaceFirst("-", ""); + } + if (result.endsWith("-")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + /** + * Transliterate the device name to valid hostname that could be human-readable string. + */ + public String transliterate(@NonNull String deviceName) { + if (deviceName == null) return null; + if (mTransliterator == null) { + if (!deviceName.matches("\\p{ASCII}*")) return null; + deviceName = maybeRemoveRedundantSymbols(deviceName); + if (TextUtils.isEmpty(deviceName)) return null; + return deviceName.length() > MAX_DNS_LABEL_LENGTH + ? deviceName.substring(0, MAX_DNS_LABEL_LENGTH) : deviceName; + } + + String hostname = maybeRemoveRedundantSymbols(mTransliterator.transliterate(deviceName)); + if (TextUtils.isEmpty(hostname)) return null; + return hostname.length() > MAX_DNS_LABEL_LENGTH + ? hostname.substring(0, MAX_DNS_LABEL_LENGTH) : hostname; + } +} diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java index 8679a9d..d30b4e9 100644 --- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java +++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java @@ -54,6 +54,7 @@ import static junit.framework.Assert.fail; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.argThat; @@ -212,6 +213,8 @@ public class IpClientIntegrationTest { private static final int TEST_DEFAULT_MTU = 1500; private static final int TEST_MIN_MTU = 1280; private static final byte[] SERVER_MAC = new byte[] { 0x00, 0x1A, 0x11, 0x22, 0x33, 0x44 }; + private static final String TEST_HOST_NAME = "AOSP on Crosshatch"; + private static final String TEST_HOST_NAME_TRANSLITERATION = "AOSP-on-Crosshatch"; private static class TapPacketReader extends PacketReader { private final ParcelFileDescriptor mTapFd; @@ -260,6 +263,8 @@ public class IpClientIntegrationTest { // Can't use SparseIntArray, it doesn't have an easy way to know if a key is not present. private HashMap<String, Integer> mIntConfigProperties = new HashMap<>(); private DhcpClient mDhcpClient; + private boolean mIsHostnameConfigurationEnabled; + private String mHostname; public void setDhcpLeaseCacheEnabled(final boolean enable) { mIsDhcpLeaseCacheEnabled = enable; @@ -273,6 +278,11 @@ public class IpClientIntegrationTest { mIsDhcpIpConflictDetectEnabled = enable; } + public void setHostnameConfiguration(final boolean enable, final String hostname) { + mIsHostnameConfigurationEnabled = enable; + mHostname = hostname; + } + @Override public INetd getNetd(Context context) { return mNetd; @@ -320,6 +330,16 @@ public class IpClientIntegrationTest { public PowerManager.WakeLock getWakeLock(final PowerManager powerManager) { return mTimeoutWakeLock; } + + @Override + public boolean getSendHostnameOption(final Context context) { + return mIsHostnameConfigurationEnabled; + } + + @Override + public String getDeviceName(final Context context) { + return mIsHostnameConfigurationEnabled ? mHostname : null; + } }; } @@ -516,7 +536,9 @@ public class IpClientIntegrationTest { private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled, final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled, - final boolean isDhcpIpConflictDetectEnabled) throws RemoteException { + final boolean isDhcpIpConflictDetectEnabled, + final boolean isHostnameConfigurationEnabled, final String hostname) + throws RemoteException { ProvisioningConfiguration.Builder builder = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withoutIPv6(); @@ -525,6 +547,7 @@ public class IpClientIntegrationTest { mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled); mDependencies.setDhcpRapidCommitEnabled(shouldReplyRapidCommitAck); mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled); + mDependencies.setHostnameConfiguration(isHostnameConfigurationEnabled, hostname); mIpc.setL2KeyAndGroupHint(TEST_L2KEY, TEST_GROUPHINT); mIpc.startProvisioning(builder.build()); verify(mCb).setNeighborDiscoveryOffload(true); @@ -534,6 +557,15 @@ public class IpClientIntegrationTest { verify(mCb, never()).onProvisioningFailure(any()); } + private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled, + final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled, + final boolean isDhcpIpConflictDetectEnabled) + throws RemoteException { + startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled, + isPreconnectionEnabled, isDhcpIpConflictDetectEnabled, + false /* isHostnameConfigurationEnabled */, null /* hostname */); + } + private void assertIpMemoryStoreNetworkAttributes(final Integer leaseTimeSec, final long startTime, final int mtu) { final ArgumentCaptor<NetworkAttributes> networkAttributes = @@ -561,16 +593,33 @@ public class IpClientIntegrationTest { verify(mIpMemoryStore, never()).storeNetworkAttributes(any(), any(), any()); } + private void assertHostname(final boolean isHostnameConfigurationEnabled, + final String hostname, final String hostnameAfterTransliteration, + final List<DhcpPacket> packetList) throws Exception { + for (DhcpPacket packet : packetList) { + if (!isHostnameConfigurationEnabled || hostname == null) { + assertNull(packet.getHostname()); + } else { + assertEquals(packet.getHostname(), hostnameAfterTransliteration); + } + } + } + // Helper method to complete DHCP 2-way or 4-way handshake - private void performDhcpHandshake(final boolean isSuccessLease, + private List<DhcpPacket> performDhcpHandshake(final boolean isSuccessLease, final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled, final boolean shouldReplyRapidCommitAck, final int mtu, - final boolean isDhcpIpConflictDetectEnabled) throws Exception { + final boolean isDhcpIpConflictDetectEnabled, + final boolean isHostnameConfigurationEnabled, final String hostname) + throws Exception { + final List<DhcpPacket> packetList = new ArrayList<DhcpPacket>(); startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck, - false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled); + false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled, + isHostnameConfigurationEnabled, hostname); DhcpPacket packet; while ((packet = getNextDhcpPacket()) != null) { + packetList.add(packet); if (packet instanceof DhcpDiscoverPacket) { if (shouldReplyRapidCommitAck) { sendResponse(buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu, @@ -588,9 +637,21 @@ public class IpClientIntegrationTest { fail("invalid DHCP packet"); } // wait for reply to DHCPOFFER packet if disabling rapid commit option - if (shouldReplyRapidCommitAck || !(packet instanceof DhcpDiscoverPacket)) return; + if (shouldReplyRapidCommitAck || !(packet instanceof DhcpDiscoverPacket)) { + return packetList; + } } fail("No DHCPREQUEST received on interface"); + return packetList; + } + + private List<DhcpPacket> performDhcpHandshake(final boolean isSuccessLease, + final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled, + final boolean isDhcpRapidCommitEnabled, final int mtu, + final boolean isDhcpIpConflictDetectEnabled) throws Exception { + return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled, + isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled, + false /* isHostnameConfigurationEnabled */, null /* hostname */); } private DhcpPacket getNextDhcpPacket() throws ParseException { @@ -1346,4 +1407,44 @@ public class IpClientIntegrationTest { true /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */, false /* shouldResponseArpReply */); } + + @Test + public void testHostname_enableConfig() throws Exception { + final long currentTime = System.currentTimeMillis(); + final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */, + TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, + false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + false /* isDhcpIpConflictDetectEnabled */, + true /* isHostnameConfigurationEnabled */, TEST_HOST_NAME /* hostname */); + assertEquals(2, sentPackets.size()); + assertHostname(true, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets); + assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); + } + + @Test + public void testHostname_disableConfig() throws Exception { + final long currentTime = System.currentTimeMillis(); + final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */, + TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, + false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + false /* isDhcpIpConflictDetectEnabled */, + false /* isHostnameConfigurationEnabled */, TEST_HOST_NAME); + assertEquals(2, sentPackets.size()); + assertHostname(false, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets); + assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); + } + + @Test + public void testHostname_enableConfigWithNullHostname() throws Exception { + final long currentTime = System.currentTimeMillis(); + final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */, + TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, + false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + false /* isDhcpIpConflictDetectEnabled */, + true /* isHostnameConfigurationEnabled */, null /* hostname */); + assertEquals(2, sentPackets.size()); + assertHostname(true, null /* hostname */, null /* hostnameAfterTransliteration */, + sentPackets); + assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); + } } diff --git a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java index fcaf655..090631b 100644 --- a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java +++ b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java @@ -92,7 +92,6 @@ public class DhcpPacketTest { @Before public void setUp() { DhcpPacket.testOverrideVendorId = "android-dhcp-???"; - DhcpPacket.testOverrideHostname = "android-01234567890abcde"; } class TestDhcpPacket extends DhcpPacket { @@ -923,17 +922,19 @@ public class DhcpPacketTest { @Test public void testDiscoverPacket() throws Exception { - short secs = 7; - int transactionId = 0xdeadbeef; - byte[] hwaddr = { + final short secs = 7; + final int transactionId = 0xdeadbeef; + final byte[] hwaddr = { (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a }; + final String testHostname = "android-01234567890abcde"; ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr, - false /* do unicast */, DhcpClient.REQUESTED_PARAMS, false /* rapid commit */); + false /* do unicast */, DhcpClient.REQUESTED_PARAMS, false /* rapid commit */, + testHostname); - byte[] headers = new byte[] { + final byte[] headers = new byte[] { // Ethernet header. (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a, @@ -958,7 +959,7 @@ public class DhcpPacketTest { (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a }; - byte[] options = new byte[] { + final byte[] options = new byte[] { // Magic cookie 0x63825363. (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63, // Message type DISCOVER. @@ -993,16 +994,17 @@ public class DhcpPacketTest { // Our packets are always of even length. TODO: find out why and possibly fix it. (byte) 0x00 }; - byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length]; + final byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length]; assertTrue((expected.length & 1) == 0); + assertEquals(DhcpPacket.MIN_PACKET_LENGTH_L2, + headers.length + 10 /* client hw addr padding */ + 64 /* sname */ + 128 /* file */); System.arraycopy(headers, 0, expected, 0, headers.length); System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length); - byte[] actual = new byte[packet.limit()]; + final byte[] actual = new byte[packet.limit()]; packet.get(actual); - String msg = - "Expected:\n " + Arrays.toString(expected) + - "\nActual:\n " + Arrays.toString(actual); + String msg = "Expected:\n " + Arrays.toString(expected) + "\nActual:\n " + + Arrays.toString(actual); assertTrue(msg, Arrays.equals(expected, actual)); } diff --git a/tests/unit/src/android/net/util/HostnameTransliteratorTest.java b/tests/unit/src/android/net/util/HostnameTransliteratorTest.java new file mode 100644 index 0000000..e6c43d1 --- /dev/null +++ b/tests/unit/src/android/net/util/HostnameTransliteratorTest.java @@ -0,0 +1,137 @@ +/* + * 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.assertNotNull; +import static org.junit.Assert.assertNull; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.charset.StandardCharsets; + +/** + * Tests for HostnameTransliterator class. + * + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class HostnameTransliteratorTest { + private static final String TEST_HOST_NAME_EN = "AOSP on Crosshatch"; + private static final String TEST_HOST_NAME_EN_TRANSLITERATION = "AOSP-on-Crosshatch"; + private static final String TEST_HOST_NAME_JP = "AOSP on ほげほげ"; + private static final String TEST_HOST_NAME_JP_TRANSLITERATION = "AOSP-on-hogehoge"; + private static final String TEST_HOST_NAME_CN = "AOSP on 安卓"; + private static final String TEST_HOST_NAME_CN_TRANSLITERATION = "AOSP-on-an-zhuo"; + private static final String TEST_HOST_NAME_UNICODE = "AOSP on àéö→ūçŗ̊œ"; + private static final String TEST_HOST_NAME_UNICODE_TRANSLITERATION = "AOSP-on-aeo-ucroe"; + private static final String TEST_HOST_NAME_LONG = + "AOSP on Ccccccrrrrrroooooosssssshhhhhhaaaaaattttttcccccchhhhhh3abcd"; + private static final String TEST_HOST_NAME_LONG_TRANSLITERATION = + "AOSP-on-Ccccccrrrrrroooooosssssshhhhhhaaaaaattttttcccccchhhhhh3"; + private static final String TEST_HOST_NAME_LONG_TRUNCATED = + "AOSP-on-Ccccccrrrrrroooooosssssshhhhhhaaaaaattttttcccccchhhhhh3"; + + @NonNull + private HostnameTransliterator mTransliterator; + + @Before + public void setUp() throws Exception { + mTransliterator = new HostnameTransliterator(); + assertNotNull(mTransliterator); + } + + @Test + public void testNullHostname() { + assertNull(mTransliterator.transliterate(null)); + } + + private void assertHostnameTransliteration(final String hostnameAftertransliteration, + final String hostname) { + assertEquals(hostnameAftertransliteration, mTransliterator.transliterate(hostname)); + } + + @Test + public void testEmptyHostname() { + assertHostnameTransliteration(null, ""); + } + + @Test + public void testHostnameOnlyTabs() { + assertHostnameTransliteration(null, "\t\t"); + } + + @Test + public void testHostnameOnlySpaces() { + assertHostnameTransliteration(null, " "); + } + + @Test + public void testHostnameOnlyUnsupportedAsciiSymbols() { + final String symbol = new String(new byte[] { 0x00, 0x1b /* ESC */, 0x7f /* DEL */, + 0x10 /* backspace */}, StandardCharsets.US_ASCII); + assertHostnameTransliteration(null, symbol); + } + + @Test + public void testHostnameMixedAsciiSymbols() { + final String symbol = new String(new byte[] { 0x00, 'a', 0x1b /* ESC */, 0x7f /* DEL */, + 'b', 0x10 /* backspace */}, StandardCharsets.US_ASCII); + assertHostnameTransliteration("a-b", symbol); + } + + @Test + public void testHostnames() { + assertHostnameTransliteration(TEST_HOST_NAME_EN_TRANSLITERATION, TEST_HOST_NAME_EN); + assertHostnameTransliteration(TEST_HOST_NAME_JP_TRANSLITERATION, TEST_HOST_NAME_JP); + assertHostnameTransliteration(TEST_HOST_NAME_CN_TRANSLITERATION, TEST_HOST_NAME_CN); + assertHostnameTransliteration(TEST_HOST_NAME_UNICODE_TRANSLITERATION, + TEST_HOST_NAME_UNICODE); + } + + @Test + public void testHostnameWithMinusSign() { + assertHostnameTransliteration(TEST_HOST_NAME_EN_TRANSLITERATION, "-AOSP on Crosshatch"); + assertHostnameTransliteration(TEST_HOST_NAME_EN_TRANSLITERATION, "AOSP on Crosshatch-"); + assertHostnameTransliteration(TEST_HOST_NAME_EN_TRANSLITERATION, "---AOSP on Crosshatch"); + assertHostnameTransliteration(TEST_HOST_NAME_EN_TRANSLITERATION, "AOSP on Crosshatch---"); + assertHostnameTransliteration(TEST_HOST_NAME_EN_TRANSLITERATION, "AOSP---on---Crosshatch"); + } + + @Test + public void testLongHostname() { + assertHostnameTransliteration(TEST_HOST_NAME_LONG_TRANSLITERATION, TEST_HOST_NAME_LONG); + } + + @Test + public void testNonAsciiHostname() { + mTransliterator = new HostnameTransliterator(null); + assertHostnameTransliteration(null, TEST_HOST_NAME_UNICODE); + } + + @Test + public void testAsciiLongHostname() { + mTransliterator = new HostnameTransliterator(null); + assertHostnameTransliteration(TEST_HOST_NAME_LONG_TRUNCATED, TEST_HOST_NAME_LONG); + } +} |