| /** |
| * |
| * 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 |