/* * Copyright (c) 2020, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "test_platform.h" #include "test_util.hpp" #include "common/instance.hpp" #include "net/dns_types.hpp" namespace ot { void TestDnsName(void) { enum { kMaxSize = 300, kMaxNameLength = Dns::Name::kMaxNameSize - 1, }; struct TestName { const char * mName; uint16_t mEncodedLength; const uint8_t *mEncodedData; const char ** mLabels; const char * mExpectedReadName; }; Instance * instance; MessagePool *messagePool; Message * message; uint8_t buffer[kMaxSize]; uint16_t len; uint16_t offset; char label[Dns::Name::kMaxLabelSize]; uint8_t labelLength; char name[Dns::Name::kMaxNameSize]; static const uint8_t kEncodedName1[] = {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0}; static const uint8_t kEncodedName2[] = {3, 'f', 'o', 'o', 1, 'a', 2, 'b', 'b', 3, 'e', 'd', 'u', 0}; static const uint8_t kEncodedName3[] = {10, 'f', 'o', 'u', 'n', 'd', 'a', 't', 'i', 'o', 'n', 0}; static const uint8_t kEncodedName4[] = {0}; static const char *kLabels1[] = {"example", "com", nullptr}; static const char *kLabels2[] = {"foo", "a", "bb", "edu", nullptr}; static const char *kLabels3[] = {"foundation", nullptr}; static const char *kLabels4[] = {nullptr}; static const TestName kTestNames[] = { {"example.com", sizeof(kEncodedName1), kEncodedName1, kLabels1, "example.com."}, {"example.com.", sizeof(kEncodedName1), kEncodedName1, kLabels1, "example.com."}, {"foo.a.bb.edu", sizeof(kEncodedName2), kEncodedName2, kLabels2, "foo.a.bb.edu."}, {"foo.a.bb.edu.", sizeof(kEncodedName2), kEncodedName2, kLabels2, "foo.a.bb.edu."}, {"foundation", sizeof(kEncodedName3), kEncodedName3, kLabels3, "foundation."}, {"foundation.", sizeof(kEncodedName3), kEncodedName3, kLabels3, "foundation."}, {"", sizeof(kEncodedName4), kEncodedName4, kLabels4, "."}, {".", sizeof(kEncodedName4), kEncodedName4, kLabels4, "."}, {nullptr, sizeof(kEncodedName4), kEncodedName4, kLabels4, "."}, }; static const char *kMaxLengthNames[] = { "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken." "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken." "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken." "SorcererAndMagicia.", "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken." "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken." "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken." "SorcererAndMagicia", }; static const char *kInvalidNames[] = { "foo..bar", "..", "a..", "..b", // Long label "a.an-invalid-very-long-label-string-with-more-than-sixty-four-characters.com", // Long name (more than 255 characters) "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken." "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken." "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken." "SorcererAndMagician.NoEyesCanEverSee.AnArtfulConjurer.MySenseFromMeTaken." "MyEyesWillNeverSee.BeautiesOfTheWholeWorld.BeholdWhoseVisionFine.MySightFromMeTaken" "PoemByRumiMolana", // Long name of 255 characters which ends with a dot "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken." "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken." "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken." "SorcererAndMagician.", // Long name of 254 characters which does not end with a dot "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken." "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken." "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken." "SorcererAndMagician", }; static const char kBadLabel[] = "badlabel"; static const char kBadName[] = "bad.name"; printf("================================================================\n"); printf("TestDnsName()\n"); instance = static_cast(testInitInstance()); VerifyOrQuit(instance != nullptr, "Null OpenThread instance"); messagePool = &instance->Get(); VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed"); message->SetOffset(0); printf("----------------------------------------------------------------\n"); printf("Verify domain name match:\n"); { const char *subDomain; const char *domain; subDomain = "my-service._ipps._tcp.local."; domain = "local."; VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); subDomain = "my-service._ipps._tcp.local"; domain = "local."; VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); subDomain = "my-service._ipps._tcp.local."; domain = "local"; VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); subDomain = "my-service._ipps._tcp.local"; domain = "local"; VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); subDomain = "my-service._ipps._tcp.default.service.arpa."; domain = "default.service.arpa."; VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); subDomain = "my-service._ipps._tcp.default.service.arpa."; domain = "service.arpa."; VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); // Verify it doesn't match a portion of a label. subDomain = "my-service._ipps._tcp.default.service.arpa."; domain = "vice.arpa."; VerifyOrQuit(!Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() succeed"); } printf("----------------------------------------------------------------\n"); printf("Append names, check encoded bytes, parse name and read labels:\n"); for (const TestName &test : kTestNames) { IgnoreError(message->SetLength(0)); SuccessOrQuit(Dns::Name::AppendName(test.mName, *message), "Name::AppendName() failed"); len = message->GetLength(); SuccessOrQuit(message->Read(0, buffer, len), "Message::Read() failed"); DumpBuffer(test.mName, buffer, len); VerifyOrQuit(len == test.mEncodedLength, "Encoded length does not match expected value"); VerifyOrQuit(memcmp(buffer, test.mEncodedData, len) == 0, "Encoded name data does not match expected data"); // Parse and skip over the name offset = 0; SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed"); VerifyOrQuit(offset == len, "Name::ParseName() returned incorrect offset"); // Read labels one by one. offset = 0; for (uint8_t index = 0; test.mLabels[index] != nullptr; index++) { labelLength = sizeof(label); SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed"); printf("Label[%d] = \"%s\"\n", index, label); VerifyOrQuit(strcmp(label, test.mLabels[index]) == 0, "Name::ReadLabel() did not get expected label"); VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length"); } labelLength = sizeof(label); VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound, "Name::ReadLabel() failed at end of the name"); // Read entire name offset = 0; SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed"); printf("Read name =\"%s\"\n", name); VerifyOrQuit(strcmp(name, test.mExpectedReadName) == 0, "Name::ReadName() did not get expected name"); VerifyOrQuit(offset == len, "Name::ReadName() returned incorrect offset"); // Read entire name with different name buffer sizes (just right and one byte off the expected size) offset = 0; SuccessOrQuit( Dns::Name::ReadName(*message, offset, name, static_cast(strlen(test.mExpectedReadName) + 1)), "Name::ReadName() failed with exact name buffer size"); offset = 0; VerifyOrQuit(Dns::Name::ReadName(*message, offset, name, static_cast(strlen(test.mExpectedReadName))) == kErrorNoBufs, "Name::ReadName() did not fail with too small name buffer size"); // Compare labels one by one. offset = 0; for (uint8_t index = 0; test.mLabels[index] != nullptr; index++) { uint16_t startOffset = offset; SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, test.mLabels[index]), "Name::CompareLabel() failed"); VerifyOrQuit(offset != startOffset, "Name::CompareLabel() did not change offset"); VerifyOrQuit(Dns::Name::CompareLabel(*message, startOffset, kBadLabel) == kErrorNotFound, "Name::CompareLabel() did not fail with incorrect label"); } // Compare the whole name. offset = 0; SuccessOrQuit(Dns::Name::CompareName(*message, offset, test.mExpectedReadName), "Name::CompareName() failed"); VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset"); offset = 0; VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound, "Name::CompareName() did not fail with incorrect name"); VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset"); // Remove the terminating '.' in expected name and verify // that it can still be used by `CompareName()`. offset = 0; strcpy(name, test.mExpectedReadName); name[strlen(name) - 1] = '\0'; SuccessOrQuit(Dns::Name::CompareName(*message, offset, name), "Name::CompareName() failed with root"); VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset"); if (strlen(name) >= 1) { name[strlen(name) - 1] = '\0'; offset = 0; VerifyOrQuit(Dns::Name::CompareName(*message, offset, name) == kErrorNotFound, "Name::CompareName() did not fail with invalid name"); VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset"); } // Compare the name with itself read from message. offset = 0; SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed"); VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset"); } printf("----------------------------------------------------------------\n"); printf("Max length names:\n"); for (const char *&maxLengthName : kMaxLengthNames) { if (maxLengthName[strlen(maxLengthName) - 1] == '.') { VerifyOrQuit(strlen(maxLengthName) == kMaxNameLength, "invalid max length string"); } else { VerifyOrQuit(strlen(maxLengthName) == kMaxNameLength - 1, "invalid max length string"); } IgnoreError(message->SetLength(0)); printf("\"%s\"\n", maxLengthName); VerifyOrQuit(Dns::Name::AppendName(maxLengthName, *message) == kErrorNone, "Name::AppendName() failed with max length name"); } printf("----------------------------------------------------------------\n"); printf("Invalid names:\n"); for (const char *&invalidName : kInvalidNames) { IgnoreError(message->SetLength(0)); printf("\"%s\"\n", invalidName); VerifyOrQuit(Dns::Name::AppendName(invalidName, *message) == kErrorInvalidArgs, "Name::AppendName() did not fail with an invalid name"); } printf("----------------------------------------------------------------\n"); printf("Append as multiple labels and terminator instead of full name:\n"); for (const TestName &test : kTestNames) { IgnoreError(message->SetLength(0)); SuccessOrQuit(Dns::Name::AppendMultipleLabels(test.mName, *message), "Name::Append() failed"); SuccessOrQuit(Dns::Name::AppendTerminator(*message), "Name::AppendTerminator() failed"); len = message->GetLength(); SuccessOrQuit(message->Read(0, buffer, len), "Message::Read() failed"); DumpBuffer(test.mName, buffer, len); VerifyOrQuit(len == test.mEncodedLength, "Encoded length does not match expected value"); VerifyOrQuit(memcmp(buffer, test.mEncodedData, len) == 0, "Encoded name data does not match expected data"); } printf("----------------------------------------------------------------\n"); printf("Append labels one by one:\n"); for (const TestName &test : kTestNames) { IgnoreError(message->SetLength(0)); for (uint8_t index = 0; test.mLabels[index] != nullptr; index++) { SuccessOrQuit(Dns::Name::AppendLabel(test.mLabels[index], *message), "Name::AppendLabel() failed"); } SuccessOrQuit(Dns::Name::AppendTerminator(*message), "Name::AppendTerminator() failed"); len = message->GetLength(); SuccessOrQuit(message->Read(0, buffer, len), "Message::Read() failed"); DumpBuffer(test.mName, buffer, len); VerifyOrQuit(len == test.mEncodedLength, "Encoded length does not match expected value"); VerifyOrQuit(memcmp(buffer, test.mEncodedData, len) == 0, "Encoded name data does not match expected data"); } message->Free(); testFreeInstance(instance); } void TestDnsCompressedName(void) { enum { kHeaderOffset = 10, kGuardBlockSize = 20, kMaxBufferSize = 100, kLabelSize = 64, kNameSize = 256, kName2EncodedSize = 4 + 2, // encoded "FOO" + pointer label (2 bytes) kName3EncodedSize = 2, // pointer label (2 bytes) kName4EncodedSize = 15 + 2, // encoded "Human.Readable" + pointer label (2 bytes). }; const char kName[] = "F.ISI.ARPA"; const char kLabel1[] = "FOO"; const char kInstanceLabel[] = "Human.Readable"; static const uint8_t kEncodedName[] = {1, 'F', 3, 'I', 'S', 'I', 4, 'A', 'R', 'P', 'A', 0}; static const uint8_t kIsiRelativeIndex = 2; // Index in kEncodedName to the start of "ISI.ARPA" portion. static const char *kName1Labels[] = {"F", "ISI", "ARPA"}; static const char *kName2Labels[] = {"FOO", "F", "ISI", "ARPA"}; static const char *kName3Labels[] = {"ISI", "ARPA"}; static const char *kName4Labels[] = {"Human.Readable", "F", "ISI", "ARPA"}; static const char kExpectedReadName1[] = "F.ISI.ARPA."; static const char kExpectedReadName2[] = "FOO.F.ISI.ARPA."; static const char kExpectedReadName3[] = "ISI.ARPA."; static const char kBadName[] = "bad.name"; Instance * instance; MessagePool *messagePool; Message * message; uint16_t offset; uint16_t name1Offset; uint16_t name2Offset; uint16_t name3Offset; uint16_t name4Offset; uint8_t buffer[kMaxBufferSize]; char label[kLabelSize]; uint8_t labelLength; char name[kNameSize]; printf("================================================================\n"); printf("TestDnsCompressedName()\n"); instance = static_cast(testInitInstance()); VerifyOrQuit(instance != nullptr, "Null OpenThread instance"); messagePool = &instance->Get(); VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed"); // Append name1 "F.ISI.ARPA" for (uint8_t index = 0; index < kHeaderOffset + kGuardBlockSize; index++) { SuccessOrQuit(message->Append(index), "Message::Append() failed"); } message->SetOffset(kHeaderOffset); name1Offset = message->GetLength(); SuccessOrQuit(Dns::Name::AppendName(kName, *message), "Name::AppendName() failed"); // Append name2 "FOO.F.ISI.ARPA" as a compressed name after some guard/extra bytes for (uint8_t index = 0; index < kGuardBlockSize; index++) { uint8_t value = 0xff; SuccessOrQuit(message->Append(value), "Message::Append() failed"); } name2Offset = message->GetLength(); SuccessOrQuit(Dns::Name::AppendLabel(kLabel1, *message), "Name::AppendLabel() failed"); SuccessOrQuit(Dns::Name::AppendPointerLabel(name1Offset - kHeaderOffset, *message), "Name::AppendPointerLabel() failed"); // Append name3 "ISI.ARPA" as a compressed name after some guard/extra bytes for (uint8_t index = 0; index < kGuardBlockSize; index++) { uint8_t value = 0xaa; SuccessOrQuit(message->Append(value), "Message::Append() failed"); } name3Offset = message->GetLength(); SuccessOrQuit(Dns::Name::AppendPointerLabel(name1Offset + kIsiRelativeIndex - kHeaderOffset, *message), "Name::AppendPointerLabel() failed"); name4Offset = message->GetLength(); SuccessOrQuit(Dns::Name::AppendLabel(kInstanceLabel, *message), "Name::AppendLabel() failed"); SuccessOrQuit(Dns::Name::AppendPointerLabel(name1Offset - kHeaderOffset, *message), "Name::AppendPointerLabel() failed"); printf("----------------------------------------------------------------\n"); printf("Read and parse the uncompressed name-1 \"F.ISI.ARPA\"\n"); SuccessOrQuit(message->Read(name1Offset, buffer, sizeof(kEncodedName)), "Message::Read() failed"); DumpBuffer(kName, buffer, sizeof(kEncodedName)); VerifyOrQuit(memcmp(buffer, kEncodedName, sizeof(kEncodedName)) == 0, "Encoded name data does not match expected data"); offset = name1Offset; SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed"); VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::ParseName() returned incorrect offset"); offset = name1Offset; for (const char *nameLabel : kName1Labels) { labelLength = sizeof(label); SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed"); printf("label: \"%s\"\n", label); VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label"); VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length"); } labelLength = sizeof(label); VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound, "Name::ReadLabel() failed at end of the name"); offset = name1Offset; SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed"); printf("Read name =\"%s\"\n", name); VerifyOrQuit(strcmp(name, kExpectedReadName1) == 0, "Name::ReadName() did not return expected name"); VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::ReadName() returned incorrect offset"); offset = name1Offset; for (const char *nameLabel : kName1Labels) { SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed"); } offset = name1Offset; SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName1), "Name::CompareName() failed"); VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset"); offset = name1Offset; VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound, "Name::CompareName() did not fail with incorrect name"); VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset"); offset = name1Offset; SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed"); VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset"); offset = name1Offset; VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name2Offset) == kErrorNotFound, "Name::CompareName() did not fail with mismatching name"); VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset"); printf("----------------------------------------------------------------\n"); printf("Read and parse compressed name-2 \"FOO.F.ISI.ARPA\"\n"); SuccessOrQuit(message->Read(name2Offset, buffer, kName2EncodedSize), "Message::Read() failed"); DumpBuffer("name2(compressed)", buffer, kName2EncodedSize); offset = name2Offset; SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed"); VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::ParseName() returned incorrect offset"); offset = name2Offset; for (const char *nameLabel : kName2Labels) { labelLength = sizeof(label); SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed"); printf("label: \"%s\"\n", label); VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label"); VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length"); } labelLength = sizeof(label); VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound, "Name::ReadLabel() failed at end of the name"); offset = name2Offset; SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed"); printf("Read name =\"%s\"\n", name); VerifyOrQuit(strcmp(name, kExpectedReadName2) == 0, "Name::ReadName() did not return expected name"); VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::ReadName() returned incorrect offset"); offset = name2Offset; for (const char *nameLabel : kName2Labels) { SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed"); } offset = name2Offset; SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName2), "Name::CompareName() failed"); VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset"); offset = name2Offset; VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound, "Name::CompareName() did not fail with incorrect name"); VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset"); offset = name2Offset; SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed"); VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset"); offset = name2Offset; VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name3Offset) == kErrorNotFound, "Name::CompareName() did not fail with mismatching name"); VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset"); printf("----------------------------------------------------------------\n"); printf("Read and parse compressed name-3 \"ISI.ARPA\"\n"); SuccessOrQuit(message->Read(name3Offset, buffer, kName3EncodedSize), "Message::Read() failed"); DumpBuffer("name2(compressed)", buffer, kName3EncodedSize); offset = name3Offset; SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed"); VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::ParseName() returned incorrect offset"); offset = name3Offset; for (const char *nameLabel : kName3Labels) { labelLength = sizeof(label); SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed"); printf("label: \"%s\"\n", label); VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label"); VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length"); } labelLength = sizeof(label); VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound, "Name::ReadLabel() failed at end of the name"); offset = name3Offset; SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed"); printf("Read name =\"%s\"\n", name); VerifyOrQuit(strcmp(name, kExpectedReadName3) == 0, "Name::ReadName() did not return expected name"); VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::ReadName() returned incorrect offset"); offset = name3Offset; for (const char *nameLabel : kName3Labels) { SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed"); } offset = name3Offset; SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName3), "Name::CompareName() failed"); VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset"); offset = name3Offset; VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound, "Name::CompareName() did not fail with incorrect name"); VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset"); offset = name3Offset; SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed"); VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset"); offset = name3Offset; VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name4Offset) == kErrorNotFound, "Name::CompareName() did not fail with mismatching name"); VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset"); printf("----------------------------------------------------------------\n"); printf("Read and parse the uncompressed name-4 \"Human\\.Readable.F.ISI.ARPA\"\n"); SuccessOrQuit(message->Read(name4Offset, buffer, kName4EncodedSize), "Message::Read() failed"); DumpBuffer("name4(compressed)", buffer, kName4EncodedSize); offset = name4Offset; SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed"); VerifyOrQuit(offset == name4Offset + kName4EncodedSize, "Name::ParseName() returned incorrect offset"); offset = name4Offset; for (const char *nameLabel : kName4Labels) { labelLength = sizeof(label); SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed"); printf("label: \"%s\"\n", label); VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label"); VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length"); } // `ReadName()` for name-4 should fails due to first label containing dot char. offset = name4Offset; VerifyOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)) == kErrorParse, "Name::ReadName() did not fail with invalid label"); offset = name4Offset; for (const char *nameLabel : kName4Labels) { SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed"); } offset = name4Offset; SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed"); offset = name4Offset; VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name1Offset) == kErrorNotFound, "Name::CompareName() did not fail with mismatching name"); message->Free(); testFreeInstance(instance); } void TestHeaderAndResourceRecords(void) { enum { kHeaderOffset = 0, kQuestionCount = 1, kAnswerCount = 2, kAdditionalCount = 5, kTtl = 7200, kTxtTtl = 7300, kSrvPort = 1234, kSrvPriority = 1, kSrvWeight = 2, kMaxSize = 600, }; const char kMessageString[] = "DnsMessage"; const char kDomainName[] = "example.com."; const char kServiceLabels[] = "_service._udp"; const char kServiceName[] = "_service._udp.example.com."; const char kInstance1Label[] = "inst1"; const char kInstance2Label[] = "instance2"; const char kInstance1Name[] = "inst1._service._udp.example.com."; const char kInstance2Name[] = "instance2._service._udp.example.com."; const char kHostName[] = "host.example.com."; const uint8_t kTxtData[] = {9, 'k', 'e', 'y', '=', 'v', 'a', 'l', 'u', 'e', 0}; const char kHostAddress[] = "fd00::abcd:"; const char *kInstanceLabels[] = {kInstance1Label, kInstance2Label}; const char *kInstanceNames[] = {kInstance1Name, kInstance2Name}; Instance * instance; MessagePool * messagePool; Message * message; Dns::Header header; uint16_t messageId; uint16_t headerOffset; uint16_t offset; uint16_t numRecords; uint16_t len; uint16_t serviceNameOffset; uint16_t hostNameOffset; uint16_t answerSectionOffset; uint16_t additionalSectionOffset; uint16_t index; Dns::PtrRecord ptrRecord; Dns::SrvRecord srvRecord; Dns::TxtRecord txtRecord; Dns::AaaaRecord aaaaRecord; Dns::ResourceRecord record; Ip6::Address hostAddress; char label[Dns::Name::kMaxLabelSize]; char name[Dns::Name::kMaxNameSize]; uint8_t buffer[kMaxSize]; printf("================================================================\n"); printf("TestHeaderAndResourceRecords()\n"); instance = static_cast(testInitInstance()); VerifyOrQuit(instance != nullptr, "Null OpenThread instance"); messagePool = &instance->Get(); VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed"); printf("----------------------------------------------------------------\n"); printf("Preparing the message\n"); SuccessOrQuit(message->Append(kMessageString), "Message::Append() failed"); // Header headerOffset = message->GetLength(); SuccessOrQuit(header.SetRandomMessageId(), "Header::SetRandomMessageId() failed"); messageId = header.GetMessageId(); header.SetType(Dns::Header::kTypeResponse); header.SetQuestionCount(kQuestionCount); header.SetAnswerCount(kAnswerCount); header.SetAdditionalRecordCount(kAdditionalCount); SuccessOrQuit(message->Append(header), "Message::Append() failed"); message->SetOffset(headerOffset); // Question section serviceNameOffset = message->GetLength() - headerOffset; SuccessOrQuit(Dns::Name::AppendMultipleLabels(kServiceLabels, *message), "AppendMultipleLabels() failed"); SuccessOrQuit(Dns::Name::AppendName(kDomainName, *message), "AppendName() failed"); SuccessOrQuit(message->Append(Dns::Question(Dns::ResourceRecord::kTypePtr)), "Message::Append() failed"); // Answer section answerSectionOffset = message->GetLength(); for (const char *instanceLabel : kInstanceLabels) { SuccessOrQuit(Dns::Name::AppendPointerLabel(serviceNameOffset, *message), "AppendPointerLabel() failed"); ptrRecord.Init(); ptrRecord.SetTtl(kTtl); offset = message->GetLength(); SuccessOrQuit(message->Append(ptrRecord), "Message::Append() failed"); SuccessOrQuit(Dns::Name::AppendLabel(instanceLabel, *message), "AppendLabel failed"); SuccessOrQuit(Dns::Name::AppendPointerLabel(serviceNameOffset, *message), "AppendPointerLabel() failed"); ptrRecord.SetLength(message->GetLength() - offset - sizeof(Dns::ResourceRecord)); message->Write(offset, ptrRecord); } // Additional section additionalSectionOffset = message->GetLength(); for (const char *instanceName : kInstanceNames) { uint16_t instanceNameOffset = message->GetLength() - headerOffset; // SRV record SuccessOrQuit(Dns::Name::AppendName(instanceName, *message), "AppendName() failed"); srvRecord.Init(); srvRecord.SetTtl(kTtl); srvRecord.SetPort(kSrvPort); srvRecord.SetWeight(kSrvWeight); srvRecord.SetPriority(kSrvPriority); offset = message->GetLength(); SuccessOrQuit(message->Append(srvRecord), "Message::Append() failed"); hostNameOffset = message->GetLength() - headerOffset; SuccessOrQuit(Dns::Name::AppendName(kHostName, *message), "AppendName() failed"); srvRecord.SetLength(message->GetLength() - offset - sizeof(Dns::ResourceRecord)); message->Write(offset, srvRecord); // TXT record SuccessOrQuit(Dns::Name::AppendPointerLabel(instanceNameOffset, *message), "AppendPointerLabel() failed"); txtRecord.Init(); txtRecord.SetTtl(kTxtTtl); txtRecord.SetLength(sizeof(kTxtData)); SuccessOrQuit(message->Append(txtRecord), "Message::Append() failed"); SuccessOrQuit(message->Append(kTxtData), "Message::Append() failed"); } SuccessOrQuit(hostAddress.FromString(kHostAddress), "Address::FromString() failed"); SuccessOrQuit(Dns::Name::AppendPointerLabel(hostNameOffset, *message), "AppendPointerLabel() failed"); aaaaRecord.Init(); aaaaRecord.SetTtl(kTtl); aaaaRecord.SetAddress(hostAddress); SuccessOrQuit(message->Append(aaaaRecord), "Message::Append()"); // Dump the entire message VerifyOrQuit(message->GetLength() < kMaxSize, "Message is too long"); SuccessOrQuit(message->Read(0, buffer, message->GetLength()), "Message::Read() failed"); DumpBuffer("message", buffer, message->GetLength()); printf("----------------------------------------------------------------\n"); printf("Parse and verify the message\n"); offset = 0; VerifyOrQuit(message->Compare(offset, kMessageString), "Message header does not match"); offset += sizeof(kMessageString); // Header VerifyOrQuit(offset == headerOffset, "headerOffset is incorrect"); SuccessOrQuit(message->Read(offset, header), "Message::Read() failed"); offset += sizeof(header); VerifyOrQuit(header.GetMessageId() == messageId, "Header::GetMessageId() failed"); VerifyOrQuit(header.GetType() == Dns::Header::kTypeResponse, "Header::GetType() failed"); VerifyOrQuit(header.GetQuestionCount() == kQuestionCount, "Header::GetQuestionCount() failed"); VerifyOrQuit(header.GetAnswerCount() == kAnswerCount, "Header::GetAnswerCount() failed"); VerifyOrQuit(header.GetAdditionalRecordCount() == kAdditionalCount, "Header::GetAdditionalRecordCount() failed"); printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n"); printf("Question Section\n"); SuccessOrQuit(Dns::Name::CompareName(*message, offset, kServiceName), "Question name does not match"); VerifyOrQuit(message->Compare(offset, Dns::Question(Dns::ResourceRecord::kTypePtr)), "Question does not match"); offset += sizeof(Dns::Question); printf("PTR for \"%s\"\n", kServiceName); printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n"); printf("Answer Section\n"); VerifyOrQuit(offset == answerSectionOffset, "answer section offset is incorrect"); for (const char *instanceName : kInstanceNames) { SuccessOrQuit(Dns::Name::CompareName(*message, offset, kServiceName), "ServiceName is incorrect"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, ptrRecord), "ReadRecord() failed"); VerifyOrQuit(ptrRecord.GetTtl() == kTtl, "Read PTR is incorrect"); SuccessOrQuit(ptrRecord.ReadPtrName(*message, offset, name, sizeof(name)), "ReadName() failed"); VerifyOrQuit(strcmp(name, instanceName) == 0, "Inst1 name is incorrect"); printf(" \"%s\" PTR %u %d \"%s\"\n", kServiceName, ptrRecord.GetTtl(), ptrRecord.GetLength(), name); } VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse"); offset = answerSectionOffset; SuccessOrQuit(Dns::ResourceRecord::ParseRecords(*message, offset, kAnswerCount), "ParseRecords() failed"); VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse"); printf("Use FindRecord() to find and iterate through all the records:\n"); offset = answerSectionOffset; numRecords = kAnswerCount; while (numRecords > 0) { uint16_t prevNumRecords = numRecords; SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kServiceName)), "FindRecord failed"); VerifyOrQuit(numRecords == prevNumRecords - 1, "Incorrect num records"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, ptrRecord), "ReadRecord() failed"); VerifyOrQuit(ptrRecord.GetTtl() == kTtl, "Read PTR is incorrect"); SuccessOrQuit(ptrRecord.ReadPtrName(*message, offset, label, sizeof(label), name, sizeof(name)), "ReadName() failed"); printf(" \"%s\" PTR %u %d inst:\"%s\" at \"%s\"\n", kServiceName, ptrRecord.GetTtl(), ptrRecord.GetLength(), label, name); } VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse"); VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kServiceName)) == kErrorNotFound, "FindRecord did not fail with no records"); // Use `ReadRecord()` with a non-matching record type. Verify that it correct skips over the record. offset = answerSectionOffset; numRecords = kAnswerCount; while (numRecords > 0) { SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kServiceName)), "FindRecord failed"); VerifyOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, srvRecord) == kErrorNotFound, "ReadRecord() did not fail with non-matching type"); } VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse"); // Use `FindRecord` with a non-matching name. Verify that it correctly skips over all records. offset = answerSectionOffset; numRecords = kAnswerCount; VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kInstance1Name)) == kErrorNotFound, "FindRecord did not fail with non-matching name"); VerifyOrQuit(numRecords == 0, "Incorrect num records"); VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse"); printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n"); printf("Additional Section\n"); for (const char *instanceName : kInstanceNames) { // SRV record SuccessOrQuit(Dns::Name::CompareName(*message, offset, instanceName), "Instance is incorrect"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, srvRecord), "ReadRecord() failed"); VerifyOrQuit(srvRecord.GetTtl() == kTtl, "Read SRV is incorrect"); VerifyOrQuit(srvRecord.GetPort() == kSrvPort, "Read SRV port is incorrect"); VerifyOrQuit(srvRecord.GetWeight() == kSrvWeight, "Read SRV weight is incorrect"); VerifyOrQuit(srvRecord.GetPriority() == kSrvPriority, "Read SRV priority is incorrect"); SuccessOrQuit(srvRecord.ReadTargetHostName(*message, offset, name, sizeof(name)), "ReadTargetHostName() failed"); VerifyOrQuit(strcmp(name, kHostName) == 0, "Inst1 name is incorrect"); printf(" \"%s\" SRV %u %d %d %d %d \"%s\"\n", instanceName, srvRecord.GetTtl(), srvRecord.GetLength(), srvRecord.GetPort(), srvRecord.GetWeight(), srvRecord.GetPriority(), name); // TXT record SuccessOrQuit(Dns::Name::CompareName(*message, offset, instanceName), "Instance is incorrect"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, txtRecord), "ReadRecord() failed"); VerifyOrQuit(txtRecord.GetTtl() == kTxtTtl, "Read TXT is incorrect"); len = sizeof(buffer); SuccessOrQuit(txtRecord.ReadTxtData(*message, offset, buffer, len), "ReadTxtData() failed"); VerifyOrQuit(len == sizeof(kTxtData), "TXT data length is not valid"); VerifyOrQuit(memcmp(buffer, kTxtData, len) == 0, "TXT data is not valid"); printf(" \"%s\" TXT %u %d \"%s\"\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength(), reinterpret_cast(buffer)); } SuccessOrQuit(Dns::Name::CompareName(*message, offset, kHostName), "HostName is incorrect"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, aaaaRecord), "ReadRecord() failed"); VerifyOrQuit(aaaaRecord.GetTtl() == kTtl, "Read AAAA is incorrect"); VerifyOrQuit(aaaaRecord.GetAddress() == hostAddress, "Read host address is incorrect"); printf(" \"%s\" AAAA %u %d \"%s\"\n", kHostName, aaaaRecord.GetTtl(), aaaaRecord.GetLength(), aaaaRecord.GetAddress().ToString().AsCString()); VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse"); // Use `ParseRecords()` to parse all records offset = additionalSectionOffset; SuccessOrQuit(Dns::ResourceRecord::ParseRecords(*message, offset, kAdditionalCount), "ParseRecords() failed"); VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse"); printf("Use FindRecord() to search for specific name:\n"); for (const char *instanceName : kInstanceNames) { offset = additionalSectionOffset; numRecords = kAdditionalCount; SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(instanceName)), "FindRecord failed"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, srvRecord), "ReadRecord() failed"); SuccessOrQuit(Dns::Name::ParseName(*message, offset), "ParseName() failed"); printf(" \"%s\" SRV %u %d %d %d %d\n", instanceName, srvRecord.GetTtl(), srvRecord.GetLength(), srvRecord.GetPort(), srvRecord.GetWeight(), srvRecord.GetPriority()); SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(instanceName)), "FindRecord failed"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, txtRecord), "ReadRecord() failed"); offset += txtRecord.GetLength(); printf(" \"%s\" TXT %u %d\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength()); VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(instanceName)) == kErrorNotFound, "FindRecord() did not fail with no more records"); VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse"); } offset = additionalSectionOffset; numRecords = kAdditionalCount; SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kHostName)), "FindRecord() failed"); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, record), "ReadRecord() failed"); VerifyOrQuit(record.GetType() == Dns::ResourceRecord::kTypeAaaa, "Read record has incorrect type"); offset += record.GetLength(); VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse"); printf("Use FindRecord() to search for specific records:\n"); printf(" Answer Section\n"); for (index = 0; index < OT_ARRAY_LENGTH(kInstanceNames); index++) { offset = answerSectionOffset; SuccessOrQuit( Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName), ptrRecord), "FindRecord() failed"); printf(" index:%d -> \"%s\" PTR %u %d\n", index, kServiceName, ptrRecord.GetTtl(), ptrRecord.GetLength()); } // Check `FindRecord()` failure with non-matching name, record type, or bad index. offset = answerSectionOffset; VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName), ptrRecord) == kErrorNotFound, "FindRecord() did not fail with bad index"); VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure"); offset = answerSectionOffset; VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kInstance1Name), ptrRecord) == kErrorNotFound, "FindRecord() did not fail with bad index"); VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure"); offset = answerSectionOffset; VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName), txtRecord) == kErrorNotFound, "FindRecord() did not fail with bad index"); VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure"); printf(" Additional Section\n"); for (const char *instanceName : kInstanceNames) { // There is a single SRV and TXT entry for each instance offset = additionalSectionOffset; SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 0, Dns::Name(instanceName), srvRecord), "FindRecord() failed"); printf(" \"%s\" SRV %u %d %d %d %d \n", instanceName, srvRecord.GetTtl(), srvRecord.GetLength(), srvRecord.GetPort(), srvRecord.GetWeight(), srvRecord.GetPriority()); offset = additionalSectionOffset; SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 0, Dns::Name(instanceName), txtRecord), "FindRecord() failed"); printf(" \"%s\" TXT %u %d\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength()); offset = additionalSectionOffset; VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 1, Dns::Name(instanceName), srvRecord) == kErrorNotFound, "FindRecord() did not fail with bad index"); offset = additionalSectionOffset; VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 1, Dns::Name(instanceName), txtRecord) == kErrorNotFound, "FindRecord() did not fail with bad index"); } for (index = 0; index < kAdditionalCount; index++) { offset = additionalSectionOffset; // Find record with empty name (matching any) and any type. SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, index, Dns::Name(), record), "FindRecord() failed"); } offset = additionalSectionOffset; VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, index, Dns::Name(), record) == kErrorNotFound, "FindRecord() did not fail with bad index"); message->Free(); testFreeInstance(instance); } void TestDnsTxtEntry(void) { enum { kMaxTxtDataSize = 255, }; struct EncodedTxtData { const uint8_t *mData; uint8_t mLength; }; const char kKey1[] = "key"; const uint8_t kValue1[] = {'v', 'a', 'l', 'u', 'e'}; const char kKey2[] = "E"; const uint8_t kValue2[] = {'m', 'c', '^', '2'}; const char kKey3[] = "space key"; const uint8_t kValue3[] = {'=', 0, '='}; const char kKey4[] = "123456789"; // Max recommended length key const uint8_t kValue4[] = {0}; const char kKey5[] = "1234567890"; // Longer than recommended key const uint8_t kValue5[] = {'a'}; const char kKey6[] = "boolKey"; // Should be encoded as "boolKey" (without `=`). const char kKey7[] = "emptyKey"; // Should be encoded as "emptyKey=". // Invalid key const char kShortKey[] = ""; const uint8_t kEncodedTxt1[] = {9, 'k', 'e', 'y', '=', 'v', 'a', 'l', 'u', 'e'}; const uint8_t kEncodedTxt2[] = {6, 'E', '=', 'm', 'c', '^', '2'}; const uint8_t kEncodedTxt3[] = {13, 's', 'p', 'a', 'c', 'e', ' ', 'k', 'e', 'y', '=', '=', 0, '='}; const uint8_t kEncodedTxt4[] = {11, '1', '2', '3', '4', '5', '6', '7', '8', '9', '=', 0}; const uint8_t kEncodedTxt5[] = {12, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '=', 'a'}; const uint8_t kEncodedTxt6[] = {7, 'b', 'o', 'o', 'l', 'K', 'e', 'y'}; const uint8_t kEncodedTxt7[] = {9, 'e', 'm', 'p', 't', 'y', 'K', 'e', 'y', '='}; const uint8_t kInvalidEncodedTxt1[] = {4, 'a', '=', 'b'}; // Incorrect length // Special encoded txt data with zero strings and string starting // with '=' (missing key) whcih should be skipped over silently. const uint8_t kSpecialEncodedTxt[] = {0, 0, 3, 'A', '=', 'B', 2, '=', 'C', 3, 'D', '=', 'E', 3, '=', '1', '2'}; const Dns::TxtEntry kTxtEntries[] = { Dns::TxtEntry(kKey1, kValue1, sizeof(kValue1)), Dns::TxtEntry(kKey2, kValue2, sizeof(kValue2)), Dns::TxtEntry(kKey3, kValue3, sizeof(kValue3)), Dns::TxtEntry(kKey4, kValue4, sizeof(kValue4)), Dns::TxtEntry(kKey5, kValue5, sizeof(kValue5)), Dns::TxtEntry(kKey6, nullptr, 0), Dns::TxtEntry(kKey7, kValue1, 0), }; const EncodedTxtData kEncodedTxtData[] = { {kEncodedTxt1, sizeof(kEncodedTxt1)}, {kEncodedTxt2, sizeof(kEncodedTxt2)}, {kEncodedTxt3, sizeof(kEncodedTxt3)}, {kEncodedTxt4, sizeof(kEncodedTxt4)}, {kEncodedTxt5, sizeof(kEncodedTxt5)}, {kEncodedTxt6, sizeof(kEncodedTxt6)}, {kEncodedTxt7, sizeof(kEncodedTxt7)}}; Instance * instance; MessagePool * messagePool; Message * message; uint8_t txtData[kMaxTxtDataSize]; uint16_t txtDataLength; uint8_t index; Dns::TxtEntry txtEntry; Dns::TxtEntry::Iterator iterator; printf("================================================================\n"); printf("TestDnsTxtEntry()\n"); instance = static_cast(testInitInstance()); VerifyOrQuit(instance != nullptr, "Null OpenThread instance"); messagePool = &instance->Get(); VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed"); SuccessOrQuit(Dns::TxtEntry::AppendEntries(kTxtEntries, OT_ARRAY_LENGTH(kTxtEntries), *message), "TxtEntry::AppendEntries() failed"); txtDataLength = message->GetLength(); VerifyOrQuit(txtDataLength < kMaxTxtDataSize, "TXT data is too long"); SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message"); DumpBuffer("txt data", txtData, txtDataLength); index = 0; for (const EncodedTxtData &encodedData : kEncodedTxtData) { VerifyOrQuit(memcmp(&txtData[index], encodedData.mData, encodedData.mLength) == 0, "TxtData is incorrectly encoded"); index += encodedData.mLength; } iterator.Init(txtData, txtDataLength); for (const Dns::TxtEntry &expectedTxtEntry : kTxtEntries) { uint8_t expectedKeyLength = static_cast(strlen(expectedTxtEntry.mKey)); SuccessOrQuit(iterator.GetNextEntry(txtEntry), "TxtEntry::GetNextEntry() failed"); printf("key:\"%s\" valueLen:%d\n", txtEntry.mKey != nullptr ? txtEntry.mKey : "(null)", txtEntry.mValueLength); if (expectedKeyLength > Dns::TxtEntry::kMaxKeyLength) { // When the key is longer than recommended max key length, // the full encoded string is returned in `mValue` and // `mValueLength` and `mKey` should be set to `nullptr`. VerifyOrQuit(txtEntry.mKey == nullptr, "TxtEntry key does not match expected value for long key"); VerifyOrQuit(txtEntry.mValueLength == expectedKeyLength + expectedTxtEntry.mValueLength + sizeof(char), "TxtEntry value length is incorrect for long key"); VerifyOrQuit(memcmp(txtEntry.mValue, expectedTxtEntry.mKey, expectedKeyLength) == 0, "txtEntry value does match expected content"); VerifyOrQuit(txtEntry.mValue[expectedKeyLength] == static_cast('='), "txtEntry value does match expected content"); VerifyOrQuit(memcmp(&txtEntry.mValue[expectedKeyLength + sizeof(uint8_t)], expectedTxtEntry.mValue, expectedTxtEntry.mValueLength) == 0, "txtEntry value does match expected content"); continue; } VerifyOrQuit(strcmp(txtEntry.mKey, expectedTxtEntry.mKey) == 0, "TxtEntry key does not match expected value"); VerifyOrQuit(txtEntry.mValueLength == expectedTxtEntry.mValueLength, "TxtEntry value length does not match expected value"); if (txtEntry.mValueLength != 0) { VerifyOrQuit(memcmp(txtEntry.mValue, expectedTxtEntry.mValue, txtEntry.mValueLength) == 0, "TxtEntry value does not match expected content"); } else { // Ensure both `txtEntry.mKey` and `expectedTxtEntry.mKey` are // null or both are non-null (for boolean or empty keys). VerifyOrQuit((txtEntry.mKey == nullptr) == (expectedTxtEntry.mKey == nullptr), "TxtEntry value does not match expected value for bool or empty key"); } } VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorNotFound, "GetNextEntry() returned unexpected entry"); VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorNotFound, "GetNextEntry() succeeded after done"); // Verify `AppendEntries()` correctly rejecting invalid key txtEntry.mValue = kValue1; txtEntry.mValueLength = sizeof(kValue1); txtEntry.mKey = kShortKey; VerifyOrQuit(Dns::TxtEntry::AppendEntries(&txtEntry, 1, *message) == kErrorInvalidArgs, "AppendEntries() did not fail with invalid key"); // Verify appending empty txt data SuccessOrQuit(message->SetLength(0), "Message::SetLength() failed"); SuccessOrQuit(Dns::TxtEntry::AppendEntries(nullptr, 0, *message), "AppendEntries() failed with empty array"); txtDataLength = message->GetLength(); VerifyOrQuit(txtDataLength == sizeof(uint8_t), "Data length is incorrect with empty array"); SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message"); VerifyOrQuit(txtData[0] == 0, "Data is invalid with empty array"); SuccessOrQuit(message->SetLength(0), "Message::SetLength() failed"); txtEntry.mKey = nullptr; txtEntry.mValue = nullptr; txtEntry.mValueLength = 0; SuccessOrQuit(Dns::TxtEntry::AppendEntries(&txtEntry, 1, *message), "AppendEntries() failed with empty entry"); txtDataLength = message->GetLength(); VerifyOrQuit(txtDataLength == sizeof(uint8_t), "Data length is incorrect with empty entry"); SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message"); VerifyOrQuit(txtData[0] == 0, "Data is invalid with empty entry"); // Verify `Iterator` behavior with invalid txt data. iterator.Init(kInvalidEncodedTxt1, sizeof(kInvalidEncodedTxt1)); VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorParse, "GetNextEntry() did not fail with invalid data"); // Verify `GetNextEntry()` correctly skipping over empty strings and // strings starting with '=' (missing key) in encoded txt. // // kSpecialEncodedTxt: // { 0, 3, 'A', '=', 'B', 2, '=', 'C', 3, 'D', '=', 'E', 3, '=', '1', '2' } iterator.Init(kSpecialEncodedTxt, sizeof(kSpecialEncodedTxt)); // We should get "A=B` (or key="A", and value="B") SuccessOrQuit(iterator.GetNextEntry(txtEntry), "GetNextEntry() failed"); VerifyOrQuit((txtEntry.mKey[0] == 'A') && (txtEntry.mKey[1] == '\0'), "GetNextEntry() got incorrect key"); VerifyOrQuit((txtEntry.mValueLength == 1) && (txtEntry.mValue[0] == 'B'), "GetNextEntry() got incorrect value"); // We should get "D=E` (or key="D", and value="E") SuccessOrQuit(iterator.GetNextEntry(txtEntry), "GetNextEntry() failed"); VerifyOrQuit((txtEntry.mKey[0] == 'D') && (txtEntry.mKey[1] == '\0'), "GetNextEntry() got incorrect key"); VerifyOrQuit((txtEntry.mValueLength == 1) && (txtEntry.mValue[0] == 'E'), "GetNextEntry() got incorrect value"); VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorNotFound, "GetNextEntry() returned extra entry"); message->Free(); testFreeInstance(instance); } } // namespace ot int main(void) { ot::TestDnsName(); ot::TestDnsCompressedName(); ot::TestHeaderAndResourceRecords(); ot::TestDnsTxtEntry(); printf("All tests passed\n"); return 0; }