1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "discovery/mdns/mdns_reader.h"
6
7 #include <memory>
8
9 #include "discovery/common/config.h"
10 #include "discovery/mdns/testing/mdns_test_util.h"
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
13
14 namespace openscreen {
15 namespace discovery {
16
17 namespace {
18
19 constexpr std::chrono::seconds kTtl{120};
20
21 template <class T>
TestReadEntrySucceeds(const uint8_t * data,size_t size,const T & expected)22 void TestReadEntrySucceeds(const uint8_t* data,
23 size_t size,
24 const T& expected) {
25 Config config;
26 MdnsReader reader(config, data, size);
27 T entry;
28 EXPECT_TRUE(reader.Read(&entry));
29 EXPECT_EQ(entry, expected);
30 EXPECT_EQ(reader.remaining(), UINT64_C(0));
31 }
32
33 template <>
TestReadEntrySucceeds(const uint8_t * data,size_t size,const MdnsMessage & expected)34 void TestReadEntrySucceeds<MdnsMessage>(const uint8_t* data,
35 size_t size,
36 const MdnsMessage& expected) {
37 MdnsReader reader(Config{}, data, size);
38 const ErrorOr<MdnsMessage> message = reader.Read();
39 EXPECT_TRUE(message.is_value());
40 EXPECT_EQ(message.value(), expected);
41 EXPECT_EQ(reader.remaining(), UINT64_C(0));
42 }
43
44 template <class T>
TestReadEntryFails(const uint8_t * data,size_t size)45 void TestReadEntryFails(const uint8_t* data, size_t size) {
46 Config config;
47 MdnsReader reader(config, data, size);
48 T entry;
49 EXPECT_FALSE(reader.Read(&entry));
50 // There should be no side effects for failing to read an entry. The
51 // underlying pointer should not have changed.
52 EXPECT_EQ(reader.offset(), UINT64_C(0));
53 }
54
55 template <>
TestReadEntryFails(const uint8_t * data,size_t size)56 void TestReadEntryFails<MdnsMessage>(const uint8_t* data, size_t size) {
57 Config config;
58 MdnsReader reader(config, data, size);
59 const ErrorOr<MdnsMessage> message = reader.Read();
60 EXPECT_TRUE(message.is_error());
61
62 // There should be no side effects for failing to read an entry. The
63 // underlying pointer should not have changed.
64 EXPECT_EQ(reader.offset(), UINT64_C(0));
65 }
66 } // namespace
67
TEST(MdnsReaderTest,ReadDomainName)68 TEST(MdnsReaderTest, ReadDomainName) {
69 constexpr uint8_t kMessage[] = {
70 // First name
71 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', // Byte: 0
72 0x05, 'l', 'o', 'c', 'a', 'l', // Byte: 8
73 0x00, // Byte: 14
74 // Second name
75 0x07, 's', 'e', 'r', 'v', 'i', 'c', 'e', // Byte: 15
76 0xc0, 0x00, // Byte: 23
77 // Third name
78 0x06, 'd', 'e', 'v', 'i', 'c', 'e', // Byte: 25
79 0xc0, 0x0f, // Byte: 32
80 // Fourth name
81 0xc0, 0x20, // Byte: 34
82 };
83 Config config;
84 MdnsReader reader(config, kMessage, sizeof(kMessage));
85 EXPECT_EQ(reader.begin(), kMessage);
86 EXPECT_EQ(reader.length(), sizeof(kMessage));
87 EXPECT_EQ(reader.offset(), 0u);
88 DomainName name;
89 EXPECT_TRUE(reader.Read(&name));
90 EXPECT_EQ(name.ToString(), "testing.local");
91 EXPECT_TRUE(reader.Read(&name));
92 EXPECT_EQ(name.ToString(), "service.testing.local");
93 EXPECT_TRUE(reader.Read(&name));
94 EXPECT_EQ(name.ToString(), "device.service.testing.local");
95 EXPECT_TRUE(reader.Read(&name));
96 EXPECT_EQ(name.ToString(), "service.testing.local");
97 EXPECT_EQ(reader.offset(), sizeof(kMessage));
98 EXPECT_EQ(0UL, reader.remaining());
99 EXPECT_FALSE(reader.Read(&name));
100 }
101
TEST(MdnsReaderTest,ReadDomainName_Empty)102 TEST(MdnsReaderTest, ReadDomainName_Empty) {
103 constexpr uint8_t kDomainName[] = {0x00};
104 TestReadEntrySucceeds(kDomainName, sizeof(kDomainName), DomainName());
105 }
106
TEST(MdnsReaderTest,ReadDomainName_TooShort)107 TEST(MdnsReaderTest, ReadDomainName_TooShort) {
108 // Length 0x03 is longer than available data for the domain name
109 constexpr uint8_t kDomainName[] = {0x03, 'a', 'b'};
110 TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName));
111 }
112
TEST(MdnsReaderTest,ReadDomainName_TooLong)113 TEST(MdnsReaderTest, ReadDomainName_TooLong) {
114 std::vector<uint8_t> kDomainName;
115 for (uint8_t letter = 'a'; letter <= 'z'; ++letter) {
116 constexpr uint8_t repetitions = 10;
117 kDomainName.push_back(repetitions);
118 kDomainName.insert(kDomainName.end(), repetitions, letter);
119 }
120 kDomainName.push_back(0);
121 TestReadEntryFails<DomainName>(kDomainName.data(), kDomainName.size());
122 }
123
TEST(MdnsReaderTest,ReadDomainName_LabelPointerOutOfBounds)124 TEST(MdnsReaderTest, ReadDomainName_LabelPointerOutOfBounds) {
125 constexpr uint8_t kDomainName[] = {0xc0, 0x02};
126 TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName));
127 }
128
TEST(MdnsReaderTest,ReadDomainName_InvalidLabel)129 TEST(MdnsReaderTest, ReadDomainName_InvalidLabel) {
130 constexpr uint8_t kDomainName[] = {0x80};
131 TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName));
132 }
133
TEST(MdnsReaderTest,ReadDomainName_CircularCompression)134 TEST(MdnsReaderTest, ReadDomainName_CircularCompression) {
135 // clang-format off
136 constexpr uint8_t kDomainName[] = {
137 // NOTE: Circular label pointer at end of name.
138 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', // Byte: 0
139 0x05, 'l', 'o', 'c', 'a', 'l', // Byte: 8
140 0xc0, 0x00, // Byte: 14
141 };
142 // clang-format on
143 TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName));
144 }
145
TEST(MdnsReaderTest,ReadRawRecordRdata)146 TEST(MdnsReaderTest, ReadRawRecordRdata) {
147 // clang-format off
148 constexpr uint8_t kRawRecordRdata[] = {
149 0x00, 0x08, // RDLENGTH = 8 bytes
150 0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00,
151 };
152 // clang-format on
153 TestReadEntrySucceeds(
154 kRawRecordRdata, sizeof(kRawRecordRdata),
155 RawRecordRdata(kRawRecordRdata + 2, sizeof(kRawRecordRdata) - 2));
156 }
157
TEST(MdnsReaderTest,ReadRawRecordRdata_TooLong)158 TEST(MdnsReaderTest, ReadRawRecordRdata_TooLong) {
159 // clang-format off
160 constexpr uint8_t kRawRecordRdata[] = {
161 0x00, 0x08, // RDLENGTH = 8 bytes
162 0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00,
163 };
164 // clang-format on
165
166 Config config;
167 config.maximum_valid_rdata_size = 1;
168 MdnsReader reader(config, kRawRecordRdata, sizeof(kRawRecordRdata));
169 RawRecordRdata entry;
170 EXPECT_FALSE(reader.Read(&entry));
171 // There should be no side effects for failing to read an entry. The
172 // underlying pointer should not have changed.
173 EXPECT_EQ(reader.offset(), UINT64_C(0));
174 }
175
TEST(MdnsReaderTest,ReadRawRecord_Empty)176 TEST(MdnsReaderTest, ReadRawRecord_Empty) {
177 // clang-format off
178 constexpr uint8_t kRawRecordRdata[] = {
179 0x00, 0x00, // RDLENGTH = 0 bytes
180 };
181 // clang-format on
182 TestReadEntrySucceeds(kRawRecordRdata, sizeof(kRawRecordRdata),
183 RawRecordRdata());
184 }
185
TEST(MdnsReaderTest,ReadRawRecordRdata_TooShort)186 TEST(MdnsReaderTest, ReadRawRecordRdata_TooShort) {
187 // clang-format off
188 constexpr uint8_t kRawRecordRdata[] = {
189 0x00, 0x05, // RDLENGTH = 5 bytes is longer that available data
190 0x03, 'f', 'o', 'o'
191 };
192 // clang-format on
193 TestReadEntryFails<RawRecordRdata>(kRawRecordRdata, sizeof(kRawRecordRdata));
194 }
195
TEST(MdnsReaderTest,ReadSrvRecordRdata)196 TEST(MdnsReaderTest, ReadSrvRecordRdata) {
197 // clang-format off
198 constexpr uint8_t kSrvRecordRdata[] = {
199 0x00, 0x15, // RDLENGTH = 21
200 0x00, 0x05, // PRIORITY = 5
201 0x00, 0x06, // WEIGHT = 6
202 0x1f, 0x49, // PORT = 8009
203 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
204 0x05, 'l', 'o', 'c', 'a', 'l', 0x00,
205 };
206 // clang-format on
207 TestReadEntrySucceeds(
208 kSrvRecordRdata, sizeof(kSrvRecordRdata),
209 SrvRecordRdata(5, 6, 8009, DomainName{"testing", "local"}));
210 }
211
TEST(MdnsReaderTest,ReadSrvRecordRdata_TooShort)212 TEST(MdnsReaderTest, ReadSrvRecordRdata_TooShort) {
213 // clang-format off
214 constexpr uint8_t kSrvRecordRdata[] = {
215 0x00, 0x14, // RDLENGTH = 21
216 0x00, 0x05, // PRIORITY = 5
217 0x00, 0x06, // WEIGHT = 6
218 // Missing bytes
219 };
220 // clang-format on
221 TestReadEntryFails<SrvRecordRdata>(kSrvRecordRdata, sizeof(kSrvRecordRdata));
222 }
223
TEST(MdnsReaderTest,ReadSrvRecordRdata_WrongLength)224 TEST(MdnsReaderTest, ReadSrvRecordRdata_WrongLength) {
225 // clang-format off
226 constexpr uint8_t kSrvRecordRdata[] = {
227 0x00, 0x14, // RDLENGTH = 20
228 0x00, 0x05, // PRIORITY = 5
229 0x00, 0x06, // WEIGHT = 6
230 0x1f, 0x49, // PORT = 8009
231 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
232 0x05, 'l', 'o', 'c', 'a', 'l', 0x00,
233 };
234 // clang-format on
235 TestReadEntryFails<SrvRecordRdata>(kSrvRecordRdata, sizeof(kSrvRecordRdata));
236 }
237
TEST(MdnsReaderTest,ReadARecordRdata)238 TEST(MdnsReaderTest, ReadARecordRdata) {
239 constexpr uint8_t kARecordRdata[] = {
240 0x00, 0x4, // RDLENGTH = 4
241 0x08, 0x08, 0x08, 0x08, // ADDRESS = 8.8.8.8
242 };
243 TestReadEntrySucceeds(kARecordRdata, sizeof(kARecordRdata),
244 ARecordRdata(IPAddress{8, 8, 8, 8}));
245 }
246
TEST(MdnsReaderTest,ReadARecordRdata_TooShort)247 TEST(MdnsReaderTest, ReadARecordRdata_TooShort) {
248 constexpr uint8_t kARecordRdata[] = {
249 0x00, 0x4, // RDLENGTH = 4
250 0x08, 0x08, // Address missing bytes
251 };
252 TestReadEntryFails<ARecordRdata>(kARecordRdata, sizeof(kARecordRdata));
253 }
254
TEST(MdnsReaderTest,ReadARecordRdata_WrongLength)255 TEST(MdnsReaderTest, ReadARecordRdata_WrongLength) {
256 constexpr uint8_t kARecordRdata[] = {
257 0x00, 0x5, // RDLENGTH = 5
258 0x08, 0x08, 0x08, 0x08, // ADDRESS = 8.8.8.8
259 };
260 TestReadEntryFails<ARecordRdata>(kARecordRdata, sizeof(kARecordRdata));
261 }
262
TEST(MdnsReaderTest,ReadAAAARecordRdata)263 TEST(MdnsReaderTest, ReadAAAARecordRdata) {
264 // clang-format off
265 constexpr uint8_t kAAAARecordRdata[] = {
266 0x00, 0x10, // RDLENGTH = 16
267 // ADDRESS = FE80:0000:0000:0000:0202:B3FF:FE1E:8329
268 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
269 0x02, 0x02, 0xb3, 0xff, 0xfe, 0x1e, 0x83, 0x29,
270 };
271 // clang-format on
272 TestReadEntrySucceeds(kAAAARecordRdata, sizeof(kAAAARecordRdata),
273 AAAARecordRdata(IPAddress(IPAddress::Version::kV6,
274 kAAAARecordRdata + 2)));
275 }
276
TEST(MdnsReaderTest,ReadAAAARecordRdata_TooShort)277 TEST(MdnsReaderTest, ReadAAAARecordRdata_TooShort) {
278 // clang-format off
279 constexpr uint8_t kAAAARecordRdata[] = {
280 0x00, 0x10, // RDLENGTH = 16
281 // ADDRESS missing bytes
282 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
283 };
284 // clang-format on
285 TestReadEntryFails<AAAARecordRdata>(kAAAARecordRdata,
286 sizeof(kAAAARecordRdata));
287 }
288
TEST(MdnsReaderTest,ReadAAAARecordRdata_WrongLength)289 TEST(MdnsReaderTest, ReadAAAARecordRdata_WrongLength) {
290 // clang-format off
291 constexpr uint8_t kAAAARecordRdata[] = {
292 0x00, 0x11, // RDLENGTH = 17
293 // ADDRESS = FE80:0000:0000:0000:0202:B3FF:FE1E:8329
294 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
295 0x02, 0x02, 0xb3, 0xff, 0xfe, 0x1e, 0x83, 0x29,
296 };
297 // clang-format on
298 TestReadEntryFails<AAAARecordRdata>(kAAAARecordRdata,
299 sizeof(kAAAARecordRdata));
300 }
301
TEST(MdnsReaderTest,ReadPtrRecordRdata)302 TEST(MdnsReaderTest, ReadPtrRecordRdata) {
303 // clang-format off
304 constexpr uint8_t kPtrRecordRdata[] = {
305 0x00, 0x18, // RDLENGTH = 24
306 0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
307 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
308 0x05, 'l', 'o', 'c', 'a', 'l',
309 0x00,
310 };
311 // clang-format on
312 TestReadEntrySucceeds(
313 kPtrRecordRdata, sizeof(kPtrRecordRdata),
314 PtrRecordRdata(DomainName{"mydevice", "testing", "local"}));
315 }
316
TEST(MdnsReaderTest,ReadPtrRecordRdata_TooShort)317 TEST(MdnsReaderTest, ReadPtrRecordRdata_TooShort) {
318 // clang-format off
319 constexpr uint8_t kPtrRecordRdata[] = {
320 0x00, 0x18, // RDLENGTH = 24
321 0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
322 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
323 0x05, 'l', 'o', 'c'
324 // Missing bytes
325 };
326 // clang-format on
327 TestReadEntryFails<PtrRecordRdata>(kPtrRecordRdata, sizeof(kPtrRecordRdata));
328 }
329
TEST(MdnsReaderTest,ReadPtrRecordRdata_WrongLength)330 TEST(MdnsReaderTest, ReadPtrRecordRdata_WrongLength) {
331 // clang-format off
332 constexpr uint8_t kPtrRecordRdata[] = {
333 0x00, 0x18, // RDLENGTH = 24
334 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
335 0x05, 'l', 'o', 'c', 'a', 'l',
336 0x00,
337 };
338 // clang-format on
339 TestReadEntryFails<PtrRecordRdata>(kPtrRecordRdata, sizeof(kPtrRecordRdata));
340 }
341
TEST(MdnsReaderTest,ReadTxtRecordRdata)342 TEST(MdnsReaderTest, ReadTxtRecordRdata) {
343 // clang-format off
344 constexpr uint8_t kTxtRecordRdata[] = {
345 0x00, 0x0C, // RDLENGTH = 12
346 0x05, 'f', 'o', 'o', '=', '1',
347 0x05, 'b', 'a', 'r', '=', '2',
348 };
349 // clang-format on
350 TestReadEntrySucceeds(kTxtRecordRdata, sizeof(kTxtRecordRdata),
351 MakeTxtRecord({"foo=1", "bar=2"}));
352 }
353
TEST(MdnsReaderTest,ReadTxtRecordRdata_Empty)354 TEST(MdnsReaderTest, ReadTxtRecordRdata_Empty) {
355 constexpr uint8_t kTxtRecordRdata[] = {
356 0x00, 0x01, // RDLENGTH = 1
357 0x00, // empty string
358 };
359 TestReadEntrySucceeds(kTxtRecordRdata, sizeof(kTxtRecordRdata),
360 TxtRecordRdata());
361 }
362
TEST(MdnsReaderTest,ReadTxtRecordRdata_WithNullInTheMiddle)363 TEST(MdnsReaderTest, ReadTxtRecordRdata_WithNullInTheMiddle) {
364 // clang-format off
365 constexpr uint8_t kTxtRecordRdata[] = {
366 0x00, 0x10, // RDLENGTH = 16
367 0x09, 'w', 'i', 't', 'h', '\0', 'N', 'U', 'L', 'L',
368 0x05, 'o', 't', 'h', 'e', 'r',
369 };
370 // clang-format on
371 TestReadEntrySucceeds(
372 kTxtRecordRdata, sizeof(kTxtRecordRdata),
373 MakeTxtRecord({absl::string_view("with\0NULL", 9), "other"}));
374 }
375
TEST(MdnsReaderTest,ReadTxtRecordRdata_EmptyEntries)376 TEST(MdnsReaderTest, ReadTxtRecordRdata_EmptyEntries) {
377 // clang-format off
378 constexpr uint8_t kTxtRecordRdata[] = {
379 0x00, 0x0F, // RDLENGTH = 12
380 0x05, 'f', 'o', 'o', '=', '1',
381 0x00,
382 0x00,
383 0x05, 'b', 'a', 'r', '=', '2',
384 0x00,
385 };
386 // clang-format on
387 TestReadEntrySucceeds(kTxtRecordRdata, sizeof(kTxtRecordRdata),
388 MakeTxtRecord({"foo=1", "bar=2"}));
389 }
390
TEST(MdnsReaderTest,ReadTxtRecordRdata_TooLong)391 TEST(MdnsReaderTest, ReadTxtRecordRdata_TooLong) {
392 // clang-format off
393 constexpr uint8_t kTxtRecordRdata[] = {
394 0x00, 0x0C, // RDLENGTH = 12
395 0x05, 'f', 'o', 'o', '=', '1',
396 0x05, 'b', 'a', 'r', '=', '2',
397 };
398 // clang-format on
399
400 Config config;
401 config.maximum_valid_rdata_size = 1;
402 MdnsReader reader(config, kTxtRecordRdata, sizeof(kTxtRecordRdata));
403 TxtRecordRdata entry;
404 EXPECT_FALSE(reader.Read(&entry));
405 // There should be no side effects for failing to read an entry. The
406 // underlying pointer should not have changed.
407 EXPECT_EQ(reader.offset(), UINT64_C(0));
408 }
409
TEST(MdnsReaderTest,ReadNsecRecordRdata)410 TEST(MdnsReaderTest, ReadNsecRecordRdata) {
411 // clang-format off
412 constexpr uint8_t kExpectedRdata[] = {
413 0x00, 0x20, // RDLENGTH = 32
414 0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
415 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
416 0x05, 'l', 'o', 'c', 'a', 'l',
417 0x00,
418 // It takes 8 bytes to encode the kA and kSRV records because:
419 // - Both record types have value less than 256, so they are both in window
420 // block 1.
421 // - The bitmap length for this block is always a single byte
422 // - DnsTypes have the following values:
423 // - kA = 1 (encoded in byte 1)
424 // kTXT = 16 (encoded in byte 3)
425 // - kSRV = 33 (encoded in byte 5)
426 // - kNSEC = 47 (encoded in 6 bytes)
427 // - The largest of these is 47, so 6 bytes are needed to encode this data.
428 // So the full encoded version is:
429 // 00000000 00000110 01000000 00000000 10000000 00000000 0100000 00000001
430 // |window| | size | | 0-7 | | 8-15 | |16-23 | |24-31 | |32-39 | |40-47 |
431 0x00, 0x06, 0x40, 0x00, 0x80, 0x00, 0x40, 0x01
432 };
433 // clang-format on
434 TestReadEntrySucceeds(
435 kExpectedRdata, sizeof(kExpectedRdata),
436 NsecRecordRdata(DomainName{"mydevice", "testing", "local"}, DnsType::kA,
437 DnsType::kTXT, DnsType::kSRV, DnsType::kNSEC));
438 }
439
TEST(MdnsReaderTest,ReadNsecRecordRdata_TooShort)440 TEST(MdnsReaderTest, ReadNsecRecordRdata_TooShort) {
441 // clang-format off
442 constexpr uint8_t kNsecRecordRdata[] = {
443 0x00, 0x20, // RDLENGTH = 32
444 0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
445 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
446 0x05, 'l', 'o', 'c', 'a', 'l',
447 0x00,
448 0x00, 0x06, 0x40, 0x00
449 };
450 // clang-format on
451 TestReadEntryFails<NsecRecordRdata>(kNsecRecordRdata,
452 sizeof(kNsecRecordRdata));
453 }
454
TEST(MdnsReaderTest,ReadNsecRecordRdata_WrongLength)455 TEST(MdnsReaderTest, ReadNsecRecordRdata_WrongLength) {
456 // clang-format off
457 constexpr uint8_t kNsecRecordRdata[] = {
458 0x00, 0x21, // RDLENGTH = 33
459 0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
460 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
461 0x05, 'l', 'o', 'c', 'a', 'l',
462 0x00,
463 0x00, 0x06, 0x40, 0x00, 0x80, 0x00, 0x40, 0x01
464 };
465 // clang-format on
466 TestReadEntryFails<NsecRecordRdata>(kNsecRecordRdata,
467 sizeof(kNsecRecordRdata));
468 }
469
TEST(MdnsReaderTest,ReadMdnsRecord_ARecordRdata)470 TEST(MdnsReaderTest, ReadMdnsRecord_ARecordRdata) {
471 // clang-format off
472 constexpr uint8_t kTestRecord[] = {
473 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
474 0x05, 'l', 'o', 'c', 'a', 'l',
475 0x00,
476 0x00, 0x01, // TYPE = A (1)
477 0x80, 0x01, // CLASS = IN (1) | CACHE_FLUSH_BIT
478 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
479 0x00, 0x04, // RDLENGTH = 4 bytes
480 0x08, 0x08, 0x08, 0x08, // RDATA = 8.8.8.8
481 };
482 // clang-format on
483 TestReadEntrySucceeds(kTestRecord, sizeof(kTestRecord),
484 MdnsRecord(DomainName{"testing", "local"}, DnsType::kA,
485 DnsClass::kIN, RecordType::kUnique, kTtl,
486 ARecordRdata(IPAddress{8, 8, 8, 8})));
487 }
488
TEST(MdnsReaderTest,ReadMdnsRecord_UnknownRecordType)489 TEST(MdnsReaderTest, ReadMdnsRecord_UnknownRecordType) {
490 // clang-format off
491 constexpr uint8_t kTestRecord[] = {
492 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
493 0x05, 'l', 'o', 'c', 'a', 'l',
494 0x00,
495 0x00, 0x05, // TYPE = CNAME (5)
496 0x80, 0x01, // CLASS = IN (1) | CACHE_FLUSH_BIT
497 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
498 0x00, 0x08, // RDLENGTH = 8 bytes
499 0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00,
500 };
501 constexpr uint8_t kCnameRdata[] = {
502 0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00,
503 };
504 // clang-format on
505 TestReadEntrySucceeds(
506 kTestRecord, sizeof(kTestRecord),
507 MdnsRecord(DomainName{"testing", "local"},
508 static_cast<DnsType>(5) /*CNAME class*/, DnsClass::kIN,
509 RecordType::kUnique, kTtl,
510 RawRecordRdata(kCnameRdata, sizeof(kCnameRdata))));
511 }
512
TEST(MdnsReaderTest,ReadMdnsRecord_CompressedNames)513 TEST(MdnsReaderTest, ReadMdnsRecord_CompressedNames) {
514 // clang-format off
515 constexpr uint8_t kTestRecord[] = {
516 // First message
517 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
518 0x05, 'l', 'o', 'c', 'a', 'l',
519 0x00,
520 0x00, 0x0c, // TYPE = PTR (12)
521 0x00, 0x01, // CLASS = IN (1)
522 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
523 0x00, 0x06, // RDLENGTH = 6 bytes
524 0x03, 'p', 't', 'r',
525 0xc0, 0x00, // Domain name label pointer to byte 0
526 // Second message
527 0x03, 'o', 'n', 'e',
528 0x03, 't', 'w', 'o',
529 0xc0, 0x00, // Domain name label pointer to byte 0
530 0x00, 0x01, // TYPE = A (1)
531 0x80, 0x01, // CLASS = IN (1) | CACHE_FLUSH_BIT
532 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
533 0x00, 0x04, // RDLENGTH = 4 bytes
534 0x08, 0x08, 0x08, 0x08, // RDATA = 8.8.8.8
535 };
536 // clang-format on
537 Config config;
538 MdnsReader reader(config, kTestRecord, sizeof(kTestRecord));
539
540 MdnsRecord record;
541 EXPECT_TRUE(reader.Read(&record));
542 EXPECT_EQ(record,
543 MdnsRecord(DomainName{"testing", "local"}, DnsType::kPTR,
544 DnsClass::kIN, RecordType::kShared, kTtl,
545 PtrRecordRdata(DomainName{"ptr", "testing", "local"})));
546 EXPECT_TRUE(reader.Read(&record));
547 EXPECT_EQ(record, MdnsRecord(DomainName{"one", "two", "testing", "local"},
548 DnsType::kA, DnsClass::kIN, RecordType::kUnique,
549 kTtl, ARecordRdata(IPAddress{8, 8, 8, 8})));
550 }
551
TEST(MdnsReaderTest,ReadMdnsRecord_MissingRdata)552 TEST(MdnsReaderTest, ReadMdnsRecord_MissingRdata) {
553 // clang-format off
554 constexpr uint8_t kTestRecord[] = {
555 0x05, 'l', 'o', 'c', 'a', 'l',
556 0x00,
557 0x00, 0x01, // TYPE = A (1)
558 0x00, 0x01, // CLASS = IN (1)
559 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
560 0x00, 0x04, // RDLENGTH = 4 bytes
561 // Missing RDATA
562 };
563 // clang-format on
564 TestReadEntryFails<MdnsRecord>(kTestRecord, sizeof(kTestRecord));
565 }
566
TEST(MdnsReaderTest,ReadMdnsRecord_InvalidHostName)567 TEST(MdnsReaderTest, ReadMdnsRecord_InvalidHostName) {
568 // clang-format off
569 constexpr uint8_t kTestRecord[] = {
570 // Invalid NAME: length byte too short
571 0x03, 'i', 'n', 'v', 'a', 'l', 'i', 'd',
572 0x00,
573 0x00, 0x01, // TYPE = A (1)
574 0x00, 0x01, // CLASS = IN (1)
575 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
576 0x00, 0x04, // RDLENGTH = 4 bytes
577 0x08, 0x08, 0x08, 0x08, // RDATA = 8.8.8.8
578 };
579 // clang-format on
580 TestReadEntryFails<MdnsRecord>(kTestRecord, sizeof(kTestRecord));
581 }
582
TEST(MdnsReaderTest,ReadMdnsQuestion)583 TEST(MdnsReaderTest, ReadMdnsQuestion) {
584 // clang-format off
585 constexpr uint8_t kTestQuestion[] = {
586 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
587 0x05, 'l', 'o', 'c', 'a', 'l',
588 0x00,
589 0x00, 0x01, // TYPE = A (1)
590 0x80, 0x01, // CLASS = IN (1) | UNICAST_BIT
591 };
592 // clang-format on
593 TestReadEntrySucceeds(
594 kTestQuestion, sizeof(kTestQuestion),
595 MdnsQuestion(DomainName{"testing", "local"}, DnsType::kA, DnsClass::kIN,
596 ResponseType::kUnicast));
597 }
598
TEST(MdnsReaderTest,ReadMdnsQuestion_CompressedNames)599 TEST(MdnsReaderTest, ReadMdnsQuestion_CompressedNames) {
600 // clang-format off
601 constexpr uint8_t kTestQuestions[] = {
602 // First Question
603 0x05, 'f', 'i', 'r', 's', 't',
604 0x05, 'l', 'o', 'c', 'a', 'l',
605 0x00,
606 0x00, 0x01, // TYPE = A (1)
607 0x80, 0x01, // CLASS = IN (1) | UNICAST_BIT
608 // Second Question
609 0x06, 's', 'e', 'c', 'o', 'n', 'd',
610 0xc0, 0x06, // Domain name label pointer
611 0x00, 0x0c, // TYPE = PTR (12)
612 0x00, 0x01, // CLASS = IN (1)
613 };
614 // clang-format on
615 Config config;
616 MdnsReader reader(config, kTestQuestions, sizeof(kTestQuestions));
617 MdnsQuestion question;
618 EXPECT_TRUE(reader.Read(&question));
619 EXPECT_EQ(question, MdnsQuestion(DomainName{"first", "local"}, DnsType::kA,
620 DnsClass::kIN, ResponseType::kUnicast));
621 EXPECT_TRUE(reader.Read(&question));
622 EXPECT_EQ(question, MdnsQuestion(DomainName{"second", "local"}, DnsType::kPTR,
623 DnsClass::kIN, ResponseType::kMulticast));
624 EXPECT_EQ(reader.remaining(), UINT64_C(0));
625 }
626
TEST(MdnsReaderTest,ReadMdnsQuestion_InvalidHostName)627 TEST(MdnsReaderTest, ReadMdnsQuestion_InvalidHostName) {
628 // clang-format off
629 constexpr uint8_t kTestQuestion[] = {
630 // Invalid NAME: length byte too short
631 0x03, 'i', 'n', 'v', 'a', 'l', 'i', 'd',
632 0x00,
633 0x00, 0x01, // TYPE = A (1)
634 0x00, 0x01, // CLASS = IN (1)
635 };
636 // clang-format on
637 TestReadEntryFails<MdnsQuestion>(kTestQuestion, sizeof(kTestQuestion));
638 }
639
TEST(MdnsReaderTest,ReadMdnsMessage)640 TEST(MdnsReaderTest, ReadMdnsMessage) {
641 // clang-format off
642 constexpr uint8_t kTestMessage[] = {
643 // Header
644 0x00, 0x01, // ID = 1
645 0x84, 0x00, // FLAGS = AA | RESPONSE
646 0x00, 0x00, // Questions = 0
647 0x00, 0x01, // Answers = 1
648 0x00, 0x00, // Authority = 0
649 0x00, 0x01, // Additional = 1
650 // Record 1
651 0x07, 'r', 'e', 'c', 'o', 'r', 'd', '1',
652 0x00,
653 0x00, 0x0c, // TYPE = PTR (12)
654 0x00, 0x01, // CLASS = IN (1)
655 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
656 0x00, 0x0f, // RDLENGTH = 15 bytes
657 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
658 0x05, 'l', 'o', 'c', 'a', 'l',
659 0x00,
660 // Record 2
661 0x07, 'r', 'e', 'c', 'o', 'r', 'd', '2',
662 0x00,
663 0x00, 0x01, // TYPE = A (1)
664 0x00, 0x01, // CLASS = IN (1)
665 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
666 0x00, 0x04, // RDLENGTH = 4 bytes
667 0xac, 0x00, 0x00, 0x01, // 172.0.0.1
668 };
669 // clang-format on
670
671 MdnsRecord record1(DomainName{"record1"}, DnsType::kPTR, DnsClass::kIN,
672 RecordType::kShared, kTtl,
673 PtrRecordRdata(DomainName{"testing", "local"}));
674 MdnsRecord record2(DomainName{"record2"}, DnsType::kA, DnsClass::kIN,
675 RecordType::kShared, kTtl,
676 ARecordRdata(IPAddress{172, 0, 0, 1}));
677 MdnsMessage message(1, MessageType::Response, std::vector<MdnsQuestion>{},
678 std::vector<MdnsRecord>{record1},
679 std::vector<MdnsRecord>{},
680 std::vector<MdnsRecord>{record2});
681 TestReadEntrySucceeds(kTestMessage, sizeof(kTestMessage), message);
682 }
683
TEST(MdnsReaderTest,ReadMdnsMessage_MissingAnswerRecord)684 TEST(MdnsReaderTest, ReadMdnsMessage_MissingAnswerRecord) {
685 // clang-format off
686 constexpr uint8_t kInvalidMessage[] = {
687 0x00, 0x00, // ID = 0
688 0x00, 0x00, // FLAGS = 0
689 0x00, 0x01, // Questions = 1
690 0x00, 0x01, // Answers = 1
691 0x00, 0x00, // Authority = 0
692 0x00, 0x00, // Additional = 0
693 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
694 0x05, 'l', 'o', 'c', 'a', 'l',
695 0x00,
696 0x00, 0x0c, // TYPE = PTR (12)
697 0x00, 0x01, // CLASS = IN (1)
698 // NOTE: Missing answer record
699 };
700 // clang-format on
701 TestReadEntryFails<MdnsMessage>(kInvalidMessage, sizeof(kInvalidMessage));
702 }
703
TEST(MdnsReaderTest,ReadMdnsMessage_MissingAdditionalRecord)704 TEST(MdnsReaderTest, ReadMdnsMessage_MissingAdditionalRecord) {
705 // clang-format off
706 constexpr uint8_t kInvalidMessage[] = {
707 0x00, 0x00, // ID = 0
708 0x00, 0x00, // FLAGS = 0
709 0x00, 0x00, // Questions = 0
710 0x00, 0x00, // Answers = 0
711 0x00, 0x00, // Authority = 0
712 0x00, 0x02, // Additional = 2
713 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
714 0x05, 'l', 'o', 'c', 'a', 'l',
715 0x00,
716 0x00, 0x0c, // TYPE = PTR (12)
717 0x00, 0x01, // CLASS = IN (1)
718 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds
719 0x00, 0x00, // RDLENGTH = 0
720 // NOTE: Only 1 answer record is given.
721 };
722 // clang-format on
723 TestReadEntryFails<MdnsMessage>(kInvalidMessage, sizeof(kInvalidMessage));
724 }
725
726 } // namespace discovery
727 } // namespace openscreen
728