1 /*
2  * Copyright (C) 2023 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.tv.mdnsoffloadmanager;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertThrows;
21 
22 import androidx.test.filters.SmallTest;
23 import org.junit.Test;
24 
25 import java.util.Arrays;
26 import java.util.List;
27 
28 import device.google.atv.mdns_offload.IMdnsOffload.MdnsProtocolData.MatchCriteria;
29 
30 @SmallTest
31 public class MdnsPacketParserTest {
32 
33     @Test
testExtractFullNameFromSingleLabel()34     public void testExtractFullNameFromSingleLabel() {
35         byte[] array = new byte[]{'x', 'x', 'x', 3, 'a', 't', 'v', 0};
36         String fullName = MdnsPacketParser.extractFullName(array, 3);
37         assertEquals("atv.", fullName);
38     }
39 
40     @Test
testExtractFullNameFromPointer()41     public void testExtractFullNameFromPointer() {
42         byte[] array = new byte[]{3, 'i', 's', 'o', 0, (byte) 0xC0, 0x00};
43         String fullName = MdnsPacketParser.extractFullName(array, 5);
44         assertEquals("iso.", fullName);
45     }
46 
47     @Test
testExtractFullNameFromMultiLabel()48     public void testExtractFullNameFromMultiLabel() {
49         byte[] array = new byte[]{'x', 'x', 'x', 3, 'a', 't', 'v', 3, 'g', 't', 'v', 0};
50         String fullName = MdnsPacketParser.extractFullName(array, 3);
51         assertEquals("atv.gtv.", fullName);
52     }
53 
54     @Test
testExtractFullNameFromLabelPointer()55     public void testExtractFullNameFromLabelPointer() {
56         byte[] array = new byte[]{3, 'i', 's', 'o', 0,//
57                 3, 'a', 't', 'v', 3, 'g', 't', 'v', (byte) 0xC0, 0x00};
58         String fullName = MdnsPacketParser.extractFullName(array, 9);
59         assertEquals("gtv.iso.", fullName);
60     }
61 
62     @Test
testExtractFullNameFromLabelDualPointerLongOffset()63     public void testExtractFullNameFromLabelDualPointerLongOffset() {
64         byte[] array = new byte[]{3, 'i', 's', 'o', 0,//
65                 3, 'a', 't', 'v', 3, 'g', 't', 'v', (byte) 0xC0, 0x64};
66 
67         //Add the string 4http at offset 0x64 and back to pointer 0x00
68         byte[] longArray = Arrays.copyOf(array, 120);
69         longArray[0x64] = 4;
70         longArray[0x64 + 1] = 'h';
71         longArray[0x64 + 2] = 't';
72         longArray[0x64 + 3] = 't';
73         longArray[0x64 + 4] = 'p';
74         longArray[0x64 + 5] = (byte) 0xC0;
75         longArray[0x64 + 6] = 0x00;
76 
77         String fullName = MdnsPacketParser.extractFullName(longArray, 9);
78         assertEquals("gtv.http.iso.", fullName);
79     }
80 
81     @Test
testExtractFullNameBadPointer()82     public void testExtractFullNameBadPointer() {
83         byte[] array = new byte[]{3, 'i', 's', 'o', 0, (byte) 0xC0, 0x20};
84         assertThrows(
85                 "Setting cursor on negative offset is not allowed.",
86                 IllegalArgumentException.class,
87                 () -> MdnsPacketParser.extractFullName(array, -10)
88         );
89     }
90 
91     @Test
testExtractFullNameLabelTooLong()92     public void testExtractFullNameLabelTooLong() {
93         byte[] longArray = new byte[70];
94         Arrays.fill(longArray, (byte) 'a');
95         // Labels maximum allowed size is 64 so this fall in the unknown categorie of
96         // pointer 01xxxxxx or 10xxxxxx
97         longArray[0] = 65;
98         longArray[66] = 0x00; // ther array is [65, 65 times 'a', 0x00]
99 
100         assertThrows(
101                 "mDNS response packet is badly formed. Not enough data.",
102                 IllegalArgumentException.class,
103                 () -> MdnsPacketParser.extractFullName(longArray, 0)
104         );
105     }
106 
107     @Test
testExtractMatchCriteriaFailureQueryCount()108     public void testExtractMatchCriteriaFailureQueryCount() {
109         //1 query, 2 answers, 0 authority, 0 additional.
110         byte[] array = new byte[]{0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0};
111         assertThrows(
112                 "mDNS response packet contains data that is not answers",
113                 IllegalArgumentException.class,
114                 () -> MdnsPacketParser.extractMatchCriteria(array)
115         );
116     }
117 
118     @Test
testExtractMatchCriteriaFailureAuthorityCount()119     public void testExtractMatchCriteriaFailureAuthorityCount() {
120         //0 query, 2 answers, 2 authority, 0 additional.
121         byte[] array = new byte[]{0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0};
122         assertThrows(
123                 "mDNS response packet contains data that is not answers",
124                 IllegalArgumentException.class,
125                 () -> MdnsPacketParser.extractMatchCriteria(array)
126         );
127     }
128 
129     @Test
testExtractMatchCriteriaFailureAdditionalCount()130     public void testExtractMatchCriteriaFailureAdditionalCount() {
131         //0 query, 2 answers, 0 authority, 3 additional.
132         byte[] array = new byte[]{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3};
133         assertThrows(
134                 "mDNS response packet contains data that is not answers",
135                 IllegalArgumentException.class,
136                 () -> MdnsPacketParser.extractMatchCriteria(array)
137         );
138     }
139 
140     @Test
testExtractMatchCriteriaSuccessOneAnswerLabel()141     public void testExtractMatchCriteriaSuccessOneAnswerLabel() {
142         byte[] array = new byte[]{
143                 0, 0, 0, 0,//Id , Flags
144                 0, 0, 0, 1, 0, 0, 0, 0,// Header section. 1 answer.
145                 //Data 1
146                 3, 'a', 't', 'v', 0x00, //atv.
147                 0x00, 0x01, //type A
148                 (byte) 0x80, 0x01,//cache flush: True, class: in
149                 0, 0, 0, 5,// TTL 5sec
150                 0, 4, // Data with size 4
151                 100, 80, 40, 20 //ip: 100.80.40.20
152         };
153 
154         List<MatchCriteria> criteria = MdnsPacketParser.extractMatchCriteria(array);
155 
156         assertEquals(1, criteria.size());
157 
158         MatchCriteria result = criteria.get(0);
159         assertEquals(12, result.nameOffset);
160         assertEquals(1, result.type);
161     }
162 
163     @Test
testExtractMatchCriteriaSuccessTwoAnswersPointers()164     public void testExtractMatchCriteriaSuccessTwoAnswersPointers() {
165         byte[] array = new byte[]{
166                 0, 0, 0, 0,//Id , Flags
167                 0, 0, 0, 2, 0, 0, 0, 0,// Header section. 2 answers.
168                 //Data 1
169                 3, 'a', 't', 'v', 0x00, //atv.
170                 0x00, 0x01, //type A
171                 (byte) 0x80, 0x01,//cache flush: True, class: in
172                 0, 0, 0, 5,// TTL 5sec
173                 0, 4, // Data with size 4
174                 100, 80, 40, 20, //ip: 100.80.40.20
175                 //Data 2
176                 3, 'g', 't', 'v', (byte) 0b11000000, 12, //gtv.[ptr->]atv.
177                 0x00, 16, //type TXT
178                 (byte) 0x80, 0x01,//cache flush: True, class: in
179                 0, 0, 0, 5,// TTL 5sec
180                 0, 3, // Data with size 3
181                 'i', 's', 'o' // "iso"
182         };
183 
184         List<MatchCriteria> criteria = MdnsPacketParser.extractMatchCriteria(array);
185 
186         assertEquals(2, criteria.size());
187 
188         MatchCriteria result0 = criteria.get(0);
189         assertEquals(12, result0.nameOffset);
190         assertEquals(1, result0.type);
191 
192         MatchCriteria result1 = criteria.get(1);
193         assertEquals(31, result1.nameOffset);
194         assertEquals(16, result1.type);
195     }
196 
197     @Test
testExtractFullName()198     public void testExtractFullName() {
199         byte[] array = new byte[]{
200             0, 0, 0, 0,//Id , Flags
201             0, 0, 0, 2, 0, 0, 0, 0,// Header section. 2 answers.
202             //Data 1
203             3, 'a', 't', 'v', 0x00, //atv.
204             0x00, 0x01, //type A
205             (byte) 0x80, 0x01,//cache flush: True, class: in
206             0, 0, 0, 5,// TTL 5sec
207             0, 4, // Data with size 4
208             100, 80, 40, 20, //ip: 100.80.40.20
209             //Data 2
210             3, 'g', 't', 'v', (byte) 0b11000000, 12, //gtv.[ptr->]atv.
211             0x00, 16, //type TXT
212             (byte) 0x80, 0x01,//cache flush: True, class: in
213             0, 0, 0, 5,// TTL 5sec
214             0, 3, // Data with size 3
215             'i', 's', 'o' // "iso"
216         };
217 
218         List<MatchCriteria> criteria = MdnsPacketParser.extractMatchCriteria(array);
219         assertEquals(2, criteria.size());
220         String name0 = MdnsPacketParser.extractFullName(array, criteria.get(0).nameOffset);
221         assertEquals("atv.", name0);
222         String name1 = MdnsPacketParser.extractFullName(array, criteria.get(1).nameOffset);
223         assertEquals("gtv.atv.", name1);
224     }
225 
226     @Test
testNegativeByteToUint8()227     public void testNegativeByteToUint8() {
228         byte[] array = new byte[]{
229                 0, 0, 0, 0,//Id , Flags
230                 0, 0, 0, 3, 0, 0, 0, 0,// Header section. 2 answers.
231                 //Data 1
232                 3, 'a', 't', 'v', 0x00, //atv.
233                 0x00, 0x01, //type A
234                 (byte) 0x80, 0x01,//cache flush: True, class: in
235                 0, 0, 0, 5,// TTL 5sec
236                 0, 4, // Data with size 4
237                 100, 80, 40, 20, //ip: 100.80.40.20
238                 //Data 2
239                 3, 'g', 't', 'v', (byte) 0b11000000, 12, //gtv.[ptr->]atv.
240                 0x00, 16, //type TXT
241                 (byte) 0x80, 0x01,//cache flush: True, class: in
242                 0, 0, 0, 5,// TTL 5sec
243                 0, (byte) 130, // Data with size 130 > 127
244                 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
245                 //Data 3
246                 4, 'f', 'a', 'i', 'l', 0x00, //fail.
247                 0x00, 0x01, //type A
248                 (byte) 0x80, 0x01,//cache flush: True, class: in
249                 0, 0, 0, 5,// TTL 5sec
250                 0, 4, // Data with size 4
251                 100, 80, 40, 20, //ip: 100.80.40.20
252         };
253 
254         List<MatchCriteria> criteria = MdnsPacketParser.extractMatchCriteria(array);
255         assertEquals(3, criteria.size());
256         String name2 = MdnsPacketParser.extractFullName(array, criteria.get(2).nameOffset);
257         assertEquals("fail.", name2);
258     }
259 
260     @Test
testExtractMatchCriteriaFailureTooMuchData()261     public void testExtractMatchCriteriaFailureTooMuchData() {
262         byte[] array = new byte[]{
263                 0, 0, 0, 0,//Id , Flags
264                 0, 0, 0, 1, 0, 0, 0, 0,// Header section. 2 answers.
265                 //Data 1
266                 3, 'a', 't', 'v', 0x00, //atv.
267                 0x00, 0x01, //type A
268                 (byte) 0x80, 0x01,//cache flush: True, class: in
269                 0, 0, 0, 5,// TTL 5sec
270                 0, 4, // Data with size 4
271                 100, 80, 40, 20, //ip: 100.80.40.20
272                 //extra data.
273                 'e','x','t','r','a'
274         };
275 
276         assertThrows(
277                 "mDNS response packet is badly formed. Too much data.",
278                 IllegalArgumentException.class,
279                 () -> MdnsPacketParser.extractMatchCriteria(array)
280         );
281     }
282 
283 }