Matter SDK Coverage Report
Current view: top level - controller - ThreadMeshcopCommissionProxy.cpp (source / functions) Coverage Total Hit
Test: SHA:3f9cd168e84cd831b7699126f5296f5c5498690f Lines: 3.5 % 229 8
Test Date: 2026-04-27 19:52:19 Functions: 11.1 % 18 2

            Line data    Source code
       1              : /*
       2              :  *   Copyright (c) 2026 Project CHIP Authors
       3              :  *   All rights reserved.
       4              :  *
       5              :  *   Licensed under the Apache License, Version 2.0 (the "License");
       6              :  *   you may not use this file except in compliance with the License.
       7              :  *   You may obtain a copy of the License at
       8              :  *
       9              :  *       http://www.apache.org/licenses/LICENSE-2.0
      10              :  *
      11              :  *   Unless required by applicable law or agreed to in writing, software
      12              :  *   distributed under the License is distributed on an "AS IS" BASIS,
      13              :  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      14              :  *   See the License for the specific language governing permissions and
      15              :  *   limitations under the License.
      16              :  *
      17              :  */
      18              : 
      19              : #include "ThreadMeshcopCommissionProxy.h"
      20              : 
      21              : #include <lib/core/CHIPEncoding.h>
      22              : #include <lib/dnssd/TxtFields.h>
      23              : #include <lib/dnssd/minimal_mdns/core/QNameString.h> // nogncheck
      24              : #include <lib/support/CHIPMemString.h>
      25              : #include <lib/support/CodeUtils.h>
      26              : #include <lib/support/logging/CHIPLogging.h>
      27              : #include <transport/raw/MessageHeader.h>
      28              : 
      29              : #include <errno.h>
      30              : #include <inttypes.h>
      31              : #include <netinet/in.h>
      32              : #include <sys/socket.h>
      33              : #include <unistd.h>
      34              : 
      35              : #include <chrono>
      36              : #include <thread>
      37              : 
      38              : using namespace chip;
      39              : 
      40              : namespace {
      41              : /**
      42              :  * Internal OT Commissioner Logger implementation.
      43              :  */
      44              : class CommissionerLogger : public ot::commissioner::Logger
      45              : {
      46              : public:
      47            0 :     void Log(ot::commissioner::LogLevel level, const std::string & region, const std::string & message) override
      48              :     {
      49            0 :         ChipLogProgress(Controller, "[ot-commissioner][%u][%s] %s", static_cast<unsigned>(level), region.c_str(), message.c_str());
      50            0 :     }
      51              : };
      52              : 
      53              : constexpr char kMatterCServiceSuffix[] = "_matterc._udp.local";
      54              : 
      55            0 : uint64_t JoinerIdFromBytes(const std::vector<uint8_t> & bytes)
      56              : {
      57            0 :     const uint8_t * buffer = bytes.data();
      58            0 :     return Encoding::BigEndian::Read64(buffer);
      59              : }
      60              : 
      61            0 : std::vector<uint8_t> DiscoveryCodeToVector(Thread::DiscoveryCode code)
      62              : {
      63              :     uint8_t bytes[sizeof(uint64_t)];
      64            0 :     Encoding::BigEndian::Put64(bytes, code.AsUInt64());
      65            0 :     return std::vector<uint8_t>(bytes, bytes + sizeof(bytes));
      66              : }
      67              : } // namespace
      68              : 
      69              : namespace chip {
      70              : namespace Controller {
      71              : 
      72           13 : ThreadMeshcopCommissionProxy::ThreadMeshcopCommissionProxy() : mState(State::kConnecting), mPromiseFulfilled(false)
      73              : {
      74           13 :     mCommissioner = ot::commissioner::Commissioner::Create(*this);
      75           13 : }
      76              : 
      77           13 : ThreadMeshcopCommissionProxy::~ThreadMeshcopCommissionProxy()
      78              : {
      79           13 :     std::lock_guard<std::recursive_mutex> lock(mMutex);
      80           13 :     if (mProxyFd != -1)
      81              :     {
      82            0 :         if (shutdown(mProxyFd, SHUT_RDWR) == 0 || errno != EBADF)
      83              :         {
      84            0 :             close(mProxyFd);
      85              :         }
      86              : 
      87            0 :         mProxyFd = -1;
      88              :     }
      89              : 
      90           13 :     if (mProxyThread.joinable())
      91              :     {
      92            0 :         mProxyThread.join();
      93              :     }
      94           13 : }
      95              : 
      96            0 : void ThreadMeshcopCommissionProxy::SetState(State state)
      97              : {
      98            0 :     mState = state;
      99            0 : }
     100              : 
     101            0 : void ThreadMeshcopCommissionProxy::OnHeader(mdns::Minimal::ConstHeaderRef & header)
     102              : {
     103            0 :     ChipLogDetail(Controller, "mDNS Response: ID=%u, Answers=%u, Additional=%u", header.GetMessageId(), header.GetAnswerCount(),
     104              :                   header.GetAdditionalCount());
     105            0 : }
     106              : 
     107            0 : void ThreadMeshcopCommissionProxy::OnQuery(const mdns::Minimal::QueryData & data)
     108              : {
     109            0 :     if (mState != State::kDiscovering)
     110              :     {
     111            0 :         ChipLogProgress(Controller, "Received mDNS query but proxy is not in discovery state");
     112              :     }
     113              : 
     114            0 :     ChipLogDetail(Controller, "mDNS query: %s", mdns::Minimal::QNameString(data.GetName()).c_str());
     115            0 :     mNodeData.Set<Dnssd::CommissionNodeData>();
     116            0 : }
     117              : 
     118            0 : void ThreadMeshcopCommissionProxy::OnResource(mdns::Minimal::ResourceType section, const mdns::Minimal::ResourceData & data)
     119              : {
     120            0 :     if (mState != State::kDiscovering)
     121              :     {
     122            0 :         return;
     123              :     }
     124              : 
     125            0 :     auto name             = mdns::Minimal::QNameString(data.GetName());
     126            0 :     auto & commissionData = mNodeData.Get<Dnssd::CommissionNodeData>();
     127              : 
     128            0 :     commissionData.threadMeshcop = true;
     129              : 
     130            0 :     switch (data.GetType())
     131              :     {
     132            0 :     case mdns::Minimal::QType::A:
     133              :     case mdns::Minimal::QType::AAAA:
     134            0 :         Platform::CopyString(commissionData.hostName, name.c_str());
     135            0 :         break;
     136              : 
     137            0 :     case mdns::Minimal::QType::SRV: {
     138            0 :         mdns::Minimal::SrvRecord srv;
     139            0 :         if (!srv.Parse(data.GetData(), mDnsPacket))
     140              :         {
     141            0 :             ChipLogError(Controller, "Failed to parse mDNS SRV record");
     142            0 :             return;
     143              :         }
     144              : 
     145            0 :         if (!name.EndsWith(kMatterCServiceSuffix))
     146              :         {
     147            0 :             ChipLogDetail(Controller, "Ignoring non-Matter service: %s", name.c_str());
     148            0 :             return;
     149              :         }
     150              : 
     151              :         // Extract the instance label (portion before "._matterc._udp.local") for CommissionNodeData::instanceName.
     152            0 :         std::string fullName(name.c_str());
     153            0 :         constexpr size_t kMatterCServiceSuffixLen = sizeof(kMatterCServiceSuffix) - 1; // exclude null terminator
     154            0 :         if (fullName.length() >= kMatterCServiceSuffixLen)
     155              :         {
     156            0 :             fullName.erase(fullName.length() - kMatterCServiceSuffixLen);
     157              :         }
     158            0 :         Platform::CopyString(commissionData.instanceName, fullName.c_str());
     159              : 
     160            0 :         mServicePort = srv.GetPort();
     161              : 
     162            0 :         if (mProxyFd == -1)
     163              :         {
     164            0 :             CHIP_ERROR err = CreateProxySocket(commissionData);
     165            0 :             if (err != CHIP_NO_ERROR)
     166              :             {
     167            0 :                 ChipLogError(Controller, "Failed to setup proxy socket: %" CHIP_ERROR_FORMAT, err.Format());
     168            0 :                 SetState(State::kAborted);
     169              :             }
     170              :         }
     171            0 :         break;
     172            0 :     }
     173              : 
     174            0 :     case mdns::Minimal::QType::TXT:
     175            0 :         mdns::Minimal::ParseTxtRecord(data.GetData(), this);
     176            0 :         break;
     177              : 
     178            0 :     default:
     179            0 :         break;
     180              :     }
     181              : }
     182              : 
     183            0 : CHIP_ERROR ThreadMeshcopCommissionProxy::CreateProxySocket(chip::Dnssd::CommissionNodeData & commissionData)
     184              : {
     185            0 :     mProxyFd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
     186            0 :     VerifyOrReturnError(mProxyFd >= 0, CHIP_ERROR_POSIX(errno));
     187              : 
     188            0 :     sockaddr_in6 addr = {};
     189            0 :     addr.sin6_family  = AF_INET6;
     190            0 :     addr.sin6_port    = 0;
     191            0 :     addr.sin6_addr    = in6addr_loopback;
     192              : 
     193            0 :     if (bind(mProxyFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) != 0)
     194              :     {
     195            0 :         close(mProxyFd);
     196            0 :         mProxyFd = -1;
     197            0 :         return CHIP_ERROR_POSIX(errno);
     198              :     }
     199              : 
     200            0 :     socklen_t addr_len = sizeof(addr);
     201            0 :     if (getsockname(mProxyFd, reinterpret_cast<struct sockaddr *>(&addr), &addr_len) == -1)
     202              :     {
     203            0 :         close(mProxyFd);
     204            0 :         mProxyFd = -1;
     205            0 :         return CHIP_ERROR_POSIX(errno);
     206              :     }
     207              : 
     208            0 :     commissionData.numIPs       = 1;
     209            0 :     commissionData.port         = ntohs(addr.sin6_port);
     210            0 :     commissionData.ipAddress[0] = Inet::IPAddress::FromSockAddr(addr);
     211            0 :     commissionData.interfaceId  = Inet::InterfaceId::FromIPAddress(commissionData.ipAddress[0]);
     212              : 
     213            0 :     ChipLogProgress(Controller, "Proxy socket created on port %u", commissionData.port);
     214            0 :     return CHIP_NO_ERROR;
     215              : }
     216              : 
     217            0 : void ThreadMeshcopCommissionProxy::OnRecord(const mdns::Minimal::BytesRange & name, const mdns::Minimal::BytesRange & value)
     218              : {
     219            0 :     ByteSpan key(name.Start(), name.Size());
     220            0 :     ByteSpan val(value.Start(), value.Size());
     221              : 
     222            0 :     Dnssd::FillNodeDataFromTxt(key, val, mNodeData.Get<Dnssd::CommissionNodeData>());
     223            0 : }
     224              : 
     225            0 : void ThreadMeshcopCommissionProxy::ProcessAnnouncement(const std::vector<uint8_t> & joinerIdBytes, uint16_t joinerPort,
     226              :                                                        const std::vector<uint8_t> & payload)
     227              : {
     228            0 :     std::lock_guard<std::recursive_mutex> lock(mMutex);
     229              : 
     230            0 :     if (mPromiseFulfilled)
     231              :     {
     232            0 :         return;
     233              :     }
     234              : 
     235            0 :     mNodeData.Set<Dnssd::CommissionNodeData>();
     236            0 :     mDnsPacket = mdns::Minimal::BytesRange(payload.data(), payload.data() + payload.size());
     237              : 
     238            0 :     if (!mdns::Minimal::ParsePacket(mDnsPacket, this))
     239              :     {
     240            0 :         ChipLogError(Controller, "Failed to parse joiner mDNS announcement");
     241            0 :         return;
     242              :     }
     243              : 
     244            0 :     uint32_t discoveredDiscriminator = mNodeData.Get<Dnssd::CommissionNodeData>().longDiscriminator;
     245            0 :     ChipLogProgress(Controller, "Discovered joiner with discriminator: %u", discoveredDiscriminator);
     246              : 
     247            0 :     if (!mExpectedDiscriminator.MatchesLongDiscriminator(static_cast<uint16_t>(discoveredDiscriminator)))
     248              :     {
     249            0 :         ChipLogProgress(Controller, "Discriminator mismatch (Expected %u, Got %u). Ignoring announcement.",
     250              :                         mExpectedDiscriminator.GetLongValue(), discoveredDiscriminator);
     251            0 :         return;
     252              :     }
     253              : 
     254            0 :     mDiscoveredNodePromise.set_value(mNodeData);
     255            0 :     mPromiseFulfilled = true;
     256              : 
     257            0 :     SetState(State::kDiscovered);
     258              : 
     259            0 :     if (mProxyThread.joinable())
     260              :     {
     261            0 :         mProxyThread.join();
     262              :     }
     263              : 
     264            0 :     mProxyThread = std::thread([id = joinerIdBytes, this]() {
     265              :         struct sockaddr_storage addr;
     266            0 :         socklen_t len = sizeof(addr);
     267              :         uint8_t buf[chip::detail::kMaxIPPacketSizeBytes];
     268              :         ssize_t received;
     269              : 
     270            0 :         while ((received = recvfrom(mProxyFd, buf, sizeof(buf), 0, reinterpret_cast<struct sockaddr *>(&addr), &len)) > 0)
     271              :         {
     272            0 :             switch (mState)
     273              :             {
     274            0 :             case State::kDiscovered: {
     275            0 :                 int rval = connect(mProxyFd, reinterpret_cast<struct sockaddr *>(&addr), len);
     276            0 :                 if (rval < 0)
     277              :                 {
     278            0 :                     ChipLogError(Controller, "Failed to connect to Matter Commissioner: %s", strerror(errno));
     279            0 :                     continue;
     280              :                 }
     281            0 :                 SetState(State::kCommissioning);
     282              :                 FALLTHROUGH;
     283              :             }
     284              : 
     285            0 :             case State::kCommissioning: {
     286            0 :                 std::vector<uint8_t> pkt(buf, buf + received);
     287              : 
     288            0 :                 auto error = mCommissioner->SendToJoiner(id, mServicePort, pkt);
     289            0 :                 if (error != ot::commissioner::ErrorCode::kNone)
     290              :                 {
     291            0 :                     ChipLogError(Controller, "Failed to send packet to joiner: %s", error.GetMessage().c_str());
     292            0 :                     return;
     293              :                 }
     294            0 :                 break;
     295            0 :             }
     296            0 :             default:
     297            0 :                 ChipLogError(Controller, "Invalid CommissionProxy state: %d", static_cast<int>(mState.load()));
     298            0 :                 return;
     299              :             }
     300              :         }
     301            0 :     });
     302            0 : }
     303              : 
     304            0 : void ThreadMeshcopCommissionProxy::OnJoinerMessage(const std::vector<uint8_t> & joinerIdBytes, uint16_t joinerPort,
     305              :                                                    const std::vector<uint8_t> & payload)
     306              : {
     307            0 :     std::lock_guard<std::recursive_mutex> lock(mMutex);
     308              : 
     309            0 :     if (joinerIdBytes.size() != sizeof(uint64_t) || mState == State::kAborted)
     310              :     {
     311            0 :         return;
     312              :     }
     313              : 
     314            0 :     uint64_t joinerId = JoinerIdFromBytes(joinerIdBytes);
     315            0 :     ChipLogDetail(Controller, "Message from joiner 0x%" PRIx64 " on port %u", joinerId, joinerPort);
     316              : 
     317            0 :     if (mJoinerId == 0)
     318              :     {
     319            0 :         mJoinerId = joinerId;
     320              :     }
     321            0 :     else if (mJoinerId != joinerId)
     322              :     {
     323            0 :         ChipLogProgress(Controller, "Ignoring message from unexpected joiner 0x%" PRIx64, joinerId);
     324            0 :         return;
     325              :     }
     326              : 
     327            0 :     switch (mState)
     328              :     {
     329            0 :     case State::kCommissioning:
     330            0 :         if (mProxyFd != -1)
     331              :         {
     332            0 :             if (send(mProxyFd, payload.data(), payload.size(), 0) < 0)
     333              :             {
     334            0 :                 ChipLogError(Controller, "Failed to forward packet to local proxy: %s", strerror(errno));
     335            0 :                 SetState(State::kAborted);
     336              :             }
     337              :         }
     338            0 :         break;
     339            0 :     case State::kAborted:
     340            0 :         break;
     341            0 :     case State::kConnecting:
     342              :         // First message from joiner is usually the mDNS announcement
     343            0 :         SetState(State::kDiscovering);
     344              :         FALLTHROUGH;
     345            0 :     case State::kDiscovering:
     346            0 :         ProcessAnnouncement(joinerIdBytes, joinerPort, payload);
     347            0 :         break;
     348            0 :     case State::kDiscovered:
     349            0 :         ChipLogProgress(Controller, "WARNING ignore unsolicited messages after joiner is already discovered");
     350            0 :         break;
     351              :     }
     352            0 : }
     353              : 
     354            0 : ot::commissioner::CommissionerDataset ThreadMeshcopCommissionProxy::MakeCommissionerDataset(Thread::DiscoveryCode code)
     355              : {
     356            0 :     ot::commissioner::CommissionerDataset dataset;
     357              : 
     358            0 :     dataset.mJoinerUdpPort = ot::commissioner::kDefaultJoinerUdpPort;
     359            0 :     dataset.mPresentFlags |= ot::commissioner::CommissionerDataset::kJoinerUdpPortBit;
     360            0 :     dataset.mPresentFlags &=
     361              :         ~(ot::commissioner::CommissionerDataset::kSessionIdBit | ot::commissioner::CommissionerDataset::kBorderAgentLocatorBit);
     362              : 
     363            0 :     if (code.IsAny())
     364              :     {
     365            0 :         dataset.mSteeringData = std::vector<uint8_t>{ 0xff };
     366              :     }
     367              :     else
     368              :     {
     369            0 :         std::vector<uint8_t> steeringData(ot::commissioner::kMaxSteeringDataLength);
     370            0 :         ot::commissioner::Commissioner::AddJoiner(steeringData, DiscoveryCodeToVector(code));
     371            0 :         dataset.mSteeringData = steeringData;
     372            0 :     }
     373              : 
     374            0 :     dataset.mPresentFlags |= ot::commissioner::CommissionerDataset::kSteeringDataBit;
     375            0 :     return dataset;
     376              : }
     377            0 : CHIP_ERROR ThreadMeshcopCommissionProxy::InitializeCommissioner(ByteSpan & pskc)
     378              : {
     379            0 :     VerifyOrReturnError(pskc.size() == Thread::kSizePSKc, CHIP_ERROR_INVALID_ARGUMENT);
     380            0 :     ot::commissioner::Config config;
     381            0 :     config.mLogger    = std::make_shared<CommissionerLogger>();
     382            0 :     config.mEnableCcm = false;
     383            0 :     config.mProxyMode = true;
     384            0 :     config.mPSKc      = std::vector<uint8_t>(pskc.begin(), pskc.end());
     385              : 
     386            0 :     auto error = mCommissioner->Init(config);
     387            0 :     if (error != ot::commissioner::ErrorCode::kNone)
     388              :     {
     389            0 :         ChipLogError(Controller, "OT Commissioner Init failed: %s", error.GetMessage().c_str());
     390            0 :         return CHIP_ERROR_INTERNAL;
     391              :     }
     392            0 :     return CHIP_NO_ERROR;
     393            0 : }
     394              : 
     395            0 : CHIP_ERROR ThreadMeshcopCommissionProxy::Discover(ByteSpan & pskc, const Transport::PeerAddress & peerAddr,
     396              :                                                   const Thread::DiscoveryCode code, SetupDiscriminator expectedDiscriminator,
     397              :                                                   Dnssd::DiscoveredNodeData & nodeData, uint16_t timeout)
     398              : {
     399              :     using ot::commissioner::Error;
     400              : 
     401            0 :     Error error;
     402              : 
     403              :     // Reset the promise and state for a new discovery session
     404            0 :     std::future<Dnssd::DiscoveredNodeData> future;
     405              :     {
     406            0 :         std::lock_guard<std::recursive_mutex> lock(mMutex);
     407            0 :         mExpectedDiscriminator = expectedDiscriminator;
     408            0 :         SetState(State::kConnecting);
     409            0 :         mDiscoveredNodePromise = std::promise<Dnssd::DiscoveredNodeData>();
     410            0 :         future                 = mDiscoveredNodePromise.get_future();
     411            0 :         mPromiseFulfilled      = false;
     412            0 :         mJoinerId              = 0;
     413            0 :     }
     414              : 
     415            0 :     ReturnErrorOnFailure(InitializeCommissioner(pskc));
     416              : 
     417              :     {
     418            0 :         std::string id;
     419              :         char host[Inet::IPAddress::kMaxStringLength];
     420            0 :         peerAddr.GetIPAddress().ToString(host);
     421              : 
     422            0 :         ChipLogProgress(Controller, "Petitioning Thread Border Agent at %s:%u", host, peerAddr.GetPort());
     423            0 :         error = mCommissioner->Petition(id, std::string(host), peerAddr.GetPort());
     424            0 :         if (error != ot::commissioner::ErrorCode::kNone)
     425              :         {
     426            0 :             ChipLogError(Controller, "Petition failed: %s", error.GetMessage().c_str());
     427            0 :             SetState(State::kAborted);
     428            0 :             return CHIP_ERROR_INTERNAL;
     429              :         }
     430              : 
     431            0 :         ChipLogProgress(Controller, "Thread Commissioner active with ID: %s", id.c_str());
     432            0 :     }
     433              : 
     434            0 :     error = mCommissioner->SetCommissionerDataset(MakeCommissionerDataset(code));
     435            0 :     if (error != ot::commissioner::ErrorCode::kNone)
     436              :     {
     437            0 :         ChipLogError(Controller, "Failed to set Steering Data: %s", error.GetMessage().c_str());
     438            0 :         SetState(State::kAborted);
     439            0 :         return CHIP_ERROR_INTERNAL;
     440              :     }
     441              : 
     442            0 :     ChipLogProgress(Controller, "Waiting for mDNS announcement from joiner...");
     443            0 :     auto waitDuration = std::chrono::seconds(timeout);
     444            0 :     if (future.wait_for(waitDuration) == std::future_status::timeout)
     445              :     {
     446            0 :         ChipLogError(Controller, "Timed out waiting for joiner mDNS announcement after %u seconds", timeout);
     447            0 :         SetState(State::kAborted);
     448            0 :         return CHIP_ERROR_TIMEOUT;
     449              :     }
     450            0 :     nodeData = future.get();
     451            0 :     return CHIP_NO_ERROR;
     452            0 : }
     453              : } // namespace Controller
     454              : } // namespace chip
        

Generated by: LCOV version 2.0-1