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