blob: 102de4af69581bfec12f438393e4d4685f977e91 [file] [log] [blame] [edit]
/**
*
* 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 <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;
VerifyOrReturnError(tlvBytesLength != 0, CHIP_NO_ERROR);
tlvArray.Alloc(tlvBytesLength);
VerifyOrReturnError(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