| /* | 
 |  *   Copyright (c) 2025 Project CHIP Authors | 
 |  *   All rights reserved. | 
 |  * | 
 |  *   Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  *   you may not use this file except in compliance with the License. | 
 |  *   You may obtain a copy of the License at | 
 |  * | 
 |  *       http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  *   Unless required by applicable law or agreed to in writing, software | 
 |  *   distributed under the License is distributed on an "AS IS" BASIS, | 
 |  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  *   See the License for the specific language governing permissions and | 
 |  *   limitations under the License. | 
 |  * | 
 |  */ | 
 |  | 
 | #include "WebRTCProviderClient.h" | 
 | #include "WebRTCManager.h" | 
 |  | 
 | using namespace ::chip; | 
 | using namespace ::chip::app; | 
 | using WebRTCSessionStruct = chip::app::Clusters::Globals::Structs::WebRTCSessionStruct::Type; | 
 |  | 
 | namespace { | 
 |  | 
 | // Constants | 
 | constexpr uint32_t kSessionTimeoutSeconds       = 5; | 
 | constexpr uint32_t kDeferredOfferTimeoutSeconds = 30; | 
 |  | 
 | } // namespace | 
 |  | 
 | void WebRTCProviderClient::Init(const ScopedNodeId & peerId, EndpointId endpointId, | 
 |                                 Clusters::WebRTCTransportRequestor::WebRTCTransportRequestorServer * requestorServer) | 
 | { | 
 |     mPeerId          = peerId; | 
 |     mEndpointId      = endpointId; | 
 |     mRequestorServer = requestorServer; | 
 |  | 
 |     ChipLogProgress(Camera, "WebRTCProviderClient: Initialized with PeerId=0x" ChipLogFormatX64 ", endpoint=%u", | 
 |                     ChipLogValueX64(peerId.GetNodeId()), static_cast<unsigned>(endpointId)); | 
 | } | 
 |  | 
 | CHIP_ERROR WebRTCProviderClient::SolicitOffer(StreamUsageEnum streamUsage, EndpointId originatingEndpointId, | 
 |                                               Optional<DataModel::Nullable<uint16_t>> videoStreamId, | 
 |                                               Optional<DataModel::Nullable<uint16_t>> audioStreamId) | 
 | { | 
 |     ChipLogProgress(Camera, "Sending SolicitOffer to node " ChipLogFormatX64, ChipLogValueX64(mPeerId.GetNodeId())); | 
 |  | 
 |     if (mState != State::Idle) | 
 |     { | 
 |         ChipLogError(Camera, "Operation NOT POSSIBLE: another sync is in progress"); | 
 |         return CHIP_ERROR_INCORRECT_STATE; | 
 |     } | 
 |  | 
 |     // Store the command type | 
 |     mCommandType = CommandType::kSolicitOffer; | 
 |  | 
 |     // Stash data in class members so the CommandSender can safely reference them async | 
 |     mSolicitOfferData.streamUsage           = streamUsage; | 
 |     mSolicitOfferData.originatingEndpointID = originatingEndpointId; | 
 |     mSolicitOfferData.videoStreamID         = videoStreamId; | 
 |     mSolicitOfferData.audioStreamID         = audioStreamId; | 
 |  | 
 |     // ICE info are sent during the ICE candidate exchange phase of this flow. | 
 |     mSolicitOfferData.ICEServers         = NullOptional; | 
 |     mSolicitOfferData.ICETransportPolicy = NullOptional; | 
 |  | 
 |     // Store the streamUsage from the original command so we can build the WebRTCSessionStruct when the response arrives. | 
 |     mCurrentStreamUsage = streamUsage; | 
 |  | 
 |     // Attempt to find or establish a CASE session to the target PeerId. | 
 |     InteractionModelEngine * engine     = InteractionModelEngine::GetInstance(); | 
 |     CASESessionManager * caseSessionMgr = engine->GetCASESessionManager(); | 
 |     VerifyOrReturnError(caseSessionMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); | 
 |  | 
 |     MoveToState(State::Connecting); | 
 |  | 
 |     // WebRTC ProvideOffer requires a large payload session establishment | 
 |     caseSessionMgr->FindOrEstablishSession(mPeerId, &mOnConnectedCallback, &mOnConnectionFailureCallback, | 
 |                                            TransportPayloadCapability::kLargePayload); | 
 |  | 
 |     return CHIP_NO_ERROR; | 
 | } | 
 |  | 
 | CHIP_ERROR WebRTCProviderClient::ProvideOffer(DataModel::Nullable<uint16_t> webRTCSessionId, std::string sdp, | 
 |                                               StreamUsageEnum streamUsage, EndpointId originatingEndpointId, | 
 |                                               Optional<DataModel::Nullable<uint16_t>> videoStreamId, | 
 |                                               Optional<DataModel::Nullable<uint16_t>> audioStreamId) | 
 | { | 
 |     ChipLogProgress(Camera, "Sending ProvideOffer to node " ChipLogFormatX64, ChipLogValueX64(mPeerId.GetNodeId())); | 
 |  | 
 |     if (mState != State::Idle) | 
 |     { | 
 |         ChipLogError(Camera, "Operation NOT POSSIBLE: another sync is in progress"); | 
 |         return CHIP_ERROR_INCORRECT_STATE; | 
 |     } | 
 |  | 
 |     // Store the command type | 
 |     mCommandType = CommandType::kProvideOffer; | 
 |  | 
 |     // Stash data in class members so the CommandSender can safely reference them async | 
 |     mSdpString                              = sdp; | 
 |     mProvideOfferData.webRTCSessionID       = webRTCSessionId; | 
 |     mProvideOfferData.sdp                   = CharSpan::fromCharString(mSdpString.c_str()); | 
 |     mProvideOfferData.streamUsage           = streamUsage; | 
 |     mProvideOfferData.originatingEndpointID = originatingEndpointId; | 
 |     mProvideOfferData.videoStreamID         = videoStreamId; | 
 |     mProvideOfferData.audioStreamID         = audioStreamId; | 
 |  | 
 |     // ICE info are sent during the ICE candidate exchange phase of this flow. | 
 |     mProvideOfferData.ICEServers         = NullOptional; | 
 |     mProvideOfferData.ICETransportPolicy = NullOptional; | 
 |  | 
 |     // Store the streamUsage from the original command so we can build the WebRTCSessionStruct when the response arrives. | 
 |     mCurrentStreamUsage = streamUsage; | 
 |  | 
 |     // Attempt to find or establish a CASE session to the target PeerId. | 
 |     InteractionModelEngine * engine     = InteractionModelEngine::GetInstance(); | 
 |     CASESessionManager * caseSessionMgr = engine->GetCASESessionManager(); | 
 |     VerifyOrReturnError(caseSessionMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); | 
 |  | 
 |     MoveToState(State::Connecting); | 
 |  | 
 |     // WebRTC ProvideOffer requires a large payload session establishment | 
 |     caseSessionMgr->FindOrEstablishSession(mPeerId, &mOnConnectedCallback, &mOnConnectionFailureCallback, | 
 |                                            TransportPayloadCapability::kLargePayload); | 
 |  | 
 |     return CHIP_NO_ERROR; | 
 | } | 
 |  | 
 | CHIP_ERROR WebRTCProviderClient::ProvideAnswer(uint16_t webRTCSessionId, const std::string & sdp) | 
 | { | 
 |     ChipLogProgress(Camera, "Sending ProvideAnswer to node " ChipLogFormatX64, ChipLogValueX64(mPeerId.GetNodeId())); | 
 |  | 
 |     if (mState != State::Idle) | 
 |     { | 
 |         ChipLogError(Camera, "Operation NOT POSSIBLE: another sync is in progress"); | 
 |         return CHIP_ERROR_INCORRECT_STATE; | 
 |     } | 
 |  | 
 |     // Store the command type | 
 |     mCommandType = CommandType::kProvideAnswer; | 
 |  | 
 |     // Stash data in class members so the CommandSender can safely reference them async | 
 |     mProvideAnswerData.webRTCSessionID = webRTCSessionId; | 
 |     mProvideAnswerData.sdp             = CharSpan::fromCharString(sdp.c_str()); | 
 |     ; | 
 |  | 
 |     // Attempt to find or establish a CASE session to the target PeerId. | 
 |     InteractionModelEngine * engine     = InteractionModelEngine::GetInstance(); | 
 |     CASESessionManager * caseSessionMgr = engine->GetCASESessionManager(); | 
 |     VerifyOrReturnError(caseSessionMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); | 
 |  | 
 |     MoveToState(State::Connecting); | 
 |  | 
 |     // WebRTC ProvideOffer requires a large payload session establishment | 
 |     caseSessionMgr->FindOrEstablishSession(mPeerId, &mOnConnectedCallback, &mOnConnectionFailureCallback, | 
 |                                            TransportPayloadCapability::kLargePayload); | 
 |  | 
 |     return CHIP_NO_ERROR; | 
 | } | 
 |  | 
 | CHIP_ERROR WebRTCProviderClient::ProvideICECandidates(uint16_t webRTCSessionId, const std::vector<ICECandidateInfo> & iceCandidates) | 
 | { | 
 |     ChipLogProgress(Camera, "Sending ProvideICECandidates to node " ChipLogFormatX64, ChipLogValueX64(mPeerId.GetNodeId())); | 
 |  | 
 |     if (mState != State::Idle) | 
 |     { | 
 |         ChipLogError(Camera, "Operation NOT POSSIBLE: another sync is in progress"); | 
 |         return CHIP_ERROR_INCORRECT_STATE; | 
 |     } | 
 |  | 
 |     // Store the command type | 
 |     mCommandType = CommandType::kProvideICECandidates; | 
 |  | 
 |     // Store ICE Candidates for lifetime management | 
 |     mClientICECandidates = iceCandidates; | 
 |     mICECandidateStructList.clear(); | 
 |  | 
 |     for (const auto & candidateInfo : mClientICECandidates) | 
 |     { | 
 |         ICECandidateStruct iceCandidate; | 
 |         iceCandidate.candidate = CharSpan(candidateInfo.candidate.data(), candidateInfo.candidate.size()); | 
 |  | 
 |         // Set SDPMid if available | 
 |         if (!candidateInfo.mid.empty()) | 
 |         { | 
 |             iceCandidate.SDPMid.SetNonNull(CharSpan(candidateInfo.mid.data(), candidateInfo.mid.size())); | 
 |         } | 
 |         else | 
 |         { | 
 |             iceCandidate.SDPMid.SetNull(); | 
 |         } | 
 |  | 
 |         // Set SDPMLineIndex if valid | 
 |         if (candidateInfo.mlineIndex >= 0) | 
 |         { | 
 |             iceCandidate.SDPMLineIndex.SetNonNull(static_cast<uint16_t>(candidateInfo.mlineIndex)); | 
 |         } | 
 |         else | 
 |         { | 
 |             iceCandidate.SDPMLineIndex.SetNull(); | 
 |         } | 
 |  | 
 |         mICECandidateStructList.push_back(iceCandidate); | 
 |     } | 
 |  | 
 |     // Stash data in class members so the CommandSender can safely reference them async | 
 |     mProvideICECandidatesData.webRTCSessionID = webRTCSessionId; | 
 |     mProvideICECandidatesData.ICECandidates = | 
 |         chip::app::DataModel::List<const ICECandidateStruct>(mICECandidateStructList.data(), mICECandidateStructList.size()); | 
 |  | 
 |     // Attempt to find or establish a CASE session to the target PeerId. | 
 |     InteractionModelEngine * engine     = InteractionModelEngine::GetInstance(); | 
 |     CASESessionManager * caseSessionMgr = engine->GetCASESessionManager(); | 
 |     VerifyOrReturnError(caseSessionMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); | 
 |  | 
 |     MoveToState(State::Connecting); | 
 |  | 
 |     // WebRTC ProvideOffer requires a large payload session establishment | 
 |     caseSessionMgr->FindOrEstablishSession(mPeerId, &mOnConnectedCallback, &mOnConnectionFailureCallback, | 
 |                                            TransportPayloadCapability::kLargePayload); | 
 |  | 
 |     return CHIP_NO_ERROR; | 
 | } | 
 |  | 
 | void WebRTCProviderClient::HandleOfferReceived(uint16_t webRTCSessionId) | 
 | { | 
 |     ChipLogProgress(Camera, "Offer command received for WebRTC session ID: %u", webRTCSessionId); | 
 |  | 
 |     DeviceLayer::SystemLayer().CancelTimer(OnSessionEstablishTimeout, this); | 
 |     MoveToState(State::Idle); | 
 | } | 
 |  | 
 | void WebRTCProviderClient::HandleAnswerReceived(uint16_t webRTCSessionId) | 
 | { | 
 |     ChipLogProgress(Camera, "Answer command received for WebRTC session ID: %u", webRTCSessionId); | 
 |  | 
 |     DeviceLayer::SystemLayer().CancelTimer(OnSessionEstablishTimeout, this); | 
 |     MoveToState(State::Idle); | 
 | } | 
 |  | 
 | void WebRTCProviderClient::OnResponse(CommandSender * client, const ConcreteCommandPath & path, const StatusIB & status, | 
 |                                       TLV::TLVReader * data) | 
 | { | 
 |     ChipLogProgress(Camera, "WebRTCProviderClient: OnResponse received for cluster: 0x%" PRIx32 " command: 0x%" PRIx32, | 
 |                     path.mClusterId, path.mCommandId); | 
 |  | 
 |     CHIP_ERROR error = status.ToChipError(); | 
 |     if (CHIP_NO_ERROR != error) | 
 |     { | 
 |         ChipLogError(Camera, "Response Failure: %s", ErrorStr(error)); | 
 |         return; | 
 |     } | 
 |  | 
 |     // Handle different command responses | 
 |     if (path.mClusterId == Clusters::WebRTCTransportProvider::Id) | 
 |     { | 
 |         switch (path.mCommandId) | 
 |         { | 
 |         case Clusters::WebRTCTransportProvider::Commands::SolicitOfferResponse::Id: | 
 |             ChipLogDetail(Camera, "Processing SolicitOfferResponse"); | 
 |             if (data == nullptr) | 
 |             { | 
 |                 ChipLogError(Camera, "Response failure: data pointer is null"); | 
 |                 return; | 
 |             } | 
 |  | 
 |             HandleSolicitOfferResponse(*data); | 
 |             break; | 
 |  | 
 |         case Clusters::WebRTCTransportProvider::Commands::ProvideOfferResponse::Id: | 
 |             ChipLogDetail(Camera, "Processing ProvideOfferResponse"); | 
 |             if (data == nullptr) | 
 |             { | 
 |                 ChipLogError(Camera, "Response failure: data pointer is null"); | 
 |                 return; | 
 |             } | 
 |             HandleProvideOfferResponse(*data); | 
 |             break; | 
 |  | 
 |         default: | 
 |             ChipLogDetail(Camera, "Unexpected command ID: 0x%" PRIx32, path.mCommandId); | 
 |             break; | 
 |         } | 
 |     } | 
 |     else | 
 |     { | 
 |         ChipLogDetail(Camera, "Unexpected cluster ID: 0x%" PRIx32, path.mClusterId); | 
 |     } | 
 | } | 
 |  | 
 | void WebRTCProviderClient::OnError(const CommandSender * client, CHIP_ERROR error) | 
 | { | 
 |     ChipLogError(Camera, "WebRTCProviderClient: OnError for command %u: %" CHIP_ERROR_FORMAT, static_cast<unsigned>(mCommandType), | 
 |                  error.Format()); | 
 | } | 
 |  | 
 | void WebRTCProviderClient::OnDone(CommandSender * client) | 
 | { | 
 |     ChipLogProgress(Camera, "WebRTCProviderClient: OnDone for command %u.", static_cast<unsigned>(mCommandType)); | 
 |  | 
 |     // Reset command type, free up the CommandSender | 
 |     mCommandType = CommandType::kUndefined; | 
 |     mCommandSender.reset(); | 
 |  | 
 |     if (mState == State::AwaitingResponse || mState == State::Connecting) | 
 |     { | 
 |         MoveToState(State::Idle); | 
 |     } | 
 | } | 
 |  | 
 | void WebRTCProviderClient::MoveToState(const State targetState) | 
 | { | 
 |     mState = targetState; | 
 |     ChipLogProgress(Camera, "WebRTCProviderClient moving to [ %s ]", GetStateStr()); | 
 | } | 
 |  | 
 | const char * WebRTCProviderClient::GetStateStr() const | 
 | { | 
 |     switch (mState) | 
 |     { | 
 |     case State::Idle: | 
 |         return "Idle"; | 
 |  | 
 |     case State::Connecting: | 
 |         return "Connecting"; | 
 |  | 
 |     case State::AwaitingResponse: | 
 |         return "AwaitingResponse"; | 
 |  | 
 |     case State::AwaitingOffer: | 
 |         return "AwaitingOffer"; | 
 |  | 
 |     case State::AwaitingAnswer: | 
 |         return "AwaitingAnswer"; | 
 |     } | 
 |     return "N/A"; | 
 | } | 
 |  | 
 | CHIP_ERROR WebRTCProviderClient::SendCommandForType(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle, | 
 |                                                     CommandType commandType) | 
 | { | 
 |     ChipLogProgress(Camera, "Sending command with Endpoint ID: %d, Command Type: %d", mEndpointId, static_cast<int>(commandType)); | 
 |  | 
 |     switch (commandType) | 
 |     { | 
 |     case CommandType::kSolicitOffer: | 
 |         return SendCommand(exchangeMgr, sessionHandle, Clusters::WebRTCTransportProvider::Commands::SolicitOffer::Id, | 
 |                            mSolicitOfferData); | 
 |  | 
 |     case CommandType::kProvideAnswer: | 
 |         return SendCommand(exchangeMgr, sessionHandle, Clusters::WebRTCTransportProvider::Commands::ProvideAnswer::Id, | 
 |                            mProvideAnswerData); | 
 |  | 
 |     case CommandType::kProvideOffer: | 
 |         return SendCommand(exchangeMgr, sessionHandle, Clusters::WebRTCTransportProvider::Commands::ProvideOffer::Id, | 
 |                            mProvideOfferData); | 
 |  | 
 |     case CommandType::kProvideICECandidates: | 
 |         return SendCommand(exchangeMgr, sessionHandle, Clusters::WebRTCTransportProvider::Commands::ProvideICECandidates::Id, | 
 |                            mProvideICECandidatesData); | 
 |  | 
 |     default: | 
 |         return CHIP_ERROR_INVALID_ARGUMENT; | 
 |     } | 
 | } | 
 |  | 
 | void WebRTCProviderClient::OnDeviceConnected(void * context, Messaging::ExchangeManager & exchangeMgr, | 
 |                                              const SessionHandle & sessionHandle) | 
 | { | 
 |     WebRTCProviderClient * self = reinterpret_cast<WebRTCProviderClient *>(context); | 
 |     VerifyOrReturn(self != nullptr, ChipLogError(Camera, "OnDeviceConnected: context is null")); | 
 |  | 
 |     ChipLogProgress(Camera, "CASE session established, sending WebRTCTransportProvider command..."); | 
 |     CHIP_ERROR sendErr = self->SendCommandForType(exchangeMgr, sessionHandle, self->mCommandType); | 
 |     if (sendErr != CHIP_NO_ERROR) | 
 |     { | 
 |         ChipLogError(Camera, "SendCommandForType failed: %" CHIP_ERROR_FORMAT, sendErr.Format()); | 
 |         self->MoveToState(State::Idle); | 
 |     } | 
 |     else | 
 |     { | 
 |         self->MoveToState(State::AwaitingResponse); | 
 |     } | 
 | } | 
 |  | 
 | void WebRTCProviderClient::OnDeviceConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR err) | 
 | { | 
 |     LogErrorOnFailure(err); | 
 |     WebRTCProviderClient * self = reinterpret_cast<WebRTCProviderClient *>(context); | 
 |     VerifyOrReturn(self != nullptr, ChipLogError(Camera, "OnDeviceConnectionFailure: context is null")); | 
 |     self->OnDone(nullptr); | 
 | } | 
 |  | 
 | void WebRTCProviderClient::OnSessionEstablishTimeout(chip::System::Layer * systemLayer, void * appState) | 
 | { | 
 |     WebRTCProviderClient * self = reinterpret_cast<WebRTCProviderClient *>(appState); | 
 |     VerifyOrReturn(self != nullptr, ChipLogError(Camera, "OnSessionEstablishTimeout: context is null")); | 
 |  | 
 |     if (self->mCurrentSessionId != 0) | 
 |     { | 
 |         self->mRequestorServer->RemoveSession(self->mCurrentSessionId); | 
 |         self->mCurrentSessionId = 0; | 
 |     } | 
 |  | 
 |     ChipLogError(Camera, "WebRTC Session establishment has timed out!"); | 
 |     self->MoveToState(State::Idle); | 
 | } | 
 |  | 
 | void WebRTCProviderClient::HandleSolicitOfferResponse(TLV::TLVReader & data) | 
 | { | 
 |     ChipLogProgress(Camera, "WebRTCProviderClient::HandleSolicitOfferResponse."); | 
 |  | 
 |     Clusters::WebRTCTransportProvider::Commands::SolicitOfferResponse::DecodableType value; | 
 |     CHIP_ERROR error = app::DataModel::Decode(data, value); | 
 |     VerifyOrReturn(error == CHIP_NO_ERROR, | 
 |                    ChipLogError(Camera, "Failed to decode command response value. Error: %" CHIP_ERROR_FORMAT, error.Format())); | 
 |  | 
 |     // Create a new session record and populate fields from the decoded command response and current secure session info | 
 |     WebRTCSessionStruct session; | 
 |     session.id             = value.webRTCSessionID; | 
 |     session.peerNodeID     = mPeerId.GetNodeId(); | 
 |     session.fabricIndex    = mPeerId.GetFabricIndex(); | 
 |     session.peerEndpointID = mEndpointId; | 
 |     session.streamUsage    = mCurrentStreamUsage; | 
 |  | 
 |     mCurrentSessionId = value.webRTCSessionID; | 
 |  | 
 |     // Populate optional fields for video/audio stream IDs if present; set them to Null otherwise | 
 |     session.videoStreamID = value.videoStreamID.HasValue() ? value.videoStreamID.Value() : DataModel::MakeNullable<uint16_t>(); | 
 |     session.audioStreamID = value.audioStreamID.HasValue() ? value.audioStreamID.Value() : DataModel::MakeNullable<uint16_t>(); | 
 |  | 
 |     // If DeferredOffer == FALSE these fields MUST be valid | 
 |     if (!value.deferredOffer) | 
 |     { | 
 |         if (session.videoStreamID.IsNull() || session.audioStreamID.IsNull()) | 
 |         { | 
 |             ChipLogError(Camera, "Provider reported DeferredOffer=FALSE but did not supply valid Video/Audio stream IDs"); | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     VerifyOrReturn(mRequestorServer != nullptr, ChipLogError(Camera, "WebRTCProviderClient is not initialized")); | 
 |  | 
 |     // Insert or update the Requestor cluster's CurrentSessions. | 
 |     mRequestorServer->UpsertSession(session); | 
 |  | 
 |     if (value.deferredOffer) | 
 |     { | 
 |         ChipLogProgress(Camera, "DeferredOffer=TRUE -- there will be a larger than normal amount of time to receive Offer command"); | 
 |  | 
 |         // Longer timeout because the provider is in low-power standby | 
 |         DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(kDeferredOfferTimeoutSeconds), OnSessionEstablishTimeout, | 
 |                                               this); | 
 |     } | 
 |     else | 
 |     { | 
 |         // Normal (shorter) timeout for an imminent Offer round-trip | 
 |         DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(kSessionTimeoutSeconds), OnSessionEstablishTimeout, this); | 
 |     } | 
 |  | 
 |     MoveToState(State::AwaitingOffer); | 
 | } | 
 |  | 
 | void WebRTCProviderClient::HandleProvideOfferResponse(TLV::TLVReader & data) | 
 | { | 
 |     ChipLogProgress(Camera, "WebRTCProviderClient::HandleProvideOfferResponse."); | 
 |  | 
 |     Clusters::WebRTCTransportProvider::Commands::ProvideOfferResponse::DecodableType value; | 
 |     CHIP_ERROR error = app::DataModel::Decode(data, value); | 
 |     VerifyOrReturn(error == CHIP_NO_ERROR, | 
 |                    ChipLogError(Camera, "Failed to decode command response value. Error: %" CHIP_ERROR_FORMAT, error.Format())); | 
 |  | 
 |     // Create a new session record and populate fields from the decoded command response and current secure session info | 
 |     WebRTCSessionStruct session; | 
 |     session.id             = value.webRTCSessionID; | 
 |     session.peerNodeID     = mPeerId.GetNodeId(); | 
 |     session.fabricIndex    = mPeerId.GetFabricIndex(); | 
 |     session.peerEndpointID = mEndpointId; | 
 |     session.streamUsage    = mCurrentStreamUsage; | 
 |  | 
 |     mCurrentSessionId = value.webRTCSessionID; | 
 |  | 
 |     // Populate optional fields for video/audio stream IDs if present; set them to Null otherwise | 
 |     session.videoStreamID = value.videoStreamID.HasValue() ? value.videoStreamID.Value() : DataModel::MakeNullable<uint16_t>(); | 
 |     session.audioStreamID = value.audioStreamID.HasValue() ? value.audioStreamID.Value() : DataModel::MakeNullable<uint16_t>(); | 
 |  | 
 |     if (mRequestorServer == nullptr) | 
 |     { | 
 |         ChipLogError(Camera, "WebRTCProviderClient is not initialized"); | 
 |         return; | 
 |     } | 
 |  | 
 |     // Insert or update the Requestor cluster's CurrentSessions. | 
 |     mRequestorServer->UpsertSession(session); | 
 |  | 
 |     DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(kSessionTimeoutSeconds), OnSessionEstablishTimeout, this); | 
 |  | 
 |     MoveToState(State::AwaitingAnswer); | 
 | } |