You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

728 lines
25 KiB

// 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 "discovery/mdns/mdns_reader.h"
#include <memory>
#include "discovery/common/config.h"
#include "discovery/mdns/testing/mdns_test_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace openscreen {
namespace discovery {
namespace {
constexpr std::chrono::seconds kTtl{120};
template <class T>
void TestReadEntrySucceeds(const uint8_t* data,
size_t size,
const T& expected) {
Config config;
MdnsReader reader(config, data, size);
T entry;
EXPECT_TRUE(reader.Read(&entry));
EXPECT_EQ(entry, expected);
EXPECT_EQ(reader.remaining(), UINT64_C(0));
}
template <>
void TestReadEntrySucceeds<MdnsMessage>(const uint8_t* data,
size_t size,
const MdnsMessage& expected) {
MdnsReader reader(Config{}, data, size);
const ErrorOr<MdnsMessage> message = reader.Read();
EXPECT_TRUE(message.is_value());
EXPECT_EQ(message.value(), expected);
EXPECT_EQ(reader.remaining(), UINT64_C(0));
}
template <class T>
void TestReadEntryFails(const uint8_t* data, size_t size) {
Config config;
MdnsReader reader(config, 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));
}
template <>
void TestReadEntryFails<MdnsMessage>(const uint8_t* data, size_t size) {
Config config;
MdnsReader reader(config, data, size);
const ErrorOr<MdnsMessage> message = reader.Read();
EXPECT_TRUE(message.is_error());
// 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
};
Config config;
MdnsReader reader(config, 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, ReadRawRecordRdata_TooLong) {
// clang-format off
constexpr uint8_t kRawRecordRdata[] = {
0x00, 0x08, // RDLENGTH = 8 bytes
0x05, 'c', 'n', 'a', 'm', 'e', 0xc0, 0x00,
};
// clang-format on
Config config;
config.maximum_valid_rdata_size = 1;
MdnsReader reader(config, kRawRecordRdata, sizeof(kRawRecordRdata));
RawRecordRdata 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));
}
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),
MakeTxtRecord({"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),
MakeTxtRecord({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),
MakeTxtRecord({"foo=1", "bar=2"}));
}
TEST(MdnsReaderTest, ReadTxtRecordRdata_TooLong) {
// clang-format off
constexpr uint8_t kTxtRecordRdata[] = {
0x00, 0x0C, // RDLENGTH = 12
0x05, 'f', 'o', 'o', '=', '1',
0x05, 'b', 'a', 'r', '=', '2',
};
// clang-format on
Config config;
config.maximum_valid_rdata_size = 1;
MdnsReader reader(config, kTxtRecordRdata, sizeof(kTxtRecordRdata));
TxtRecordRdata 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));
}
TEST(MdnsReaderTest, ReadNsecRecordRdata) {
// clang-format off
constexpr uint8_t kExpectedRdata[] = {
0x00, 0x20, // RDLENGTH = 32
0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
0x05, 'l', 'o', 'c', 'a', 'l',
0x00,
// It takes 8 bytes to encode the kA and kSRV records because:
// - Both record types have value less than 256, so they are both in window
// block 1.
// - The bitmap length for this block is always a single byte
// - DnsTypes have the following values:
// - kA = 1 (encoded in byte 1)
// kTXT = 16 (encoded in byte 3)
// - kSRV = 33 (encoded in byte 5)
// - kNSEC = 47 (encoded in 6 bytes)
// - The largest of these is 47, so 6 bytes are needed to encode this data.
// So the full encoded version is:
// 00000000 00000110 01000000 00000000 10000000 00000000 0100000 00000001
// |window| | size | | 0-7 | | 8-15 | |16-23 | |24-31 | |32-39 | |40-47 |
0x00, 0x06, 0x40, 0x00, 0x80, 0x00, 0x40, 0x01
};
// clang-format on
TestReadEntrySucceeds(
kExpectedRdata, sizeof(kExpectedRdata),
NsecRecordRdata(DomainName{"mydevice", "testing", "local"}, DnsType::kA,
DnsType::kTXT, DnsType::kSRV, DnsType::kNSEC));
}
TEST(MdnsReaderTest, ReadNsecRecordRdata_TooShort) {
// clang-format off
constexpr uint8_t kNsecRecordRdata[] = {
0x00, 0x20, // RDLENGTH = 32
0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
0x05, 'l', 'o', 'c', 'a', 'l',
0x00,
0x00, 0x06, 0x40, 0x00
};
// clang-format on
TestReadEntryFails<NsecRecordRdata>(kNsecRecordRdata,
sizeof(kNsecRecordRdata));
}
TEST(MdnsReaderTest, ReadNsecRecordRdata_WrongLength) {
// clang-format off
constexpr uint8_t kNsecRecordRdata[] = {
0x00, 0x21, // RDLENGTH = 33
0x08, 'm', 'y', 'd', 'e', 'v', 'i', 'c', 'e',
0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
0x05, 'l', 'o', 'c', 'a', 'l',
0x00,
0x00, 0x06, 0x40, 0x00, 0x80, 0x00, 0x40, 0x01
};
// clang-format on
TestReadEntryFails<NsecRecordRdata>(kNsecRecordRdata,
sizeof(kNsecRecordRdata));
}
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, kTtl,
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, kTtl,
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
Config config;
MdnsReader reader(config, kTestRecord, sizeof(kTestRecord));
MdnsRecord record;
EXPECT_TRUE(reader.Read(&record));
EXPECT_EQ(record,
MdnsRecord(DomainName{"testing", "local"}, DnsType::kPTR,
DnsClass::kIN, RecordType::kShared, kTtl,
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,
kTtl, 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
Config config;
MdnsReader reader(config, 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, kTtl,
PtrRecordRdata(DomainName{"testing", "local"}));
MdnsRecord record2(DomainName{"record2"}, DnsType::kA, DnsClass::kIN,
RecordType::kShared, kTtl,
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 discovery
} // namespace openscreen