| /* |
| * |
| * Copyright (c) 2024 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 "RpcClient.h" |
| #include "RpcClientProcessor.h" |
| |
| #include <chrono> |
| #include <condition_variable> |
| #include <mutex> |
| #include <string> |
| #include <thread> |
| |
| #include "fabric_admin_service/fabric_admin_service.rpc.pb.h" |
| #include "pw_assert/check.h" |
| #include "pw_hdlc/decoder.h" |
| #include "pw_hdlc/default_addresses.h" |
| #include "pw_hdlc/rpc_channel.h" |
| #include "pw_rpc/client.h" |
| #include "pw_stream/socket_stream.h" |
| |
| using namespace chip; |
| |
| namespace { |
| |
| // Constants |
| constexpr uint32_t kRpcTimeoutMs = 1000; |
| constexpr uint32_t kDefaultChannelId = 1; |
| |
| // Fabric Admin Client |
| rpc::pw_rpc::nanopb::FabricAdmin::Client fabricAdminClient(rpc::client::GetDefaultRpcClient(), kDefaultChannelId); |
| |
| std::mutex responseMutex; |
| std::condition_variable responseCv; |
| bool responseReceived = false; |
| CHIP_ERROR responseError = CHIP_NO_ERROR; |
| |
| // By passing the `call` parameter into WaitForResponse we are explicitly trying to insure the caller takes into consideration that |
| // the lifetime of the `call` object when calling WaitForResponse |
| template <typename CallType> |
| CHIP_ERROR WaitForResponse(CallType & call) |
| { |
| std::unique_lock<std::mutex> lock(responseMutex); |
| responseReceived = false; |
| responseError = CHIP_NO_ERROR; |
| |
| if (responseCv.wait_for(lock, std::chrono::milliseconds(kRpcTimeoutMs), [] { return responseReceived; })) |
| { |
| return responseError; |
| } |
| else |
| { |
| ChipLogError(NotSpecified, "RPC Response timed out!"); |
| return CHIP_ERROR_TIMEOUT; |
| } |
| } |
| |
| // Callback function to be called when the RPC response is received |
| void OnOpenCommissioningWindowCompleted(const chip_rpc_OperationStatus & response, pw::Status status) |
| { |
| std::lock_guard<std::mutex> lock(responseMutex); |
| responseReceived = true; |
| responseError = status.ok() ? CHIP_NO_ERROR : CHIP_ERROR_INTERNAL; |
| responseCv.notify_one(); |
| |
| if (status.ok()) |
| { |
| ChipLogProgress(NotSpecified, "OpenCommissioningWindow received operation status: %d", response.success); |
| } |
| else |
| { |
| ChipLogProgress(NotSpecified, "OpenCommissioningWindow RPC call failed with status: %d\n", status.code()); |
| } |
| } |
| |
| // Callback function to be called when the RPC response is received for generic empty response. |
| void RpcCompletedWithEmptyResponse(const pw_protobuf_Empty & response, pw::Status status) |
| { |
| std::lock_guard<std::mutex> lock(responseMutex); |
| responseReceived = true; |
| responseError = status.ok() ? CHIP_NO_ERROR : CHIP_ERROR_INTERNAL; |
| responseCv.notify_one(); |
| |
| if (status.ok()) |
| { |
| ChipLogProgress(NotSpecified, "RPC call succeeded!"); |
| } |
| else |
| { |
| ChipLogProgress(NotSpecified, "RPC call failed with status: %d", status.code()); |
| } |
| } |
| |
| } // namespace |
| |
| CHIP_ERROR InitRpcClient(uint16_t rpcServerPort) |
| { |
| rpc::client::SetRpcServerPort(rpcServerPort); |
| return rpc::client::StartPacketProcessing(); |
| } |
| |
| CHIP_ERROR OpenCommissioningWindow(chip_rpc_DeviceCommissioningWindowInfo device) |
| { |
| ChipLogProgress(NotSpecified, "OpenCommissioningWindow with Node Id 0x" ChipLogFormatX64, ChipLogValueX64(device.node_id)); |
| |
| // The RPC call is kept alive until it completes. When a response is received, it will be logged by the handler |
| // function and the call will complete. |
| auto call = fabricAdminClient.OpenCommissioningWindow(device, OnOpenCommissioningWindowCompleted); |
| |
| if (!call.active()) |
| { |
| // The RPC call was not sent. This could occur due to, for example, an invalid channel ID. Handle if necessary. |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| return WaitForResponse(call); |
| } |
| |
| CHIP_ERROR |
| OpenCommissioningWindow(chip::Controller::CommissioningWindowPasscodeParams params) |
| { |
| chip_rpc_DeviceCommissioningWindowInfo device; |
| device.node_id = params.GetNodeId(); |
| device.commissioning_timeout = params.GetTimeout().count(); |
| device.discriminator = params.GetDiscriminator(); |
| device.iterations = params.GetIteration(); |
| |
| return OpenCommissioningWindow(device); |
| } |
| |
| CHIP_ERROR |
| OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams params) |
| { |
| chip_rpc_DeviceCommissioningWindowInfo device; |
| device.node_id = params.GetNodeId(); |
| device.commissioning_timeout = params.GetTimeout().count(); |
| device.discriminator = params.GetDiscriminator(); |
| device.iterations = params.GetIteration(); |
| |
| VerifyOrReturnError(params.GetSalt().size() <= sizeof(device.salt.bytes), CHIP_ERROR_BUFFER_TOO_SMALL); |
| memcpy(device.salt.bytes, params.GetSalt().data(), params.GetSalt().size()); |
| device.salt.size = static_cast<size_t>(params.GetSalt().size()); |
| |
| VerifyOrReturnError(params.GetVerifier().size() <= sizeof(device.verifier.bytes), CHIP_ERROR_BUFFER_TOO_SMALL); |
| memcpy(device.verifier.bytes, params.GetVerifier().data(), params.GetVerifier().size()); |
| device.verifier.size = static_cast<size_t>(params.GetVerifier().size()); |
| |
| return OpenCommissioningWindow(device); |
| } |
| |
| CHIP_ERROR |
| CommissionNode(chip::Controller::CommissioningWindowPasscodeParams params, VendorId vendorId, uint16_t productId) |
| { |
| chip_rpc_DeviceCommissioningInfo device; |
| device.setup_pin = params.GetSetupPIN(); |
| device.discriminator = params.GetDiscriminator(); |
| device.iterations = params.GetIteration(); |
| device.vendor_id = vendorId; |
| device.product_id = productId; |
| |
| VerifyOrReturnError(params.GetSalt().size() <= sizeof(device.salt.bytes), CHIP_ERROR_BUFFER_TOO_SMALL); |
| memcpy(device.salt.bytes, params.GetSalt().data(), params.GetSalt().size()); |
| device.salt.size = static_cast<size_t>(params.GetSalt().size()); |
| |
| // The RPC call is kept alive until it completes. When a response is received, it will be logged by the handler |
| // function and the call will complete. |
| auto call = fabricAdminClient.CommissionNode(device, RpcCompletedWithEmptyResponse); |
| |
| if (!call.active()) |
| { |
| // The RPC call was not sent. This could occur due to, for example, an invalid channel ID. Handle if necessary. |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| return WaitForResponse(call); |
| } |
| |
| CHIP_ERROR KeepActive(chip::NodeId nodeId, uint32_t stayActiveDurationMs, uint32_t timeoutMs) |
| { |
| chip_rpc_KeepActiveParameters params; |
| params.node_id = nodeId; |
| params.stay_active_duration_ms = stayActiveDurationMs; |
| params.timeout_ms = timeoutMs; |
| |
| // The RPC call is kept alive until it completes. When a response is received, it will be logged by the handler |
| // function and the call will complete. |
| auto call = fabricAdminClient.KeepActive(params, RpcCompletedWithEmptyResponse); |
| |
| if (!call.active()) |
| { |
| // The RPC call was not sent. This could occur due to, for example, an invalid channel ID. Handle if necessary. |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| return WaitForResponse(call); |
| } |