1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.nsd; 18 19 import static org.junit.Assert.assertArrayEquals; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertFalse; 22 import static org.junit.Assert.assertThrows; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assert.fail; 25 26 import android.net.InetAddresses; 27 import android.net.Network; 28 import android.os.Build; 29 import android.os.Bundle; 30 import android.os.Parcel; 31 32 import androidx.test.filters.SmallTest; 33 34 import com.android.testutils.ConnectivityModuleTest; 35 import com.android.testutils.DevSdkIgnoreRule; 36 import com.android.testutils.DevSdkIgnoreRunner; 37 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.net.InetAddress; 42 import java.util.Arrays; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Set; 46 47 @RunWith(DevSdkIgnoreRunner.class) 48 @SmallTest 49 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2) 50 @ConnectivityModuleTest 51 public class NsdServiceInfoTest { 52 53 private static final InetAddress IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1"); 54 private static final InetAddress IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8::"); 55 private static final byte[] PUBLIC_KEY_RDATA = new byte[] { 56 (byte) 0x02, (byte)0x01, // flag 57 (byte) 0x03, // protocol 58 (byte) 0x0d, // algorithm 59 // 64-byte public key below 60 (byte) 0xC1, (byte) 0x41, (byte) 0xD0, (byte) 0x63, (byte) 0x79, (byte) 0x60, 61 (byte) 0xB9, (byte) 0x8C, (byte) 0xBC, (byte) 0x12, (byte) 0xCF, (byte) 0xCA, 62 (byte) 0x22, (byte) 0x1D, (byte) 0x28, (byte) 0x79, (byte) 0xDA, (byte) 0xC2, 63 (byte) 0x6E, (byte) 0xE5, (byte) 0xB4, (byte) 0x60, (byte) 0xE9, (byte) 0x00, 64 (byte) 0x7C, (byte) 0x99, (byte) 0x2E, (byte) 0x19, (byte) 0x02, (byte) 0xD8, 65 (byte) 0x97, (byte) 0xC3, (byte) 0x91, (byte) 0xB0, (byte) 0x37, (byte) 0x64, 66 (byte) 0xD4, (byte) 0x48, (byte) 0xF7, (byte) 0xD0, (byte) 0xC7, (byte) 0x72, 67 (byte) 0xFD, (byte) 0xB0, (byte) 0x3B, (byte) 0x1D, (byte) 0x9D, (byte) 0x6D, 68 (byte) 0x52, (byte) 0xFF, (byte) 0x88, (byte) 0x86, (byte) 0x76, (byte) 0x9E, 69 (byte) 0x8E, (byte) 0x23, (byte) 0x62, (byte) 0x51, (byte) 0x35, (byte) 0x65, 70 (byte) 0x27, (byte) 0x09, (byte) 0x62, (byte) 0xD3 71 }; 72 73 @Test testLimits()74 public void testLimits() throws Exception { 75 NsdServiceInfo info = new NsdServiceInfo(); 76 77 // Non-ASCII keys. 78 boolean exceptionThrown = false; 79 try { 80 info.setAttribute("猫", "meow"); 81 } catch (IllegalArgumentException e) { 82 exceptionThrown = true; 83 } 84 assertTrue(exceptionThrown); 85 assertEmptyServiceInfo(info); 86 87 // ASCII keys with '=' character. 88 exceptionThrown = false; 89 try { 90 info.setAttribute("kitten=", "meow"); 91 } catch (IllegalArgumentException e) { 92 exceptionThrown = true; 93 } 94 assertTrue(exceptionThrown); 95 assertEmptyServiceInfo(info); 96 97 // Single key + value length too long. 98 exceptionThrown = false; 99 try { 100 String longValue = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" 101 + "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" 102 + "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" 103 + "ooooooooooooooooooooooooooooong"; // 248 characters. 104 info.setAttribute("longcat", longValue); // Key + value == 255 characters. 105 } catch (IllegalArgumentException e) { 106 exceptionThrown = true; 107 } 108 assertTrue(exceptionThrown); 109 assertEmptyServiceInfo(info); 110 111 // Total TXT record length too long. 112 exceptionThrown = false; 113 int recordsAdded = 0; 114 try { 115 for (int i = 100; i < 300; ++i) { 116 // 6 char key + 5 char value + 2 bytes overhead = 13 byte record length. 117 String key = String.format("key%d", i); 118 info.setAttribute(key, "12345"); 119 recordsAdded++; 120 } 121 } catch (IllegalArgumentException e) { 122 exceptionThrown = true; 123 } 124 assertTrue(exceptionThrown); 125 assertTrue(100 == recordsAdded); 126 assertTrue(info.getTxtRecord().length == 1300); 127 } 128 129 @Test testParcel()130 public void testParcel() throws Exception { 131 NsdServiceInfo emptyInfo = new NsdServiceInfo(); 132 checkParcelable(emptyInfo); 133 134 NsdServiceInfo fullInfo = new NsdServiceInfo(); 135 fullInfo.setServiceName("kitten"); 136 fullInfo.setServiceType("_kitten._tcp"); 137 fullInfo.setSubtypes(Set.of("_thread", "_matter")); 138 fullInfo.setPort(4242); 139 fullInfo.setHostAddresses(List.of(IPV4_ADDRESS)); 140 fullInfo.setHostname("home"); 141 fullInfo.setPublicKey(PUBLIC_KEY_RDATA); 142 fullInfo.setNetwork(new Network(123)); 143 fullInfo.setInterfaceIndex(456); 144 checkParcelable(fullInfo); 145 146 NsdServiceInfo noHostInfo = new NsdServiceInfo(); 147 noHostInfo.setServiceName("kitten"); 148 noHostInfo.setServiceType("_kitten._tcp"); 149 noHostInfo.setPort(4242); 150 checkParcelable(noHostInfo); 151 152 NsdServiceInfo attributedInfo = new NsdServiceInfo(); 153 attributedInfo.setServiceName("kitten"); 154 attributedInfo.setServiceType("_kitten._tcp"); 155 attributedInfo.setPort(4242); 156 attributedInfo.setHostAddresses(List.of(IPV6_ADDRESS, IPV4_ADDRESS)); 157 attributedInfo.setHostname("home"); 158 attributedInfo.setPublicKey(PUBLIC_KEY_RDATA); 159 attributedInfo.setAttribute("color", "pink"); 160 attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8")); 161 attributedInfo.setAttribute("adorable", (String) null); 162 attributedInfo.setAttribute("sticky", "yes"); 163 attributedInfo.setAttribute("siblings", new byte[] {}); 164 attributedInfo.setAttribute("edge cases", new byte[] {0, -1, 127, -128}); 165 attributedInfo.removeAttribute("sticky"); 166 checkParcelable(attributedInfo); 167 168 // Sanity check that we actually wrote attributes to attributedInfo. 169 assertTrue(attributedInfo.getAttributes().keySet().contains("adorable")); 170 String sound = new String(attributedInfo.getAttributes().get("sound"), "UTF-8"); 171 assertTrue(sound.equals("にゃあ")); 172 byte[] edgeCases = attributedInfo.getAttributes().get("edge cases"); 173 assertTrue(Arrays.equals(edgeCases, new byte[] {0, -1, 127, -128})); 174 assertFalse(attributedInfo.getAttributes().keySet().contains("sticky")); 175 } 176 checkParcelable(NsdServiceInfo original)177 private static void checkParcelable(NsdServiceInfo original) { 178 // Write to parcel. 179 Parcel p = Parcel.obtain(); 180 Bundle writer = new Bundle(); 181 writer.putParcelable("test_info", original); 182 writer.writeToParcel(p, 0); 183 184 // Extract from parcel. 185 p.setDataPosition(0); 186 Bundle reader = p.readBundle(); 187 reader.setClassLoader(NsdServiceInfo.class.getClassLoader()); 188 NsdServiceInfo result = reader.getParcelable("test_info"); 189 190 // Assert equality of base fields. 191 assertEquals(original.getServiceName(), result.getServiceName()); 192 assertEquals(original.getServiceType(), result.getServiceType()); 193 assertEquals(original.getHost(), result.getHost()); 194 assertEquals(original.getHostname(), result.getHostname()); 195 assertArrayEquals(original.getPublicKey(), result.getPublicKey()); 196 assertTrue(original.getPort() == result.getPort()); 197 assertEquals(original.getNetwork(), result.getNetwork()); 198 assertEquals(original.getInterfaceIndex(), result.getInterfaceIndex()); 199 200 // Assert equality of attribute map. 201 Map<String, byte[]> originalMap = original.getAttributes(); 202 Map<String, byte[]> resultMap = result.getAttributes(); 203 assertEquals(originalMap.keySet(), resultMap.keySet()); 204 for (String key : originalMap.keySet()) { 205 assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key))); 206 } 207 } 208 assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty)209 private static void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) { 210 byte[] txtRecord = shouldBeEmpty.getTxtRecord(); 211 if (txtRecord == null || txtRecord.length == 0) { 212 return; 213 } 214 fail("NsdServiceInfo.getTxtRecord did not return null but " + Arrays.toString(txtRecord)); 215 } 216 217 @Test testSubtypesValidSubtypesSuccess()218 public void testSubtypesValidSubtypesSuccess() { 219 NsdServiceInfo info = new NsdServiceInfo(); 220 221 info.setSubtypes(Set.of("_thread", "_matter")); 222 223 assertEquals(Set.of("_thread", "_matter"), info.getSubtypes()); 224 } 225 } 226