| /* |
| * |
| * 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 <lib/core/CHIPSafeCasts.h> |
| #include <system/TLVPacketBufferBackingStore.h> |
| |
| #include <unistd.h> |
| |
| namespace chip { |
| namespace Protocols { |
| namespace UserDirectedCommissioning { |
| |
| void UserDirectedCommissioningServer::OnMessageReceived(const Transport::PeerAddress & source, System::PacketBufferHandle && msg) |
| { |
| 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()=%d", msg->DataLength()); |
| |
| uint8_t udcPayload[IdentificationDeclaration::kUdcTLVDataMaxBytes]; |
| size_t udcPayloadLength = std::min<size_t>(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<size_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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)); |
| } |
| } |
| 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.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 & nodeData) |
| { |
| if (nodeData.resolutionData.numIPs == 0) |
| { |
| ChipLogError(AppServer, "OnCommissionableNodeFound no IP addresses returned for instance name=%s", |
| nodeData.nodeData.instanceName); |
| return; |
| } |
| if (nodeData.resolutionData.port == 0) |
| { |
| ChipLogError(AppServer, "OnCommissionableNodeFound no port returned for instance name=%s", nodeData.nodeData.instanceName); |
| return; |
| } |
| |
| UDCClientState * client = mUdcClients.FindUDCClientState(nodeData.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.resolutionData.numIPs; ++i) |
| { |
| if (nodeData.resolutionData.ipAddress[i].IsIPv4()) |
| { |
| foundV4 = true; |
| client->SetPeerAddress( |
| chip::Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[i], nodeData.resolutionData.port)); |
| break; |
| } |
| } |
| // use IPv6 as last resort |
| if (!foundV4) |
| { |
| client->SetPeerAddress( |
| chip::Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[0], nodeData.resolutionData.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.resolutionData.numIPs; ++i) |
| { |
| if (nodeData.resolutionData.ipAddress[i].IsIPv6()) |
| { |
| foundV6 = true; |
| client->SetPeerAddress( |
| chip::Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[i], nodeData.resolutionData.port)); |
| break; |
| } |
| } |
| // last resort, try with what we have |
| if (!foundV6) |
| { |
| ChipLogError(AppServer, "OnCommissionableNodeFound no v6 returned for instance name=%s", |
| nodeData.nodeData.instanceName); |
| client->SetPeerAddress( |
| chip::Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[0], nodeData.resolutionData.port)); |
| } |
| #endif // INET_CONFIG_ENABLE_IPV4 |
| |
| client->SetDeviceName(nodeData.nodeData.deviceName); |
| client->SetLongDiscriminator(nodeData.nodeData.longDiscriminator); |
| client->SetVendorId(nodeData.nodeData.vendorId); |
| client->SetProductId(nodeData.nodeData.productId); |
| client->SetRotatingId(nodeData.nodeData.rotatingId, nodeData.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 |