summaryrefslogtreecommitdiffstats
path: root/confirmationui
diff options
context:
space:
mode:
authorJanis Danisevskis <jdanis@google.com>2017-11-03 12:18:34 -0700
committerJanis Danisevskis <jdanis@google.com>2018-01-24 10:10:55 -0800
commita9f0fb0db1e62168534c5a27e123e1413b818586 (patch)
treee0abc9e47ba9edaaeaa305b64e52edd56df8b623 /confirmationui
parentb70f2b2521f518fe85ffd03948ac52948187d306 (diff)
downloadandroid_hardware_interfaces-a9f0fb0db1e62168534c5a27e123e1413b818586.tar.gz
android_hardware_interfaces-a9f0fb0db1e62168534c5a27e123e1413b818586.tar.bz2
android_hardware_interfaces-a9f0fb0db1e62168534c5a27e123e1413b818586.zip
Add confirmation UI support libaray
Includes: A light weight CBOR generator This patch adds a header only CBOR generator and unit tests. It allows expressing CBOR datastructures in C++ for subsequent serialization. The implementation performs no memory allocation and only depends on stdint.h (for (u)intx_t) and stddefs.h (for size_t). It is tailored for use in constrained environments such as TEEs. Convenience method for generating a SHA256 HMAC Bug: 63928580 Test: android.hardware.confirmationui@support-lib-tests Change-Id: I1d93a85503f861281e71e09b1ede5cbb74219694
Diffstat (limited to 'confirmationui')
-rw-r--r--confirmationui/support/Android.bp51
-rw-r--r--confirmationui/support/OWNERS2
-rw-r--r--confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h188
-rw-r--r--confirmationui/support/include/android/hardware/confirmationui/support/cbor.h335
-rw-r--r--confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h213
-rw-r--r--confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h471
-rw-r--r--confirmationui/support/src/cbor.cpp111
-rw-r--r--confirmationui/support/src/confirmationui_utils.cpp39
-rw-r--r--confirmationui/support/test/android_cbor_test.cpp197
-rw-r--r--confirmationui/support/test/gtest_main.cpp23
-rw-r--r--confirmationui/support/test/msg_formatting_test.cpp128
11 files changed, 1758 insertions, 0 deletions
diff --git a/confirmationui/support/Android.bp b/confirmationui/support/Android.bp
new file mode 100644
index 000000000..62156b341
--- /dev/null
+++ b/confirmationui/support/Android.bp
@@ -0,0 +1,51 @@
+//
+// Copyright (C) 2017 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 {
+ name: "android.hardware.confirmationui-support-lib",
+ vendor_available: true,
+ host_supported: true,
+ vndk: {
+ enabled: true,
+ },
+ srcs: [
+ "src/cbor.cpp",
+ "src/confirmationui_utils.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ]
+}
+
+cc_test {
+ name: "android.hardware.confirmationui-support-lib-tests",
+ srcs: [
+ "test/gtest_main.cpp",
+ "test/android_cbor_test.cpp",
+ "test/msg_formatting_test.cpp",
+ ],
+ static_libs: [
+ "libgtest",
+ "android.hardware.confirmationui-support-lib",
+ ],
+ shared_libs: [
+ "android.hardware.confirmationui@1.0",
+ "android.hardware.keymaster@4.0",
+ "libhidlbase",
+ ],
+ clang: true,
+ cflags: [ "-O0" ],
+}
diff --git a/confirmationui/support/OWNERS b/confirmationui/support/OWNERS
new file mode 100644
index 000000000..335660da3
--- /dev/null
+++ b/confirmationui/support/OWNERS
@@ -0,0 +1,2 @@
+jdanis@google.com
+swillden@google.com
diff --git a/confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h b/confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h
new file mode 100644
index 000000000..a88cd40e3
--- /dev/null
+++ b/confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h
@@ -0,0 +1,188 @@
+/*
+**
+** Copyright 2017, 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.
+*/
+
+#ifndef CONFIRMATIONUI_1_0_DEFAULT_GENERICOPERATION_H_
+#define CONFIRMATIONUI_1_0_DEFAULT_GENERICOPERATION_H_
+
+#include <android/hardware/confirmationui/1.0/types.h>
+#include <android/hardware/confirmationui/support/cbor.h>
+#include <android/hardware/confirmationui/support/confirmationui_utils.h>
+#include <android/hardware/keymaster/4.0/types.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace generic {
+
+namespace {
+using namespace ::android::hardware::confirmationui::support;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+using ::android::hardware::keymaster::V4_0::HardwareAuthenticatorType;
+
+inline bool hasOption(UIOption option, const hidl_vec<UIOption>& uiOptions) {
+ for (auto& o : uiOptions) {
+ if (o == option) return true;
+ }
+ return false;
+}
+
+template <typename Callback, typename TimeStamper, typename HmacImplementation>
+class Operation {
+ using HMacer = support::HMac<HmacImplementation>;
+
+ public:
+ Operation() : error_(ResponseCode::Ignored), formattedMessageLength_(0) {}
+
+ ResponseCode init(const Callback& resultCB, const hidl_string& promptText,
+ const hidl_vec<uint8_t>& extraData, const hidl_string& locale,
+ const hidl_vec<UIOption>& uiOptions) {
+ (void)locale;
+ (void)uiOptions;
+ resultCB_ = resultCB;
+ if (error_ != ResponseCode::Ignored) return ResponseCode::OperationPending;
+ // TODO make copy of promptText before using it may reside in shared buffer
+ auto state = write(
+ WriteState(formattedMessageBuffer_),
+ map(pair(text("prompt"), text(promptText)), pair(text("extra"), bytes(extraData))));
+ switch (state.error_) {
+ case Error::OK:
+ break;
+ case Error::OUT_OF_DATA:
+ return ResponseCode::UIErrorMessageTooLong;
+ case Error::MALFORMED_UTF8:
+ return ResponseCode::UIErrorMalformedUTF8Encoding;
+ case Error::MALFORMED:
+ default:
+ return ResponseCode::Unexpected;
+ }
+ formattedMessageLength_ = state.data_ - formattedMessageBuffer_;
+ // setup TUI and diagnose more UI errors here.
+ // on success record the start time
+ startTime_ = TimeStamper::now();
+ if (!startTime_.isOk()) {
+ return ResponseCode::SystemError;
+ }
+ error_ = ResponseCode::OK;
+ return ResponseCode::OK;
+ }
+
+ void setHmacKey(const uint8_t (&key)[32]) { hmacKey_ = {key}; }
+
+ void abort() {
+ // tear down TUI here
+ if (isPending()) {
+ resultCB_->result(ResponseCode::Aborted, {}, {});
+ error_ = ResponseCode::Ignored;
+ }
+ }
+
+ void userCancel() {
+ // tear down TUI here
+ if (isPending()) error_ = ResponseCode::Canceled;
+ }
+
+ void finalize(const uint8_t key[32]) {
+ if (error_ == ResponseCode::Ignored) return;
+ resultCB_->result(error_, getMessage(), userConfirm(key));
+ error_ = ResponseCode::Ignored;
+ resultCB_ = {};
+ }
+
+ bool isPending() const { return error_ != ResponseCode::Ignored; }
+
+ static Operation& get() {
+ static Operation operation;
+ return operation;
+ }
+
+ ResponseCode deliverSecureInputEvent(const HardwareAuthToken& secureInputToken) {
+ constexpr uint8_t testKeyByte = static_cast<uint8_t>(TestKeyBits::BYTE);
+ constexpr uint8_t testKey[32] = {testKeyByte, testKeyByte, testKeyByte, testKeyByte,
+ testKeyByte, testKeyByte, testKeyByte, testKeyByte,
+ testKeyByte, testKeyByte, testKeyByte, testKeyByte,
+ testKeyByte, testKeyByte, testKeyByte, testKeyByte};
+
+ auto hmac = HMacer::hmac256(testKey, "\0", bytes_cast(secureInputToken.challenge),
+ bytes_cast(secureInputToken.userId),
+ bytes_cast(secureInputToken.authenticatorId),
+ bytes_cast(hton(secureInputToken.authenticatorType)),
+ bytes_cast(hton(secureInputToken.timestamp)));
+ if (!hmac.isOk()) return ResponseCode::Unexpected;
+ if (hmac.value() == secureInputToken.mac) {
+ // okay so this is a test token
+ switch (static_cast<TestModeCommands>(secureInputToken.challenge)) {
+ case TestModeCommands::OK_EVENT: {
+ if (isPending()) {
+ finalize(testKey);
+ return ResponseCode::OK;
+ } else {
+ return ResponseCode::Ignored;
+ }
+ }
+ case TestModeCommands::CANCEL_EVENT: {
+ bool ignored = !isPending();
+ userCancel();
+ finalize(testKey);
+ return ignored ? ResponseCode::Ignored : ResponseCode::OK;
+ }
+ default:
+ return ResponseCode::Ignored;
+ }
+ }
+ return ResponseCode::Ignored;
+ }
+
+ private:
+ bool acceptAuthToken(const HardwareAuthToken&) { return false; }
+ hidl_vec<uint8_t> getMessage() {
+ hidl_vec<uint8_t> result;
+ if (error_ != ResponseCode::OK) return {};
+ result.setToExternal(formattedMessageBuffer_, formattedMessageLength_);
+ return result;
+ }
+ hidl_vec<uint8_t> userConfirm(const uint8_t key[32]) {
+ // tear down TUI here
+ if (error_ != ResponseCode::OK) return {};
+ confirmationTokenScratchpad_ = HMacer::hmac256(key, "confirmation token", getMessage());
+ if (!confirmationTokenScratchpad_.isOk()) {
+ error_ = ResponseCode::Unexpected;
+ return {};
+ }
+ hidl_vec<uint8_t> result;
+ result.setToExternal(confirmationTokenScratchpad_->data(),
+ confirmationTokenScratchpad_->size());
+ return result;
+ }
+
+ ResponseCode error_;
+ uint8_t formattedMessageBuffer_[uint32_t(MessageSize::MAX)];
+ size_t formattedMessageLength_;
+ NullOr<array<uint8_t, 32>> confirmationTokenScratchpad_;
+ Callback resultCB_;
+ typename TimeStamper::TimeStamp startTime_;
+ NullOr<array<uint8_t, 32>> hmacKey_;
+};
+
+} // namespace
+} // namespace generic
+} // namespace V1_0
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
+
+#endif // CONFIRMATIONUI_1_0_DEFAULT_GENERICOPERATION_H_
diff --git a/confirmationui/support/include/android/hardware/confirmationui/support/cbor.h b/confirmationui/support/include/android/hardware/confirmationui/support/cbor.h
new file mode 100644
index 000000000..f5814d4f0
--- /dev/null
+++ b/confirmationui/support/include/android/hardware/confirmationui/support/cbor.h
@@ -0,0 +1,335 @@
+/*
+**
+** Copyright 2017, 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.
+*/
+
+#ifndef CONFIRMATIONUI_1_0_DEFAULT_CBOR_H_
+#define CONFIRMATIONUI_1_0_DEFAULT_CBOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <type_traits>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace support {
+
+template <typename In, typename Out>
+Out copy(In begin, In end, Out out) {
+ while (begin != end) {
+ *out++ = *begin++;
+ }
+ return out;
+}
+
+enum class Type : uint8_t {
+ NUMBER = 0,
+ NEGATIVE = 1,
+ BYTE_STRING = 2,
+ TEXT_STRING = 3,
+ ARRAY = 4,
+ MAP = 5,
+ TAG = 6,
+ FLOAT = 7,
+};
+
+enum class Error : uint32_t {
+ OK = 0,
+ OUT_OF_DATA = 1,
+ MALFORMED = 2,
+ MALFORMED_UTF8 = 3,
+};
+
+template <typename Key, typename Value>
+struct MapElement {
+ const Key& key_;
+ const Value& value_;
+ MapElement(const Key& key, const Value& value) : key_(key), value_(value) {}
+};
+
+template <typename... Elems>
+struct Array;
+
+template <typename Head, typename... Tail>
+struct Array<Head, Tail...> {
+ const Head& head_;
+ Array<Tail...> tail_;
+ Array(const Head& head, const Tail&... tail) : head_(head), tail_(tail...) {}
+ constexpr size_t size() const { return sizeof...(Tail) + 1; };
+};
+
+template <>
+struct Array<> {};
+
+struct TextStr {};
+struct ByteStr {};
+
+template <typename T, typename Variant>
+struct StringBuffer {
+ const T* data_;
+ size_t size_;
+ StringBuffer(const T* data, size_t size) : data_(data), size_(size) {
+ static_assert(sizeof(T) == 1, "elements too large");
+ }
+ const T* data() const { return data_; }
+ size_t size() const { return size_; }
+};
+
+/**
+ * Takes a char array turns it into a StringBuffer of TextStr type. The length of the resulting
+ * StringBuffer is size - 1, effectively stripping the 0 character from the region being considered.
+ * If the terminating 0 shall not be stripped use text_keep_last.
+ */
+template <size_t size>
+StringBuffer<char, TextStr> text(const char (&str)[size]) {
+ if (size > 0) return StringBuffer<char, TextStr>(str, size - 1);
+ return StringBuffer<char, TextStr>(str, size);
+}
+
+/**
+ * As opposed to text(const char (&str)[size] this function does not strips the last character.
+ */
+template <size_t size>
+StringBuffer<char, TextStr> text_keep_last(const char (&str)[size]) {
+ return StringBuffer<char, TextStr>(str, size);
+}
+
+template <typename T>
+auto getData(const T& v) -> decltype(v.data()) {
+ return v.data();
+}
+
+template <typename T>
+auto getData(const T& v) -> decltype(v.c_str()) {
+ return v.c_str();
+}
+
+template <typename T>
+auto text(const T& str) -> StringBuffer<std::decay_t<decltype(*getData(str))>, TextStr> {
+ return StringBuffer<std::decay_t<decltype(*getData(str))>, TextStr>(getData(str), str.size());
+}
+
+inline StringBuffer<char, TextStr> text(const char* str, size_t size) {
+ return StringBuffer<char, TextStr>(str, size);
+}
+
+template <typename T, size_t size>
+StringBuffer<T, ByteStr> bytes(const T (&str)[size]) {
+ return StringBuffer<T, ByteStr>(str, size);
+}
+
+template <typename T>
+StringBuffer<T, ByteStr> bytes(const T* str, size_t size) {
+ return StringBuffer<T, ByteStr>(str, size);
+}
+
+template <typename T>
+auto bytes(const T& str) -> StringBuffer<std::decay_t<decltype(*getData(str))>, ByteStr> {
+ return StringBuffer<std::decay_t<decltype(*getData(str))>, ByteStr>(getData(str), str.size());
+}
+
+template <typename... Elems>
+struct Map;
+
+template <typename HeadKey, typename HeadValue, typename... Tail>
+struct Map<MapElement<HeadKey, HeadValue>, Tail...> {
+ const MapElement<HeadKey, HeadValue>& head_;
+ Map<Tail...> tail_;
+ Map(const MapElement<HeadKey, HeadValue>& head, const Tail&... tail)
+ : head_(head), tail_(tail...) {}
+ constexpr size_t size() const { return sizeof...(Tail) + 1; };
+};
+
+template <>
+struct Map<> {};
+
+template <typename... Keys, typename... Values>
+Map<MapElement<Keys, Values>...> map(const MapElement<Keys, Values>&... elements) {
+ return Map<MapElement<Keys, Values>...>(elements...);
+}
+
+template <typename... Elements>
+Array<Elements...> arr(const Elements&... elements) {
+ return Array<Elements...>(elements...);
+}
+
+template <typename Key, typename Value>
+MapElement<Key, Value> pair(const Key& k, const Value& v) {
+ return MapElement<Key, Value>(k, v);
+}
+
+template <size_t size>
+struct getUnsignedType;
+
+template <>
+struct getUnsignedType<sizeof(uint8_t)> {
+ typedef uint8_t type;
+};
+template <>
+struct getUnsignedType<sizeof(uint16_t)> {
+ typedef uint16_t type;
+};
+template <>
+struct getUnsignedType<sizeof(uint32_t)> {
+ typedef uint32_t type;
+};
+template <>
+struct getUnsignedType<sizeof(uint64_t)> {
+ typedef uint64_t type;
+};
+
+template <size_t size>
+using Unsigned = typename getUnsignedType<size>::type;
+
+class WriteState {
+ public:
+ WriteState() : data_(nullptr), size_(0), error_(Error::OK) {}
+ WriteState(uint8_t* buffer, size_t size) : data_(buffer), size_(size), error_(Error::OK) {}
+ WriteState(uint8_t* buffer, size_t size, Error error)
+ : data_(buffer), size_(size), error_(error) {}
+ template <size_t size>
+ WriteState(uint8_t (&buffer)[size]) : data_(buffer), size_(size), error_(Error::OK) {}
+
+ WriteState& operator++() {
+ if (size_) {
+ ++data_;
+ --size_;
+ } else {
+ error_ = Error::OUT_OF_DATA;
+ }
+ return *this;
+ }
+ WriteState& operator+=(size_t offset) {
+ if (offset > size_) {
+ error_ = Error::OUT_OF_DATA;
+ } else {
+ data_ += offset;
+ size_ -= offset;
+ }
+ return *this;
+ }
+ operator bool() const { return error_ == Error::OK; }
+
+ uint8_t* data_;
+ size_t size_;
+ Error error_;
+};
+
+WriteState writeHeader(WriteState wState, Type type, const uint64_t value);
+bool checkUTF8Copy(const char* begin, const char* const end, uint8_t* out);
+
+template <typename T>
+WriteState writeNumber(WriteState wState, const T& v) {
+ if (!wState) return wState;
+ if (v >= 0) {
+ return writeHeader(wState, Type::NUMBER, v);
+ } else {
+ return writeHeader(wState, Type::NEGATIVE, UINT64_C(-1) - v);
+ }
+}
+
+inline WriteState write(const WriteState& wState, const uint8_t& v) {
+ return writeNumber(wState, v);
+}
+inline WriteState write(const WriteState& wState, const int8_t& v) {
+ return writeNumber(wState, v);
+}
+inline WriteState write(const WriteState& wState, const uint16_t& v) {
+ return writeNumber(wState, v);
+}
+inline WriteState write(const WriteState& wState, const int16_t& v) {
+ return writeNumber(wState, v);
+}
+inline WriteState write(const WriteState& wState, const uint32_t& v) {
+ return writeNumber(wState, v);
+}
+inline WriteState write(const WriteState& wState, const int32_t& v) {
+ return writeNumber(wState, v);
+}
+inline WriteState write(const WriteState& wState, const uint64_t& v) {
+ return writeNumber(wState, v);
+}
+inline WriteState write(const WriteState& wState, const int64_t& v) {
+ return writeNumber(wState, v);
+}
+
+template <typename T>
+WriteState write(WriteState wState, const StringBuffer<T, TextStr>& v) {
+ wState = writeHeader(wState, Type::TEXT_STRING, v.size());
+ uint8_t* buffer = wState.data_;
+ wState += v.size();
+ if (!wState) return wState;
+ if (!checkUTF8Copy(v.data(), v.data() + v.size(), buffer)) {
+ wState.error_ = Error::MALFORMED_UTF8;
+ }
+ return wState;
+}
+
+template <typename T>
+WriteState write(WriteState wState, const StringBuffer<T, ByteStr>& v) {
+ wState = writeHeader(wState, Type::BYTE_STRING, v.size());
+ uint8_t* buffer = wState.data_;
+ wState += v.size();
+ if (!wState) return wState;
+ static_assert(sizeof(*v.data()) == 1, "elements too large");
+ copy(v.data(), v.data() + v.size(), buffer);
+ return wState;
+}
+
+template <template <typename...> class Arr>
+WriteState writeArrayHelper(WriteState wState, const Arr<>&) {
+ return wState;
+}
+
+template <template <typename...> class Arr, typename Head, typename... Tail>
+WriteState writeArrayHelper(WriteState wState, const Arr<Head, Tail...>& arr) {
+ wState = write(wState, arr.head_);
+ return writeArrayHelper(wState, arr.tail_);
+}
+
+template <typename... Elems>
+WriteState write(WriteState wState, const Map<Elems...>& map) {
+ if (!wState) return wState;
+ wState = writeHeader(wState, Type::MAP, map.size());
+ return writeArrayHelper(wState, map);
+}
+
+template <typename... Elems>
+WriteState write(WriteState wState, const Array<Elems...>& arr) {
+ if (!wState) return wState;
+ wState = writeHeader(wState, Type::ARRAY, arr.size());
+ return writeArrayHelper(wState, arr);
+}
+
+template <typename Key, typename Value>
+WriteState write(WriteState wState, const MapElement<Key, Value>& element) {
+ if (!wState) return wState;
+ wState = write(wState, element.key_);
+ return write(wState, element.value_);
+}
+
+template <typename Head, typename... Tail>
+WriteState write(WriteState wState, const Head& head, const Tail&... tail) {
+ wState = write(wState, head);
+ return write(wState, tail...);
+}
+
+} // namespace support
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
+
+#endif // CONFIRMATIONUI_1_0_DEFAULT_CBOR_H_
diff --git a/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h b/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h
new file mode 100644
index 000000000..d55143362
--- /dev/null
+++ b/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h
@@ -0,0 +1,213 @@
+/*
+**
+** Copyright 2017, 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.
+*/
+
+#ifndef CONFIRMATIONUI_1_0_SUPPORT_INCLUDE_CONFIRMATIONUI_UTILS_H_
+#define CONFIRMATIONUI_1_0_SUPPORT_INCLUDE_CONFIRMATIONUI_UTILS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+#include <initializer_list>
+#include <type_traits>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace support {
+
+/**
+ * This class wraps a (mostly return) value and stores whether or not the wrapped value is valid out
+ * of band. Note that if the wrapped value is a reference it is unsafe to access the value if
+ * !isOk(). If the wrapped type is a pointer or value and !isOk(), it is still safe to access the
+ * wrapped value. In this case the pointer will be NULL though, and the value will be default
+ * constructed.
+ */
+template <typename ValueT>
+class NullOr {
+ template <typename T>
+ struct reference_initializer {
+ static T&& init() { return *static_cast<std::remove_reference_t<T>*>(nullptr); }
+ };
+ template <typename T>
+ struct pointer_initializer {
+ static T init() { return nullptr; }
+ };
+ template <typename T>
+ struct value_initializer {
+ static T init() { return T(); }
+ };
+ template <typename T>
+ using initializer_t =
+ std::conditional_t<std::is_lvalue_reference<T>::value, reference_initializer<T>,
+ std::conditional_t<std::is_pointer<T>::value, pointer_initializer<T>,
+ value_initializer<T>>>;
+
+ public:
+ NullOr() : value_(initializer_t<ValueT>::init()), null_(true) {}
+ NullOr(ValueT&& value) : value_(std::forward<ValueT>(value)), null_(false) {}
+
+ bool isOk() const { return !null_; }
+
+ const ValueT& value() const & { return value_; }
+ ValueT& value() & { return value_; }
+ ValueT&& value() && { return std::move(value_); }
+
+ const std::remove_reference_t<ValueT>* operator->() const { return &value_; }
+ std::remove_reference_t<ValueT>* operator->() { return &value_; }
+
+ private:
+ ValueT value_;
+ bool null_;
+};
+
+template <typename T, size_t elements>
+class array {
+ using array_type = T[elements];
+
+ public:
+ array() : data_{} {}
+ array(const T (&data)[elements]) { std::copy(data, data + elements, data_); }
+
+ T* data() { return data_; }
+ const T* data() const { return data_; }
+ constexpr size_t size() const { return elements; }
+ operator const array_type&() const { return data_; }
+
+ T* begin() { return data_; }
+ T* end() { return data_ + elements; }
+ const T* begin() const { return data_; }
+ const T* end() const { return data_ + elements; }
+
+ private:
+ array_type data_;
+};
+
+template <typename T>
+auto bytes_cast(const T& v) -> const uint8_t (&)[sizeof(T)] {
+ return *reinterpret_cast<const uint8_t(*)[sizeof(T)]>(&v);
+}
+template <typename T>
+auto bytes_cast(T& v) -> uint8_t (&)[sizeof(T)] {
+ return *reinterpret_cast<uint8_t(*)[sizeof(T)]>(&v);
+}
+
+class ByteBufferProxy {
+ template <typename T>
+ struct has_data {
+ template <typename U>
+ static int f(const U*, const void*) {
+ return 0;
+ }
+ template <typename U>
+ static int* f(const U* u, decltype(u->data())) {
+ return nullptr;
+ }
+ static constexpr bool value = std::is_pointer<decltype(f((T*)nullptr, ""))>::value;
+ };
+
+ public:
+ template <typename T>
+ ByteBufferProxy(const T& buffer, decltype(buffer.data()) = nullptr)
+ : data_(reinterpret_cast<const uint8_t*>(buffer.data())), size_(buffer.size()) {
+ static_assert(sizeof(decltype(*buffer.data())) == 1, "elements to large");
+ }
+
+ // this overload kicks in for types that have .c_str() but not .data(), such as hidl_string.
+ // std::string has both so we need to explicitly disable this overload if .data() is present.
+ template <typename T>
+ ByteBufferProxy(const T& buffer,
+ std::enable_if_t<!has_data<T>::value, decltype(buffer.c_str())> = nullptr)
+ : data_(reinterpret_cast<const uint8_t*>(buffer.c_str())), size_(buffer.size()) {
+ static_assert(sizeof(decltype(*buffer.c_str())) == 1, "elements to large");
+ }
+
+ template <size_t size>
+ ByteBufferProxy(const char (&buffer)[size])
+ : data_(reinterpret_cast<const uint8_t*>(buffer)), size_(size - 1) {
+ static_assert(size > 0, "even an empty string must be 0-terminated");
+ }
+
+ template <size_t size>
+ ByteBufferProxy(const uint8_t (&buffer)[size]) : data_(buffer), size_(size) {}
+
+ ByteBufferProxy() : data_(nullptr), size_(0) {}
+
+ const uint8_t* data() const { return data_; }
+ size_t size() const { return size_; }
+
+ const uint8_t* begin() const { return data_; }
+ const uint8_t* end() const { return data_ + size_; }
+
+ private:
+ const uint8_t* data_;
+ size_t size_;
+};
+
+/**
+ * Implementer are expected to provide an implementation with the following prototype:
+ * static NullOr<array<uint8_t, 32>> hmac256(const uint8_t key[32],
+ * std::initializer_list<ByteBufferProxy> buffers);
+ */
+template <typename Impl>
+class HMac {
+ public:
+ template <typename... Data>
+ static NullOr<array<uint8_t, 32>> hmac256(const uint8_t key[32], const Data&... data) {
+ return Impl::hmac256(key, {data...});
+ }
+};
+
+bool operator==(const ByteBufferProxy& lhs, const ByteBufferProxy& rhs);
+
+template <typename IntType, uint32_t byteOrder>
+struct choose_hton;
+
+template <typename IntType>
+struct choose_hton<IntType, __ORDER_LITTLE_ENDIAN__> {
+ inline static IntType hton(const IntType& value) {
+ IntType result = {};
+ const unsigned char* inbytes = reinterpret_cast<const unsigned char*>(&value);
+ unsigned char* outbytes = reinterpret_cast<unsigned char*>(&result);
+ for (int i = sizeof(IntType) - 1; i >= 0; --i) {
+ *(outbytes++) = inbytes[i];
+ }
+ return result;
+ }
+};
+
+template <typename IntType>
+struct choose_hton<IntType, __ORDER_BIG_ENDIAN__> {
+ inline static IntType hton(const IntType& value) { return value; }
+};
+
+template <typename IntType>
+inline IntType hton(const IntType& value) {
+ return choose_hton<IntType, __BYTE_ORDER__>::hton(value);
+}
+
+template <typename IntType>
+inline IntType ntoh(const IntType& value) {
+ // same operation as hton
+ return choose_hton<IntType, __BYTE_ORDER__>::hton(value);
+}
+
+} // namespace support
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
+
+#endif // CONFIRMATIONUI_1_0_SUPPORT_INCLUDE_CONFIRMATIONUI_UTILS_H_
diff --git a/confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h b/confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h
new file mode 100644
index 000000000..0d0359177
--- /dev/null
+++ b/confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h
@@ -0,0 +1,471 @@
+/*
+**
+** Copyright 2017, 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.
+*/
+
+#ifndef CONFIRMATIONUI_SUPPORT_INCLUDE_ANDROID_HARDWARE_CONFIRMATIONUI_SUPPORT_MSG_FORMATTING_H_
+#define CONFIRMATIONUI_SUPPORT_INCLUDE_ANDROID_HARDWARE_CONFIRMATIONUI_SUPPORT_MSG_FORMATTING_H_
+
+#include <android/hardware/confirmationui/1.0/types.h>
+#include <android/hardware/keymaster/4.0/types.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+#include <tuple>
+#include <type_traits>
+
+#include <android/hardware/confirmationui/support/confirmationui_utils.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace support {
+
+template <size_t... I>
+class IntegerSequence {};
+
+namespace integer_sequence {
+
+template <typename Lhs, typename Rhs>
+struct conc {};
+
+template <size_t... ILhs, size_t... IRhs>
+struct conc<IntegerSequence<ILhs...>, IntegerSequence<IRhs...>> {
+ using type = IntegerSequence<ILhs..., IRhs...>;
+};
+
+template <typename Lhs, typename Rhs>
+using conc_t = typename conc<Lhs, Rhs>::type;
+
+template <size_t... n>
+struct make {};
+
+template <size_t n>
+struct make<n> {
+ using type = conc_t<typename make<n - 1>::type, IntegerSequence<n - 1>>;
+};
+template <size_t start, size_t n>
+struct make<start, n> {
+ using type = conc_t<typename make<start, n - 1>::type, IntegerSequence<start + n - 1>>;
+};
+
+template <size_t start>
+struct make<start, start> {
+ using type = IntegerSequence<start>;
+};
+
+template <>
+struct make<0> {
+ using type = IntegerSequence<>;
+};
+
+template <size_t... n>
+using make_t = typename make<n...>::type;
+
+} // namespace integer_sequence
+
+template <size_t... idx, typename... T>
+std::tuple<std::remove_reference_t<T>&&...> tuple_move_helper(IntegerSequence<idx...>,
+ std::tuple<T...>&& t) {
+ return {std::move(std::get<idx>(t))...};
+}
+
+template <typename... T>
+std::tuple<std::remove_reference_t<T>&&...> tuple_move(std::tuple<T...>&& t) {
+ return tuple_move_helper(integer_sequence::make_t<sizeof...(T)>(), std::move(t));
+}
+
+template <typename... T>
+std::tuple<std::remove_reference_t<T>&&...> tuple_move(std::tuple<T...>& t) {
+ return tuple_move_helper(integer_sequence::make_t<sizeof...(T)>(), std::move(t));
+}
+
+using ::android::hardware::confirmationui::V1_0::ResponseCode;
+using ::android::hardware::confirmationui::V1_0::UIOption;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+
+template <typename... fields>
+class Message {};
+
+enum class Command : uint32_t {
+ PromptUserConfirmation,
+ DeliverSecureInputEvent,
+ Abort,
+};
+
+template <Command cmd>
+struct Cmd {};
+
+#define DECLARE_COMMAND(cmd) using cmd##_t = Cmd<Command::cmd>
+
+DECLARE_COMMAND(PromptUserConfirmation);
+DECLARE_COMMAND(DeliverSecureInputEvent);
+DECLARE_COMMAND(Abort);
+
+using PromptUserConfirmationMsg = Message<PromptUserConfirmation_t, hidl_string, hidl_vec<uint8_t>,
+ hidl_string, hidl_vec<UIOption>>;
+using PromptUserConfirmationResponse = Message<ResponseCode>;
+using DeliverSecureInputEventMsg = Message<DeliverSecureInputEvent_t, HardwareAuthToken>;
+using DeliverSecureInputEventRespose = Message<ResponseCode>;
+using AbortMsg = Message<Abort_t>;
+using ResultMsg = Message<ResponseCode, hidl_vec<uint8_t>, hidl_vec<uint8_t>>;
+
+template <typename T>
+struct StreamState {
+ using ptr_t = volatile T*;
+ volatile T* pos_;
+ size_t bytes_left_;
+ bool good_;
+ template <size_t size>
+ StreamState(T (&buffer)[size]) : pos_(buffer), bytes_left_(size), good_(size > 0) {}
+ StreamState(T* buffer, size_t size) : pos_(buffer), bytes_left_(size), good_(size > 0) {}
+ StreamState() : pos_(nullptr), bytes_left_(0), good_(false) {}
+ StreamState& operator++() {
+ if (good_ && bytes_left_) {
+ ++pos_;
+ --bytes_left_;
+ } else {
+ good_ = false;
+ }
+ return *this;
+ }
+ StreamState& operator+=(size_t offset) {
+ if (!good_ || offset > bytes_left_) {
+ good_ = false;
+ } else {
+ pos_ += offset;
+ bytes_left_ -= offset;
+ }
+ return *this;
+ }
+ operator bool() const { return good_; }
+ volatile T* pos() const { return pos_; };
+};
+
+using WriteStream = StreamState<uint8_t>;
+using ReadStream = StreamState<const uint8_t>;
+
+inline void zero(volatile uint8_t* begin, const volatile uint8_t* end) {
+ while (begin != end) {
+ *begin++ = 0xaa;
+ }
+}
+inline void zero(const volatile uint8_t*, const volatile uint8_t*) {}
+// This odd alignment function aligns the stream position to a 4byte and never 8byte boundary
+// It is to accommodate the 4 byte size field which is then followed by 8byte alligned data.
+template <typename T>
+StreamState<T> unalign(StreamState<T> s) {
+ uint8_t unalignment = uintptr_t(s.pos_) & 0x3;
+ auto pos = s.pos_;
+ if (unalignment) {
+ s += 4 - unalignment;
+ }
+ // now s.pos_ is aligned on a 4byte boundary
+ if ((uintptr_t(s.pos_) & 0x4) == 0) {
+ // if we are 8byte aligned add 4
+ s += 4;
+ }
+ // zero out the gaps when writing
+ zero(pos, s.pos_);
+ return s;
+}
+
+inline WriteStream write(WriteStream out, const uint8_t* buffer, size_t size) {
+ auto pos = out.pos();
+ uint32_t v = size;
+ out += 4 + size;
+ if (out) {
+ if (size != v) {
+ out.good_ = false;
+ return out;
+ }
+ auto& s = bytes_cast(v);
+ pos = std::copy(s, s + 4, pos);
+ std::copy(buffer, buffer + size, pos);
+ }
+ return out;
+}
+template <size_t size>
+WriteStream write(WriteStream out, const uint8_t (&v)[size]) {
+ return write(out, v, size);
+}
+
+inline std::tuple<ReadStream, ReadStream::ptr_t, size_t> read(ReadStream in) {
+ auto pos = in.pos();
+ in += 4;
+ if (!in) return {in, nullptr, 0};
+ uint32_t size;
+ std::copy(pos, pos + 4, bytes_cast(size));
+ pos = in.pos();
+ in += size;
+ if (!in) return {in, nullptr, 0};
+ return {in, pos, size};
+}
+
+template <typename T>
+std::tuple<ReadStream, T> readSimpleType(ReadStream in) {
+ T result;
+ ReadStream::ptr_t pos = nullptr;
+ size_t read_size = 0;
+ std::tie(in, pos, read_size) = read(in);
+ if (!in || read_size != sizeof(T)) {
+ in.good_ = false;
+ return {in, {}};
+ }
+ std::copy(pos, pos + sizeof(T), bytes_cast(result));
+ return {in, std::move(result)};
+}
+
+template <typename T>
+std::tuple<ReadStream, hidl_vec<T>> readSimpleHidlVecInPlace(ReadStream in) {
+ std::tuple<ReadStream, hidl_vec<T>> result;
+ ReadStream::ptr_t pos = nullptr;
+ size_t read_size = 0;
+ std::tie(std::get<0>(result), pos, read_size) = read(in);
+ if (!std::get<0>(result) || read_size % sizeof(T)) {
+ std::get<0>(result).good_ = false;
+ return result;
+ }
+ std::get<1>(result).setToExternal(reinterpret_cast<T*>(const_cast<uint8_t*>(pos)),
+ read_size / sizeof(T));
+ return result;
+}
+
+template <typename T>
+WriteStream writeSimpleHidlVec(WriteStream out, const hidl_vec<T>& vec) {
+ return write(out, reinterpret_cast<const uint8_t*>(vec.data()), vec.size() * sizeof(T));
+}
+
+// HardwareAuthToken
+constexpr size_t hatSizeNoMac() {
+ HardwareAuthToken* hat = nullptr;
+ return sizeof hat->challenge + sizeof hat->userId + sizeof hat->authenticatorId +
+ sizeof hat->authenticatorType + sizeof hat->timestamp;
+}
+
+template <typename T>
+inline volatile const uint8_t* copyField(T& field, volatile const uint8_t*(&pos)) {
+ auto& s = bytes_cast(field);
+ std::copy(pos, pos + sizeof(T), s);
+ return pos + sizeof(T);
+}
+inline std::tuple<ReadStream, HardwareAuthToken> read(Message<HardwareAuthToken>, ReadStream in_) {
+ std::tuple<ReadStream, HardwareAuthToken> result;
+ ReadStream& in = std::get<0>(result) = in_;
+ auto& hat = std::get<1>(result);
+ constexpr size_t hatSize = hatSizeNoMac();
+ ReadStream::ptr_t pos = nullptr;
+ size_t read_size = 0;
+ std::tie(in, pos, read_size) = read(in);
+ if (!in || read_size != hatSize) {
+ in.good_ = false;
+ return result;
+ }
+ pos = copyField(hat.challenge, pos);
+ pos = copyField(hat.userId, pos);
+ pos = copyField(hat.authenticatorId, pos);
+ pos = copyField(hat.authenticatorType, pos);
+ pos = copyField(hat.timestamp, pos);
+ std::tie(in, hat.mac) = readSimpleHidlVecInPlace<uint8_t>(in);
+ return result;
+}
+
+template <typename T>
+inline volatile uint8_t* copyField(const T& field, volatile uint8_t*(&pos)) {
+ auto& s = bytes_cast(field);
+ return std::copy(s, &s[sizeof(T)], pos);
+}
+
+inline WriteStream write(WriteStream out, const HardwareAuthToken& v) {
+ auto pos = out.pos();
+ uint32_t size_field = hatSizeNoMac();
+ out += 4 + size_field;
+ if (!out) return out;
+ pos = copyField(size_field, pos);
+ pos = copyField(v.challenge, pos);
+ pos = copyField(v.userId, pos);
+ pos = copyField(v.authenticatorId, pos);
+ pos = copyField(v.authenticatorType, pos);
+ pos = copyField(v.timestamp, pos);
+ return writeSimpleHidlVec(out, v.mac);
+}
+
+// ResponseCode
+inline std::tuple<ReadStream, ResponseCode> read(Message<ResponseCode>, ReadStream in) {
+ return readSimpleType<ResponseCode>(in);
+}
+inline WriteStream write(WriteStream out, const ResponseCode& v) {
+ return write(out, bytes_cast(v));
+}
+
+// hidl_vec<uint8_t>
+inline std::tuple<ReadStream, hidl_vec<uint8_t>> read(Message<hidl_vec<uint8_t>>, ReadStream in) {
+ return readSimpleHidlVecInPlace<uint8_t>(in);
+}
+inline WriteStream write(WriteStream out, const hidl_vec<uint8_t>& v) {
+ return writeSimpleHidlVec(out, v);
+}
+
+// hidl_vec<UIOption>
+inline std::tuple<ReadStream, hidl_vec<UIOption>> read(Message<hidl_vec<UIOption>>, ReadStream in) {
+ in = unalign(in);
+ return readSimpleHidlVecInPlace<UIOption>(in);
+}
+inline WriteStream write(WriteStream out, const hidl_vec<UIOption>& v) {
+ out = unalign(out);
+ return writeSimpleHidlVec(out, v);
+}
+
+// hidl_string
+inline std::tuple<ReadStream, hidl_string> read(Message<hidl_string>, ReadStream in) {
+ std::tuple<ReadStream, hidl_string> result;
+ ReadStream& in_ = std::get<0>(result);
+ hidl_string& result_ = std::get<1>(result);
+ ReadStream::ptr_t pos = nullptr;
+ size_t read_size = 0;
+ std::tie(in_, pos, read_size) = read(in);
+ auto terminating_zero = in_.pos();
+ ++in_; // skip the terminating zero. Does nothing if the stream was already bad
+ if (!in_) return result;
+ if (*terminating_zero) {
+ in_.good_ = false;
+ return result;
+ }
+ result_.setToExternal(reinterpret_cast<const char*>(const_cast<const uint8_t*>(pos)),
+ read_size);
+ return result;
+}
+inline WriteStream write(WriteStream out, const hidl_string& v) {
+ out = write(out, reinterpret_cast<const uint8_t*>(v.c_str()), v.size());
+ auto terminating_zero = out.pos();
+ ++out;
+ if (out) {
+ *terminating_zero = 0;
+ }
+ return out;
+}
+
+inline WriteStream write(WriteStream out, Command cmd) {
+ volatile Command* pos = reinterpret_cast<volatile Command*>(out.pos_);
+ out += sizeof(Command);
+ if (out) {
+ *pos = cmd;
+ }
+ return out;
+}
+template <Command cmd>
+WriteStream write(WriteStream out, Cmd<cmd>) {
+ return write(out, cmd);
+}
+
+inline std::tuple<ReadStream, bool> read(ReadStream in, Command cmd) {
+ volatile const Command* pos = reinterpret_cast<volatile const Command*>(in.pos_);
+ in += sizeof(Command);
+ if (!in) return {in, false};
+ return {in, *pos == cmd};
+}
+
+template <Command cmd>
+std::tuple<ReadStream, bool> read(Message<Cmd<cmd>>, ReadStream in) {
+ return read(in, cmd);
+}
+
+inline WriteStream write(Message<>, WriteStream out) {
+ return out;
+}
+
+template <typename Head, typename... Tail>
+WriteStream write(Message<Head, Tail...>, WriteStream out, const Head& head, const Tail&... tail) {
+ out = write(out, head);
+ return write(Message<Tail...>(), out, tail...);
+}
+
+template <Command cmd, typename... Tail>
+WriteStream write(Message<Cmd<cmd>, Tail...>, WriteStream out, const Tail&... tail) {
+ out = write(out, cmd);
+ return write(Message<Tail...>(), out, tail...);
+}
+
+template <Command cmd, typename HEAD, typename... Tail>
+std::tuple<ReadStream, bool, HEAD, Tail...> read(Message<Cmd<cmd>, HEAD, Tail...>, ReadStream in) {
+ bool command_matches;
+ std::tie(in, command_matches) = read(in, cmd);
+ if (!command_matches) return {in, false, HEAD(), Tail()...};
+
+ return {in, true,
+ [&]() -> HEAD {
+ HEAD result;
+ std::tie(in, result) = read(Message<HEAD>(), in);
+ return result;
+ }(),
+ [&]() -> Tail {
+ Tail result;
+ std::tie(in, result) = read(Message<Tail>(), in);
+ return result;
+ }()...};
+}
+
+template <typename... Msg>
+std::tuple<ReadStream, Msg...> read(Message<Msg...>, ReadStream in) {
+ return {in, [&in]() -> Msg {
+ Msg result;
+ std::tie(in, result) = read(Message<Msg>(), in);
+ return result;
+ }()...};
+}
+
+template <typename T>
+struct msg2tuple {};
+
+template <typename... T>
+struct msg2tuple<Message<T...>> {
+ using type = std::tuple<T...>;
+};
+template <Command cmd, typename... T>
+struct msg2tuple<Message<Cmd<cmd>, T...>> {
+ using type = std::tuple<T...>;
+};
+
+template <typename T>
+using msg2tuple_t = typename msg2tuple<T>::type;
+
+template <size_t... idx, typename HEAD, typename... T>
+std::tuple<T&&...> tuple_tail(IntegerSequence<idx...>, std::tuple<HEAD, T...>&& t) {
+ return {std::move(std::get<idx>(t))...};
+}
+
+template <size_t... idx, typename HEAD, typename... T>
+std::tuple<const T&...> tuple_tail(IntegerSequence<idx...>, const std::tuple<HEAD, T...>& t) {
+ return {std::get<idx>(t)...};
+}
+
+template <typename HEAD, typename... Tail>
+std::tuple<Tail&&...> tuple_tail(std::tuple<HEAD, Tail...>&& t) {
+ return tuple_tail(integer_sequence::make_t<1, sizeof...(Tail)>(), std::move(t));
+}
+
+template <typename HEAD, typename... Tail>
+std::tuple<const Tail&...> tuple_tail(const std::tuple<HEAD, Tail...>& t) {
+ return tuple_tail(integer_sequence::make_t<1, sizeof...(Tail)>(), t);
+}
+
+} // namespace support
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
+
+#endif // CONFIRMATIONUI_SUPPORT_INCLUDE_ANDROID_HARDWARE_CONFIRMATIONUI_SUPPORT_MSG_FORMATTING_H_
diff --git a/confirmationui/support/src/cbor.cpp b/confirmationui/support/src/cbor.cpp
new file mode 100644
index 000000000..e7ea164d9
--- /dev/null
+++ b/confirmationui/support/src/cbor.cpp
@@ -0,0 +1,111 @@
+/*
+**
+** Copyright 2017, 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 <android/hardware/confirmationui/support/cbor.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace support {
+namespace {
+
+inline uint8_t getByte(const uint64_t& v, const uint8_t index) {
+ return v >> (index * 8);
+}
+
+WriteState writeBytes(WriteState state, uint64_t value, uint8_t size) {
+ auto pos = state.data_;
+ if (!(state += size)) return state;
+ switch (size) {
+ case 8:
+ *pos++ = getByte(value, 7);
+ *pos++ = getByte(value, 6);
+ *pos++ = getByte(value, 5);
+ *pos++ = getByte(value, 4);
+ case 4:
+ *pos++ = getByte(value, 3);
+ *pos++ = getByte(value, 2);
+ case 2:
+ *pos++ = getByte(value, 1);
+ case 1:
+ *pos++ = value;
+ break;
+ default:
+ state.error_ = Error::MALFORMED;
+ }
+ return state;
+}
+
+} // anonymous namespace
+
+WriteState writeHeader(WriteState wState, Type type, const uint64_t value) {
+ if (!wState) return wState;
+ uint8_t& header = *wState.data_;
+ if (!++wState) return wState;
+ header = static_cast<uint8_t>(type) << 5;
+ if (value < 24) {
+ header |= static_cast<uint8_t>(value);
+ } else if (value < 0x100) {
+ header |= 24;
+ wState = writeBytes(wState, value, 1);
+ } else if (value < 0x10000) {
+ header |= 25;
+ wState = writeBytes(wState, value, 2);
+ } else if (value < 0x100000000) {
+ header |= 26;
+ wState = writeBytes(wState, value, 4);
+ } else {
+ header |= 27;
+ wState = writeBytes(wState, value, 8);
+ }
+ return wState;
+}
+
+bool checkUTF8Copy(const char* begin, const char* const end, uint8_t* out) {
+ uint32_t multi_byte_length = 0;
+ while (begin != end) {
+ if (multi_byte_length) {
+ // parsing multi byte character - must start with 10xxxxxx
+ --multi_byte_length;
+ if ((*begin & 0xc0) != 0x80) return false;
+ } else if (!((*begin) & 0x80)) {
+ // 7bit character -> nothing to be done
+ } else {
+ // msb is set and we were not parsing a multi byte character
+ // so this must be a header byte
+ char c = *begin << 1;
+ while (c & 0x80) {
+ ++multi_byte_length;
+ c <<= 1;
+ }
+ // headers of the form 10xxxxxx are not allowed
+ if (multi_byte_length < 1) return false;
+ // chars longer than 4 bytes are not allowed (multi_byte_length does not count the
+ // header thus > 3
+ if (multi_byte_length > 3) return false;
+ }
+ if (out) *out++ = *reinterpret_cast<const uint8_t*>(begin++);
+ }
+ // if the string ends in the middle of a multi byte char it is invalid
+ if (multi_byte_length) return false;
+ return true;
+}
+
+} // namespace support
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
diff --git a/confirmationui/support/src/confirmationui_utils.cpp b/confirmationui/support/src/confirmationui_utils.cpp
new file mode 100644
index 000000000..708f0d56a
--- /dev/null
+++ b/confirmationui/support/src/confirmationui_utils.cpp
@@ -0,0 +1,39 @@
+/*
+**
+** Copyright 2017, 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 <android/hardware/confirmationui/support/confirmationui_utils.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace support {
+
+bool operator==(const ByteBufferProxy& lhs, const ByteBufferProxy& rhs) {
+ if (lhs.size() == rhs.size()) {
+ auto lhsi = lhs.begin();
+ auto rhsi = rhs.begin();
+ while (lhsi != lhs.end()) {
+ if (*lhsi++ != *rhsi++) return false;
+ }
+ }
+ return true;
+}
+
+} // namespace support
+} // namespace confirmationui
+} // namespace hardware
+} // namespace android
diff --git a/confirmationui/support/test/android_cbor_test.cpp b/confirmationui/support/test/android_cbor_test.cpp
new file mode 100644
index 000000000..4a5a3628c
--- /dev/null
+++ b/confirmationui/support/test/android_cbor_test.cpp
@@ -0,0 +1,197 @@
+/*
+**
+** Copyright 2017, 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 <android/hardware/confirmationui/support/cbor.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <iomanip>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+using namespace android::hardware::confirmationui::support;
+
+uint8_t testVector[] = {
+ 0xA4, 0x63, 0x6B, 0x65, 0x79, 0x65, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x63, 0x6B, 0x65, 0x79, 0x4D,
+ 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x31, 0x30, 0x00, 0x04, 0x07, 0x1B,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x07, 0x1A, 0xFD, 0x49, 0x8C, 0xFF,
+ 0xFF, 0x82, 0x69, 0xE2, 0x99, 0xA8, 0xE2, 0x9A, 0x96, 0xE2, 0xB6, 0x96, 0x59, 0x01, 0x91, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00,
+};
+
+// 400 'a's and a '\0'
+constexpr char fourHundredAs[] =
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaa";
+
+WriteState writeTest(WriteState state) {
+ return write(state, //
+ map( //
+ pair(text("key"), text("value")), //
+ pair(text("key"), bytes("100101010010")), //
+ pair(4, 7), //
+ pair((UINT64_C(1) << 62), INT64_C(-2000000000000000)) //
+ ), //
+ arr(text("♨⚖ⶖ"), bytes(fourHundredAs)));
+}
+
+TEST(Cbor, FeatureTest) {
+ uint8_t buffer[0x1000];
+ WriteState state(buffer);
+ state = writeTest(state);
+ ASSERT_EQ(sizeof(testVector), size_t(state.data_ - buffer));
+ ASSERT_EQ(Error::OK, state.error_);
+ ASSERT_EQ(0, memcmp(buffer, testVector, sizeof(testVector)));
+}
+
+// Test if in all write cases an out of data error is correctly propagated and we don't
+// write beyond the end of the buffer.
+TEST(Cbor, BufferTooShort) {
+ uint8_t buffer[0x1000];
+ for (size_t s = 1; s < sizeof(testVector); ++s) {
+ memset(buffer, 0x22, 0x1000); // 0x22 is not in the testVector
+ WriteState state(buffer, s);
+ state = writeTest(state);
+ for (size_t t = s; t < 0x1000; ++t) {
+ ASSERT_EQ(0x22, buffer[t]); // check if a canary has been killed
+ }
+ ASSERT_EQ(Error::OUT_OF_DATA, state.error_);
+ }
+}
+
+TEST(Cbor, MalformedUTF8Test_Stray) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ char malformed[] = {char(0x80), 0};
+ state = write(state, text(malformed));
+ ASSERT_EQ(Error::MALFORMED_UTF8, state.error_);
+}
+
+TEST(Cbor, MalformendUTF8Test_StringEndsMidMultiByte) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ char malformed[] = {char(0xc0), 0};
+ state = write(state, text(malformed));
+ ASSERT_EQ(Error::MALFORMED_UTF8, state.error_);
+}
+
+TEST(Cbor, UTF8Test_TwoBytes) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ char neat[] = {char(0xc3), char(0x82), 0};
+ state = write(state, text(neat));
+ ASSERT_EQ(Error::OK, state.error_);
+}
+
+TEST(Cbor, UTF8Test_ThreeBytes) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ char neat[] = {char(0xe3), char(0x82), char(0x82), 0};
+ state = write(state, text(neat));
+ ASSERT_EQ(Error::OK, state.error_);
+}
+
+TEST(Cbor, UTF8Test_FourBytes) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ char neat[] = {char(0xf3), char(0x82), char(0x82), char(0x82), 0};
+ state = write(state, text(neat));
+ ASSERT_EQ(Error::OK, state.error_);
+}
+
+TEST(Cbor, MalformendUTF8Test_CharacterTooLong) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ char malformed[] = {char(0xf8), char(0x82), char(0x82), char(0x82), char(0x82), 0};
+ state = write(state, text(malformed));
+ ASSERT_EQ(Error::MALFORMED_UTF8, state.error_);
+}
+
+TEST(Cbor, MalformendUTF8Test_StringEndsMidMultiByte2) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ char malformed[] = {char(0xc0), char(0x82), char(0x83), 0};
+ state = write(state, text(malformed));
+ ASSERT_EQ(Error::MALFORMED_UTF8, state.error_);
+}
+
+TEST(Cbor, MinimalViableHeaderSizeTest) {
+ uint8_t buffer[20];
+ WriteState state(buffer);
+ state = writeHeader(state, Type::NUMBER, 23);
+ ASSERT_EQ(state.data_ - buffer, 1);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 24);
+ ASSERT_EQ(state.data_ - buffer, 2);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 0xff);
+ ASSERT_EQ(state.data_ - buffer, 2);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 0x100);
+ ASSERT_EQ(state.data_ - buffer, 3);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 0xffff);
+ ASSERT_EQ(state.data_ - buffer, 3);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 0x10000);
+ ASSERT_EQ(state.data_ - buffer, 5);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 0xffffffff);
+ ASSERT_EQ(state.data_ - buffer, 5);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 0x100000000);
+ ASSERT_EQ(state.data_ - buffer, 9);
+
+ state = WriteState(buffer);
+ state = writeHeader(state, Type::NUMBER, 0xffffffffffffffff);
+ ASSERT_EQ(state.data_ - buffer, 9);
+}
diff --git a/confirmationui/support/test/gtest_main.cpp b/confirmationui/support/test/gtest_main.cpp
new file mode 100644
index 000000000..43fc5a343
--- /dev/null
+++ b/confirmationui/support/test/gtest_main.cpp
@@ -0,0 +1,23 @@
+/*
+**
+** Copyright 2017, 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 <gtest/gtest.h>
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/confirmationui/support/test/msg_formatting_test.cpp b/confirmationui/support/test/msg_formatting_test.cpp
new file mode 100644
index 000000000..90ed84cfa
--- /dev/null
+++ b/confirmationui/support/test/msg_formatting_test.cpp
@@ -0,0 +1,128 @@
+/*
+**
+** Copyright 2017, 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 <stddef.h>
+#include <stdint.h>
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#include <android/hardware/confirmationui/support/msg_formatting.h>
+#include <gtest/gtest.h>
+
+using android::hardware::confirmationui::support::Message;
+using android::hardware::confirmationui::support::WriteStream;
+using android::hardware::confirmationui::support::ReadStream;
+using android::hardware::confirmationui::support::PromptUserConfirmationMsg;
+using android::hardware::confirmationui::support::write;
+using ::android::hardware::confirmationui::V1_0::UIOption;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+using ::android::hardware::keymaster::V4_0::HardwareAuthenticatorType;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+
+#ifdef DEBUG_MSG_FORMATTING
+namespace {
+
+char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+std::ostream& hexdump(std::ostream& out, const uint8_t* data, size_t size) {
+ for (size_t i = 0; i < size; ++i) {
+ uint8_t byte = data[i];
+ out << (nibble2hex[0x0F & (byte >> 4)]);
+ out << (nibble2hex[0x0F & byte]);
+ switch (i & 0xf) {
+ case 0xf:
+ out << "\n";
+ break;
+ case 7:
+ out << " ";
+ break;
+ default:
+ out << " ";
+ break;
+ }
+ }
+ return out;
+}
+
+} // namespace
+#endif
+
+TEST(MsgFormattingTest, FeatureTest) {
+ uint8_t buffer[0x1000];
+
+ WriteStream out(buffer);
+ out = unalign(out);
+ out += 4;
+ auto begin = out.pos();
+ out = write(
+ PromptUserConfirmationMsg(), out, hidl_string("Do you?"),
+ hidl_vec<uint8_t>{0x01, 0x02, 0x03}, hidl_string("en"),
+ hidl_vec<UIOption>{UIOption::AccessibilityInverted, UIOption::AccessibilityMagnified});
+
+ ReadStream in(buffer);
+ in = unalign(in);
+ in += 4;
+ hidl_string prompt;
+ hidl_vec<uint8_t> extra;
+ hidl_string locale;
+ hidl_vec<UIOption> uiOpts;
+ bool command_matches;
+ std::tie(in, command_matches, prompt, extra, locale, uiOpts) =
+ read(PromptUserConfirmationMsg(), in);
+ ASSERT_TRUE(in);
+ ASSERT_TRUE(command_matches);
+ ASSERT_EQ(hidl_string("Do you?"), prompt);
+ ASSERT_EQ((hidl_vec<uint8_t>{0x01, 0x02, 0x03}), extra);
+ ASSERT_EQ(hidl_string("en"), locale);
+ ASSERT_EQ(
+ (hidl_vec<UIOption>{UIOption::AccessibilityInverted, UIOption::AccessibilityMagnified}),
+ uiOpts);
+
+#ifdef DEBUG_MSG_FORMATTING
+ hexdump(std::cout, buffer, 100) << std::endl;
+#endif
+
+ // The following assertions check that the hidl_[vec|string] types are in fact read in place,
+ // and no copying occurs. Copying results in heap allocation which we intend to avoid.
+ ASSERT_EQ(8, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(prompt.c_str())) - begin);
+ ASSERT_EQ(20, extra.data() - begin);
+ ASSERT_EQ(27, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(locale.c_str())) - begin);
+ ASSERT_EQ(40, reinterpret_cast<uint8_t*>(uiOpts.data()) - begin);
+}
+
+TEST(MsgFormattingTest, HardwareAuthTokenTest) {
+ uint8_t buffer[0x1000];
+
+ HardwareAuthToken expected, actual;
+ expected.authenticatorId = 0xa1a3a4a5a6a7a8;
+ expected.authenticatorType = HardwareAuthenticatorType::NONE;
+ expected.challenge = 0xb1b2b3b4b5b6b7b8;
+ expected.userId = 0x1122334455667788;
+ expected.timestamp = 0xf1f2f3f4f5f6f7f8;
+ expected.mac =
+ hidl_vec<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
+
+ WriteStream out(buffer);
+ out = write(Message<HardwareAuthToken>(), out, expected);
+ ReadStream in(buffer);
+ std::tie(in, actual) = read(Message<HardwareAuthToken>(), in);
+ ASSERT_EQ(expected, actual);
+}