• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.server.connectivity.mdns;
18 
19 import static com.android.server.connectivity.mdns.MdnsConstants.QCLASS_INTERNET;
20 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
21 
22 import static org.junit.Assert.assertArrayEquals;
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotEquals;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertNull;
28 import static org.junit.Assert.assertThrows;
29 import static org.junit.Assert.assertTrue;
30 
31 import static java.util.Collections.emptyList;
32 
33 import android.util.Log;
34 
35 import com.android.net.module.util.HexDump;
36 import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
37 import com.android.testutils.DevSdkIgnoreRule;
38 import com.android.testutils.DevSdkIgnoreRunner;
39 
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 
43 import java.io.EOFException;
44 import java.io.IOException;
45 import java.net.DatagramPacket;
46 import java.net.Inet4Address;
47 import java.net.Inet6Address;
48 import java.net.InetSocketAddress;
49 import java.util.List;
50 
51 // The record test data does not use compressed names (label pointers), since that would require
52 // additional data to populate the label dictionary accordingly.
53 @RunWith(DevSdkIgnoreRunner.class)
54 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
55 public class MdnsRecordTests {
56     private static final String TAG = "MdnsRecordTests";
57     private static final int MAX_PACKET_SIZE = 4096;
58     private static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
59             new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
60     private static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
61             new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
62 
63     @Test
testInet4AddressRecord()64     public void testInet4AddressRecord() throws IOException {
65         final byte[] dataIn = HexDump.hexStringToByteArray(
66                 "0474657374000001" + "0001000011940004" + "0A010203");
67         assertNotNull(dataIn);
68         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
69 
70         // Decode
71         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
72         MdnsPacketReader reader = new MdnsPacketReader(packet);
73 
74         String[] name = reader.readLabels();
75         assertNotNull(name);
76         assertEquals(1, name.length);
77         assertEquals("test", name[0]);
78         String fqdn = MdnsRecord.labelsToString(name);
79         assertEquals("test", fqdn);
80 
81         int type = reader.readUInt16();
82         assertEquals(MdnsRecord.TYPE_A, type);
83 
84         MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
85         Inet4Address addr = record.getInet4Address();
86         assertEquals("/10.1.2.3", addr.toString());
87 
88         String dataOutText = toHex(record);
89         Log.d(TAG, dataOutText);
90 
91         assertEquals(dataInText, dataOutText);
92     }
93 
94     @Test
testTypeAAAInet6AddressRecord()95     public void testTypeAAAInet6AddressRecord() throws IOException {
96         final byte[] dataIn = HexDump.hexStringToByteArray(
97                 "047465737400001C"
98                         + "0001000011940010"
99                         + "AABBCCDD11223344"
100                         + "A0B0C0D010203040");
101         assertNotNull(dataIn);
102         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
103 
104         // Decode
105         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
106         packet.setSocketAddress(
107                 new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
108         MdnsPacketReader reader = new MdnsPacketReader(packet);
109 
110         String[] name = reader.readLabels();
111         assertNotNull(name);
112         assertEquals(1, name.length);
113         String fqdn = MdnsRecord.labelsToString(name);
114         assertEquals("test", fqdn);
115 
116         int type = reader.readUInt16();
117         assertEquals(MdnsRecord.TYPE_AAAA, type);
118 
119         MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA,
120                 reader);
121         assertNull(record.getInet4Address());
122         Inet6Address addr = record.getInet6Address();
123         assertEquals("/aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040", addr.toString());
124 
125         String dataOutText = toHex(record);
126         Log.d(TAG, dataOutText);
127 
128         assertEquals(dataInText, dataOutText);
129     }
130 
131     @Test
testTypeAAAInet4AddressRecord()132     public void testTypeAAAInet4AddressRecord() throws IOException {
133         final byte[] dataIn = HexDump.hexStringToByteArray(
134                 "047465737400001C"
135                         + "0001000011940010"
136                         + "0000000000000000"
137                         + "0000FFFF10203040");
138         assertNotNull(dataIn);
139         HexDump.dumpHexString(dataIn, 0, dataIn.length);
140 
141         // Decode
142         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
143         packet.setSocketAddress(
144                 new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
145         MdnsPacketReader reader = new MdnsPacketReader(packet);
146 
147         String[] name = reader.readLabels();
148         assertNotNull(name);
149         assertEquals(1, name.length);
150         String fqdn = MdnsRecord.labelsToString(name);
151         assertEquals("test", fqdn);
152 
153         int type = reader.readUInt16();
154         assertEquals(MdnsRecord.TYPE_AAAA, type);
155 
156         MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA,
157                 reader);
158         assertNull(record.getInet6Address());
159         Inet4Address addr = record.getInet4Address();
160         assertEquals("/16.32.48.64", addr.toString());
161 
162         String dataOutText = toHex(record);
163         Log.d(TAG, dataOutText);
164 
165         final byte[] expectedDataIn =
166                 HexDump.hexStringToByteArray("047465737400001C000100001194000410203040");
167         assertNotNull(expectedDataIn);
168         String expectedDataInText = HexDump.dumpHexString(expectedDataIn, 0, expectedDataIn.length);
169 
170         assertEquals(expectedDataInText, dataOutText);
171     }
172 
173     @Test
testPointerRecord()174     public void testPointerRecord() throws IOException {
175         final byte[] dataIn = HexDump.hexStringToByteArray(
176                 "047465737400000C"
177                         + "000100001194000E"
178                         + "03666F6F03626172"
179                         + "047175787800");
180         assertNotNull(dataIn);
181         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
182 
183         // Decode
184         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
185         MdnsPacketReader reader = new MdnsPacketReader(packet);
186 
187         String[] name = reader.readLabels();
188         assertNotNull(name);
189         assertEquals(1, name.length);
190         String fqdn = MdnsRecord.labelsToString(name);
191         assertEquals("test", fqdn);
192 
193         int type = reader.readUInt16();
194         assertEquals(MdnsRecord.TYPE_PTR, type);
195 
196         MdnsPointerRecord record = new MdnsPointerRecord(name, reader);
197         String[] pointer = record.getPointer();
198         assertEquals("foo.bar.quxx", MdnsRecord.labelsToString(pointer));
199 
200         assertFalse(record.hasSubtype());
201         assertNull(record.getSubtype());
202 
203         String dataOutText = toHex(record);
204         Log.d(TAG, dataOutText);
205 
206         assertEquals(dataInText, dataOutText);
207     }
208 
209     @Test
testServiceRecord()210     public void testServiceRecord() throws IOException {
211         final byte[] dataIn = HexDump.hexStringToByteArray(
212                 "0474657374000021"
213                         + "0001000011940014"
214                         + "000100FF1F480366"
215                         + "6F6F036261720471"
216                         + "75787800");
217         assertNotNull(dataIn);
218         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
219 
220         // Decode
221         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
222         MdnsPacketReader reader = new MdnsPacketReader(packet);
223 
224         String[] name = reader.readLabels();
225         assertNotNull(name);
226         assertEquals(1, name.length);
227         String fqdn = MdnsRecord.labelsToString(name);
228         assertEquals("test", fqdn);
229 
230         int type = reader.readUInt16();
231         assertEquals(MdnsRecord.TYPE_SRV, type);
232 
233         MdnsServiceRecord record = new MdnsServiceRecord(name, reader);
234 
235         int servicePort = record.getServicePort();
236         assertEquals(8008, servicePort);
237 
238         String serviceHost = MdnsRecord.labelsToString(record.getServiceHost());
239         assertEquals("foo.bar.quxx", serviceHost);
240 
241         assertEquals(1, record.getServicePriority());
242         assertEquals(255, record.getServiceWeight());
243 
244         String dataOutText = toHex(record);
245         Log.d(TAG, dataOutText);
246 
247         assertEquals(dataInText, dataOutText);
248     }
249 
250     @Test
testAnyRecord()251     public void testAnyRecord() throws IOException {
252         final byte[] dataIn = HexDump.hexStringToByteArray(
253                 "047465737407616E64726F696403636F6D0000FF0001000000000000");
254         assertNotNull(dataIn);
255         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
256 
257         // Decode
258         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
259         MdnsPacketReader reader = new MdnsPacketReader(packet);
260 
261         String[] name = reader.readLabels();
262         assertNotNull(name);
263         assertEquals(3, name.length);
264         String fqdn = MdnsRecord.labelsToString(name);
265         assertEquals("test.android.com", fqdn);
266 
267         int type = reader.readUInt16();
268         assertEquals(MdnsRecord.TYPE_ANY, type);
269 
270         MdnsAnyRecord record = new MdnsAnyRecord(name, reader);
271 
272         String dataOutText = toHex(record);
273         Log.d(TAG, dataOutText);
274 
275         assertEquals(dataInText, dataOutText);
276     }
277 
278     @Test
testNsecRecord()279     public void testNsecRecord() throws IOException {
280         final byte[] dataIn = HexDump.hexStringToByteArray(
281                 // record.android.com
282                 "067265636F726407616E64726F696403636F6D00"
283                         // Type 0x002f (NSEC), cache flush set on class IN (0x8001)
284                         + "002F8001"
285                         // TTL 0x0000003c (60 secs)
286                         + "0000003C"
287                         // Data length
288                         + "0031"
289                         // nextdomain.android.com, with compression for android.com
290                         + "0A6E657874646F6D61696EC007"
291                         // Type bitmaps: window block 0x00, bitmap length 0x05,
292                         // bits 16 (TXT) and 33 (SRV) set: 0x0000800040
293                         + "00050000800040"
294                         // For 1234, 4*256 + 210 = 1234, so window block 0x04, bitmap length 27/0x1B
295                         // (26*8 + 2 = 210, need 27 bytes to set bit 210),
296                         // bit 2 set on byte 27 (0x20).
297                         + "041B000000000000000000000000000000000000000000000000000020");
298         assertNotNull(dataIn);
299         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
300 
301         // Decode
302         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
303         MdnsPacketReader reader = new MdnsPacketReader(packet);
304 
305         String[] name = reader.readLabels();
306         assertNotNull(name);
307         assertEquals(3, name.length);
308         String fqdn = MdnsRecord.labelsToString(name);
309         assertEquals("record.android.com", fqdn);
310 
311         int type = reader.readUInt16();
312         assertEquals(MdnsRecord.TYPE_NSEC, type);
313 
314         MdnsNsecRecord record = new MdnsNsecRecord(name, reader);
315         assertTrue(record.getCacheFlush());
316         assertEquals(60_000L, record.getTtl());
317         assertEquals("nextdomain.android.com", MdnsRecord.labelsToString(record.getNextDomain()));
318         assertArrayEquals(new int[] { MdnsRecord.TYPE_TXT,
319                 MdnsRecord.TYPE_SRV,
320                 // Non-existing record type, > 256
321                 1234 }, record.getTypes());
322 
323         String dataOutText = toHex(record);
324         assertEquals(dataInText, dataOutText);
325     }
326 
327     @Test
testTextRecord()328     public void testTextRecord() throws IOException {
329         final byte[] dataIn = HexDump.hexStringToByteArray(
330                 "0474657374000010"
331                         + "0001000011940024"
332                         + "0D613D68656C6C6F"
333                         + "2074686572650C62"
334                         + "3D31323334353637"
335                         + "3839300878797A3D"
336                         + "21402324");
337         assertNotNull(dataIn);
338         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
339 
340         // Decode
341         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
342         MdnsPacketReader reader = new MdnsPacketReader(packet);
343 
344         String[] name = reader.readLabels();
345         assertNotNull(name);
346         assertEquals(1, name.length);
347         String fqdn = MdnsRecord.labelsToString(name);
348         assertEquals("test", fqdn);
349 
350         int type = reader.readUInt16();
351         assertEquals(MdnsRecord.TYPE_TXT, type);
352 
353         MdnsTextRecord record = new MdnsTextRecord(name, reader);
354 
355         List<String> strings = record.getStrings();
356         assertNotNull(strings);
357         assertEquals(3, strings.size());
358 
359         assertEquals("a=hello there", strings.get(0));
360         assertEquals("b=1234567890", strings.get(1));
361         assertEquals("xyz=!@#$", strings.get(2));
362 
363         List<TextEntry> entries = record.getEntries();
364         assertNotNull(entries);
365         assertEquals(3, entries.size());
366 
367         assertEquals(new TextEntry("a", "hello there"), entries.get(0));
368         assertEquals(new TextEntry("b", "1234567890"), entries.get(1));
369         assertEquals(new TextEntry("xyz", "!@#$"), entries.get(2));
370 
371         String dataOutText = toHex(record);
372         Log.d(TAG, dataOutText);
373 
374         assertEquals(dataInText, dataOutText);
375     }
376 
makeTextRecordWithEntries(List<TextEntry> entries)377     private static MdnsTextRecord makeTextRecordWithEntries(List<TextEntry> entries) {
378         return new MdnsTextRecord(new String[] { "test", "record" }, 0L /* receiptTimeMillis */,
379                 true /* cacheFlush */, 120_000L /* ttlMillis */, entries);
380     }
381 
382     @Test
testTextRecord_EmptyRecordsAreEquivalent()383     public void testTextRecord_EmptyRecordsAreEquivalent() {
384         final MdnsTextRecord record1 = makeTextRecordWithEntries(emptyList());
385         final MdnsTextRecord record2 = makeTextRecordWithEntries(
386                 List.of(new TextEntry("", (byte[]) null)));
387         final MdnsTextRecord record3 = makeTextRecordWithEntries(
388                 List.of(new TextEntry(null, (byte[]) null)));
389         final MdnsTextRecord nonEmptyRecord = makeTextRecordWithEntries(
390                 List.of(new TextEntry("a", (byte[]) null)));
391 
392         assertEquals(record1, record1);
393         assertEquals(record1, record2);
394         assertEquals(record1, record3);
395 
396         assertNotEquals(nonEmptyRecord, record1);
397         assertNotEquals(nonEmptyRecord, record2);
398         assertNotEquals(nonEmptyRecord, record3);
399     }
400 
toHex(MdnsRecord record)401     private static String toHex(MdnsRecord record) throws IOException {
402         MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
403         record.write(writer, record.getReceiptTime());
404 
405         // The address does not matter as only the data is used
406         final DatagramPacket packet = writer.getPacket(MULTICAST_IPV4_ADDRESS);
407         final byte[] dataOut = packet.getData();
408 
409         return HexDump.dumpHexString(dataOut, 0, packet.getLength());
410     }
411 
412     @Test
textRecord_recordDoesNotHaveDataOfGivenLength_throwsEOFException()413     public void textRecord_recordDoesNotHaveDataOfGivenLength_throwsEOFException()
414             throws Exception {
415         final byte[] dataIn = HexDump.hexStringToByteArray(
416                 "0474657374000010"
417                         + "000100001194000D"
418                         + "0D613D68656C6C6F" //The TXT entry starts with length of 13, but only 12
419                         + "2074686572"); // characters are following it.
420         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
421         MdnsPacketReader reader = new MdnsPacketReader(packet);
422         String[] name = reader.readLabels();
423         MdnsRecord.labelsToString(name);
424         reader.readUInt16();
425 
426         assertThrows(EOFException.class, () -> new MdnsTextRecord(name, reader));
427     }
428 
429     @Test
textRecord_entriesIncludeNonUtf8Bytes_returnsTheSameUtf8Bytes()430     public void textRecord_entriesIncludeNonUtf8Bytes_returnsTheSameUtf8Bytes() throws Exception {
431         final byte[] dataIn = HexDump.hexStringToByteArray(
432                 "0474657374000010"
433                         + "0001000011940024"
434                         + "0D613D68656C6C6F"
435                         + "2074686572650C62"
436                         + "3D31323334353637"
437                         + "3839300878797A3D"
438                         + "FFEFDFCF");
439         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
440         MdnsPacketReader reader = new MdnsPacketReader(packet);
441         String[] name = reader.readLabels();
442         MdnsRecord.labelsToString(name);
443         reader.readUInt16();
444 
445         MdnsTextRecord record = new MdnsTextRecord(name, reader);
446 
447         List<TextEntry> entries = record.getEntries();
448         assertNotNull(entries);
449         assertEquals(3, entries.size());
450         assertEquals(new TextEntry("a", "hello there"), entries.get(0));
451         assertEquals(new TextEntry("b", "1234567890"), entries.get(1));
452         assertEquals(new TextEntry("xyz", HexDump.hexStringToByteArray("FFEFDFCF")),
453                 entries.get(2));
454     }
455 
456     @Test
testKeyRecord()457     public void testKeyRecord() throws IOException {
458         final byte[] dataIn =
459                 HexDump.hexStringToByteArray(
460                         "09746573742d686f7374056c6f63616c"
461                                 + "00001980010000000a00440201030dc1"
462                                 + "41d0637960b98cbc12cfca221d2879da"
463                                 + "c26ee5b460e9007c992e1902d897c391"
464                                 + "b03764d448f7d0c772fdb03b1d9d6d52"
465                                 + "ff8886769e8e2362513565270962d3");
466         final byte[] rData =
467                 HexDump.hexStringToByteArray(
468                         "0201030dc141d0637960b98cbc12cfca"
469                                 + "221d2879dac26ee5b460e9007c992e19"
470                                 + "02d897c391b03764d448f7d0c772fdb0"
471                                 + "3b1d9d6d52ff8886769e8e2362513565"
472                                 + "270962d3");
473         assertNotNull(dataIn);
474         String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
475 
476         // Decode
477         DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
478         MdnsPacketReader reader = new MdnsPacketReader(packet);
479 
480         String[] name = reader.readLabels();
481         assertNotNull(name);
482         assertEquals(2, name.length);
483         String fqdn = MdnsRecord.labelsToString(name);
484         assertEquals("test-host.local", fqdn);
485 
486         int type = reader.readUInt16();
487         assertEquals(MdnsRecord.TYPE_KEY, type);
488 
489         MdnsKeyRecord keyRecord;
490 
491         // MdnsKeyRecord(String[] name, MdnsPacketReader reader)
492         reader = new MdnsPacketReader(packet);
493         reader.readLabels(); // Skip labels
494         reader.readUInt16(); // Skip type
495         keyRecord = new MdnsKeyRecord(name, reader);
496         assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
497         assertTrue(keyRecord.getTtl() > 0); // Not a question so the TTL is greater than 0
498         assertTrue(keyRecord.getCacheFlush());
499         assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
500         assertArrayEquals(rData, keyRecord.getRData());
501         assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA
502         assertEquals(dataInText, toHex(keyRecord));
503 
504         // MdnsKeyRecord(String[] name, MdnsPacketReader reader, boolean isQuestion)
505         reader = new MdnsPacketReader(packet);
506         reader.readLabels(); // Skip labels
507         reader.readUInt16(); // Skip type
508         keyRecord = new MdnsKeyRecord(name, reader, false /* isQuestion */);
509         assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
510         assertTrue(keyRecord.getTtl() > 0); // Not a question, so the TTL is greater than 0
511         assertTrue(keyRecord.getCacheFlush());
512         assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
513         assertArrayEquals(rData, keyRecord.getRData());
514         assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA
515 
516         // MdnsKeyRecord(String[] name, boolean isUnicast)
517         keyRecord = new MdnsKeyRecord(name, false /* isUnicast */);
518         assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
519         assertEquals(0, keyRecord.getTtl());
520         assertEquals(QCLASS_INTERNET, keyRecord.getRecordClass());
521         assertFalse(keyRecord.getCacheFlush());
522         assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
523         assertArrayEquals(null, keyRecord.getRData());
524 
525         // MdnsKeyRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis,
526         // byte[] rData)
527         keyRecord =
528                 new MdnsKeyRecord(
529                         name,
530                         10 /* receiptTimeMillis */,
531                         true /* cacheFlush */,
532                         20_000 /* ttlMillis */,
533                         rData);
534         assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
535         assertEquals(10, keyRecord.getReceiptTime());
536         assertTrue(keyRecord.getCacheFlush());
537         assertEquals(20_000, keyRecord.getTtl());
538         assertEquals(QCLASS_INTERNET, keyRecord.getRecordClass());
539         assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
540         assertArrayEquals(rData, keyRecord.getRData());
541         assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA
542     }
543 }
544