/* * * 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 #include #include #include #include #include #include #include #include #include #include #include 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(1 << index % 8); bits[index / 8] = static_cast(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(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 optionalData = outPayload.getAllOptionalVendorData(); std::vector 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(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(estimate), CHIP_ERROR_NO_MEMORY); Platform::ScopedMemoryBuffer buf; VerifyOrReturnError(buf.Alloc(estimate), CHIP_ERROR_NO_MEMORY); return payloadBase38Representation(base38Representation, buf.Get(), static_cast(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 bits(kTotalPayloadDataSizeInBytes + tlvDataLengthInBytes); MutableByteSpan bitsSpan(bits.data(), bits.capacity()); std::vector 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