/* * Copyright (c) 2026, 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 "platform/nexus_core.hpp" #include "platform/nexus_node.hpp" #include "thread/uri_paths.hpp" #include "utils/verhoeff_checksum.hpp" namespace ot { namespace Nexus { /** * Time to advance for a node to form a network and become leader, in milliseconds. */ static constexpr uint32_t kFormNetworkTime = 50 * Time::kOneSecondInMsec; /** * Time to advance for the DUT to connect to the network, in milliseconds. */ static constexpr uint32_t kAttachTime = 10 * Time::kOneSecondInMsec; /** * Time to advance for mDNS discovery, in milliseconds. */ static constexpr uint32_t kMdnsDiscoveryTime = 5 * Time::kOneSecondInMsec; /** * Time to advance for DTLS connection, in milliseconds. */ static constexpr uint32_t kConnectTimeout = 3 * Time::kOneSecondInMsec; /** * Time to advance for TMF message exchange, in milliseconds. */ static constexpr uint32_t kTmfExchangeTime = 1 * Time::kOneSecondInMsec; /** * Max ePSKc connection attempts. */ static constexpr uint8_t kMaxEpskcConnAttempts = 10; /** * The filename for the DTLS key log. */ static const char kKeyLogFilename[] = "/tmp/test_1_4_CS_TC_3.keys"; /** * The master secret length in bytes. */ static constexpr uint8_t kMasterSecretLength = 48; /** * The random buffer length in bytes. */ static constexpr uint8_t kRandomBufferLength = MeshCoP::SecureTransport::kSecureTransportRandomBufferSize; /** * Infrastructure interface index. */ static constexpr uint32_t kInfraIfIndex = 1; /** * The UDP port for ePSKc DTLS session. */ static constexpr uint16_t kEpskcPort = 1234; /** * Time to wait for DTLS retransmissions to finish (in milliseconds). */ static constexpr uint32_t kDtlsRetransmissionWaitTime = 60 * Time::kOneSecondInMsec; /** * Time to wait for DUT state to be cleaned up after DTLS session (in milliseconds). */ static constexpr uint32_t kDutStateCleanTime = 100 * Time::kOneSecondInMsec; using EphemeralKeyManager = MeshCoP::BorderAgent::EphemeralKeyManager; using Manager = MeshCoP::BorderAgent::Manager; static void HandleMbedtlsExportKeys(void *aContext, mbedtls_ssl_key_export_type aType, const unsigned char *aMasterSecret, size_t aMasterSecretLen, const unsigned char aClientRandom[kRandomBufferLength], const unsigned char aServerRandom[kRandomBufferLength], mbedtls_tls_prf_types aTlsPrfType) { FILE *file = fopen(kKeyLogFilename, "a"); OT_UNUSED_VARIABLE(aContext); OT_UNUSED_VARIABLE(aMasterSecretLen); OT_UNUSED_VARIABLE(aServerRandom); OT_UNUSED_VARIABLE(aTlsPrfType); if (file != nullptr) { if (aType == MBEDTLS_SSL_KEY_EXPORT_TLS12_MASTER_SECRET) { fprintf(file, "CLIENT_RANDOM "); for (int i = 0; i < kRandomBufferLength; i++) { fprintf(file, "%02x", aClientRandom[i]); } fprintf(file, " "); for (int i = 0; i < kMasterSecretLength; i++) { fprintf(file, "%02x", aMasterSecret[i]); } fprintf(file, "\n"); } fclose(file); } } typedef Dns::Name::Buffer DnsName; struct BrowseOutcome { DnsName mServiceInstance; DnsName mHostName; uint16_t mPort; Ip6::Address mAddress; }; static Array sBrowseOutcomes; static void HandleBrowseCallback(otInstance *aInstance, const Dns::Multicast::Core::BrowseResult *aResult) { BrowseOutcome *outcome; OT_UNUSED_VARIABLE(aInstance); outcome = sBrowseOutcomes.PushBack(); VerifyOrQuit(outcome != nullptr); SuccessOrQuit(StringCopy(outcome->mServiceInstance, aResult->mServiceInstance)); } static void HandleSrvCallback(otInstance *aInstance, const Dns::Multicast::Core::SrvResult *aResult) { OT_UNUSED_VARIABLE(aInstance); for (BrowseOutcome &outcome : sBrowseOutcomes) { if (StringMatch(outcome.mServiceInstance, aResult->mServiceInstance)) { outcome.mPort = aResult->mPort; SuccessOrQuit(StringCopy(outcome.mHostName, aResult->mHostName)); break; } } } static void HandleAddressCallback(otInstance *aInstance, const Dns::Multicast::Core::AddressResult *aResult) { OT_UNUSED_VARIABLE(aInstance); for (BrowseOutcome &outcome : sBrowseOutcomes) { if (StringMatch(outcome.mHostName, aResult->mHostName)) { if (aResult->mAddressesLength > 0) { outcome.mAddress = AsCoreType(&aResult->mAddresses[0].mAddress); } break; } } } static void DiscoverMeshcopEService(Node &aCommNode, Core &aNexus, Ip6::Address &aAddress, uint16_t &aPort) { sBrowseOutcomes.Clear(); { Dns::Multicast::Core::Browser browser; ClearAllBytes(browser); browser.mServiceType = "_meshcop-e._udp"; browser.mInfraIfIndex = kInfraIfIndex; browser.mCallback = HandleBrowseCallback; SuccessOrQuit(aCommNode.Get().StartBrowser(browser)); aNexus.AdvanceTime(kMdnsDiscoveryTime); SuccessOrQuit(aCommNode.Get().StopBrowser(browser)); } VerifyOrQuit(sBrowseOutcomes.GetLength() == 1); { Dns::Multicast::Core::SrvResolver srvResolver; ClearAllBytes(srvResolver); srvResolver.mServiceInstance = sBrowseOutcomes[0].mServiceInstance; srvResolver.mServiceType = "_meshcop-e._udp"; srvResolver.mInfraIfIndex = kInfraIfIndex; srvResolver.mCallback = HandleSrvCallback; SuccessOrQuit(aCommNode.Get().StartSrvResolver(srvResolver)); aNexus.AdvanceTime(kMdnsDiscoveryTime); SuccessOrQuit(aCommNode.Get().StopSrvResolver(srvResolver)); } { Dns::Multicast::Core::AddressResolver addrResolver; ClearAllBytes(addrResolver); addrResolver.mHostName = sBrowseOutcomes[0].mHostName; addrResolver.mInfraIfIndex = kInfraIfIndex; addrResolver.mCallback = HandleAddressCallback; SuccessOrQuit(aCommNode.Get().StartIp6AddressResolver(addrResolver)); aNexus.AdvanceTime(kMdnsDiscoveryTime); SuccessOrQuit(aCommNode.Get().StopIp6AddressResolver(addrResolver)); } aPort = sBrowseOutcomes[0].mPort; aAddress = sBrowseOutcomes[0].mAddress; } void Test1_4_CS_TC_3(void) { /** * 12.3. [1.4] [CERT] [COMPONENT] BR component creates numeric passcode and Candidate connects * * 12.3.1. Purpose * This test is for automatically testing BR component DUTs. Instead of a test operator typing the passcode, the * DUT generates a passcode and shares it via the Thread Harness API to the Harness so that it can execute the * automated test. No UI, GUI or display is needed on the BR DUT in this case. * In detail, the purpose is to verify that the Border Router DUT component: * - correctly implements the Thread Credential Sharing feature by supporting the Thread Administration Sharing * procedure * - is able to generate a numeric Thread Administration One-Time Passcode consisting of 9 digits * - allows an external Candidate to connect using the ePSKc that is based on the passcode * - is able to facilitate the Candidate’s operations * - enables access to a Candidate using the ePSKc, only once. * * 12.3.2. Topology * - BR_1 – Border Router Thread Component DUT * - Router_1 – Thread Router reference device and Leader * - Comm_1 – IPv6 host reference device on the AIL that implements a Commissioner Candidate role. * * Spec Reference | V1.4 Section * --------------------------|----------------- * Thread Credential Sharing | 8.4.1.2.3, 8.4.9 */ Core nexus; Node &router1 = nexus.CreateNode(); Node &br1 = nexus.CreateNode(); Node &comm1 = nexus.CreateNode(); router1.SetName("Router_1"); br1.SetName("BR_1"); comm1.SetName("Comm_1"); router1.Get().SetEnabled(false); comm1.Get().SetEnabled(false); nexus.AdvanceTime(0); SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); // Remove any existing key log file. remove(kKeyLogFilename); #if OPENTHREAD_CONFIG_MBEDTLS_PROVIDES_SSL_KEY_EXPORT static_cast(comm1.Get()) .SetKeylogCallback(HandleMbedtlsExportKeys, &comm1.Get()); #endif SuccessOrQuit(br1.Get().SetEnabled(true, kInfraIfIndex)); SuccessOrQuit(comm1.Get().SetEnabled(true, kInfraIfIndex)); comm1.Get().Up(); // Wait for the interfaces to be ready and addresses to be configured. nexus.AdvanceTime(Time::kOneSecondInMsec); Log("---------------------------------------------------------------------------------------"); Log("Step 1: Enable and form network."); /** * Step 1 * - Device: Router_1, BR_1 (DUT) * - Description (CS-12.3): Enable and form network. The DUT is configured with the Network Key of the test * Thread Network, so that it can connect to it. * - Pass Criteria: The DUT MUST connect to Router_1’s network. */ router1.Form(); nexus.AdvanceTime(kFormNetworkTime); br1.Join(router1); nexus.AdvanceTime(kAttachTime); VerifyOrQuit(br1.Get().IsAttached()); SuccessOrQuit(comm1.Get().SetPanChannel(router1.Get().GetPanChannel())); comm1.Get().SetPanId(router1.Get().GetPanId()); comm1.Get().Up(); br1.Get().SetEnabled(true); nexus.AdvanceTime(Time::kOneSecondInMsec); Log("---------------------------------------------------------------------------------------"); Log("Step 2: Comm_1 discovers BR DUT service."); /** * Step 2 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to do mDNS Qtype=PTR discovery query for DNS-SD service type * “_meshcop._udp.local”, to find the BR DUT service. ... Harness then verifies data in the SRV record and TXT * record as detailed to the right. * - Pass Criteria: * - The DUT MUST respond with an mDNS message that contains one PTR record of type “_meshcop._udp.local” in the * Answer section. * - Comm_1 MUST be able to obtain the TXT record as detailed on the left. * - In the TXT record: * - ePSKc Support flag (Bit 11) in State Bitmap (sb) MUST be ‘1’ (ePSKc supported) * - ‘vn’ field MUST be present and contain >= 1 characters. * - ‘mn’ field MUST be present and contain >= 1 characters. * - Comm_1 MUST be able to obtain the SRV record as detailed on the left. Harness logs and stores the port * number in the SRV record for later use. */ // Border Agent starts automatically when attached. VerifyOrQuit(br1.Get().IsEnabled()); { Dns::Multicast::Core::Browser browser; ClearAllBytes(browser); browser.mServiceType = "_meshcop._udp"; browser.mInfraIfIndex = kInfraIfIndex; browser.mCallback = HandleBrowseCallback; SuccessOrQuit(comm1.Get().StartBrowser(browser)); nexus.AdvanceTime(kMdnsDiscoveryTime); SuccessOrQuit(comm1.Get().StopBrowser(browser)); } VerifyOrQuit(sBrowseOutcomes.GetLength() == 1); Log("---------------------------------------------------------------------------------------"); Log("Step 3: BR_1 (DUT) produces passcode."); /** * Step 3 * - Device: BR_1 (DUT) * - Description (CS-12.3): Harness instructs the DUT, via Test Harness API, to produce the Thread Administration * One-Time Passcode. * - Pass Criteria: Passcode is successfully retrieved. */ EphemeralKeyManager::Tap tap1; SuccessOrQuit(tap1.GenerateRandom()); Log("Passcode 1: %s", tap1.mTap); nexus.AddTestVar("Passcode1", tap1.mTap); Log("---------------------------------------------------------------------------------------"); Log("Step 4: Harness validates the passcode."); /** * Step 4 * - Device: Harness * - Description (CS-12.3): Harness validates the passcode. Harness calculates the ePSKc as follows: each * character from the passcode string is taken as an ePSKc octet. * - Pass Criteria: * - Harness validates the following, using its passcode validation function: * - The passcode MUST contain only digits 0-9. * - The passcode MUST be 9 digits long. * - The last digit of the passcode MUST be equal to the check digit calculated by the Verhoeff algorithm on * the first 8 digits. */ SuccessOrQuit(tap1.Validate()); Log("---------------------------------------------------------------------------------------"); Log("Step 5: Comm_1 discovers meshcop-e service."); /** * Step 5 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to discover a DNS-SD service type via mDNS, Qtype=PTR, name * “_meshcop-e._udp.local”. ... Harness stores the discovered IPv6 address and port for later use; and logs it. * - Pass Criteria: * - The DUT MUST respond (mDNS) with a DNS-SD message that contains one PTR record of type * “_meshcop-e._udp.local” in the Answer section. * - Comm_1 MUST be able to obtain an IPv6 address (from AAAA record) and port (from SRV record) of the * “meshcop-e” service as detailed on the left. */ SuccessOrQuit(br1.Get().Start(tap1.mTap, 0, kEpskcPort)); uint16_t epskcPort; Ip6::Address br1Addr; DiscoverMeshcopEService(comm1, nexus, br1Addr, epskcPort); Log("Discovered ePSKc address: %s, port: %u", br1Addr.ToString().AsCString(), epskcPort); Log("---------------------------------------------------------------------------------------"); Log("Step 6: Comm_1 connects with incorrect ePSKc."); /** * Step 6 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to connect to the DUT on the address and port found in step 5. * For the DTLS PSK, it uses as ePSKc an incorrect ePSKc which is calculated as follows: 1. Seven octets * (b[0]…b[6]) copied from the correct ePSKc of step 4. 2. One octet (b[7]) as a random digit that differs from * the correct ePSKc of step 4. 3. One octet (b[8]) calculated as the correct Verhoeff check digit over the * previous 8 numeric digits. ... Harness then waits for 3 seconds. * - Pass Criteria: * - The DTLS handshake/connection to the DUT MUST NOT succeed: the DUT MUST reject attempt to establish a DTLS * session. */ { char incorrectTap[EphemeralKeyManager::Tap::kLength + 1]; char checksum; uint8_t i = 7; memcpy(incorrectTap, tap1.mTap, EphemeralKeyManager::Tap::kLength + 1); incorrectTap[i]++; if (incorrectTap[i] > '9') { incorrectTap[i] = '0'; } incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = '\0'; SuccessOrQuit(Utils::VerhoeffChecksum::Calculate(incorrectTap, checksum)); incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = checksum; Ip6::SockAddr br1SockAddr; br1SockAddr.SetAddress(br1Addr); br1SockAddr.SetPort(epskcPort); SuccessOrQuit(comm1.Get().SetPsk(reinterpret_cast(incorrectTap), EphemeralKeyManager::Tap::kLength)); SuccessOrQuit(comm1.Get().Open(Ip6::kNetifBackbone)); SuccessOrQuit(comm1.Get().Connect(br1SockAddr)); nexus.AdvanceTime(kConnectTimeout); VerifyOrQuit(!comm1.Get().IsConnected()); comm1.Get().Close(); // Wait longer to ensure all retransmissions are finished. nexus.AdvanceTime(kDtlsRetransmissionWaitTime); } Log("---------------------------------------------------------------------------------------"); Log("Step 7: Comm_1 connects with correct ePSKc."); /** * Step 7 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to connect again to the DUT on the address and port found in * step 5. For the DTLS PSK, it now uses the correct ePSKc as calculated in step 4. * - Pass Criteria: The DTLS handshake/connection to the DUT MUST succeed. */ { Ip6::SockAddr br1SockAddr; br1SockAddr.SetAddress(br1Addr); br1SockAddr.SetPort(epskcPort); SuccessOrQuit(comm1.Get().SetPsk(reinterpret_cast(tap1.mTap), EphemeralKeyManager::Tap::kLength)); SuccessOrQuit(comm1.Get().Open(Ip6::kNetifBackbone)); SuccessOrQuit(comm1.Get().Connect(br1SockAddr)); nexus.AdvanceTime(kConnectTimeout); VerifyOrQuit(comm1.Get().IsConnected()); } Log("---------------------------------------------------------------------------------------"); Log("Step 8: Comm_1 sends MGMT_ACTIVE_GET.req."); /** * Step 8 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to send TMF message MGMT_ACTIVE_GET.req to the DUT over the * secure DTLS session. The Get TLV is not included in the request. * - Pass Criteria: * - Comm_1 MUST receive the response TMF message MGMT_ACTIVE_GET.rsp over the secure DTLS session. * - Response payload MUST contain Network Key TLV, ... */ { Coap::Message *message; message = comm1.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriActiveGet); VerifyOrQuit(message != nullptr); SuccessOrQuit(comm1.Get().SendMessage(*message)); } nexus.AdvanceTime(kTmfExchangeTime); Log("---------------------------------------------------------------------------------------"); Log("Step 9: Comm_1 sends MGMT_PENDING_GET.req."); /** * Step 9 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to send MGMT_PENDING_GET.req to the DUT over the secure DTLS * session. The Get TLV is not included in the request. * - Pass Criteria: * - Comm_1 MUST receive the TMF response message MGMT_PENDING_GET.rsp. * - Response CoAP payload MUST be empty. */ { Coap::Message *message; message = comm1.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriPendingGet); VerifyOrQuit(message != nullptr); SuccessOrQuit(comm1.Get().SendMessage(*message)); } nexus.AdvanceTime(kTmfExchangeTime); Log("---------------------------------------------------------------------------------------"); Log("Step 10: Comm_1 closes the DTLS connection."); /** * Step 10 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to close the Commissioner session, i.e. close the DTLS * connection. Then, wait for 3 seconds. * - Pass Criteria: N/A */ comm1.Get().Close(); nexus.AdvanceTime(3 * Time::kOneSecondInMsec); Log("---------------------------------------------------------------------------------------"); Log("Step 11: Comm_1 connects again (should fail)."); /** * Step 11 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to connect again to the DUT on the address and port found in * step 5. For the DTLS PSK, it uses the correct ePSKc as determined in step 4. ... * - Pass Criteria: * - The DTLS handshake/connection to the DUT MUST NOT succeed: the DUT MUST reject the DTLS connection attempt. */ { Ip6::SockAddr br1SockAddr; br1SockAddr.SetAddress(br1Addr); br1SockAddr.SetPort(epskcPort); SuccessOrQuit(comm1.Get().Open(Ip6::kNetifBackbone)); SuccessOrQuit(comm1.Get().Connect(br1SockAddr)); nexus.AdvanceTime(kConnectTimeout); VerifyOrQuit(!comm1.Get().IsConnected()); comm1.Get().Close(); // Wait much longer to ensure all retransmissions are finished and the DUT state is clean. // MbedTLS retransmissions can last for a while. nexus.AdvanceTime(kDutStateCleanTime); } Log("---------------------------------------------------------------------------------------"); Log("Step 12: Wait 3s and clear mDNS cache."); /** * Step 12 * - Device: Harness * - Description (CS-12.3): Harness waits for 3 seconds. Harness clears its mDNS discovery cache. * - Pass Criteria: N/A */ nexus.AdvanceTime(3 * Time::kOneSecondInMsec); // Nexus mDNS cache is handled by AdvanceTime usually or explicitly. // In this test, we just clear our browse outcomes. sBrowseOutcomes.Clear(); Log("---------------------------------------------------------------------------------------"); Log("Step 13: Comm_1 discovers meshcop-e service (should fail)."); /** * Step 13 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to discover a service type via mDNS, Qtype=PTR, name * “_meshcop-e._udp.local”. * - Pass Criteria: The DUT MUST NOT respond with an mDNS message that contains a PTR record of type * “_meshcop-e._udp.local” in the Answer section. */ { Dns::Multicast::Core::Browser browser; ClearAllBytes(browser); browser.mServiceType = "_meshcop-e._udp"; browser.mInfraIfIndex = kInfraIfIndex; browser.mCallback = HandleBrowseCallback; SuccessOrQuit(comm1.Get().StartBrowser(browser)); nexus.AdvanceTime(kMdnsDiscoveryTime); SuccessOrQuit(comm1.Get().StopBrowser(browser)); } VerifyOrQuit(sBrowseOutcomes.GetLength() == 0); Log("---------------------------------------------------------------------------------------"); Log("Step 14: BR_1 (DUT) produces new passcode."); /** * Step 14 * - Device: BR_1 (DUT) * - Description (CS-12.3): Harness instructs the DUT, via Test Harness API, to produce the Thread Administration * One-Time Passcode. * - Pass Criteria: Passcode is successfully retrieved. */ EphemeralKeyManager::Tap tap2; SuccessOrQuit(tap2.GenerateRandom()); Log("Passcode 2: %s", tap2.mTap); nexus.AddTestVar("Passcode2", tap2.mTap); Log("---------------------------------------------------------------------------------------"); Log("Step 15: Harness validates the new passcode."); /** * Step 15 * - Device: Harness * - Description (CS-12.3): Harness validates the passcode. From the passcode, the Harness calculates the ePSKc * using the procedure of step 4. * - Pass Criteria: * - Harness validates the following, using its passcode validation function: * - The passcode MUST differ from the passcode previously obtained in step 4 * - The passcode MUST contain only digits 0-9 * - The passcode MUST be 9 digits long * - The last digit of the passcode MUST be equal to the check digit calculated by the Verhoeff algorithm on * the first 8 digits. */ SuccessOrQuit(tap2.Validate()); VerifyOrQuit(!StringMatch(tap2.mTap, tap1.mTap)); Log("---------------------------------------------------------------------------------------"); Log("Step 16: Comm_1 discovers meshcop-e service."); /** * Step 16 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to discover a service type via mDNS, Qtype=PTR, name * “_meshcop-e._udp.local”. ... Harness stores the discovered IPv6 address and port for later use; and logs these. * - Pass Criteria: * - The DUT MUST respond with an mDNS message that contains one PTR record of type “_meshcop-e._udp.local” in * the Answer section. * - Comm_1 MUST be able to obtain an IPv6 address (from AAAA record) and port (from SRV record) of the * “meshcop-e” service as detailed on the left. */ SuccessOrQuit(br1.Get().Start(tap2.mTap, 0, kEpskcPort + 1)); DiscoverMeshcopEService(comm1, nexus, br1Addr, epskcPort); Log("Discovered ePSKc address: %s, port: %u", br1Addr.ToString().AsCString(), epskcPort); Log("---------------------------------------------------------------------------------------"); Log("Step 17: Comm_1 connects with correct ePSKc."); /** * Step 17 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to connect to the DUT on the address and port found in step * 16. For the DTLS PSK, it uses the correct ePSKc as calculated in step 15. * - Pass Criteria: The DTLS handshake/connection to the DUT MUST succeed. */ { Ip6::SockAddr br1SockAddr; br1SockAddr.SetAddress(br1Addr); br1SockAddr.SetPort(epskcPort); SuccessOrQuit(comm1.Get().SetPsk(reinterpret_cast(tap2.mTap), EphemeralKeyManager::Tap::kLength)); SuccessOrQuit(comm1.Get().Open(Ip6::kNetifBackbone)); SuccessOrQuit(comm1.Get().Connect(br1SockAddr)); nexus.AdvanceTime(kConnectTimeout); VerifyOrQuit(comm1.Get().IsConnected()); } Log("---------------------------------------------------------------------------------------"); Log("Step 18: Comm_1 sends MGMT_ACTIVE_GET.req."); /** * Step 18 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs the device to send TMF message MGMT_ACTIVE_GET.req to the DUT over * the secure DTLS session. The Get TLV is not included in the request. * - Pass Criteria: * - Comm_1 MUST receive the response TMF message MGMT_ACTIVE_GET.rsp over the secure DTLS session. * - Response payload MUST contain Network Key TLV, ... */ { Coap::Message *message; message = comm1.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriActiveGet); VerifyOrQuit(message != nullptr); SuccessOrQuit(comm1.Get().SendMessage(*message)); } nexus.AdvanceTime(kTmfExchangeTime); Log("---------------------------------------------------------------------------------------"); Log("Step 19: Comm_1 closes the DTLS connection."); /** * Step 19 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to close the DTLS connection. * - Pass Criteria: N/A */ comm1.Get().Close(); nexus.AdvanceTime(3 * Time::kOneSecondInMsec); Log("---------------------------------------------------------------------------------------"); Log("Step 20: BR_1 (DUT) produces new passcode."); /** * Step 20 * - Device: BR_1 (DUT) * - Description (CS-12.3): Harness instructs the DUT, via Test Harness API, to produce the Thread Administration * One-Time Passcode. * - Pass Criteria: Passcode is successfully retrieved. */ EphemeralKeyManager::Tap tap3; SuccessOrQuit(tap3.GenerateRandom()); Log("Passcode 3: %s", tap3.mTap); nexus.AddTestVar("Passcode3", tap3.mTap); Log("---------------------------------------------------------------------------------------"); Log("Step 21: Harness validates the new passcode."); /** * Step 21 * - Device: Harness * - Description (CS-12.3): Harness validates the passcode. From the passcode, the Harness calculates the ePSKc * again using the procedure of step 4. * - Pass Criteria: * - Harness validates the following, equal to step 4, using its passcode validation function: * - The passcode MUST contain only digits 0-9. * - The passcode MUST be 9 digits long. * - The last digit of the passcode MUST be equal to the check digit calculated by the Verhoeff algorithm on * the first 8 digits. * - Harness also validates that the passcode MUST be different from the passcodes obtained before in steps 4 * and 15. */ SuccessOrQuit(tap3.Validate()); VerifyOrQuit(!StringMatch(tap3.mTap, tap1.mTap)); VerifyOrQuit(!StringMatch(tap3.mTap, tap2.mTap)); Log("---------------------------------------------------------------------------------------"); Log("Step 22: Comm_1 discovers meshcop-e service."); /** * Step 22 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to discover a service type via mDNS, Qtype=PTR, name * “_meshcop-e._udp.local”. ... Harness stores the discovered IPv6 address and port for later use; and logs these. * - Pass Criteria: * - DUT MUST respond with an mDNS message that contains one PTR record of type “_meshcop-e._udp.local” in the * Answer section. * - Comm_1 MUST be able to obtain an IPv6 address (from AAAA record) and port (from SRV record) of the * “meshcop-e” service as detailed on the left. */ SuccessOrQuit(br1.Get().Start(tap3.mTap, 0, kEpskcPort + 2)); DiscoverMeshcopEService(comm1, nexus, br1Addr, epskcPort); Log("Discovered ePSKc address: %s, port: %u", br1Addr.ToString().AsCString(), epskcPort); Log("---------------------------------------------------------------------------------------"); Log("Step 23: Comm_1 connects with incorrect ePSKc 10 times."); /** * Step 23 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to connect to the DUT on the address and port found in step * 22. For the DTLS PSK, it uses an incorrect ePSKc as calculated using the procedure of step 6 applied to the * passcode found in step 21. This connection attempt is done in total MAX_EPSKC_CONN_ATTEMPTS (10) times. Each * time, the pass criteria are checked. Between attempts, the Harness pauses for 2 seconds. * - Pass Criteria: * - The DTLS handshake/connection to DUT MUST NOT succeed. * - In all cases, the DUT MUST NOT send ICMPv6 Port Unreachable error - this way of failing is not allowed here. * The DUT MUST respond with DTLS messages. */ { char incorrectTap[EphemeralKeyManager::Tap::kLength + 1]; char checksum; uint8_t i = 7; memcpy(incorrectTap, tap3.mTap, EphemeralKeyManager::Tap::kLength + 1); incorrectTap[i]++; if (incorrectTap[i] > '9') { incorrectTap[i] = '0'; } incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = '\0'; SuccessOrQuit(Utils::VerhoeffChecksum::Calculate(incorrectTap, checksum)); incorrectTap[EphemeralKeyManager::Tap::kLength - 1] = checksum; Ip6::SockAddr br1SockAddr; br1SockAddr.SetAddress(br1Addr); br1SockAddr.SetPort(epskcPort); SuccessOrQuit(comm1.Get().SetPsk(reinterpret_cast(incorrectTap), EphemeralKeyManager::Tap::kLength)); for (uint8_t attempt = 1; attempt <= kMaxEpskcConnAttempts; attempt++) { Log("Attempt %u with incorrect ePSKc.", attempt); SuccessOrQuit(comm1.Get().Open(Ip6::kNetifBackbone)); SuccessOrQuit(comm1.Get().Connect(br1SockAddr)); nexus.AdvanceTime(kConnectTimeout); VerifyOrQuit(!comm1.Get().IsConnected()); comm1.Get().Close(); nexus.AdvanceTime(2 * Time::kOneSecondInMsec); } } Log("---------------------------------------------------------------------------------------"); Log("Step 24: Comm_1 connects with correct ePSKc (should fail)."); /** * Step 24 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to connect again to the DUT on the address and port found in * step 22. For the DTLS PSK, it uses the correct ePSKc as calculated in step 21. But the connection still fails * due to the maximum number of attempts passed. * - Pass Criteria: * - The DTLS handshake/connection to DUT MUST NOT succeed. * - Note: the reason that the correct ePSKc does not succeed here is that the maximum number of failed * connection attempts has been reached. This is being tested here. */ { Ip6::SockAddr br1SockAddr; br1SockAddr.SetAddress(br1Addr); br1SockAddr.SetPort(epskcPort); SuccessOrQuit(comm1.Get().SetPsk(reinterpret_cast(tap3.mTap), EphemeralKeyManager::Tap::kLength)); SuccessOrQuit(comm1.Get().Open(Ip6::kNetifBackbone)); SuccessOrQuit(comm1.Get().Connect(br1SockAddr)); nexus.AdvanceTime(kConnectTimeout); VerifyOrQuit(!comm1.Get().IsConnected()); comm1.Get().Close(); } Log("---------------------------------------------------------------------------------------"); Log("Step 25: Comm_1 clears its mDNS discovery cache."); /** * Step 25 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs device to clear its mDNS discovery cache. * - Pass Criteria: N/A */ sBrowseOutcomes.Clear(); Log("---------------------------------------------------------------------------------------"); Log("Step 26: Comm_1 discovers meshcop-e service (should fail)."); /** * Step 26 * - Device: Comm_1 * - Description (CS-12.3): Harness instructs the device to discover a service type via mDNS, Qtype=PTR, name * “_meshcop-e._udp.local”. * - Pass Criteria: The DUT MUST NOT respond with an mDNS message that contains a PTR record of type * “_meshcop-e._udp.local” in the Answer section. */ { Dns::Multicast::Core::Browser browser; ClearAllBytes(browser); browser.mServiceType = "_meshcop-e._udp"; browser.mInfraIfIndex = kInfraIfIndex; browser.mCallback = HandleBrowseCallback; SuccessOrQuit(comm1.Get().StartBrowser(browser)); nexus.AdvanceTime(kMdnsDiscoveryTime); SuccessOrQuit(comm1.Get().StopBrowser(browser)); } VerifyOrQuit(sBrowseOutcomes.GetLength() == 0); nexus.SaveTestInfo("test_1_4_CS_TC_3.json"); } } // namespace Nexus } // namespace ot int main(void) { ot::Nexus::Test1_4_CS_TC_3(); printf("All tests passed\n"); return 0; }