|  | /* | 
|  | * | 
|  | *    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 implements a QRCode Setup Payload generator in accordance | 
|  | *      with the CHIP specification. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include "QRCodeSetupPayloadGenerator.h" | 
|  | #include "Base38Encode.h" | 
|  |  | 
|  | #include <lib/core/CHIPCore.h> | 
|  | #include <lib/core/TLV.h> | 
|  | #include <lib/core/TLVData.h> | 
|  | #include <lib/core/TLVDebug.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> | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <string> | 
|  |  | 
|  | namespace chip { | 
|  |  | 
|  | // Populates numberOfBits starting from LSB of input into bits, which is assumed to be zero-initialized | 
|  | static CHIP_ERROR populateBits(uint8_t * bits, size_t & offset, uint64_t input, size_t numberOfBits, | 
|  | size_t totalPayloadDataSizeInBits) | 
|  | { | 
|  | VerifyOrReturnError(offset + numberOfBits <= totalPayloadDataSizeInBits, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(input < 1u << numberOfBits, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | size_t index = offset; | 
|  | offset += numberOfBits; | 
|  | while (input != 0) | 
|  | { | 
|  | if (input & 1) | 
|  | { | 
|  | const uint8_t mask = static_cast<uint8_t>(1 << index % 8); | 
|  | bits[index / 8]    = static_cast<uint8_t>(bits[index / 8] | mask); | 
|  | } | 
|  | index++; | 
|  | input >>= 1; | 
|  | } | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR populateTLVBits(uint8_t * bits, size_t & offset, const uint8_t * tlvBuf, size_t tlvBufSizeInBytes, | 
|  | size_t totalPayloadDataSizeInBits) | 
|  | { | 
|  | for (size_t i = 0; i < tlvBufSizeInBytes; i++) | 
|  | { | 
|  | const uint8_t value = tlvBuf[i]; | 
|  | ReturnErrorOnFailure(populateBits(bits, offset, value, 8, totalPayloadDataSizeInBits)); | 
|  | } | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR writeTag(TLV::TLVWriter & writer, TLV::Tag tag, OptionalQRCodeInfo & info) | 
|  | { | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  |  | 
|  | if (info.type == optionalQRCodeInfoTypeString) | 
|  | { | 
|  | err = writer.PutString(tag, info.data.c_str()); | 
|  | } | 
|  | else if (info.type == optionalQRCodeInfoTypeInt32) | 
|  | { | 
|  | err = writer.Put(tag, info.int32); | 
|  | } | 
|  | else | 
|  | { | 
|  | err = CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR writeTag(TLV::TLVWriter & writer, TLV::Tag tag, OptionalQRCodeInfoExtension & info) | 
|  | { | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  |  | 
|  | if (info.type == optionalQRCodeInfoTypeString || info.type == optionalQRCodeInfoTypeInt32) | 
|  | { | 
|  | err = writeTag(writer, tag, static_cast<OptionalQRCodeInfo &>(info)); | 
|  | } | 
|  | else if (info.type == optionalQRCodeInfoTypeInt64) | 
|  | { | 
|  | err = writer.Put(tag, info.int64); | 
|  | } | 
|  | else if (info.type == optionalQRCodeInfoTypeUInt32) | 
|  | { | 
|  | err = writer.Put(tag, info.uint32); | 
|  | } | 
|  | else if (info.type == optionalQRCodeInfoTypeUInt64) | 
|  | { | 
|  | err = writer.Put(tag, info.uint64); | 
|  | } | 
|  | else | 
|  | { | 
|  | err = CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadGenerator::generateTLVFromOptionalData(SetupPayload & outPayload, uint8_t * tlvDataStart, | 
|  | uint32_t maxLen, size_t & tlvDataLengthInBytes) | 
|  | { | 
|  | std::vector<OptionalQRCodeInfo> optionalData                   = outPayload.getAllOptionalVendorData(); | 
|  | std::vector<OptionalQRCodeInfoExtension> optionalExtensionData = outPayload.getAllOptionalExtensionData(); | 
|  | VerifyOrReturnError(!optionalData.empty() || !optionalExtensionData.empty(), CHIP_NO_ERROR); | 
|  |  | 
|  | TLV::TLVWriter rootWriter; | 
|  | rootWriter.Init(tlvDataStart, maxLen); | 
|  |  | 
|  | TLV::TLVWriter innerStructureWriter; | 
|  |  | 
|  | ReturnErrorOnFailure(rootWriter.OpenContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, innerStructureWriter)); | 
|  |  | 
|  | for (OptionalQRCodeInfo info : optionalData) | 
|  | { | 
|  | ReturnErrorOnFailure(writeTag(innerStructureWriter, TLV::ContextTag(info.tag), info)); | 
|  | } | 
|  |  | 
|  | for (OptionalQRCodeInfoExtension info : optionalExtensionData) | 
|  | { | 
|  | ReturnErrorOnFailure(writeTag(innerStructureWriter, TLV::ContextTag(info.tag), info)); | 
|  | } | 
|  |  | 
|  | ReturnErrorOnFailure(rootWriter.CloseContainer(innerStructureWriter)); | 
|  |  | 
|  | ReturnErrorOnFailure(rootWriter.Finalize()); | 
|  |  | 
|  | tlvDataLengthInBytes = rootWriter.GetLengthWritten(); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR generateBitSet(PayloadContents & payload, MutableByteSpan & bits, uint8_t * tlvDataStart, | 
|  | size_t tlvDataLengthInBytes) | 
|  | { | 
|  | size_t offset                 = 0; | 
|  | size_t totalPayloadSizeInBits = kTotalPayloadDataSizeInBits + (tlvDataLengthInBytes * 8); | 
|  | VerifyOrReturnError(bits.size() * 8 >= totalPayloadSizeInBits, CHIP_ERROR_BUFFER_TOO_SMALL); | 
|  |  | 
|  | // isValidQRCodePayload() has already performed all relevant checks (including that we have a | 
|  | // long discriminator and rendevouz information). But if AllowInvalidPayload is set these | 
|  | // requirements might be violated; in that case simply encode 0 for the relevant fields. | 
|  | // Encoding an invalid (or partially valid) payload is useful for clients that need to be able | 
|  | // to serialize and deserialize partially populated or invalid payloads. | 
|  | ReturnErrorOnFailure( | 
|  | populateBits(bits.data(), offset, payload.version, kVersionFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | ReturnErrorOnFailure( | 
|  | populateBits(bits.data(), offset, payload.vendorID, kVendorIDFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | ReturnErrorOnFailure( | 
|  | populateBits(bits.data(), offset, payload.productID, kProductIDFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | ReturnErrorOnFailure(populateBits(bits.data(), offset, static_cast<uint64_t>(payload.commissioningFlow), | 
|  | kCommissioningFlowFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | ReturnErrorOnFailure(populateBits(bits.data(), offset, | 
|  | payload.rendezvousInformation.ValueOr(RendezvousInformationFlag::kNone).Raw(), | 
|  | kRendezvousInfoFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | auto const & pd = payload.discriminator; | 
|  | ReturnErrorOnFailure(populateBits(bits.data(), offset, (!pd.IsShortDiscriminator() ? pd.GetLongValue() : 0), | 
|  | kPayloadDiscriminatorFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | ReturnErrorOnFailure( | 
|  | populateBits(bits.data(), offset, payload.setUpPINCode, kSetupPINCodeFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | ReturnErrorOnFailure(populateBits(bits.data(), offset, 0, kPaddingFieldLengthInBits, kTotalPayloadDataSizeInBits)); | 
|  | ReturnErrorOnFailure(populateTLVBits(bits.data(), offset, tlvDataStart, tlvDataLengthInBytes, totalPayloadSizeInBits)); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR payloadBase38RepresentationWithTLV(PayloadContents & payload, MutableCharSpan & outBuffer, MutableByteSpan bits, | 
|  | uint8_t * tlvDataStart, size_t tlvDataLengthInBytes) | 
|  | { | 
|  | memset(bits.data(), 0, bits.size()); | 
|  | ReturnErrorOnFailure(generateBitSet(payload, bits, tlvDataStart, tlvDataLengthInBytes)); | 
|  |  | 
|  | CHIP_ERROR err   = CHIP_NO_ERROR; | 
|  | size_t prefixLen = strlen(kQRCodePrefix); | 
|  |  | 
|  | if (outBuffer.size() < prefixLen + 1) | 
|  | { | 
|  | err = CHIP_ERROR_BUFFER_TOO_SMALL; | 
|  | } | 
|  | else | 
|  | { | 
|  | MutableCharSpan subSpan = outBuffer.SubSpan(prefixLen, outBuffer.size() - prefixLen); | 
|  | memcpy(outBuffer.data(), kQRCodePrefix, prefixLen); | 
|  | err = base38Encode(bits, subSpan); | 
|  | // Reduce output span size to be the size of written data | 
|  | outBuffer.reduce_size(subSpan.size() + prefixLen); | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadGenerator::payloadBase38Representation(std::string & base38Representation) | 
|  | { | 
|  | // 6.1.2.2. Table: Packed Binary Data Structure | 
|  | // The TLV Data should be 0 length if TLV is not included. | 
|  | return payloadBase38Representation(base38Representation, nullptr, 0); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadGenerator::payloadBase38RepresentationWithAutoTLVBuffer(std::string & base38Representation) | 
|  | { | 
|  | // Estimate the size of the needed buffer. | 
|  | size_t estimate = 0; | 
|  |  | 
|  | auto dataItemSizeEstimate = [](const OptionalQRCodeInfo & item) { | 
|  | // Each data item needs a control byte and a context tag. | 
|  | size_t size = 2; | 
|  |  | 
|  | if (item.type == optionalQRCodeInfoTypeString) | 
|  | { | 
|  | // We'll need to encode the string length and then the string data. | 
|  | // Length is at most 8 bytes. | 
|  | size += 8; | 
|  | size += item.data.size(); | 
|  | } | 
|  | else | 
|  | { | 
|  | // Integer.  Assume it might need up to 8 bytes, for simplicity. | 
|  | size += 8; | 
|  | } | 
|  | return size; | 
|  | }; | 
|  |  | 
|  | auto vendorData = mPayload.getAllOptionalVendorData(); | 
|  | for (auto & data : vendorData) | 
|  | { | 
|  | estimate += dataItemSizeEstimate(data); | 
|  | } | 
|  |  | 
|  | auto extensionData = mPayload.getAllOptionalExtensionData(); | 
|  | for (auto & data : extensionData) | 
|  | { | 
|  | estimate += dataItemSizeEstimate(data); | 
|  | } | 
|  |  | 
|  | estimate = TLV::EstimateStructOverhead(estimate); | 
|  |  | 
|  | VerifyOrReturnError(CanCastTo<uint32_t>(estimate), CHIP_ERROR_NO_MEMORY); | 
|  |  | 
|  | Platform::ScopedMemoryBuffer<uint8_t> buf; | 
|  | VerifyOrReturnError(buf.Alloc(estimate), CHIP_ERROR_NO_MEMORY); | 
|  |  | 
|  | return payloadBase38Representation(base38Representation, buf.Get(), static_cast<uint32_t>(estimate)); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeSetupPayloadGenerator::payloadBase38Representation(std::string & base38Representation, uint8_t * tlvDataStart, | 
|  | uint32_t tlvDataStartSize) | 
|  | { | 
|  | size_t tlvDataLengthInBytes = 0; | 
|  |  | 
|  | VerifyOrReturnError(mAllowInvalidPayload || mPayload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | ReturnErrorOnFailure(generateTLVFromOptionalData(mPayload, tlvDataStart, tlvDataStartSize, tlvDataLengthInBytes)); | 
|  |  | 
|  | std::vector<uint8_t> bits(kTotalPayloadDataSizeInBytes + tlvDataLengthInBytes); | 
|  | MutableByteSpan bitsSpan(bits.data(), bits.capacity()); | 
|  | std::vector<char> buffer(base38EncodedLength(bits.capacity()) + strlen(kQRCodePrefix)); | 
|  | MutableCharSpan bufferSpan(buffer.data(), buffer.capacity()); | 
|  |  | 
|  | ReturnErrorOnFailure(payloadBase38RepresentationWithTLV(mPayload, bufferSpan, bitsSpan, tlvDataStart, tlvDataLengthInBytes)); | 
|  |  | 
|  | base38Representation.assign(bufferSpan.data()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR QRCodeBasicSetupPayloadGenerator::payloadBase38Representation(MutableCharSpan & outBuffer) | 
|  | { | 
|  | uint8_t bits[kTotalPayloadDataSizeInBytes]; | 
|  | VerifyOrReturnError(mPayload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | return payloadBase38RepresentationWithTLV(mPayload, outBuffer, MutableByteSpan(bits), nullptr, 0); | 
|  | } | 
|  |  | 
|  | } // namespace chip |