| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cast/common/mdns/mdns_reader.h" |
| |
| #include <memory> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| namespace cast { |
| namespace mdns { |
| |
| namespace { |
| |
| template <class T> |
| void TestReadEntrySucceeds(const uint8_t* data, |
| size_t size, |
| const T& expected) { |
| MdnsReader reader(data, size); |
| T entry; |
| EXPECT_TRUE(reader.Read(&entry)); |
| EXPECT_EQ(entry, expected); |
| EXPECT_EQ(reader.remaining(), UINT64_C(0)); |
| } |
| |
| template <class T> |
| void TestReadEntryFails(const uint8_t* data, size_t size) { |
| MdnsReader reader(data, size); |
| T entry; |
| EXPECT_FALSE(reader.Read(&entry)); |
| // There should be no side effects for failing to read an entry. The |
| // underlying pointer should not have changed. |
| EXPECT_EQ(reader.offset(), UINT64_C(0)); |
| } |
| |
| } // namespace |
| |
| TEST(MdnsReaderTest, ReadDomainName) { |
| constexpr uint8_t kMessage[] = { |
| // First name |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', // Byte: 0 |
| 0x05, 'l', 'o', 'c', 'a', 'l', // Byte: 8 |
| 0x00, // Byte: 14 |
| // Second name |
| 0x07, 's', 'e', 'r', 'v', 'i', 'c', 'e', // Byte: 15 |
| 0xc0, 0x00, // Byte: 23 |
| // Third name |
| 0x06, 'd', 'e', 'v', 'i', 'c', 'e', // Byte: 25 |
| 0xc0, 0x0f, // Byte: 32 |
| // Fourth name |
| 0xc0, 0x20, // Byte: 34 |
| }; |
| MdnsReader reader(kMessage, sizeof(kMessage)); |
| EXPECT_EQ(reader.begin(), kMessage); |
| EXPECT_EQ(reader.length(), sizeof(kMessage)); |
| EXPECT_EQ(reader.offset(), 0u); |
| DomainName name; |
| EXPECT_TRUE(reader.Read(&name)); |
| EXPECT_EQ(name.ToString(), "testing.local"); |
| EXPECT_TRUE(reader.Read(&name)); |
| EXPECT_EQ(name.ToString(), "service.testing.local"); |
| EXPECT_TRUE(reader.Read(&name)); |
| EXPECT_EQ(name.ToString(), "device.service.testing.local"); |
| EXPECT_TRUE(reader.Read(&name)); |
| EXPECT_EQ(name.ToString(), "service.testing.local"); |
| EXPECT_EQ(reader.offset(), sizeof(kMessage)); |
| EXPECT_EQ(0UL, reader.remaining()); |
| EXPECT_FALSE(reader.Read(&name)); |
| } |
| |
| TEST(MdnsReaderTest, ReadDomainName_Empty) { |
| constexpr uint8_t kDomainName[] = {0x00}; |
| TestReadEntrySucceeds(kDomainName, sizeof(kDomainName), DomainName()); |
| } |
| |
| TEST(MdnsReaderTest, ReadDomainName_TooShort) { |
| // Length 0x03 is longer than available data for the domain name |
| constexpr uint8_t kDomainName[] = {0x03, 'a', 'b'}; |
| TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName)); |
| } |
| |
| TEST(MdnsReaderTest, ReadDomainName_TooLong) { |
| std::vector<uint8_t> kDomainName; |
| for (uint8_t letter = 'a'; letter <= 'z'; ++letter) { |
| constexpr uint8_t repetitions = 10; |
| kDomainName.push_back(repetitions); |
| kDomainName.insert(kDomainName.end(), repetitions, letter); |
| } |
| kDomainName.push_back(0); |
| TestReadEntryFails<DomainName>(kDomainName.data(), kDomainName.size()); |
| } |
| |
| TEST(MdnsReaderTest, ReadDomainName_LabelPointerOutOfBounds) { |
| constexpr uint8_t kDomainName[] = {0xc0, 0x02}; |
| TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName)); |
| } |
| |
| TEST(MdnsReaderTest, ReadDomainName_InvalidLabel) { |
| constexpr uint8_t kDomainName[] = {0x80}; |
| TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName)); |
| } |
| |
| TEST(MdnsReaderTest, ReadDomainName_CircularCompression) { |
| // clang-format off |
| constexpr uint8_t kDomainName[] = { |
| // NOTE: Circular label pointer at end of name. |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', // Byte: 0 |
| 0x05, 'l', 'o', 'c', 'a', 'l', // Byte: 8 |
| 0xc0, 0x00, // Byte: 14 |
| }; |
| // clang-format on |
| TestReadEntryFails<DomainName>(kDomainName, sizeof(kDomainName)); |
| } |
| |
| TEST(MdnsReaderTest, ReadRawRecordRdata) { |
| // clang-format off |
| constexpr uint8_t kRawRecordRdata[] = { |
| 0x00, 0x08, // RDLENGTH = 8 bytes |
| 0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00, |
| }; |
| // clang-format on |
| TestReadEntrySucceeds( |
| kRawRecordRdata, sizeof(kRawRecordRdata), |
| RawRecordRdata(kRawRecordRdata + 2, sizeof(kRawRecordRdata) - 2)); |
| } |
| |
| TEST(MdnsReaderTest, ReadRawRecord_Empty) { |
| // clang-format off |
| constexpr uint8_t kRawRecordRdata[] = { |
| 0x00, 0x00, // RDLENGTH = 0 bytes |
| }; |
| // clang-format on |
| TestReadEntrySucceeds(kRawRecordRdata, sizeof(kRawRecordRdata), |
| RawRecordRdata()); |
| } |
| |
| TEST(MdnsReaderTest, ReadRawRecordRdata_TooShort) { |
| // clang-format off |
| constexpr uint8_t kRawRecordRdata[] = { |
| 0x00, 0x05, // RDLENGTH = 5 bytes is longer that available data |
| 0x03, 'f', 'o', 'o' |
| }; |
| // clang-format on |
| TestReadEntryFails<RawRecordRdata>(kRawRecordRdata, sizeof(kRawRecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadSrvRecordRdata) { |
| // clang-format off |
| constexpr uint8_t kSrvRecordRdata[] = { |
| 0x00, 0x15, // RDLENGTH = 21 |
| 0x00, 0x05, // PRIORITY = 5 |
| 0x00, 0x06, // WEIGHT = 6 |
| 0x1f, 0x49, // PORT = 8009 |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, |
| }; |
| // clang-format on |
| TestReadEntrySucceeds( |
| kSrvRecordRdata, sizeof(kSrvRecordRdata), |
| SrvRecordRdata(5, 6, 8009, DomainName{"testing", "local"})); |
| } |
| |
| TEST(MdnsReaderTest, ReadSrvRecordRdata_TooShort) { |
| // clang-format off |
| constexpr uint8_t kSrvRecordRdata[] = { |
| 0x00, 0x14, // RDLENGTH = 21 |
| 0x00, 0x05, // PRIORITY = 5 |
| 0x00, 0x06, // WEIGHT = 6 |
| // Missing bytes |
| }; |
| // clang-format on |
| TestReadEntryFails<SrvRecordRdata>(kSrvRecordRdata, sizeof(kSrvRecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadSrvRecordRdata_WrongLength) { |
| // clang-format off |
| constexpr uint8_t kSrvRecordRdata[] = { |
| 0x00, 0x14, // RDLENGTH = 20 |
| 0x00, 0x05, // PRIORITY = 5 |
| 0x00, 0x06, // WEIGHT = 6 |
| 0x1f, 0x49, // PORT = 8009 |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, |
| }; |
| // clang-format on |
| TestReadEntryFails<SrvRecordRdata>(kSrvRecordRdata, sizeof(kSrvRecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadARecordRdata) { |
| constexpr uint8_t kARecordRdata[] = { |
| 0x00, 0x4, // RDLENGTH = 4 |
| 0x08, 0x08, 0x08, 0x08, // ADDRESS = 8.8.8.8 |
| }; |
| TestReadEntrySucceeds(kARecordRdata, sizeof(kARecordRdata), |
| ARecordRdata(IPAddress{8, 8, 8, 8})); |
| } |
| |
| TEST(MdnsReaderTest, ReadARecordRdata_TooShort) { |
| constexpr uint8_t kARecordRdata[] = { |
| 0x00, 0x4, // RDLENGTH = 4 |
| 0x08, 0x08, // Address missing bytes |
| }; |
| TestReadEntryFails<ARecordRdata>(kARecordRdata, sizeof(kARecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadARecordRdata_WrongLength) { |
| constexpr uint8_t kARecordRdata[] = { |
| 0x00, 0x5, // RDLENGTH = 5 |
| 0x08, 0x08, 0x08, 0x08, // ADDRESS = 8.8.8.8 |
| }; |
| TestReadEntryFails<ARecordRdata>(kARecordRdata, sizeof(kARecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadAAAARecordRdata) { |
| // clang-format off |
| constexpr uint8_t kAAAARecordRdata[] = { |
| 0x00, 0x10, // RDLENGTH = 16 |
| // ADDRESS = FE80:0000:0000:0000:0202:B3FF:FE1E:8329 |
| 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x02, 0x02, 0xb3, 0xff, 0xfe, 0x1e, 0x83, 0x29, |
| }; |
| // clang-format on |
| TestReadEntrySucceeds(kAAAARecordRdata, sizeof(kAAAARecordRdata), |
| AAAARecordRdata(IPAddress(IPAddress::Version::kV6, |
| kAAAARecordRdata + 2))); |
| } |
| |
| TEST(MdnsReaderTest, ReadAAAARecordRdata_TooShort) { |
| // clang-format off |
| constexpr uint8_t kAAAARecordRdata[] = { |
| 0x00, 0x10, // RDLENGTH = 16 |
| // ADDRESS missing bytes |
| 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| // clang-format on |
| TestReadEntryFails<AAAARecordRdata>(kAAAARecordRdata, |
| sizeof(kAAAARecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadAAAARecordRdata_WrongLength) { |
| // clang-format off |
| constexpr uint8_t kAAAARecordRdata[] = { |
| 0x00, 0x11, // RDLENGTH = 17 |
| // ADDRESS = FE80:0000:0000:0000:0202:B3FF:FE1E:8329 |
| 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x02, 0x02, 0xb3, 0xff, 0xfe, 0x1e, 0x83, 0x29, |
| }; |
| // clang-format on |
| TestReadEntryFails<AAAARecordRdata>(kAAAARecordRdata, |
| sizeof(kAAAARecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadPtrRecordRdata) { |
| // clang-format off |
| constexpr uint8_t kPtrRecordRdata[] = { |
| 0x00, 0x18, // RDLENGTH = 24 |
| 0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e', |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| }; |
| // clang-format on |
| TestReadEntrySucceeds( |
| kPtrRecordRdata, sizeof(kPtrRecordRdata), |
| PtrRecordRdata(DomainName{"mydevice", "testing", "local"})); |
| } |
| |
| TEST(MdnsReaderTest, ReadPtrRecordRdata_TooShort) { |
| // clang-format off |
| constexpr uint8_t kPtrRecordRdata[] = { |
| 0x00, 0x18, // RDLENGTH = 24 |
| 0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e', |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c' |
| // Missing bytes |
| }; |
| // clang-format on |
| TestReadEntryFails<PtrRecordRdata>(kPtrRecordRdata, sizeof(kPtrRecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadPtrRecordRdata_WrongLength) { |
| // clang-format off |
| constexpr uint8_t kPtrRecordRdata[] = { |
| 0x00, 0x18, // RDLENGTH = 24 |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| }; |
| // clang-format on |
| TestReadEntryFails<PtrRecordRdata>(kPtrRecordRdata, sizeof(kPtrRecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadTxtRecordRdata) { |
| // clang-format off |
| constexpr uint8_t kTxtRecordRdata[] = { |
| 0x00, 0x0C, // RDLENGTH = 12 |
| 0x05, 'f', 'o', 'o', '=', '1', |
| 0x05, 'b', 'a', 'r', '=', '2', |
| }; |
| // clang-format on |
| TestReadEntrySucceeds(kTxtRecordRdata, sizeof(kTxtRecordRdata), |
| TxtRecordRdata{"foo=1", "bar=2"}); |
| } |
| |
| TEST(MdnsReaderTest, ReadTxtRecordRdata_Empty) { |
| constexpr uint8_t kTxtRecordRdata[] = { |
| 0x00, 0x01, // RDLENGTH = 1 |
| 0x00, // empty string |
| }; |
| TestReadEntrySucceeds(kTxtRecordRdata, sizeof(kTxtRecordRdata), |
| TxtRecordRdata()); |
| } |
| |
| TEST(MdnsReaderTest, ReadTxtRecordRdata_WithNullInTheMiddle) { |
| // clang-format off |
| constexpr uint8_t kTxtRecordRdata[] = { |
| 0x00, 0x10, // RDLENGTH = 16 |
| 0x09, 'w', 'i', 't', 'h', '\0', 'N', 'U', 'L', 'L', |
| 0x05, 'o', 't', 'h', 'e', 'r', |
| }; |
| // clang-format on |
| TestReadEntrySucceeds( |
| kTxtRecordRdata, sizeof(kTxtRecordRdata), |
| TxtRecordRdata{absl::string_view("with\0NULL", 9), "other"}); |
| } |
| |
| TEST(MdnsReaderTest, ReadTxtRecordRdata_EmptyEntries) { |
| // clang-format off |
| constexpr uint8_t kTxtRecordRdata[] = { |
| 0x00, 0x0F, // RDLENGTH = 12 |
| 0x05, 'f', 'o', 'o', '=', '1', |
| 0x00, |
| 0x00, |
| 0x05, 'b', 'a', 'r', '=', '2', |
| 0x00, |
| }; |
| // clang-format on |
| TestReadEntrySucceeds(kTxtRecordRdata, sizeof(kTxtRecordRdata), |
| TxtRecordRdata{"foo=1", "bar=2"}); |
| } |
| |
| TEST(MdnsReaderTest, ReadTxtRecordRdata_TooShort) { |
| // clang-format off |
| constexpr uint8_t kTxtRecordRdata[] = { |
| 0x00, 0x0C, // RDLENGTH = 12 |
| 0x05, 'f', 'o', 'o', '=', '1', |
| }; |
| // clang-format on |
| TestReadEntryFails<TxtRecordRdata>(kTxtRecordRdata, sizeof(kTxtRecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadTxtRecordRdata_WrongLength) { |
| // clang-format off |
| constexpr uint8_t kTxtRecordRdata[] = { |
| 0x00, 0x0F, // Wrong length specified |
| 0x05, 'f', 'o', 'o', '=', '1', |
| 0x05, 'b', 'a', 'r', '=', '2', |
| }; |
| // clang-format on |
| TestReadEntryFails<TxtRecordRdata>(kTxtRecordRdata, sizeof(kTxtRecordRdata)); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsRecord_ARecordRdata) { |
| // clang-format off |
| constexpr uint8_t kTestRecord[] = { |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x01, // TYPE = A (1) |
| 0x80, 0x01, // CLASS = IN (1) | CACHE_FLUSH_BIT |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x04, // RDLENGTH = 4 bytes |
| 0x08, 0x08, 0x08, 0x08, // RDATA = 8.8.8.8 |
| }; |
| // clang-format on |
| TestReadEntrySucceeds(kTestRecord, sizeof(kTestRecord), |
| MdnsRecord(DomainName{"testing", "local"}, DnsType::kA, |
| DnsClass::kIN, RecordType::kUnique, 120, |
| ARecordRdata(IPAddress{8, 8, 8, 8}))); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsRecord_UnknownRecordType) { |
| // clang-format off |
| constexpr uint8_t kTestRecord[] = { |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x05, // TYPE = CNAME (5) |
| 0x80, 0x01, // CLASS = IN (1) | CACHE_FLUSH_BIT |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x08, // RDLENGTH = 8 bytes |
| 0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00, |
| }; |
| constexpr uint8_t kCnameRdata[] = { |
| 0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00, |
| }; |
| // clang-format on |
| TestReadEntrySucceeds( |
| kTestRecord, sizeof(kTestRecord), |
| MdnsRecord(DomainName{"testing", "local"}, |
| static_cast<DnsType>(5) /*CNAME class*/, DnsClass::kIN, |
| RecordType::kUnique, 120, |
| RawRecordRdata(kCnameRdata, sizeof(kCnameRdata)))); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsRecord_CompressedNames) { |
| // clang-format off |
| constexpr uint8_t kTestRecord[] = { |
| // First message |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x0c, // TYPE = PTR (12) |
| 0x00, 0x01, // CLASS = IN (1) |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x06, // RDLENGTH = 6 bytes |
| 0x03, 'p', 't', 'r', |
| 0xc0, 0x00, // Domain name label pointer to byte 0 |
| // Second message |
| 0x03, 'o', 'n', 'e', |
| 0x03, 't', 'w', 'o', |
| 0xc0, 0x00, // Domain name label pointer to byte 0 |
| 0x00, 0x01, // TYPE = A (1) |
| 0x80, 0x01, // CLASS = IN (1) | CACHE_FLUSH_BIT |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x04, // RDLENGTH = 4 bytes |
| 0x08, 0x08, 0x08, 0x08, // RDATA = 8.8.8.8 |
| }; |
| // clang-format on |
| MdnsReader reader(kTestRecord, sizeof(kTestRecord)); |
| |
| MdnsRecord record; |
| EXPECT_TRUE(reader.Read(&record)); |
| EXPECT_EQ(record, |
| MdnsRecord(DomainName{"testing", "local"}, DnsType::kPTR, |
| DnsClass::kIN, RecordType::kShared, 120, |
| PtrRecordRdata(DomainName{"ptr", "testing", "local"}))); |
| EXPECT_TRUE(reader.Read(&record)); |
| EXPECT_EQ(record, MdnsRecord(DomainName{"one", "two", "testing", "local"}, |
| DnsType::kA, DnsClass::kIN, RecordType::kUnique, |
| 120, ARecordRdata(IPAddress{8, 8, 8, 8}))); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsRecord_MissingRdata) { |
| // clang-format off |
| constexpr uint8_t kTestRecord[] = { |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x01, // TYPE = A (1) |
| 0x00, 0x01, // CLASS = IN (1) |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x04, // RDLENGTH = 4 bytes |
| // Missing RDATA |
| }; |
| // clang-format on |
| TestReadEntryFails<MdnsRecord>(kTestRecord, sizeof(kTestRecord)); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsRecord_InvalidHostName) { |
| // clang-format off |
| constexpr uint8_t kTestRecord[] = { |
| // Invalid NAME: length byte too short |
| 0x03, 'i', 'n', 'v', 'a', 'l', 'i', 'd', |
| 0x00, |
| 0x00, 0x01, // TYPE = A (1) |
| 0x00, 0x01, // CLASS = IN (1) |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x04, // RDLENGTH = 4 bytes |
| 0x08, 0x08, 0x08, 0x08, // RDATA = 8.8.8.8 |
| }; |
| // clang-format on |
| TestReadEntryFails<MdnsRecord>(kTestRecord, sizeof(kTestRecord)); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsQuestion) { |
| // clang-format off |
| constexpr uint8_t kTestQuestion[] = { |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x01, // TYPE = A (1) |
| 0x80, 0x01, // CLASS = IN (1) | UNICAST_BIT |
| }; |
| // clang-format on |
| TestReadEntrySucceeds( |
| kTestQuestion, sizeof(kTestQuestion), |
| MdnsQuestion(DomainName{"testing", "local"}, DnsType::kA, DnsClass::kIN, |
| ResponseType::kUnicast)); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsQuestion_CompressedNames) { |
| // clang-format off |
| constexpr uint8_t kTestQuestions[] = { |
| // First Question |
| 0x05, 'f', 'i', 'r', 's', 't', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x01, // TYPE = A (1) |
| 0x80, 0x01, // CLASS = IN (1) | UNICAST_BIT |
| // Second Question |
| 0x06, 's', 'e', 'c', 'o', 'n', 'd', |
| 0xc0, 0x06, // Domain name label pointer |
| 0x00, 0x0c, // TYPE = PTR (12) |
| 0x00, 0x01, // CLASS = IN (1) |
| }; |
| // clang-format on |
| MdnsReader reader(kTestQuestions, sizeof(kTestQuestions)); |
| MdnsQuestion question; |
| EXPECT_TRUE(reader.Read(&question)); |
| EXPECT_EQ(question, MdnsQuestion(DomainName{"first", "local"}, DnsType::kA, |
| DnsClass::kIN, ResponseType::kUnicast)); |
| EXPECT_TRUE(reader.Read(&question)); |
| EXPECT_EQ(question, MdnsQuestion(DomainName{"second", "local"}, DnsType::kPTR, |
| DnsClass::kIN, ResponseType::kMulticast)); |
| EXPECT_EQ(reader.remaining(), UINT64_C(0)); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsQuestion_InvalidHostName) { |
| // clang-format off |
| constexpr uint8_t kTestQuestion[] = { |
| // Invalid NAME: length byte too short |
| 0x03, 'i', 'n', 'v', 'a', 'l', 'i', 'd', |
| 0x00, |
| 0x00, 0x01, // TYPE = A (1) |
| 0x00, 0x01, // CLASS = IN (1) |
| }; |
| // clang-format on |
| TestReadEntryFails<MdnsQuestion>(kTestQuestion, sizeof(kTestQuestion)); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsMessage) { |
| // clang-format off |
| constexpr uint8_t kTestMessage[] = { |
| // Header |
| 0x00, 0x01, // ID = 1 |
| 0x84, 0x00, // FLAGS = AA | RESPONSE |
| 0x00, 0x00, // Questions = 0 |
| 0x00, 0x01, // Answers = 1 |
| 0x00, 0x00, // Authority = 0 |
| 0x00, 0x01, // Additional = 1 |
| // Record 1 |
| 0x07, 'r', 'e', 'c', 'o', 'r', 'd', '1', |
| 0x00, |
| 0x00, 0x0c, // TYPE = PTR (12) |
| 0x00, 0x01, // CLASS = IN (1) |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x0f, // RDLENGTH = 15 bytes |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| // Record 2 |
| 0x07, 'r', 'e', 'c', 'o', 'r', 'd', '2', |
| 0x00, |
| 0x00, 0x01, // TYPE = A (1) |
| 0x00, 0x01, // CLASS = IN (1) |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x04, // RDLENGTH = 4 bytes |
| 0xac, 0x00, 0x00, 0x01, // 172.0.0.1 |
| }; |
| // clang-format on |
| |
| MdnsRecord record1(DomainName{"record1"}, DnsType::kPTR, DnsClass::kIN, |
| RecordType::kShared, 120, |
| PtrRecordRdata(DomainName{"testing", "local"})); |
| MdnsRecord record2(DomainName{"record2"}, DnsType::kA, DnsClass::kIN, |
| RecordType::kShared, 120, |
| ARecordRdata(IPAddress{172, 0, 0, 1})); |
| MdnsMessage message(1, MessageType::Response, std::vector<MdnsQuestion>{}, |
| std::vector<MdnsRecord>{record1}, |
| std::vector<MdnsRecord>{}, |
| std::vector<MdnsRecord>{record2}); |
| TestReadEntrySucceeds(kTestMessage, sizeof(kTestMessage), message); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsMessage_MissingAnswerRecord) { |
| // clang-format off |
| constexpr uint8_t kInvalidMessage[] = { |
| 0x00, 0x00, // ID = 0 |
| 0x00, 0x00, // FLAGS = 0 |
| 0x00, 0x01, // Questions = 1 |
| 0x00, 0x01, // Answers = 1 |
| 0x00, 0x00, // Authority = 0 |
| 0x00, 0x00, // Additional = 0 |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x0c, // TYPE = PTR (12) |
| 0x00, 0x01, // CLASS = IN (1) |
| // NOTE: Missing answer record |
| }; |
| // clang-format on |
| TestReadEntryFails<MdnsMessage>(kInvalidMessage, sizeof(kInvalidMessage)); |
| } |
| |
| TEST(MdnsReaderTest, ReadMdnsMessage_MissingAdditionalRecord) { |
| // clang-format off |
| constexpr uint8_t kInvalidMessage[] = { |
| 0x00, 0x00, // ID = 0 |
| 0x00, 0x00, // FLAGS = 0 |
| 0x00, 0x00, // Questions = 0 |
| 0x00, 0x00, // Answers = 0 |
| 0x00, 0x00, // Authority = 0 |
| 0x00, 0x02, // Additional = 2 |
| 0x07, 't', 'e', 's', 't', 'i', 'n', 'g', |
| 0x05, 'l', 'o', 'c', 'a', 'l', |
| 0x00, |
| 0x00, 0x0c, // TYPE = PTR (12) |
| 0x00, 0x01, // CLASS = IN (1) |
| 0x00, 0x00, 0x00, 0x78, // TTL = 120 seconds |
| 0x00, 0x00, // RDLENGTH = 0 |
| // NOTE: Only 1 answer record is given. |
| }; |
| // clang-format on |
| TestReadEntryFails<MdnsMessage>(kInvalidMessage, sizeof(kInvalidMessage)); |
| } |
| |
| } // namespace mdns |
| } // namespace cast |