diff --git a/src/core/net/dns_types.cpp b/src/core/net/dns_types.cpp index aa19c73bf..30e991829 100644 --- a/src/core/net/dns_types.cpp +++ b/src/core/net/dns_types.cpp @@ -100,6 +100,68 @@ Error Header::ResponseCodeToError(Response aResponse) return error; } +bool Name::Matches(const char *aFirstLabels, const char *aSecondLabels, const char *aDomain) const +{ + bool matches = false; + const char *namePtr; + Buffer nameBuffer; + + VerifyOrExit(!IsEmpty()); + + if (IsFromCString()) + { + namePtr = mString; + } + else + { + uint16_t offset = mOffset; + + SuccessOrExit(ReadName(*mMessage, offset, nameBuffer)); + namePtr = nameBuffer; + } + + if (aFirstLabels != nullptr) + { + matches = CompareAndSkipLabels(namePtr, aFirstLabels, kLabelSeparatorChar); + VerifyOrExit(matches); + } + + if (aSecondLabels != nullptr) + { + matches = CompareAndSkipLabels(namePtr, aSecondLabels, kLabelSeparatorChar); + VerifyOrExit(matches); + } + + matches = CompareAndSkipLabels(namePtr, aDomain, kNullChar); + +exit: + return matches; +} + +bool Name::CompareAndSkipLabels(const char *&aNamePtr, const char *aLabels, char aExpectedNextChar) +{ + // Compares `aNamePtr` to the label string `aLabels` followed by + // the `aExpectedNextChar`(using case-insensitive match). Upon + // successful comparison, `aNamePtr` is advanced to point after + // the matched portion. + + bool matches = false; + uint16_t len = StringLength(aLabels, kMaxNameSize); + + VerifyOrExit(len < kMaxNameSize); + + VerifyOrExit(StringStartsWith(aNamePtr, aLabels, kStringCaseInsensitiveMatch)); + aNamePtr += len; + + VerifyOrExit(*aNamePtr == aExpectedNextChar); + aNamePtr++; + + matches = true; + +exit: + return matches; +} + Error Name::AppendTo(Message &aMessage) const { Error error; diff --git a/src/core/net/dns_types.hpp b/src/core/net/dns_types.hpp index a8fb88a15..f857f8319 100644 --- a/src/core/net/dns_types.hpp +++ b/src/core/net/dns_types.hpp @@ -662,6 +662,25 @@ public: return *mMessage; } + /** + * Matches the `Name` with a given set of labels and domain name. + * + * This method allows the caller to specify name components separately, enabling scenarios like comparing "service + * instance name" with separate instance label, service type, and domain strings. + * + * @p aFirstLabels or @p aSecondLabels can be `nullptr` if not needed. But if non-null, these strings MUST NOT + * end with dot. @p aDomain MUST NOT be `nullptr` and MUST always end with a dot `.` character. + * + * @param[in] aFirstLabels A string of dot separated labels, MUST NOT end with dot. Can be `nullptr`. + * @param[in] aSecondLabels A string of dot separated labels, MUST NOT end with dot. Can be `nullptr`. + * @param[in] aDomain Domain name. MUST end with dot. + * + * @retval TRUE The name matches the given labels. + * @retval FALSE The name does not match the given labels. + * + */ + bool Matches(const char *aFirstLabels, const char *aSecondLabels, const char *aDomain) const; + /** * Encodes and appends the name to a message. * @@ -1094,6 +1113,7 @@ private: { } + static bool CompareAndSkipLabels(const char *&aNamePtr, const char *aLabels, char aExpectedNextChar); static Error AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage); const char *mString; // String containing the name or `nullptr` if name is not from string. diff --git a/tests/unit/test_dns.cpp b/tests/unit/test_dns.cpp index 8e6ef0dd8..d07a05abd 100644 --- a/tests/unit/test_dns.cpp +++ b/tests/unit/test_dns.cpp @@ -56,6 +56,15 @@ void TestDnsName(void) const char *mExpectedReadName; }; + struct TestMatches + { + const char *mFullName; + const char *mFirstLabels; + const char *mSecondLabels; + const char *mDomain; + bool mShouldMatch; + }; + Instance *instance; MessagePool *messagePool; Message *message; @@ -139,6 +148,25 @@ void TestDnsName(void) static const char kBadLabel[] = "badlabel"; static const char kBadName[] = "bad.name"; + static const TestMatches kTestMatches[] = { + {"foo.bar.local.", "foo", "bar", "local.", true}, + {"foo.bar.local.", "foo.bar", nullptr, "local.", true}, + {"foo.bar.local.", nullptr, "foo.bar", "local.", true}, + {"foo.bar.local.", nullptr, nullptr, "foo.bar.local.", true}, + {"foo.bar.local.", "foo", "ba", "local.", false}, + {"foo.bar.local.", "fooooo", "bar", "local.", false}, + {"foo.bar.local.", "foo", "bar", "locall.", false}, + {"foo.bar.local.", "f", "bar", "local.", false}, + {"foo.bar.local.", "foo", "barr", "local.", false}, + {"foo.bar.local.", "foo", "bar", ".local.", false}, + {"My Lovely Instance._mt._udp.local.", "mY lovely instancE", "_mt._udp", "local.", true}, + {"My Lovely Instance._mt._udp.local.", "mY lovely instancE._mt", "_udp", "local.", true}, + {"_s1._sub._srv._udp.default.service.arpa.", "_s1._sub", "_srv._udp", "default.service.arpa.", true}, + {"_s1._sub._srv._udp.default.service.arpa.", "_s1._sub", "_srv._udp", "default.service.arpa", false}, + {"_s1._sub._srv._udp.default.service.arpa.", "_s1._sub", "_srv._udp.", "default.service.arpa.", false}, + {"_s1._sub._srv._udp.default.service.arpa.", "_s1._sub.", "_srv._udp", "default.service.arpa.", false}, + }; + printf("================================================================\n"); printf("TestDnsName()\n"); @@ -496,6 +524,25 @@ void TestDnsName(void) VerifyOrQuit(memcmp(buffer, test.mEncodedData, len) == 0, "Encoded name data does not match expected data"); } + printf("----------------------------------------------------------------\n"); + printf("Name::Matches() variations\n"); + + for (const TestMatches &test : kTestMatches) + { + Dns::Name name; + + printf(" \"%s\"\n", test.mFullName); + + name.Set(test.mFullName); + VerifyOrQuit(name.Matches(test.mFirstLabels, test.mSecondLabels, test.mDomain) == test.mShouldMatch); + + IgnoreError(message->SetLength(0)); + SuccessOrQuit(name.AppendTo(*message)); + + name.SetFromMessage(*message, 0); + VerifyOrQuit(name.Matches(test.mFirstLabels, test.mSecondLabels, test.mDomain) == test.mShouldMatch); + } + message->Free(); testFreeInstance(instance); }