Loading rebootescrow/aidl/default/Android.bp 0 → 100644 +47 −0 Original line number Diff line number Diff line // // Copyright (C) 2019 The Android Open Source Project // // 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. // cc_library_static { name: "libhadamardutils", vendor_available: true, host_supported: true, shared_libs: [ "libbase", ], srcs: [ "HadamardUtils.cpp", ], visibility: [ ":__subpackages__", ], } cc_test { name: "HadamardUtilsTest", host_supported: true, srcs: [ "HadamardUtilsTest.cpp", ], static_libs: [ "libhadamardutils", "libgtest_prod", ], shared_libs: [ "liblog", "libbase", ], test_suites: ["device-tests"], } rebootescrow/aidl/default/HadamardUtils.cpp 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 <HadamardUtils.h> #include <android-base/logging.h> namespace aidl { namespace android { namespace hardware { namespace rebootescrow { namespace hadamard { constexpr auto BYTE_LENGTH = 8u; std::vector<uint8_t> BitsetToBytes(const std::bitset<ENCODE_LENGTH>& encoded_bits) { CHECK_EQ(0, (encoded_bits.size() % BYTE_LENGTH)); std::vector<uint8_t> result; for (size_t i = 0; i < encoded_bits.size(); i += 8) { uint8_t current = 0; // Set each byte starting from the LSB. for (size_t j = 0; j < BYTE_LENGTH; j++) { CHECK_LE(i + j, encoded_bits.size()); if (encoded_bits[i + j]) { current |= (1u << j); } } result.push_back(current); } return result; } std::bitset<ENCODE_LENGTH> BytesToBitset(const std::vector<uint8_t>& encoded) { CHECK_EQ(ENCODE_LENGTH, encoded.size() * BYTE_LENGTH); std::bitset<ENCODE_LENGTH> result; size_t offset = 0; for (const auto& byte : encoded) { // Set each byte starting from the LSB. for (size_t j = 0; j < BYTE_LENGTH; j++) { result[offset + j] = byte & (1u << j); } offset += BYTE_LENGTH; } return result; } // The encoding is equivalent to multiply the word with the generator matrix (and take the module // of 2). Here is an example of encoding a number with 3 bits. The encoded length is thus // 2^(3-1) = 4 bits. // |1 1 1 1| |0| // |0 1 1| * |0 0 1 1| = |1| // |0 1 0 1| |1| // |0| std::bitset<ENCODE_LENGTH> EncodeWord(uint16_t word) { std::bitset<ENCODE_LENGTH> result; for (uint64_t i = ENCODE_LENGTH; i < 2 * ENCODE_LENGTH; i++) { uint32_t wi = word & i; // Sum all the bits in the word and check its parity. wi ^= wi >> 8u; wi ^= wi >> 4u; wi ^= wi >> 2u; wi ^= wi >> 1u; result[i - ENCODE_LENGTH] = wi & 1u; } return result; } std::vector<uint8_t> EncodeKey(const std::vector<uint8_t>& key) { CHECK_EQ(KEY_SIZE_IN_BYTES, key.size()); std::vector<uint8_t> result; for (size_t i = 0; i < key.size(); i += 2) { uint16_t word = static_cast<uint16_t>(key[i + 1]) << BYTE_LENGTH | key[i]; auto encoded_bits = EncodeWord(word); auto byte_array = BitsetToBytes(encoded_bits); std::move(byte_array.begin(), byte_array.end(), std::back_inserter(result)); } return result; } std::vector<uint8_t> DecodeKey(const std::vector<uint8_t>& encoded) { CHECK_EQ(0, (encoded.size() * 8) % ENCODE_LENGTH); std::vector<uint8_t> result; for (size_t i = 0; i < encoded.size(); i += ENCODE_LENGTH / 8) { auto current = std::vector<uint8_t>{encoded.begin() + i, encoded.begin() + i + ENCODE_LENGTH / 8}; auto bits = BytesToBitset(current); auto candidates = DecodeWord(bits); CHECK(!candidates.empty()); // TODO(xunchang) Do we want to try other candidates? uint16_t val = candidates.top().second; result.push_back(val & 0xffu); result.push_back(val >> BYTE_LENGTH); } return result; } std::priority_queue<std::pair<int32_t, uint16_t>> DecodeWord( const std::bitset<ENCODE_LENGTH>& encoded) { std::vector<int32_t> scores; scores.reserve(ENCODE_LENGTH); // Convert 0 -> -1 in the encoded bits. e.g [0, 1, 1, 0] -> [-1, 1, 1, -1] for (uint32_t i = 0; i < ENCODE_LENGTH; i++) { scores.push_back(2 * encoded[i] - 1); } // Multiply the hadamard matrix by the transformed input. // |1 1 1 1| |-1| | 0| // |1 -1 1 -1| * | 1| = | 0| // |1 1 -1 -1| | 1| | 0| // |1 -1 -1 1| |-1| |-4| for (uint32_t i = 0; i < CODE_K; i++) { uint16_t step = 1u << i; for (uint32_t j = 0; j < ENCODE_LENGTH; j += 2 * step) { for (uint32_t k = j; k < j + step; k++) { auto a0 = scores[k]; auto a1 = scores[k + step]; scores[k] = a0 + a1; scores[k + step] = a0 - a1; } } } // Assign the corresponding score to each index; larger score indicates higher probability. e.g. // value 3, encoding [0, 1, 1, 0] -> score: 4 // value 7, encoding [1, 0, 0, 1] (3's complement) -> score: -4 std::priority_queue<std::pair<int32_t, uint16_t>> candidates; // TODO(xunchang) limit the candidate size since we don't need all of them? for (uint32_t i = 0; i < scores.size(); i++) { candidates.emplace(-scores[i], i); candidates.emplace(scores[i], (1u << CODE_K) | i); } CHECK_EQ(2 * ENCODE_LENGTH, candidates.size()); return candidates; } } // namespace hadamard } // namespace rebootescrow } // namespace hardware } // namespace android } // namespace aidl rebootescrow/aidl/default/HadamardUtils.h 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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. */ #pragma once #include <stdint.h> #include <bitset> #include <queue> #include <utility> #include <vector> namespace aidl { namespace android { namespace hardware { namespace rebootescrow { namespace hadamard { constexpr uint32_t CODE_K = 15; constexpr uint32_t ENCODE_LENGTH = 1u << CODE_K; constexpr auto KEY_SIZE_IN_BYTES = 32u; // Encodes a 2 bytes word with hadamard code. The encoding expands a word of k+1 bits to a 2^k // bitset. Returns the encoded bitset. std::bitset<ENCODE_LENGTH> EncodeWord(uint16_t word); // Decodes the input bitset, and returns a sorted list of pair with (score, value). The value with // a higher score indicates a greater likehood. std::priority_queue<std::pair<int32_t, uint16_t>> DecodeWord( const std::bitset<ENCODE_LENGTH>& encoded); // Encodes a key that has a size of KEY_SIZE_IN_BYTES. Returns a byte array representation of the // encoded bitset. So a 32 bytes key will expand to 16*(2^15) bits = 64KiB. std::vector<uint8_t> EncodeKey(const std::vector<uint8_t>& input); // Given a byte array representation of the encoded keys, decodes it and return the result. std::vector<uint8_t> DecodeKey(const std::vector<uint8_t>& encoded); // Converts a bitset of length |ENCODE_LENGTH| to a byte array. std::vector<uint8_t> BitsetToBytes(const std::bitset<ENCODE_LENGTH>& encoded_bits); // Converts a byte array of encoded words back to the bitset. std::bitset<ENCODE_LENGTH> BytesToBitset(const std::vector<uint8_t>& encoded); } // namespace hadamard } // namespace rebootescrow } // namespace hardware } // namespace android } // namespace aidl rebootescrow/aidl/default/HadamardUtilsTest.cpp 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 <stdint.h> #include <random> #include <bitset> #include <utility> #include <vector> #include <gtest/gtest.h> #include <HadamardUtils.h> using namespace aidl::android::hardware::rebootescrow::hadamard; class HadamardTest : public testing::Test { protected: void SetUp() override { auto ones = std::bitset<ENCODE_LENGTH>{}.set(); // Expects 0x4000 to encode as top half as ones, and lower half as zeros. i.e. // [1, 1 .. 1, 0, 0 .. 0] expected_half_size_ = ones << half_size_; // Expects 0x1 to encode as interleaved 1 and 0s i.e. [1, 0, 1, 0 ..] expected_one_ = ones; for (uint32_t i = ENCODE_LENGTH / 2; i >= 1; i /= 2) { expected_one_ ^= (expected_one_ >> i); } } uint16_t half_size_ = ENCODE_LENGTH / 2; std::bitset<ENCODE_LENGTH> expected_one_; std::bitset<ENCODE_LENGTH> expected_half_size_; }; static void AddError(std::bitset<ENCODE_LENGTH>* corrupted_bits) { // The hadamard code has a hamming distance of ENCODE_LENGTH/2. So we should always be able to // correct the data if less than a quarter of the encoded bits are corrupted. auto corrupted_max = 0.24f * corrupted_bits->size(); auto corrupted_num = 0; for (size_t i = 0; i < corrupted_bits->size() && corrupted_num < corrupted_max; i++) { if (random() % 2 == 0) { (*corrupted_bits)[i] = !(*corrupted_bits)[i]; corrupted_num += 1; } } } static void EncodeAndDecodeKeys(const std::vector<uint8_t>& key) { auto encoded = EncodeKey(key); ASSERT_EQ(64 * 1024, encoded.size()); auto decoded = DecodeKey(encoded); ASSERT_EQ(key, std::vector<uint8_t>(decoded.begin(), decoded.begin() + key.size())); } TEST_F(HadamardTest, Encode_smoke) { ASSERT_EQ(expected_half_size_, EncodeWord(half_size_)); ASSERT_EQ(expected_one_, EncodeWord(1)); // Check the complement of 1. ASSERT_EQ(~expected_one_, EncodeWord(1u << CODE_K | 1u)); } TEST_F(HadamardTest, Decode_smoke) { auto candidate = DecodeWord(expected_half_size_); auto expected = std::pair<int32_t, uint16_t>{ENCODE_LENGTH, half_size_}; ASSERT_EQ(expected, candidate.top()); candidate = DecodeWord(expected_one_); expected = std::pair<int32_t, uint16_t>{ENCODE_LENGTH, 1}; ASSERT_EQ(expected, candidate.top()); } TEST_F(HadamardTest, Decode_error_correction) { constexpr auto iteration = 10; for (int i = 0; i < iteration; i++) { uint16_t word = random() % (ENCODE_LENGTH * 2); auto corrupted_bits = EncodeWord(word); AddError(&corrupted_bits); auto candidate = DecodeWord(corrupted_bits); ASSERT_EQ(word, candidate.top().second); } } TEST_F(HadamardTest, BytesToBitset_smoke) { auto bytes = BitsetToBytes(expected_one_); auto read_back = BytesToBitset(bytes); ASSERT_EQ(expected_one_, read_back); } TEST_F(HadamardTest, EncodeAndDecodeKey) { std::vector<uint8_t> KEY_1{ 0xA5, 0x00, 0xFF, 0x01, 0xA5, 0x5a, 0xAA, 0x55, 0x00, 0xD3, 0x2A, 0x8C, 0x2E, 0x83, 0x0E, 0x65, 0x9E, 0x8D, 0xC6, 0xAC, 0x1E, 0x83, 0x21, 0xB3, 0x95, 0x02, 0x89, 0x64, 0x64, 0x92, 0x12, 0x1F, }; std::vector<uint8_t> KEY_2{ 0xFF, 0x00, 0x00, 0xAA, 0x5A, 0x19, 0x20, 0x71, 0x9F, 0xFB, 0xDA, 0xB6, 0x2D, 0x06, 0xD5, 0x49, 0x7E, 0xEF, 0x63, 0xAC, 0x18, 0xFF, 0x5A, 0xA3, 0x40, 0xBB, 0x64, 0xFA, 0x67, 0xC1, 0x10, 0x18, }; EncodeAndDecodeKeys(KEY_1); EncodeAndDecodeKeys(KEY_2); std::vector<uint8_t> key; for (uint8_t i = 0; i < KEY_SIZE_IN_BYTES; i++) { key.push_back(i); }; EncodeAndDecodeKeys(key); } Loading
rebootescrow/aidl/default/Android.bp 0 → 100644 +47 −0 Original line number Diff line number Diff line // // Copyright (C) 2019 The Android Open Source Project // // 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. // cc_library_static { name: "libhadamardutils", vendor_available: true, host_supported: true, shared_libs: [ "libbase", ], srcs: [ "HadamardUtils.cpp", ], visibility: [ ":__subpackages__", ], } cc_test { name: "HadamardUtilsTest", host_supported: true, srcs: [ "HadamardUtilsTest.cpp", ], static_libs: [ "libhadamardutils", "libgtest_prod", ], shared_libs: [ "liblog", "libbase", ], test_suites: ["device-tests"], }
rebootescrow/aidl/default/HadamardUtils.cpp 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 <HadamardUtils.h> #include <android-base/logging.h> namespace aidl { namespace android { namespace hardware { namespace rebootescrow { namespace hadamard { constexpr auto BYTE_LENGTH = 8u; std::vector<uint8_t> BitsetToBytes(const std::bitset<ENCODE_LENGTH>& encoded_bits) { CHECK_EQ(0, (encoded_bits.size() % BYTE_LENGTH)); std::vector<uint8_t> result; for (size_t i = 0; i < encoded_bits.size(); i += 8) { uint8_t current = 0; // Set each byte starting from the LSB. for (size_t j = 0; j < BYTE_LENGTH; j++) { CHECK_LE(i + j, encoded_bits.size()); if (encoded_bits[i + j]) { current |= (1u << j); } } result.push_back(current); } return result; } std::bitset<ENCODE_LENGTH> BytesToBitset(const std::vector<uint8_t>& encoded) { CHECK_EQ(ENCODE_LENGTH, encoded.size() * BYTE_LENGTH); std::bitset<ENCODE_LENGTH> result; size_t offset = 0; for (const auto& byte : encoded) { // Set each byte starting from the LSB. for (size_t j = 0; j < BYTE_LENGTH; j++) { result[offset + j] = byte & (1u << j); } offset += BYTE_LENGTH; } return result; } // The encoding is equivalent to multiply the word with the generator matrix (and take the module // of 2). Here is an example of encoding a number with 3 bits. The encoded length is thus // 2^(3-1) = 4 bits. // |1 1 1 1| |0| // |0 1 1| * |0 0 1 1| = |1| // |0 1 0 1| |1| // |0| std::bitset<ENCODE_LENGTH> EncodeWord(uint16_t word) { std::bitset<ENCODE_LENGTH> result; for (uint64_t i = ENCODE_LENGTH; i < 2 * ENCODE_LENGTH; i++) { uint32_t wi = word & i; // Sum all the bits in the word and check its parity. wi ^= wi >> 8u; wi ^= wi >> 4u; wi ^= wi >> 2u; wi ^= wi >> 1u; result[i - ENCODE_LENGTH] = wi & 1u; } return result; } std::vector<uint8_t> EncodeKey(const std::vector<uint8_t>& key) { CHECK_EQ(KEY_SIZE_IN_BYTES, key.size()); std::vector<uint8_t> result; for (size_t i = 0; i < key.size(); i += 2) { uint16_t word = static_cast<uint16_t>(key[i + 1]) << BYTE_LENGTH | key[i]; auto encoded_bits = EncodeWord(word); auto byte_array = BitsetToBytes(encoded_bits); std::move(byte_array.begin(), byte_array.end(), std::back_inserter(result)); } return result; } std::vector<uint8_t> DecodeKey(const std::vector<uint8_t>& encoded) { CHECK_EQ(0, (encoded.size() * 8) % ENCODE_LENGTH); std::vector<uint8_t> result; for (size_t i = 0; i < encoded.size(); i += ENCODE_LENGTH / 8) { auto current = std::vector<uint8_t>{encoded.begin() + i, encoded.begin() + i + ENCODE_LENGTH / 8}; auto bits = BytesToBitset(current); auto candidates = DecodeWord(bits); CHECK(!candidates.empty()); // TODO(xunchang) Do we want to try other candidates? uint16_t val = candidates.top().second; result.push_back(val & 0xffu); result.push_back(val >> BYTE_LENGTH); } return result; } std::priority_queue<std::pair<int32_t, uint16_t>> DecodeWord( const std::bitset<ENCODE_LENGTH>& encoded) { std::vector<int32_t> scores; scores.reserve(ENCODE_LENGTH); // Convert 0 -> -1 in the encoded bits. e.g [0, 1, 1, 0] -> [-1, 1, 1, -1] for (uint32_t i = 0; i < ENCODE_LENGTH; i++) { scores.push_back(2 * encoded[i] - 1); } // Multiply the hadamard matrix by the transformed input. // |1 1 1 1| |-1| | 0| // |1 -1 1 -1| * | 1| = | 0| // |1 1 -1 -1| | 1| | 0| // |1 -1 -1 1| |-1| |-4| for (uint32_t i = 0; i < CODE_K; i++) { uint16_t step = 1u << i; for (uint32_t j = 0; j < ENCODE_LENGTH; j += 2 * step) { for (uint32_t k = j; k < j + step; k++) { auto a0 = scores[k]; auto a1 = scores[k + step]; scores[k] = a0 + a1; scores[k + step] = a0 - a1; } } } // Assign the corresponding score to each index; larger score indicates higher probability. e.g. // value 3, encoding [0, 1, 1, 0] -> score: 4 // value 7, encoding [1, 0, 0, 1] (3's complement) -> score: -4 std::priority_queue<std::pair<int32_t, uint16_t>> candidates; // TODO(xunchang) limit the candidate size since we don't need all of them? for (uint32_t i = 0; i < scores.size(); i++) { candidates.emplace(-scores[i], i); candidates.emplace(scores[i], (1u << CODE_K) | i); } CHECK_EQ(2 * ENCODE_LENGTH, candidates.size()); return candidates; } } // namespace hadamard } // namespace rebootescrow } // namespace hardware } // namespace android } // namespace aidl
rebootescrow/aidl/default/HadamardUtils.h 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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. */ #pragma once #include <stdint.h> #include <bitset> #include <queue> #include <utility> #include <vector> namespace aidl { namespace android { namespace hardware { namespace rebootescrow { namespace hadamard { constexpr uint32_t CODE_K = 15; constexpr uint32_t ENCODE_LENGTH = 1u << CODE_K; constexpr auto KEY_SIZE_IN_BYTES = 32u; // Encodes a 2 bytes word with hadamard code. The encoding expands a word of k+1 bits to a 2^k // bitset. Returns the encoded bitset. std::bitset<ENCODE_LENGTH> EncodeWord(uint16_t word); // Decodes the input bitset, and returns a sorted list of pair with (score, value). The value with // a higher score indicates a greater likehood. std::priority_queue<std::pair<int32_t, uint16_t>> DecodeWord( const std::bitset<ENCODE_LENGTH>& encoded); // Encodes a key that has a size of KEY_SIZE_IN_BYTES. Returns a byte array representation of the // encoded bitset. So a 32 bytes key will expand to 16*(2^15) bits = 64KiB. std::vector<uint8_t> EncodeKey(const std::vector<uint8_t>& input); // Given a byte array representation of the encoded keys, decodes it and return the result. std::vector<uint8_t> DecodeKey(const std::vector<uint8_t>& encoded); // Converts a bitset of length |ENCODE_LENGTH| to a byte array. std::vector<uint8_t> BitsetToBytes(const std::bitset<ENCODE_LENGTH>& encoded_bits); // Converts a byte array of encoded words back to the bitset. std::bitset<ENCODE_LENGTH> BytesToBitset(const std::vector<uint8_t>& encoded); } // namespace hadamard } // namespace rebootescrow } // namespace hardware } // namespace android } // namespace aidl
rebootescrow/aidl/default/HadamardUtilsTest.cpp 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 <stdint.h> #include <random> #include <bitset> #include <utility> #include <vector> #include <gtest/gtest.h> #include <HadamardUtils.h> using namespace aidl::android::hardware::rebootescrow::hadamard; class HadamardTest : public testing::Test { protected: void SetUp() override { auto ones = std::bitset<ENCODE_LENGTH>{}.set(); // Expects 0x4000 to encode as top half as ones, and lower half as zeros. i.e. // [1, 1 .. 1, 0, 0 .. 0] expected_half_size_ = ones << half_size_; // Expects 0x1 to encode as interleaved 1 and 0s i.e. [1, 0, 1, 0 ..] expected_one_ = ones; for (uint32_t i = ENCODE_LENGTH / 2; i >= 1; i /= 2) { expected_one_ ^= (expected_one_ >> i); } } uint16_t half_size_ = ENCODE_LENGTH / 2; std::bitset<ENCODE_LENGTH> expected_one_; std::bitset<ENCODE_LENGTH> expected_half_size_; }; static void AddError(std::bitset<ENCODE_LENGTH>* corrupted_bits) { // The hadamard code has a hamming distance of ENCODE_LENGTH/2. So we should always be able to // correct the data if less than a quarter of the encoded bits are corrupted. auto corrupted_max = 0.24f * corrupted_bits->size(); auto corrupted_num = 0; for (size_t i = 0; i < corrupted_bits->size() && corrupted_num < corrupted_max; i++) { if (random() % 2 == 0) { (*corrupted_bits)[i] = !(*corrupted_bits)[i]; corrupted_num += 1; } } } static void EncodeAndDecodeKeys(const std::vector<uint8_t>& key) { auto encoded = EncodeKey(key); ASSERT_EQ(64 * 1024, encoded.size()); auto decoded = DecodeKey(encoded); ASSERT_EQ(key, std::vector<uint8_t>(decoded.begin(), decoded.begin() + key.size())); } TEST_F(HadamardTest, Encode_smoke) { ASSERT_EQ(expected_half_size_, EncodeWord(half_size_)); ASSERT_EQ(expected_one_, EncodeWord(1)); // Check the complement of 1. ASSERT_EQ(~expected_one_, EncodeWord(1u << CODE_K | 1u)); } TEST_F(HadamardTest, Decode_smoke) { auto candidate = DecodeWord(expected_half_size_); auto expected = std::pair<int32_t, uint16_t>{ENCODE_LENGTH, half_size_}; ASSERT_EQ(expected, candidate.top()); candidate = DecodeWord(expected_one_); expected = std::pair<int32_t, uint16_t>{ENCODE_LENGTH, 1}; ASSERT_EQ(expected, candidate.top()); } TEST_F(HadamardTest, Decode_error_correction) { constexpr auto iteration = 10; for (int i = 0; i < iteration; i++) { uint16_t word = random() % (ENCODE_LENGTH * 2); auto corrupted_bits = EncodeWord(word); AddError(&corrupted_bits); auto candidate = DecodeWord(corrupted_bits); ASSERT_EQ(word, candidate.top().second); } } TEST_F(HadamardTest, BytesToBitset_smoke) { auto bytes = BitsetToBytes(expected_one_); auto read_back = BytesToBitset(bytes); ASSERT_EQ(expected_one_, read_back); } TEST_F(HadamardTest, EncodeAndDecodeKey) { std::vector<uint8_t> KEY_1{ 0xA5, 0x00, 0xFF, 0x01, 0xA5, 0x5a, 0xAA, 0x55, 0x00, 0xD3, 0x2A, 0x8C, 0x2E, 0x83, 0x0E, 0x65, 0x9E, 0x8D, 0xC6, 0xAC, 0x1E, 0x83, 0x21, 0xB3, 0x95, 0x02, 0x89, 0x64, 0x64, 0x92, 0x12, 0x1F, }; std::vector<uint8_t> KEY_2{ 0xFF, 0x00, 0x00, 0xAA, 0x5A, 0x19, 0x20, 0x71, 0x9F, 0xFB, 0xDA, 0xB6, 0x2D, 0x06, 0xD5, 0x49, 0x7E, 0xEF, 0x63, 0xAC, 0x18, 0xFF, 0x5A, 0xA3, 0x40, 0xBB, 0x64, 0xFA, 0x67, 0xC1, 0x10, 0x18, }; EncodeAndDecodeKeys(KEY_1); EncodeAndDecodeKeys(KEY_2); std::vector<uint8_t> key; for (uint8_t i = 0; i < KEY_SIZE_IN_BYTES; i++) { key.push_back(i); }; EncodeAndDecodeKeys(key); }