diff --git a/src/Makefile b/src/Makefile index d564ac77087e82..c3b3986aeea776 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,6 @@ TOPTARGETS := all clean run_tests -SUBDIRS = lwip system lib/core lib/support ble inet +SUBDIRS = lwip system lib/core lib/support ble inet qrcode $(TOPTARGETS): $(SUBDIRS) $(SUBDIRS): diff --git a/src/qrcode/Makefile b/src/qrcode/Makefile new file mode 100644 index 00000000000000..422526e61da0c7 --- /dev/null +++ b/src/qrcode/Makefile @@ -0,0 +1,28 @@ +TOP_DIR = ../.. +Test_Dir = tests + +.PHONY: all clean run_tests + +all: run_tests + +include $(TOP_DIR)/.yams/cpp_rules.min + +Module_Includes = \ + -I. \ + -I$(TOP_DIR)/src/include \ + -I$(TOP_DIR)/src/lib/ \ + -I$(TOP_DIR)/build/config/standalone/ + +Module_Test_Includes = $(Module_Includes) + +CPP_Files = \ + SetupCodeUtils.cpp \ + SetupPayloadGenerator.cpp \ + +libqr.a: $(CPP_Objects) + ar rvs $@ $^ + @echo "LINK => $@" + +clean: my_clean + @rm -f $(LIB_NAME) *.gcda *.gcno *.gcov + diff --git a/src/qrcode/SetupCodeUtils.cpp b/src/qrcode/SetupCodeUtils.cpp new file mode 100644 index 00000000000000..545bd20c8109e3 --- /dev/null +++ b/src/qrcode/SetupCodeUtils.cpp @@ -0,0 +1,82 @@ +/* + * + * + * + * 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 converting an input into a Base45 String + * + */ + +#include "SetupCodeUtils.h" + +#include +#include + +using namespace std; + +namespace chip { + +map base45CharacterMap() +{ + map characterMap; + // Special characters + characterMap[36] = ' '; + characterMap[37] = '$'; + characterMap[38] = '%'; + characterMap[39] = '*'; + characterMap[40] = '+'; + characterMap[41] = '-'; + characterMap[42] = '.'; + characterMap[43] = '/'; + characterMap[44] = ':'; + + return characterMap; +} + +string base45EncodedString(uint64_t input, size_t minLength) +{ + static std::map BS45_CHARS = base45CharacterMap(); + string result; + int radix = 45; + do { + int remainder = input % radix; + char base45char; + if (remainder >= 0 && remainder <= 9) { + // Numbers + base45char = '0' + remainder; + } + else if (remainder >= 10 && remainder <= 35) { + // Uppercase Characters + base45char = 'A' + (remainder - 10); + } + else { + // Special Characters + base45char = BS45_CHARS[remainder]; + } + result += base45char; + input = input / radix; + } while (input != 0); + + while (result.length() < minLength) { + result.append("0"); + } + + reverse(result.begin(), result.end()); + return result; +} + +} // namespace chip diff --git a/src/qrcode/SetupCodeUtils.h b/src/qrcode/SetupCodeUtils.h new file mode 100644 index 00000000000000..ba4ad9b2301abb --- /dev/null +++ b/src/qrcode/SetupCodeUtils.h @@ -0,0 +1,36 @@ +/* + * + * + * + * 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 + * Utility header to encode an input into a Base45 String + */ + +#ifndef _SETUP_CODE_UTILS_H_ +#define _SETUP_CODE_UTILS_H_ + +#include + +using namespace std; + +namespace chip { + +std::string base45EncodedString(uint64_t input, size_t minLength); + +} // namespace chip + +#endif /* _SETUP_CODE_UTILS_H_ */ \ No newline at end of file diff --git a/src/qrcode/SetupPayload.h b/src/qrcode/SetupPayload.h new file mode 100644 index 00000000000000..b49ca5ec800bb9 --- /dev/null +++ b/src/qrcode/SetupPayload.h @@ -0,0 +1,46 @@ +/* + * + * + * + * 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 class to hold + * data enumerated from a byte stream + */ + + +#ifndef _SETUP_PAYLOAD_H_ +#define _SETUP_PAYLOAD_H_ + +#include + +namespace chip { + +class SetupPayload +{ + public: + uint8_t version; + uint16_t vendorID; + uint16_t productID; + uint8_t requiresCustomFlow; + uint16_t rendezvousInformation; + uint16_t discriminator; + uint32_t setUpPINCode; +}; + +}; // namespace chip + +#endif /* _SETUP_PAYLOAD_H_ */ \ No newline at end of file diff --git a/src/qrcode/SetupPayloadGenerator.cpp b/src/qrcode/SetupPayloadGenerator.cpp new file mode 100644 index 00000000000000..42cfd7e6dde618 --- /dev/null +++ b/src/qrcode/SetupPayloadGenerator.cpp @@ -0,0 +1,172 @@ +/* + * + * + * + * 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 "SetupPayloadGenerator.h" +#include "SetupCodeUtils.h" + +#include +#include + +using namespace chip; +using namespace std; + +void SetupPayloadGenerator::resetBitSet() +{ + mPayloadBitsIndex = kTotalPayloadDataSizeInBits; + mPayloadBits.reset(); +} + +// Populates numberOfBits starting from LSB of input into mPayloadBits +void SetupPayloadGenerator::populateInteger(uint64_t input, size_t numberOfBits) +{ + int numberOfBitsModified = 0; + + mPayloadBitsIndex -= numberOfBits; + int currentIndex = mPayloadBitsIndex; + + int endIndex = currentIndex + (numberOfBits - 1); + + int itercount = 1; + while (currentIndex <= endIndex) { + itercount++; + input & 1 ? mPayloadBits.set(currentIndex) : mPayloadBits.reset(currentIndex); + currentIndex++; + input /= 2; + } +} + +void SetupPayloadGenerator::populateVersion() +{ + populateInteger(mPayload.version, kVersionFieldLengthInBits); +} + +void SetupPayloadGenerator::populateVendorID() +{ + populateInteger(mPayload.vendorID, kVendorIDFieldLengthInBits); +} + +void SetupPayloadGenerator::populateProductID() +{ + populateInteger(mPayload.productID, kProductIDFieldLengthInBits); +} + +void SetupPayloadGenerator::populateCustomFlowRequiredField() +{ + populateInteger(mPayload.requiresCustomFlow, kCustomFlowRequiredFieldLengthInBits); +} + +void SetupPayloadGenerator::populateRendezVousInfo() +{ + populateInteger(mPayload.rendezvousInformation, kRendezvousInfoFieldLengthInBits); +} + +void SetupPayloadGenerator::populateDiscriminator() +{ + populateInteger(mPayload.discriminator, kPayloadDiscriminatorFieldLengthInBits); +} + +void SetupPayloadGenerator::populateSetupPIN() +{ + populateInteger(mPayload.setUpPINCode, kSetupPINCodeFieldLengthInBits); +} + +void SetupPayloadGenerator::populateReservedField() +{ + populateInteger(0, kReservedFieldLengthInBits); +} + +void SetupPayloadGenerator::generateBitSet() +{ + resetBitSet(); + populateVersion(); + populateVendorID(); + populateProductID(); + populateCustomFlowRequiredField(); + populateRendezVousInfo(); + populateDiscriminator(); + populateSetupPIN(); + populateReservedField(); +} + +string SetupPayloadGenerator::payloadBinaryRepresentation() +{ + generateBitSet(); + return mPayloadBits.to_string(); +} + +// This function assumes bits.size() % 8 == 0 +// TODO: Can this method be written in a more elegant way? +vector arrayFromBits(bitset bits) +{ + vector resultVector; + size_t numberOfBits = bits.size(); + size_t numberOfBytes = numberOfBits / 8; + bool oddNumOfBytes = (numberOfBytes % 2) ? true : false; + + // Traversing in reverse, hence startIndex > endIndex + int endIndex = 0; + int startIndex = bits.size() - 1; + + /* + Every 2 bytes (16 bits) of binary source data are encoded to 3 characters of the Base-45 alphabet. + If an odd number of bytes are to be encoded, the remaining single byte will be encoded + to 2 characters of the Base-45 alphabet. + */ + if (oddNumOfBytes) { + endIndex = 8; + } + + while (startIndex > endIndex) { + int currentIntegerIndex = startIndex; + uint16_t result = 0; + for (int i = currentIntegerIndex; i > currentIntegerIndex - 16; i--) { + result = result << 1; + result = result | bits.test(i); + } + resultVector.push_back(result); + startIndex -= 16; + } + + // If we have odd number of bytes append the last byte. + if (oddNumOfBytes) { + uint16_t result = 0; + for (int i = 7; i >= 0 ; i--) { + result = result << 1; + result = result & bits.test(i); + } + resultVector.push_back(result); + } + return resultVector; +} + +string SetupPayloadGenerator::payloadBase45Representation() +{ + generateBitSet(); + vector integerArray = arrayFromBits(mPayloadBits); + string result; + for (int idx = 0; idx < integerArray.size(); idx++) { + result += base45EncodedString(integerArray[idx], 3); + } + return result; +} diff --git a/src/qrcode/SetupPayloadGenerator.h b/src/qrcode/SetupPayloadGenerator.h new file mode 100644 index 00000000000000..99c88fd5eb37b8 --- /dev/null +++ b/src/qrcode/SetupPayloadGenerator.h @@ -0,0 +1,90 @@ +/* + * + * + * + * 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 generator based on the + * CHIP specification. + * + * The encoding of the binary data to a base45 string is as follows: + * - Every 2 bytes (16 bits) of binary source data are encoded to 3 + * characters of the Base-45 alphabet. + * - If an odd number of bytes are to be encoded, the remaining + * single byte is encoded to 2 characters of the Base-45 alphabet. + */ + +#include "SetupPayload.h" + +#include +#include +using namespace std; + +#ifndef _SETUP_PAYLOAD_GENERATOR_ +#define _SETUP_PAYLOAD_GENERATOR_ + +const int kVersionFieldLengthInBits = 3; +const int kVendorIDFieldLengthInBits = 16; +const int kProductIDFieldLengthInBits = 16; +const int kCustomFlowRequiredFieldLengthInBits = 1; +const int kRendezvousInfoFieldLengthInBits = 8; +const int kPayloadDiscriminatorFieldLengthInBits = 8; +const int kSetupPINCodeFieldLengthInBits = 27; +const int kReservedFieldLengthInBits = 1; + + +const int kTotalPayloadDataSizeInBits = ( kVersionFieldLengthInBits + \ + kVendorIDFieldLengthInBits + \ + kProductIDFieldLengthInBits + \ + kCustomFlowRequiredFieldLengthInBits + + kRendezvousInfoFieldLengthInBits + \ + kPayloadDiscriminatorFieldLengthInBits + \ + kSetupPINCodeFieldLengthInBits + \ + kReservedFieldLengthInBits + ); + +namespace chip { + +class SetupPayloadGenerator +{ + private: + bitset mPayloadBits; + SetupPayload mPayload; + // points to the current index within the bitset + int mPayloadBitsIndex; + + void populateInteger(uint64_t input, size_t numberOfBits); + void populateVersion(); + void populateVendorID(); + void populateProductID(); + void populateCustomFlowRequiredField(); + void populateRendezVousInfo(); + void populateDiscriminator(); + void populateSetupPIN(); + void populateReservedField(); + void resetBitSet(); + void generateBitSet(); + + public: + string payloadBinaryRepresentation(); + string payloadBase45Representation(); + SetupPayloadGenerator(SetupPayload setupPayload) : mPayload(setupPayload) {}; + +}; + +}; // namespace chip + +#endif /* _SETUP_PAYLOAD_GENERATOR_ */ \ No newline at end of file diff --git a/src/qrcode/tests/tests.cpp b/src/qrcode/tests/tests.cpp new file mode 100644 index 00000000000000..e9719ff2755bcc --- /dev/null +++ b/src/qrcode/tests/tests.cpp @@ -0,0 +1,88 @@ +/* + * + * + * + * 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. + */ + +#include + +#include + +#include "SetupCodeUtils.cpp" +#include "SetupPayload.h" +#include "SetupPayloadGenerator.cpp" + +using namespace chip; +using namespace std; + +void testPayloadByteArrayRep() { + printf("---Running Test--- %s\n", __FUNCTION__); + SetupPayload payload; + + payload.version = 5; + payload.vendorID = 12; + payload.productID = 1; + payload.requiresCustomFlow = 0; + payload.rendezvousInformation = 1; + payload.discriminator = 128; + payload.setUpPINCode = 2048; + + SetupPayloadGenerator generator(payload); + string result = generator.payloadBinaryRepresentation(); + string expectedResult = + "101000000000000110000000000000000010000000011000000000000000000000010000" + "00000000"; + + assert(result.compare(expectedResult) == 0); +} + +void testPayloadBase45Rep() { + printf("---Running Test--- %s\n", __FUNCTION__); + SetupPayload payload; + + payload.version = 5; + payload.vendorID = 12; + payload.productID = 1; + payload.requiresCustomFlow = 0; + payload.rendezvousInformation = 1; + payload.discriminator = 128; + payload.setUpPINCode = 2048; + + SetupPayloadGenerator generator(payload); + string result = generator.payloadBase45Representation(); + string expectedResult = "KABG8842Q000211"; + + assert(result.compare(expectedResult) == 0); +} + +void testBase45Encoding() { + printf("---Running Test--- %s\n", __FUNCTION__); + uint16_t input = 10; + string result = base45EncodedString(input, 3); + string expectedResult = "00A"; + assert(result.compare(expectedResult) == 0); +} + +void testBitsetLen() { + printf("---Running Test--- %s\n", __FUNCTION__); + assert(kTotalPayloadDataSizeInBits % 8 == 0); +} + +int main(int argc, char** argv) { + printf("---Running Test--- tests from %s\n", __FILE__); + testBitsetLen(); + testPayloadByteArrayRep(); + testPayloadBase45Rep(); + testBase45Encoding(); +} \ No newline at end of file