summaryrefslogtreecommitdiffstats
path: root/rebootescrow
diff options
context:
space:
mode:
Diffstat (limited to 'rebootescrow')
-rw-r--r--rebootescrow/aidl/default/Android.bp47
-rw-r--r--rebootescrow/aidl/default/HadamardUtils.cpp157
-rw-r--r--rebootescrow/aidl/default/HadamardUtils.h62
-rw-r--r--rebootescrow/aidl/default/HadamardUtilsTest.cpp126
4 files changed, 392 insertions, 0 deletions
diff --git a/rebootescrow/aidl/default/Android.bp b/rebootescrow/aidl/default/Android.bp
new file mode 100644
index 0000000000..eb228ad827
--- /dev/null
+++ b/rebootescrow/aidl/default/Android.bp
@@ -0,0 +1,47 @@
+//
+// 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"],
+}
diff --git a/rebootescrow/aidl/default/HadamardUtils.cpp b/rebootescrow/aidl/default/HadamardUtils.cpp
new file mode 100644
index 0000000000..5853d2def5
--- /dev/null
+++ b/rebootescrow/aidl/default/HadamardUtils.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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
diff --git a/rebootescrow/aidl/default/HadamardUtils.h b/rebootescrow/aidl/default/HadamardUtils.h
new file mode 100644
index 0000000000..21cfc787e3
--- /dev/null
+++ b/rebootescrow/aidl/default/HadamardUtils.h
@@ -0,0 +1,62 @@
+/*
+ * 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
diff --git a/rebootescrow/aidl/default/HadamardUtilsTest.cpp b/rebootescrow/aidl/default/HadamardUtilsTest.cpp
new file mode 100644
index 0000000000..e397e7624e
--- /dev/null
+++ b/rebootescrow/aidl/default/HadamardUtilsTest.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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);
+}