|  | /** | 
|  | * | 
|  | *    Copyright (c) 2020 Project CHIP Authors | 
|  | * | 
|  | *    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 describes a QRCode Setup Payload parser based on the | 
|  | *      CHIP specification. | 
|  | */ | 
|  |  | 
|  | #include "QRCodeSetupPayloadParser.h" | 
|  | #include "Base38Decode.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string.h> | 
|  | #include <vector> | 
|  |  | 
|  | #include <lib/core/CHIPCore.h> | 
|  | #include <lib/core/CHIPError.h> | 
|  | #include <lib/core/TLVData.h> | 
|  | #include <lib/core/TLVUtilities.h> | 
|  | #include <lib/support/CodeUtils.h> | 
|  | #include <lib/support/SafeInt.h> | 
|  | #include <lib/support/ScopedBuffer.h> | 
|  | #include <protocols/Protocols.h> | 
|  |  | 
|  | namespace chip { | 
|  |  | 
|  | // Populate numberOfBits into dest from buf starting at startIndex | 
|  | static CHIP_ERROR readBits(std::vector<uint8_t> buf, size_t & index, uint64_t & dest, size_t numberOfBitsToRead) | 
|  | { | 
|  | dest = 0; | 
|  | if (index + numberOfBitsToRead > buf.size() * 8 || numberOfBitsToRead > sizeof(uint64_t) * 8) | 
|  | { | 
|  | ChipLogError(SetupPayload, "Error parsing QR code. startIndex %u numberOfBitsToLoad %u buf_len %u ", | 
|  | static_cast<unsigned int>(index), static_cast<unsigned int>(numberOfBitsToRead), | 
|  | static_cast<unsigned int>(buf.size())); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | size_t currentIndex = index; | 
|  | for (size_t bitsRead = 0; bitsRead < numberOfBitsToRead; bitsRead++) | 
|  | { | 
|  | if (buf[currentIndex / 8] & (1 << (currentIndex % 8))) | 
|  | { | 
|  | dest |= (1 << bitsRead); | 
|  | } | 
|  | currentIndex++; | 
|  | } | 
|  | index += numberOfBitsToRead; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR openTLVContainer(TLV::ContiguousBufferTLVReader & reader, TLV::TLVType type, TLV::Tag tag, | 
|  | TLV::ContiguousBufferTLVReader & containerReader) | 
|  | { | 
|  | VerifyOrReturnError(reader.GetType() == type, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(reader.GetTag() == tag, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(reader.GetLength() == 0, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | ReturnErrorOnFailure(reader.OpenContainer(containerReader)); | 
|  |  | 
|  | VerifyOrReturnError(containerReader.GetContainerType() == type, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR retrieveOptionalInfoString(TLV::ContiguousBufferTLVReader & reader, OptionalQRCodeInfo & info) | 
|  | { | 
|  | Span<const char> data; | 
|  | ReturnErrorOnFailure(reader.GetStringView(data)); | 
|  |  | 
|  | info.type = optionalQRCodeInfoTypeString; | 
|  | info.data = std::string(data.data(), data.size()); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR retrieveOptionalInfoInt32(TLV::TLVReader & reader, OptionalQRCodeInfo & info) | 
|  | { | 
|  | int32_t value; | 
|  | ReturnErrorOnFailure(reader.Get(value)); | 
|  |  | 
|  | info.type  = optionalQRCodeInfoTypeInt32; | 
|  | info.int32 = value; | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR retrieveOptionalInfoInt64(TLV::TLVReader & reader, OptionalQRCodeInfoExtension & info) | 
|  | { | 
|  | int64_t value; | 
|  | ReturnErrorOnFailure(reader.Get(value)); | 
|  |  | 
|  | info.type  = optionalQRCodeInfoTypeInt64; | 
|  | info.int64 = value; | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR retrieveOptionalInfoUInt32(TLV::TLVReader & reader, OptionalQRCodeInfoExtension & info) | 
|  | { | 
|  | uint32_t value; | 
|  | ReturnErrorOnFailure(reader.Get(value)); | 
|  |  | 
|  | info.type   = optionalQRCodeInfoTypeUInt32; | 
|  | info.uint32 = value; | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR retrieveOptionalInfoUInt64(TLV::TLVReader & reader, OptionalQRCodeInfoExtension & info) | 
|  | { | 
|  | uint64_t value; | 
|  | ReturnErrorOnFailure(reader.Get(value)); | 
|  |  | 
|  | info.type   = optionalQRCodeInfoTypeUInt64; | 
|  | info.uint64 = value; | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR retrieveOptionalInfo(TLV::ContiguousBufferTLVReader & reader, OptionalQRCodeInfo & info, | 
|  | optionalQRCodeInfoType type) | 
|  | { | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  |  | 
|  | if (type == optionalQRCodeInfoTypeString) | 
|  | { | 
|  | err = retrieveOptionalInfoString(reader, info); | 
|  | } | 
|  | else if (type == optionalQRCodeInfoTypeInt32) | 
|  | { | 
|  | err = retrieveOptionalInfoInt32(reader, info); | 
|  | } | 
|  | else | 
|  | { | 
|  | err = CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR retrieveOptionalInfo(TLV::ContiguousBufferTLVReader & reader, OptionalQRCodeInfoExtension & info, | 
|  | optionalQRCodeInfoType type) | 
|  | { | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  |  | 
|  | if (type == optionalQRCodeInfoTypeString || type == optionalQRCodeInfoTypeInt32) | 
|  | { | 
|  | err = retrieveOptionalInfo(reader, static_cast<OptionalQRCodeInfo &>(info), type); | 
|  | } | 
|  | else if (type == optionalQRCodeInfoTypeInt64) | 
|  | { | 
|  | err = retrieveOptionalInfoInt64(reader, info); | 
|  | } | 
|  | else if (type == optionalQRCodeInfoTypeUInt32) | 
|  | { | 
|  | err = retrieveOptionalInfoUInt32(reader, info); | 
|  | } | 
|  | else if (type == optionalQRCodeInfoTypeUInt64) | 
|  | { | 
|  | err = retrieveOptionalInfoUInt64(reader, info); | 
|  | } | 
|  | else | 
|  | { | 
|  | err = CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadParser::retrieveOptionalInfos(SetupPayload & outPayload, TLV::ContiguousBufferTLVReader & reader) | 
|  | { | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  | while (err == CHIP_NO_ERROR) | 
|  | { | 
|  | const TLV::TLVType type = reader.GetType(); | 
|  | if (type != TLV::kTLVType_UTF8String && type != TLV::kTLVType_SignedInteger && type != TLV::kTLVType_UnsignedInteger) | 
|  | { | 
|  | err = reader.Next(); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | TLV::Tag tag = reader.GetTag(); | 
|  | VerifyOrReturnError(TLV::IsContextTag(tag), CHIP_ERROR_INVALID_TLV_TAG); | 
|  | const uint8_t tagNumber = static_cast<uint8_t>(TLV::TagNumFromTag(tag)); | 
|  |  | 
|  | optionalQRCodeInfoType elemType = optionalQRCodeInfoTypeUnknown; | 
|  | if (type == TLV::kTLVType_UTF8String) | 
|  | { | 
|  | elemType = optionalQRCodeInfoTypeString; | 
|  | } | 
|  | if (type == TLV::kTLVType_SignedInteger || type == TLV::kTLVType_UnsignedInteger) | 
|  | { | 
|  | elemType = outPayload.getNumericTypeFor(tagNumber); | 
|  | } | 
|  |  | 
|  | if (SetupPayload::IsCommonTag(tagNumber)) | 
|  | { | 
|  | OptionalQRCodeInfoExtension info; | 
|  | info.tag = tagNumber; | 
|  | ReturnErrorOnFailure(retrieveOptionalInfo(reader, info, elemType)); | 
|  |  | 
|  | ReturnErrorOnFailure(outPayload.addOptionalExtensionData(info)); | 
|  | } | 
|  | else | 
|  | { | 
|  | OptionalQRCodeInfo info; | 
|  | info.tag = tagNumber; | 
|  | ReturnErrorOnFailure(retrieveOptionalInfo(reader, info, elemType)); | 
|  |  | 
|  | ReturnErrorOnFailure(outPayload.addOptionalVendorData(info)); | 
|  | } | 
|  | err = reader.Next(); | 
|  | } | 
|  | if (err == CHIP_END_OF_TLV) | 
|  | { | 
|  | err = CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadParser::parseTLVFields(SetupPayload & outPayload, uint8_t * tlvDataStart, size_t tlvDataLengthInBytes) | 
|  | { | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  | if (!CanCastTo<uint32_t>(tlvDataLengthInBytes)) | 
|  | { | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  | TLV::ContiguousBufferTLVReader rootReader; | 
|  | rootReader.Init(tlvDataStart, tlvDataLengthInBytes); | 
|  | ReturnErrorOnFailure(rootReader.Next()); | 
|  |  | 
|  | if (rootReader.GetType() != TLV::kTLVType_Structure) | 
|  | { | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | TLV::ContiguousBufferTLVReader innerStructureReader; | 
|  | ReturnErrorOnFailure(openTLVContainer(rootReader, TLV::kTLVType_Structure, TLV::AnonymousTag(), innerStructureReader)); | 
|  | ReturnErrorOnFailure(innerStructureReader.Next()); | 
|  | err = retrieveOptionalInfos(outPayload, innerStructureReader); | 
|  |  | 
|  | if (err == CHIP_END_OF_TLV) | 
|  | { | 
|  | err = CHIP_NO_ERROR; | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadParser::populateTLV(SetupPayload & outPayload, const std::vector<uint8_t> & buf, size_t & index) | 
|  | { | 
|  | size_t bitsLeftToRead = (buf.size() * 8) - index; | 
|  | size_t tlvBytesLength = (bitsLeftToRead + 7) / 8; // ceil(bitsLeftToRead/8) | 
|  | chip::Platform::ScopedMemoryBuffer<uint8_t> tlvArray; | 
|  |  | 
|  | ReturnErrorCodeIf(tlvBytesLength == 0, CHIP_NO_ERROR); | 
|  |  | 
|  | tlvArray.Alloc(tlvBytesLength); | 
|  | ReturnErrorCodeIf(!tlvArray, CHIP_ERROR_NO_MEMORY); | 
|  |  | 
|  | for (size_t i = 0; i < tlvBytesLength; i++) | 
|  | { | 
|  | uint64_t dest; | 
|  | readBits(buf, index, dest, 8); | 
|  | tlvArray[i] = static_cast<uint8_t>(dest); | 
|  | } | 
|  |  | 
|  | return parseTLVFields(outPayload, tlvArray.Get(), tlvBytesLength); | 
|  | } | 
|  |  | 
|  | std::string QRCodeSetupPayloadParser::ExtractPayload(std::string inString) | 
|  | { | 
|  | std::string chipSegment; | 
|  | char delimiter = '%'; | 
|  | std::vector<size_t> startIndices; | 
|  | startIndices.push_back(0); | 
|  |  | 
|  | for (size_t i = 0; i < inString.length(); i++) | 
|  | { | 
|  | if (inString[i] == delimiter) | 
|  | { | 
|  | startIndices.push_back(i + 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Find the first string between delimiters that starts with kQRCodePrefix | 
|  | for (size_t i = 0; i < startIndices.size(); i++) | 
|  | { | 
|  | size_t startIndex   = startIndices[i]; | 
|  | size_t endIndex     = (i == startIndices.size() - 1 ? std::string::npos : startIndices[i + 1] - 1); | 
|  | size_t length       = (endIndex != std::string::npos ? endIndex - startIndex : std::string::npos); | 
|  | std::string segment = inString.substr(startIndex, length); | 
|  |  | 
|  | // Find a segment that starts with kQRCodePrefix | 
|  | if (segment.find(kQRCodePrefix, 0) == 0 && segment.length() > strlen(kQRCodePrefix)) | 
|  | { | 
|  | chipSegment = segment; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (chipSegment.length() > 0) | 
|  | { | 
|  | return chipSegment.substr(strlen(kQRCodePrefix)); // strip out prefix before returning | 
|  | } | 
|  |  | 
|  | return chipSegment; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadParser::populatePayload(SetupPayload & outPayload) | 
|  | { | 
|  | std::vector<uint8_t> buf; | 
|  | size_t indexToReadFrom = 0; | 
|  | uint64_t dest; | 
|  |  | 
|  | std::string payload = ExtractPayload(mBase38Representation); | 
|  | VerifyOrReturnError(payload.length() != 0, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | ReturnErrorOnFailure(base38Decode(payload, buf)); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kVersionFieldLengthInBits)); | 
|  | static_assert(kVersionFieldLengthInBits <= 8, "Won't fit in uint8_t"); | 
|  | outPayload.version = static_cast<uint8_t>(dest); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kVendorIDFieldLengthInBits)); | 
|  | static_assert(kVendorIDFieldLengthInBits <= 16, "Won't fit in uint16_t"); | 
|  | outPayload.vendorID = static_cast<uint16_t>(dest); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kProductIDFieldLengthInBits)); | 
|  | static_assert(kProductIDFieldLengthInBits <= 16, "Won't fit in uint16_t"); | 
|  | outPayload.productID = static_cast<uint16_t>(dest); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kCommissioningFlowFieldLengthInBits)); | 
|  | static_assert(kCommissioningFlowFieldLengthInBits <= std::numeric_limits<std::underlying_type_t<CommissioningFlow>>::digits, | 
|  | "Won't fit in CommissioningFlow"); | 
|  | outPayload.commissioningFlow = static_cast<CommissioningFlow>(dest); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kRendezvousInfoFieldLengthInBits)); | 
|  | static_assert(kRendezvousInfoFieldLengthInBits <= 8 * sizeof(RendezvousInformationFlag), | 
|  | "Won't fit in RendezvousInformationFlags"); | 
|  | outPayload.rendezvousInformation.SetValue( | 
|  | RendezvousInformationFlags().SetRaw(static_cast<std::underlying_type_t<RendezvousInformationFlag>>(dest))); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kPayloadDiscriminatorFieldLengthInBits)); | 
|  | static_assert(kPayloadDiscriminatorFieldLengthInBits <= 16, "Won't fit in uint16_t"); | 
|  | outPayload.discriminator.SetLongValue(static_cast<uint16_t>(dest)); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kSetupPINCodeFieldLengthInBits)); | 
|  | static_assert(kSetupPINCodeFieldLengthInBits <= 32, "Won't fit in uint32_t"); | 
|  | outPayload.setUpPINCode = static_cast<uint32_t>(dest); | 
|  |  | 
|  | ReturnErrorOnFailure(readBits(buf, indexToReadFrom, dest, kPaddingFieldLengthInBits)); | 
|  | if (dest != 0) | 
|  | { | 
|  | ChipLogError(SetupPayload, "Payload padding bits are not all 0: 0x%x", static_cast<unsigned>(dest)); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | return populateTLV(outPayload, buf, indexToReadFrom); | 
|  | } | 
|  |  | 
|  | } // namespace chip |