blob: 01dea7e6ad18d99703ff8ad6264a1d1153de6794 [file]
/*
* Copyright (c) 2026 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <controller/ThreadMeshcopCommissionProxy.h>
#include <json/json.h>
#include <pw_unit_test/framework.h>
#include <cstdint>
#include <sstream>
#include <string_view>
#include <vector>
namespace chip {
namespace Controller {
namespace {
constexpr uint8_t kJoinerId[] = { 0x1a, 0x6f, 0x7e, 0xf3, 0x12, 0x66, 0xa6, 0x30 };
constexpr uint8_t kMattercSrvQuery[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DNS header
0x08, '_', 'm', 'a', 't', 't', 'e', 'r', 'c', // _matterc
0x04, '_', 'u', 'd', 'p', // _udp
0x05, 'l', 'o', 'c', 'a', 'l', // local
0x00, 0x00, 0x21, 0x00, 0x01, // SRV, IN
};
constexpr uint8_t kMalformedMattercSrvResponse[] = {
0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // DNS response header
0x08, '_', 'm', 'a', 't', 't', 'e', 'r', 'c', // _matterc
0x04, '_', 'u', 'd', 'p', // _udp
0x05, 'l', 'o', 'c', 'a', 'l', // local
0x00, 0x00, 0x21, 0x00, 0x01, // SRV, IN
0x00, 0x00, 0x00, 0x78, 0x00, 0x01, 0x00, // TTL, invalid one-byte SRV data
};
constexpr uint8_t kMattercSrvOnlyResponse[] = {
0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // DNS response header
0x08, '_', 'm', 'a', 't', 't', 'e', 'r', 'c', // _matterc
0x04, '_', 'u', 'd', 'p', // _udp
0x05, 'l', 'o', 'c', 'a', 'l', // local
0x00, 0x00, 0x21, 0x00, 0x01, // SRV, IN
0x00, 0x00, 0x00, 0x78, 0x00, 0x07, // TTL, data length
0x00, 0x00, 0x00, 0x00, 0x15, 0xa4, 0x00, // priority, weight, port, root target
};
constexpr uint8_t kCompleteMattercResponse[] = {
0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, // DNS response header
0x08, '_', 'm', 'a', 't', 't', 'e', 'r', 'c', // _matterc
0x04, '_', 'u', 'd', 'p', // _udp
0x05, 'l', 'o', 'c', 'a', 'l', // local
0x00, 0x00, 0x21, 0x00, 0x01, // SRV, IN
0x00, 0x00, 0x00, 0x78, 0x00, 0x07, // TTL, data length
0x00, 0x00, 0x00, 0x00, 0x15, 0xa4, 0x00, // priority, weight, port, root target
0x08, '_', 'm', 'a', 't', 't', 'e', 'r', 'c', // _matterc
0x04, '_', 'u', 'd', 'p', // _udp
0x05, 'l', 'o', 'c', 'a', 'l', // local
0x00, 0x00, 0x10, 0x00, 0x01, // TXT, IN
0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0x03, 'D', '=', '0', // TTL and D=0 TXT entry
};
std::vector<uint8_t> MakeMattercResponseWithDiscriminator(std::string_view discriminator)
{
constexpr size_t kEncodedDiscriminatorTxtRecordSize = 6;
std::vector<uint8_t> response(std::begin(kCompleteMattercResponse),
std::end(kCompleteMattercResponse) - kEncodedDiscriminatorTxtRecordSize);
const size_t txtEntrySize = 2 + discriminator.size(); // "D=" and its value
response.push_back(0);
response.push_back(static_cast<uint8_t>(txtEntrySize + 1));
response.push_back(static_cast<uint8_t>(txtEntrySize));
response.push_back('D');
response.push_back('=');
response.insert(response.end(), discriminator.begin(), discriminator.end());
return response;
}
template <typename Packet>
void SendJoinerPacket(ThreadMeshcopCommissionProxy & proxy, const Packet & packet)
{
proxy.OnJoinerMessage(std::vector<uint8_t>(std::begin(kJoinerId), std::end(kJoinerId)), 49154,
std::vector<uint8_t>(std::begin(packet), std::end(packet)));
}
bool LastDiscoveryIsValid(ThreadMeshcopCommissionProxy & proxy)
{
Json::Value root;
std::string errors;
std::istringstream diagnostic(proxy.GetLastDiscoveryDiagnosticJson());
bool parsed = Json::parseFromStream(Json::CharReaderBuilder(), diagnostic, &root, &errors);
EXPECT_TRUE(parsed);
return parsed && root["valid"].asBool();
}
TEST(TestThreadMeshcopCommissionProxy, WaitsForCompleteResponseAfterSrvQuery)
{
ThreadMeshcopCommissionProxy proxy;
SendJoinerPacket(proxy, kMattercSrvQuery);
EXPECT_FALSE(LastDiscoveryIsValid(proxy));
SendJoinerPacket(proxy, kCompleteMattercResponse);
EXPECT_TRUE(LastDiscoveryIsValid(proxy));
}
TEST(TestThreadMeshcopCommissionProxy, WaitsForCompleteResponseAfterMalformedSrv)
{
ThreadMeshcopCommissionProxy proxy;
SendJoinerPacket(proxy, kMalformedMattercSrvResponse);
EXPECT_FALSE(LastDiscoveryIsValid(proxy));
SendJoinerPacket(proxy, kCompleteMattercResponse);
EXPECT_TRUE(LastDiscoveryIsValid(proxy));
}
TEST(TestThreadMeshcopCommissionProxy, WaitsForCompleteResponseAfterSrvOnlyResponse)
{
ThreadMeshcopCommissionProxy proxy;
SendJoinerPacket(proxy, kMattercSrvOnlyResponse);
EXPECT_FALSE(LastDiscoveryIsValid(proxy));
SendJoinerPacket(proxy, kCompleteMattercResponse);
EXPECT_TRUE(LastDiscoveryIsValid(proxy));
}
TEST(TestThreadMeshcopCommissionProxy, WaitsForValidDiscriminatorAfterMalformedValues)
{
constexpr std::string_view kMalformedDiscriminators[] = { "", "abc", "0123", "70000" };
ThreadMeshcopCommissionProxy proxy;
for (std::string_view discriminator : kMalformedDiscriminators)
{
SendJoinerPacket(proxy, MakeMattercResponseWithDiscriminator(discriminator));
EXPECT_FALSE(LastDiscoveryIsValid(proxy));
}
SendJoinerPacket(proxy, kCompleteMattercResponse);
EXPECT_TRUE(LastDiscoveryIsValid(proxy));
}
TEST(TestThreadMeshcopCommissionProxy, IgnoresMismatchWithShortDiscriminator)
{
ThreadMeshcopCommissionProxy proxy;
SetupDiscriminator expectedDiscriminator;
expectedDiscriminator.SetShortValue(1);
ByteSpan invalidPskc;
Transport::PeerAddress peerAddress;
Dnssd::DiscoveredNodeData nodeData;
EXPECT_EQ(proxy.Discover(invalidPskc, peerAddress, Thread::DiscoveryCode(), expectedDiscriminator, nodeData, 0),
CHIP_ERROR_INVALID_ARGUMENT);
SendJoinerPacket(proxy, kCompleteMattercResponse);
EXPECT_FALSE(LastDiscoveryIsValid(proxy));
}
TEST(TestThreadMeshcopCommissionProxy, AcceptsCompleteResponse)
{
ThreadMeshcopCommissionProxy proxy;
SendJoinerPacket(proxy, kCompleteMattercResponse);
EXPECT_TRUE(LastDiscoveryIsValid(proxy));
}
TEST(TestThreadMeshcopCommissionProxy, CanRediscoverAfterStoppingProxy)
{
ThreadMeshcopCommissionProxy proxy;
ByteSpan invalidPskc;
Transport::PeerAddress peerAddress;
Dnssd::DiscoveredNodeData nodeData;
for (unsigned i = 0; i < 4; ++i)
{
SendJoinerPacket(proxy, kCompleteMattercResponse);
ASSERT_TRUE(LastDiscoveryIsValid(proxy));
// Discover stops the previous receiver before validating the PSKc. An empty PSKc
// avoids contacting a border agent while exercising socket teardown under TSAN.
ASSERT_EQ(proxy.Discover(invalidPskc, peerAddress, Thread::DiscoveryCode(), SetupDiscriminator(), nodeData, 0),
CHIP_ERROR_INVALID_ARGUMENT);
EXPECT_FALSE(LastDiscoveryIsValid(proxy));
}
// A fresh receiver must also be stopped when the proxy is destroyed.
SendJoinerPacket(proxy, kCompleteMattercResponse);
EXPECT_TRUE(LastDiscoveryIsValid(proxy));
}
} // namespace
} // namespace Controller
} // namespace chip