/* * * Copyright (c) 2021 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. */ #pragma once #include "attributes_service/attributes_service.rpc.pb.h" #include "pigweed/rpc_services/internal/StatusUtils.h" #include #include #include #include #include #include #include #include #include #include #include #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE #include #include #include #include #endif namespace chip { namespace rpc { // Implementation class for chip.rpc.Attributes. class Attributes : public pw_rpc::nanopb::Attributes::Service { public: ::pw::Status Write(const chip_rpc_AttributeWrite & request, pw_protobuf_Empty & response) { const void * data; DeviceLayer::StackLock lock; switch (request.data.which_data) { case chip_rpc_AttributeData_data_bool_tag: data = &request.data.data.data_bool; break; case chip_rpc_AttributeData_data_uint8_tag: data = &request.data.data.data_uint8; break; case chip_rpc_AttributeData_data_uint16_tag: data = &request.data.data.data_uint16; break; case chip_rpc_AttributeData_data_uint32_tag: data = &request.data.data.data_uint32; break; case chip_rpc_AttributeData_data_int8_tag: data = &request.data.data.data_int8; break; case chip_rpc_AttributeData_data_int16_tag: data = &request.data.data.data_int16; break; case chip_rpc_AttributeData_data_int32_tag: data = &request.data.data.data_int32; break; case chip_rpc_AttributeData_data_bytes_tag: data = &request.data.data.data_bytes; break; case chip_rpc_AttributeData_data_single_tag: data = &request.data.data.data_single; break; default: return pw::Status::InvalidArgument(); } RETURN_STATUS_IF_NOT_OK( emberAfWriteAttribute(request.metadata.endpoint, request.metadata.cluster, request.metadata.attribute_id, const_cast(static_cast(data)), request.metadata.type)); return pw::OkStatus(); } ::pw::Status Read(const chip_rpc_AttributeMetadata & request, chip_rpc_AttributeData & response) { app::ConcreteAttributePath path(request.endpoint, request.cluster, request.attribute_id); MutableByteSpan tlvBuffer(response.tlv_data.bytes); PW_TRY(ReadAttributeIntoTlvBuffer(path, tlvBuffer)); response.tlv_data.size = tlvBuffer.size(); response.has_tlv_data = true; switch (request.type) { case chip_rpc_AttributeType_ZCL_BOOLEAN_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_Boolean, response.data.data_bool)); response.which_data = chip_rpc_AttributeData_data_bool_tag; break; case chip_rpc_AttributeType_ZCL_ENUM8_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_UnsignedInteger, response.data.data_uint8)); response.which_data = chip_rpc_AttributeData_data_uint8_tag; break; case chip_rpc_AttributeType_ZCL_ENUM16_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_UnsignedInteger, response.data.data_uint16)); response.which_data = chip_rpc_AttributeData_data_uint16_tag; break; case chip_rpc_AttributeType_ZCL_INT8U_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_UnsignedInteger, response.data.data_uint8)); response.which_data = chip_rpc_AttributeData_data_uint8_tag; break; case chip_rpc_AttributeType_ZCL_INT16U_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_UnsignedInteger, response.data.data_uint16)); response.which_data = chip_rpc_AttributeData_data_uint16_tag; break; case chip_rpc_AttributeType_ZCL_INT32U_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_UnsignedInteger, response.data.data_uint32)); response.which_data = chip_rpc_AttributeData_data_uint32_tag; break; case chip_rpc_AttributeType_ZCL_INT8S_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_SignedInteger, response.data.data_int8)); response.which_data = chip_rpc_AttributeData_data_int8_tag; break; case chip_rpc_AttributeType_ZCL_INT16S_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_SignedInteger, response.data.data_int16)); response.which_data = chip_rpc_AttributeData_data_int16_tag; break; case chip_rpc_AttributeType_ZCL_INT32S_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_SignedInteger, response.data.data_int32)); response.which_data = chip_rpc_AttributeData_data_int32_tag; break; case chip_rpc_AttributeType_ZCL_SINGLE_ATTRIBUTE_TYPE: PW_TRY(TlvBufferGetData(tlvBuffer, TLV::kTLVType_FloatingPointNumber, response.data.data_single)); response.which_data = chip_rpc_AttributeData_data_single_tag; break; case chip_rpc_AttributeType_ZCL_BITMAP8_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_BITMAP16_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_BITMAP32_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_ARRAY_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_BITMAP64_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT24U_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT40U_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT48U_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT56U_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT64U_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT24S_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT40S_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT48S_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT56S_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_INT64S_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_DOUBLE_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_OCTET_STRING_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_CHAR_STRING_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_STRUCT_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_TOD_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_DATE_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_UTC_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_EPOCH_US_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_EPOCH_S_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_SYSTIME_US_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_PERCENT_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_PERCENT100THS_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_FIELD_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_EVENT_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_COMMAND_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_ACTION_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_TRANS_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_NODE_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_VENDOR_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_FABRIC_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_GROUP_ID_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_STATUS_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_DATA_VER_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_EVENT_NO_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_IPADR_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_IPV4ADR_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_IPV6ADR_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_IPV6PRE_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_HWADR_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_NO_DATA_ATTRIBUTE_TYPE: case chip_rpc_AttributeType_ZCL_UNKNOWN_ATTRIBUTE_TYPE: default: break; // These are currently not returned as decoded data, but can be // decoded from the returned TLV data. } return pw::OkStatus(); } private: static constexpr uint8_t kReportContextTag = 0x01; ::pw::Status ReadAttributeIntoTlvBuffer(const app::ConcreteAttributePath & path, MutableByteSpan & tlvBuffer) { Access::SubjectDescriptor subjectDescriptor{ .authMode = chip::Access::AuthMode::kPase }; app::AttributeReportIBs::Builder attributeReports; TLV::TLVWriter writer; TLV::TLVType outer; DeviceLayer::StackLock lock; writer.Init(tlvBuffer); PW_TRY(ChipErrorToPwStatus(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer))); PW_TRY(ChipErrorToPwStatus(attributeReports.Init(&writer, kReportContextTag))); #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE // TODO: this assumes a singleton data model provider app::DataModel::Provider * provider = app::InteractionModelEngine::GetInstance()->GetDataModelProvider(); app::DataModel::ReadAttributeRequest request; request.path = path; request.operationFlags.Set(app::DataModel::OperationFlags::kInternal); request.subjectDescriptor = subjectDescriptor; std::optional info = provider->GetClusterInfo(path); if (!info.has_value()) { return ::pw::Status::NotFound(); } app::AttributeValueEncoder encoder(attributeReports, subjectDescriptor, path, info->dataVersion, false /* isFabricFiltered */, nullptr /* attributeEncodingState */); app::DataModel::ActionReturnStatus result = provider->ReadAttribute(request, encoder); if (!result.IsSuccess()) { app::DataModel::ActionReturnStatus::StringStorage storage; ChipLogError(Support, "Failed to read data: %s", result.c_str(storage)); return ::pw::Status::Internal(); } #else PW_TRY(ChipErrorToPwStatus(app::ReadSingleClusterData(subjectDescriptor, false, path, attributeReports, nullptr))); #endif attributeReports.EndOfContainer(); PW_TRY(ChipErrorToPwStatus(writer.EndContainer(outer))); PW_TRY(ChipErrorToPwStatus(writer.Finalize())); tlvBuffer.reduce_size(writer.GetLengthWritten()); return ::pw::OkStatus(); } template ::pw::Status TlvBufferGetData(ByteSpan tlvBuffer, TLV::TLVType expectedDataType, T & responseData) { TLV::TLVReader reader; reader.Init(tlvBuffer); // Open outer container PW_TRY(ChipErrorToPwStatus(reader.Next(TLV::TLVType::kTLVType_Structure, TLV::AnonymousTag()))); TLV::TLVType readerRoot; PW_TRY(ChipErrorToPwStatus(reader.EnterContainer(readerRoot))); // Open report container PW_TRY(ChipErrorToPwStatus(reader.Next(TLV::TLVType::kTLVType_Array, TLV::ContextTag(kReportContextTag)))); TLV::TLVType readerArray; PW_TRY(ChipErrorToPwStatus(reader.EnterContainer(readerArray))); // Skip first array element which is the empty array from spec 10.5.4.3 PW_TRY(ChipErrorToPwStatus(reader.Next(TLV::TLVType::kTLVType_Structure, TLV::AnonymousTag()))); // Parse the AttributeDataIB to pull out data app::AttributeReportIB::Parser reportParser; PW_TRY(ChipErrorToPwStatus(reportParser.Init(reader))); app::AttributeDataIB::Parser dataParser; PW_TRY(ChipErrorToPwStatus(reportParser.GetAttributeData(&dataParser))); TLV::TLVReader dataReader; PW_TRY(ChipErrorToPwStatus(dataParser.GetData(&dataReader))); PW_TRY(CheckTlvTagAndType(&dataReader, TLV::ContextTag(0x2), expectedDataType)); PW_TRY(ChipErrorToPwStatus(dataReader.Get(responseData))); return ::pw::OkStatus(); } static ::pw::Status ChipErrorToPwStatus(CHIP_ERROR err) { if (err == CHIP_NO_ERROR) { return ::pw::OkStatus(); } else if (err == CHIP_ERROR_BUFFER_TOO_SMALL) { return ::pw::Status::ResourceExhausted(); } return ::pw::Status::Internal(); } static ::pw::Status CheckTlvTagAndType(TLV::TLVReader * reader, TLV::Tag expectedTag, TLV::TLVType expectedType) { if (reader->GetTag() != expectedTag || reader->GetType() != expectedType) { return ::pw::Status::NotFound(); } return ::pw::OkStatus(); } }; } // namespace rpc } // namespace chip