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