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