[dns] add Name::Matches() to compare DNS names (#9734)

This commit adds `Name::Matches()` method which compares a `Name`
instance (which can be from a C string or encoded in a `Message`)
with a given set of labels and domain name strings. 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. Unit test
`test_dns` is also updated to validate the behavior of the newly
added method.
This commit is contained in:
Abtin Keshavarzian
2023-12-20 14:20:58 -08:00
committed by GitHub
parent 16596a38c3
commit 51a682ec0e
3 changed files with 129 additions and 0 deletions
+62
View File
@@ -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;
+20
View File
@@ -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.
+47
View File
@@ -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);
}