blob: 3b20e2429ce77085f11bfcf90273679fd767b311 [file] [log] [blame]
/**
*
* 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 Manul Setup Payload parser based on the
* CHIP specification.
*/
#include "ManualSetupPayloadParser.h"
#include <lib/support/SafeInt.h>
#include <lib/support/logging/CHIPLogging.h>
#include <lib/support/verhoeff/Verhoeff.h>
#include <string>
#include <vector>
namespace chip {
CHIP_ERROR ManualSetupPayloadParser::CheckDecimalStringValidity(std::string decimalString,
std::string & decimalStringWithoutCheckDigit)
{
if (decimalString.length() < 2)
{
ChipLogError(SetupPayload, "Failed decoding base10. Input was empty. %u",
static_cast<unsigned int>(decimalString.length()));
return CHIP_ERROR_INVALID_STRING_LENGTH;
}
std::string repWithoutCheckChar = decimalString.substr(0, decimalString.length() - 1);
char checkChar = decimalString.back();
if (!Verhoeff10::ValidateCheckChar(checkChar, repWithoutCheckChar.c_str()))
{
return CHIP_ERROR_INTEGRITY_CHECK_FAILED;
}
decimalStringWithoutCheckDigit = repWithoutCheckChar;
return CHIP_NO_ERROR;
}
CHIP_ERROR ManualSetupPayloadParser::CheckCodeLengthValidity(const std::string & decimalString, bool isLongCode)
{
size_t expectedCharLength = isLongCode ? kManualSetupLongCodeCharLength : kManualSetupShortCodeCharLength;
if (decimalString.length() != expectedCharLength)
{
ChipLogError(SetupPayload, "Failed decoding base10. Input length %u was not expected length %u",
static_cast<unsigned int>(decimalString.length()), static_cast<unsigned int>(expectedCharLength));
return CHIP_ERROR_INVALID_STRING_LENGTH;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ManualSetupPayloadParser::ToNumber(const std::string & decimalString, uint32_t & dest)
{
uint32_t number = 0;
for (char c : decimalString)
{
if (!isdigit(c))
{
ChipLogError(SetupPayload, "Failed decoding base10. Character was invalid %c", c);
return CHIP_ERROR_INVALID_INTEGER_VALUE;
}
number *= 10;
number += static_cast<uint32_t>(c - '0');
}
dest = number;
return CHIP_NO_ERROR;
}
// Populate numberOfChars into dest from decimalString starting at startIndex (least significant digit = left-most digit)
CHIP_ERROR ManualSetupPayloadParser::ReadDigitsFromDecimalString(const std::string & decimalString, size_t & index, uint32_t & dest,
size_t numberOfCharsToRead)
{
if (decimalString.length() < numberOfCharsToRead || (numberOfCharsToRead + index > decimalString.length()))
{
ChipLogError(SetupPayload, "Failed decoding base10. Input was too short. %u",
static_cast<unsigned int>(decimalString.length()));
return CHIP_ERROR_INVALID_STRING_LENGTH;
}
std::string decimalSubstring = decimalString.substr(index, numberOfCharsToRead);
index += numberOfCharsToRead;
return ToNumber(decimalSubstring, dest);
}
CHIP_ERROR ManualSetupPayloadParser::populatePayload(SetupPayload & outPayload)
{
CHIP_ERROR result = CHIP_NO_ERROR;
SetupPayload payload;
std::string representationWithoutCheckDigit;
result = CheckDecimalStringValidity(mDecimalStringRepresentation, representationWithoutCheckDigit);
if (result != CHIP_NO_ERROR)
{
return result;
}
size_t stringOffset = 0;
uint32_t chunk1, chunk2, chunk3;
result = ReadDigitsFromDecimalString(representationWithoutCheckDigit, stringOffset, chunk1, kManualSetupCodeChunk1CharLength);
if (result != CHIP_NO_ERROR)
{
return result;
}
result = ReadDigitsFromDecimalString(representationWithoutCheckDigit, stringOffset, chunk2, kManualSetupCodeChunk2CharLength);
if (result != CHIP_NO_ERROR)
{
return result;
}
result = ReadDigitsFromDecimalString(representationWithoutCheckDigit, stringOffset, chunk3, kManualSetupCodeChunk3CharLength);
if (result != CHIP_NO_ERROR)
{
return result;
}
// First digit of '8' or '9' would be invalid for v1 and would indicate new format (e.g. version 2)
if (chunk1 == 8 || chunk1 == 9)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
bool isLongCode = ((chunk1 >> kManualSetupChunk1VidPidPresentBitPos) & 1) == 1;
result = CheckCodeLengthValidity(representationWithoutCheckDigit, isLongCode);
if (result != CHIP_NO_ERROR)
{
return result;
}
constexpr uint32_t kDiscriminatorMsbitsMask = (1 << kManualSetupChunk1DiscriminatorMsbitsLength) - 1;
constexpr uint32_t kDiscriminatorLsbitsMask = (1 << kManualSetupChunk2DiscriminatorLsbitsLength) - 1;
uint32_t discriminator = ((chunk2 >> kManualSetupChunk2DiscriminatorLsbitsPos) & kDiscriminatorLsbitsMask);
discriminator |= ((chunk1 >> kManualSetupChunk1DiscriminatorMsbitsPos) & kDiscriminatorMsbitsMask)
<< kManualSetupChunk2DiscriminatorLsbitsLength;
constexpr uint32_t kPincodeMsbitsMask = (1 << kManualSetupChunk3PINCodeMsbitsLength) - 1;
constexpr uint32_t kPincodeLsbitsMask = (1 << kManualSetupChunk2PINCodeLsbitsLength) - 1;
uint32_t setUpPINCode = ((chunk2 >> kManualSetupChunk2PINCodeLsbitsPos) & kPincodeLsbitsMask);
setUpPINCode |= ((chunk3 >> kManualSetupChunk3PINCodeMsbitsPos) & kPincodeMsbitsMask) << kManualSetupChunk2PINCodeLsbitsLength;
if (setUpPINCode == 0)
{
ChipLogError(SetupPayload, "Failed decoding base10. SetUpPINCode was 0.");
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (isLongCode)
{
uint32_t vendorID;
result =
ReadDigitsFromDecimalString(representationWithoutCheckDigit, stringOffset, vendorID, kManualSetupVendorIdCharLength);
if (result != CHIP_NO_ERROR)
{
return result;
}
uint32_t productID;
result =
ReadDigitsFromDecimalString(representationWithoutCheckDigit, stringOffset, productID, kManualSetupProductIdCharLength);
if (result != CHIP_NO_ERROR)
{
return result;
}
// Need to do dynamic checks, because we are reading 5 chars, so could
// have 99,999 here or something.
if (!CanCastTo<uint16_t>(vendorID))
{
return CHIP_ERROR_INVALID_INTEGER_VALUE;
}
outPayload.vendorID = static_cast<uint16_t>(vendorID);
if (!CanCastTo<uint16_t>(productID))
{
return CHIP_ERROR_INVALID_INTEGER_VALUE;
}
outPayload.productID = static_cast<uint16_t>(productID);
}
outPayload.commissioningFlow = isLongCode ? CommissioningFlow::kCustom : CommissioningFlow::kStandard;
static_assert(kSetupPINCodeFieldLengthInBits <= 32, "Won't fit in uint32_t");
outPayload.setUpPINCode = static_cast<uint32_t>(setUpPINCode);
static_assert(kManualSetupDiscriminatorFieldLengthInBits <= 8, "Won't fit in uint8_t");
outPayload.discriminator.SetShortValue(static_cast<uint8_t>(discriminator));
return result;
}
} // namespace chip