/* * * Copyright (c) 2021-2022 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. */ /** * @file * This file implements an object for a Matter User Directed Commissioning unsolicited * recipient (server). * */ #include "UserDirectedCommissioning.h" #include #include #include #include namespace chip { namespace Protocols { namespace UserDirectedCommissioning { void UserDirectedCommissioningServer::OnMessageReceived(const Transport::PeerAddress & source, System::PacketBufferHandle && msg, Transport::MessageTransportContext * ctxt) { char addrBuffer[chip::Transport::PeerAddress::kMaxToStringSize]; source.ToString(addrBuffer); ChipLogProgress(AppServer, "UserDirectedCommissioningServer::OnMessageReceived from %s", addrBuffer); PacketHeader packetHeader; ReturnOnFailure(packetHeader.DecodeAndConsume(msg)); if (packetHeader.IsEncrypted()) { ChipLogError(AppServer, "UDC encryption flag set - ignoring"); return; } PayloadHeader payloadHeader; ReturnOnFailure(payloadHeader.DecodeAndConsume(msg)); ChipLogProgress(AppServer, "IdentityDeclaration DataLength()=%" PRIu32, static_cast(msg->DataLength())); uint8_t udcPayload[IdentificationDeclaration::kUdcTLVDataMaxBytes]; size_t udcPayloadLength = std::min(msg->DataLength(), sizeof(udcPayload)); msg->Read(udcPayload, udcPayloadLength); IdentificationDeclaration id; id.ReadPayload(udcPayload, sizeof(udcPayload)); if (id.GetCancelPasscode()) { HandleUDCCancel(id); return; } if (id.GetCommissionerPasscodeReady()) { HandleUDCCommissionerPasscodeReady(id); return; } HandleNewUDC(source, id); } void UserDirectedCommissioningServer::HandleNewUDC(const Transport::PeerAddress & source, IdentificationDeclaration & id) { char * instanceName = (char *) id.GetInstanceName(); ChipLogProgress(AppServer, "HandleNewUDC instance=%s ", id.GetInstanceName()); UDCClientState * client = mUdcClients.FindUDCClientState(instanceName); if (client == nullptr) { ChipLogProgress(AppServer, "UDC new instance state received"); id.DebugLog(); CHIP_ERROR err; err = mUdcClients.CreateNewUDCClientState(instanceName, &client); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "UDC error creating new connection state"); return; } if (id.HasDiscoveryInfo()) { // if we received mDNS info, skip the commissionable lookup ChipLogDetail(AppServer, "UDC discovery info provided"); mUdcClients.MarkUDCClientActive(client); client->SetUDCClientProcessingState(UDCClientProcessingState::kPromptingUser); client->SetPeerAddress(source); id.UpdateClientState(client); // Call the registered mUserConfirmationProvider, if any. if (mUserConfirmationProvider != nullptr) { mUserConfirmationProvider->OnUserDirectedCommissioningRequest(*client); } return; } // Call the registered InstanceNameResolver, if any. if (mInstanceNameResolver != nullptr) { mInstanceNameResolver->FindCommissionableNode(instanceName); } else { ChipLogError(AppServer, "UserDirectedCommissioningServer::OnMessageReceived no mInstanceNameResolver registered"); } } mUdcClients.MarkUDCClientActive(client); } void UserDirectedCommissioningServer::HandleUDCCancel(IdentificationDeclaration & id) { char * instanceName = (char *) id.GetInstanceName(); ChipLogProgress(AppServer, "HandleUDCCancel instance=%s ", id.GetInstanceName()); UDCClientState * client = mUdcClients.FindUDCClientState(instanceName); if (client == nullptr) { ChipLogProgress(AppServer, "UDC no matching instance found"); return; } id.DebugLog(); mUdcClients.MarkUDCClientActive(client); // Call the registered mUserConfirmationProvider, if any. if (mUserConfirmationProvider != nullptr) { mUserConfirmationProvider->OnCancel(*client); } // reset this entry so that the client can try again without waiting an hour client->Reset(); } void UserDirectedCommissioningServer::HandleUDCCommissionerPasscodeReady(IdentificationDeclaration & id) { char * instanceName = (char *) id.GetInstanceName(); ChipLogProgress(AppServer, "HandleUDCCommissionerPasscodeReady instance=%s ", id.GetInstanceName()); UDCClientState * client = mUdcClients.FindUDCClientState(instanceName); if (client == nullptr) { ChipLogProgress(AppServer, "UDC no matching instance found"); return; } if (client->GetUDCClientProcessingState() != UDCClientProcessingState::kWaitingForCommissionerPasscodeReady) { ChipLogProgress(AppServer, "UDC instance not in waiting for passcode ready state"); return; } id.DebugLog(); mUdcClients.MarkUDCClientActive(client); client->SetUDCClientProcessingState(UDCClientProcessingState::kObtainingOnboardingPayload); // Call the registered mUserConfirmationProvider, if any. if (mUserConfirmationProvider != nullptr) { mUserConfirmationProvider->OnCommissionerPasscodeReady(*client); } } CHIP_ERROR UserDirectedCommissioningServer::SendCDCMessage(CommissionerDeclaration cd, chip::Transport::PeerAddress peerAddress) { if (mTransportMgr == nullptr) { ChipLogError(AppServer, "CDC: No transport manager\n"); return CHIP_ERROR_INCORRECT_STATE; } uint8_t idBuffer[IdentificationDeclaration::kUdcTLVDataMaxBytes]; uint32_t length = cd.WritePayload(idBuffer, sizeof(idBuffer)); if (length == 0) { ChipLogError(AppServer, "CDC: error writing payload\n"); return CHIP_ERROR_INTERNAL; } chip::System::PacketBufferHandle payload = chip::MessagePacketBuffer::NewWithData(idBuffer, length); if (payload.IsNull()) { ChipLogError(AppServer, "Unable to allocate packet buffer\n"); return CHIP_ERROR_NO_MEMORY; } ReturnErrorOnFailure(EncodeUDCMessage(payload)); cd.DebugLog(); ChipLogProgress(Inet, "Sending CDC msg"); auto err = mTransportMgr->SendMessage(peerAddress, std::move(payload)); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CDC SendMessage failed: %" CHIP_ERROR_FORMAT, err.Format()); return err; } ChipLogProgress(Inet, "CDC msg sent"); return CHIP_NO_ERROR; } CHIP_ERROR UserDirectedCommissioningServer::EncodeUDCMessage(const System::PacketBufferHandle & payload) { PayloadHeader payloadHeader; PacketHeader packetHeader; payloadHeader.SetMessageType(MsgType::IdentificationDeclaration).SetInitiator(true).SetNeedsAck(false); VerifyOrReturnError(!payload.IsNull(), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(!payload->HasChainedBuffer(), CHIP_ERROR_INVALID_MESSAGE_LENGTH); VerifyOrReturnError(payload->TotalLength() <= kMaxAppMessageLen, CHIP_ERROR_MESSAGE_TOO_LONG); ReturnErrorOnFailure(payloadHeader.EncodeBeforeData(payload)); ReturnErrorOnFailure(packetHeader.EncodeBeforeData(payload)); return CHIP_NO_ERROR; } CHIP_ERROR IdentificationDeclaration::ReadPayload(uint8_t * udcPayload, size_t payloadBufferSize) { size_t i = 0; while (i < std::min(sizeof(mInstanceName), payloadBufferSize) && udcPayload[i] != '\0') { mInstanceName[i] = (char) udcPayload[i]; i++; } mInstanceName[i] = '\0'; if (payloadBufferSize <= sizeof(mInstanceName)) { ChipLogProgress(AppServer, "UDC - No TLV information in Identification Declaration"); return CHIP_NO_ERROR; } // advance i to the end of the fixed length block containing instance name i = sizeof(mInstanceName); CHIP_ERROR err; TLV::TLVReader reader; reader.Init(udcPayload + i, payloadBufferSize - i); // read the envelope ReturnErrorOnFailure(reader.Next(chip::TLV::kTLVType_Structure, chip::TLV::AnonymousTag())); chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure; ReturnErrorOnFailure(reader.EnterContainer(outerContainerType)); while ((err = reader.Next()) == CHIP_NO_ERROR) { chip::TLV::Tag containerTag = reader.GetTag(); if (!TLV::IsContextTag(containerTag)) { ChipLogError(AppServer, "Unexpected non-context TLV tag."); return CHIP_ERROR_INVALID_TLV_TAG; } uint8_t tagNum = static_cast(chip::TLV::TagNumFromTag(containerTag)); switch (tagNum) { case kVendorIdTag: // vendorId err = reader.Get(mVendorId); break; case kProductIdTag: // productId err = reader.Get(mProductId); break; case kCdPortTag: // port err = reader.Get(mCdPort); break; case kDeviceNameTag: // deviceName err = reader.GetString(mDeviceName, sizeof(mDeviceName)); break; case kPairingInstTag: // pairingInst err = reader.GetString(mPairingInst, sizeof(mPairingInst)); break; case kPairingHintTag: // pairingHint err = reader.Get(mPairingHint); break; case kRotatingIdTag: // rotatingId mRotatingIdLen = reader.GetLength(); err = reader.GetBytes(mRotatingId, sizeof(mRotatingId)); break; case kTargetAppListTag: // app vendor list { ChipLogProgress(AppServer, "TLV found an applist"); chip::TLV::TLVType listContainerType = chip::TLV::kTLVType_List; ReturnErrorOnFailure(reader.EnterContainer(listContainerType)); while ((err = reader.Next()) == CHIP_NO_ERROR && mNumTargetAppInfos < sizeof(mTargetAppInfos)) { containerTag = reader.GetTag(); if (!TLV::IsContextTag(containerTag)) { ChipLogError(AppServer, "Unexpected non-context TLV tag."); return CHIP_ERROR_INVALID_TLV_TAG; } tagNum = static_cast(chip::TLV::TagNumFromTag(containerTag)); if (tagNum == kTargetAppTag) { ReturnErrorOnFailure(reader.EnterContainer(outerContainerType)); uint16_t appVendorId = 0; uint16_t appProductId = 0; while ((err = reader.Next()) == CHIP_NO_ERROR) { containerTag = reader.GetTag(); if (!TLV::IsContextTag(containerTag)) { ChipLogError(AppServer, "Unexpected non-context TLV tag."); return CHIP_ERROR_INVALID_TLV_TAG; } tagNum = static_cast(chip::TLV::TagNumFromTag(containerTag)); if (tagNum == kAppVendorIdTag) { err = reader.Get(appVendorId); } else if (tagNum == kAppProductIdTag) { err = reader.Get(appProductId); } } if (err == CHIP_END_OF_TLV) { ChipLogProgress(AppServer, "TLV end of struct TLV"); ReturnErrorOnFailure(reader.ExitContainer(outerContainerType)); } if (appVendorId != 0) { mTargetAppInfos[mNumTargetAppInfos].vendorId = appVendorId; mTargetAppInfos[mNumTargetAppInfos].productId = appProductId; mNumTargetAppInfos++; } } else { ChipLogError(AppServer, "unrecognized tag %d", tagNum); } } if (err == CHIP_END_OF_TLV) { ChipLogProgress(AppServer, "TLV end of array"); ReturnErrorOnFailure(reader.ExitContainer(listContainerType)); err = CHIP_NO_ERROR; } } break; case kNoPasscodeTag: err = reader.Get(mNoPasscode); break; case kCdUponPasscodeDialogTag: err = reader.Get(mCdUponPasscodeDialog); break; case kCommissionerPasscodeTag: err = reader.Get(mCommissionerPasscode); break; case kCommissionerPasscodeReadyTag: err = reader.Get(mCommissionerPasscodeReady); break; case kCancelPasscodeTag: err = reader.Get(mCancelPasscode); break; } if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "IdentificationDeclaration::ReadPayload read error %" CHIP_ERROR_FORMAT, err.Format()); } } if (err == CHIP_END_OF_TLV) { // Exiting container ReturnErrorOnFailure(reader.ExitContainer(outerContainerType)); } else { ChipLogError(AppServer, "IdentificationDeclaration::ReadPayload exiting early error %" CHIP_ERROR_FORMAT, err.Format()); } ChipLogProgress(AppServer, "UDC TLV parse complete"); return CHIP_NO_ERROR; } /** * Reset the connection state to a completely uninitialized status. */ uint32_t CommissionerDeclaration::WritePayload(uint8_t * payloadBuffer, size_t payloadBufferSize) { CHIP_ERROR err; chip::TLV::TLVWriter writer; writer.Init(payloadBuffer, payloadBufferSize); chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure; VerifyOrExit(CHIP_NO_ERROR == (err = writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, outerContainerType)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.Put(chip::TLV::ContextTag(kErrorCodeTag), GetErrorCode())), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kNeedsPasscodeTag), mNeedsPasscode)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kNoAppsFoundTag), mNoAppsFound)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kPasscodeDialogDisplayedTag), mPasscodeDialogDisplayed)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kCommissionerPasscodeTag), mCommissionerPasscode)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kQRCodeDisplayedTag), mQRCodeDisplayed)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kCancelPasscodeTag), mCancelPasscode)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.EndContainer(outerContainerType)), LogErrorOnFailure(err)); VerifyOrExit(CHIP_NO_ERROR == (err = writer.Finalize()), LogErrorOnFailure(err)); ChipLogProgress(AppServer, "TLV write done"); return writer.GetLengthWritten(); exit: return 0; } void UserDirectedCommissioningServer::SetUDCClientProcessingState(char * instanceName, UDCClientProcessingState state) { UDCClientState * client = mUdcClients.FindUDCClientState(instanceName); if (client == nullptr) { CHIP_ERROR err; err = mUdcClients.CreateNewUDCClientState(instanceName, &client); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "UserDirectedCommissioningServer::SetUDCClientProcessingState error creating new connection state"); return; } } ChipLogDetail(AppServer, "SetUDCClientProcessingState instance=%s new state=%d", StringOrNullMarker(instanceName), (int) state); client->SetUDCClientProcessingState(state); mUdcClients.MarkUDCClientActive(client); } void UserDirectedCommissioningServer::OnCommissionableNodeFound(const Dnssd::DiscoveredNodeData & discNodeData) { if (!discNodeData.Is()) { return; } const Dnssd::CommissionNodeData & nodeData = discNodeData.Get(); if (nodeData.numIPs == 0) { ChipLogError(AppServer, "OnCommissionableNodeFound no IP addresses returned for instance name=%s", nodeData.instanceName); return; } if (nodeData.port == 0) { ChipLogError(AppServer, "OnCommissionableNodeFound no port returned for instance name=%s", nodeData.instanceName); return; } UDCClientState * client = mUdcClients.FindUDCClientState(nodeData.instanceName); if (client != nullptr && client->GetUDCClientProcessingState() == UDCClientProcessingState::kDiscoveringNode) { ChipLogDetail(AppServer, "OnCommissionableNodeFound instance: name=%s old_state=%d new_state=%d", client->GetInstanceName(), (int) client->GetUDCClientProcessingState(), (int) UDCClientProcessingState::kPromptingUser); client->SetUDCClientProcessingState(UDCClientProcessingState::kPromptingUser); #if INET_CONFIG_ENABLE_IPV4 // prefer IPv4 if its an option bool foundV4 = false; for (unsigned i = 0; i < nodeData.numIPs; ++i) { if (nodeData.ipAddress[i].IsIPv4()) { foundV4 = true; client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[i], nodeData.port)); break; } } // use IPv6 as last resort if (!foundV4) { client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[0], nodeData.port)); } #else // INET_CONFIG_ENABLE_IPV4 // if we only support V6, then try to find a v6 address bool foundV6 = false; for (unsigned i = 0; i < nodeData.numIPs; ++i) { if (nodeData.ipAddress[i].IsIPv6()) { foundV6 = true; client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[i], nodeData.port)); break; } } // last resort, try with what we have if (!foundV6) { ChipLogError(AppServer, "OnCommissionableNodeFound no v6 returned for instance name=%s", nodeData.instanceName); client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[0], nodeData.port)); } #endif // INET_CONFIG_ENABLE_IPV4 client->SetDeviceName(nodeData.deviceName); client->SetLongDiscriminator(nodeData.longDiscriminator); client->SetVendorId(nodeData.vendorId); client->SetProductId(nodeData.productId); client->SetRotatingId(nodeData.rotatingId, nodeData.rotatingIdLen); // Call the registered mUserConfirmationProvider, if any. if (mUserConfirmationProvider != nullptr) { mUserConfirmationProvider->OnUserDirectedCommissioningRequest(*client); } } } void UserDirectedCommissioningServer::PrintUDCClients() { for (uint8_t i = 0; i < kMaxUDCClients; i++) { UDCClientState * state = GetUDCClients().GetUDCClientState(i); if (state == nullptr) { ChipLogProgress(AppServer, "UDC Client[%d] null", i); } else { char addrBuffer[chip::Transport::PeerAddress::kMaxToStringSize]; state->GetPeerAddress().ToString(addrBuffer); char rotatingIdString[chip::Dnssd::kMaxRotatingIdLen * 2 + 1] = ""; Encoding::BytesToUppercaseHexString(state->GetRotatingId(), chip::Dnssd::kMaxRotatingIdLen, rotatingIdString, sizeof(rotatingIdString)); ChipLogProgress(AppServer, "UDC Client[%d] instance=%s deviceName=%s address=%s, vid/pid=%d/%d disc=%d rid=%s", i, state->GetInstanceName(), state->GetDeviceName(), addrBuffer, state->GetVendorId(), state->GetProductId(), state->GetLongDiscriminator(), rotatingIdString); } } } } // namespace UserDirectedCommissioning } // namespace Protocols } // namespace chip