From b70f2b2521f518fe85ffd03948ac52948187d306 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Tue, 24 Oct 2017 10:22:43 -0700 Subject: [PATCH 1/3] ConfirmationUI HAL definition HAL definition for high assurance confirmation providers. High assurance confirmation providers allow relying parties to prompt the user for confirming a short piece of information. If the user confirms, the result is a signed message indicating that the user has seen the message. For a high assurance confirmation provider this must also be true if Android and the Linux kernel are compromised. Bug: 63928580 Test: VTS tests in the following commit Change-Id: I72017b39c01b4333d0146c648637a19fafcb7278 --- confirmationui/1.0/Android.bp | 27 +++++ .../1.0/IConfirmationResultCallback.hal | 61 ++++++++++ confirmationui/1.0/IConfirmationUI.hal | 81 ++++++++++++++ confirmationui/1.0/types.hal | 104 ++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 confirmationui/1.0/Android.bp create mode 100644 confirmationui/1.0/IConfirmationResultCallback.hal create mode 100644 confirmationui/1.0/IConfirmationUI.hal create mode 100644 confirmationui/1.0/types.hal diff --git a/confirmationui/1.0/Android.bp b/confirmationui/1.0/Android.bp new file mode 100644 index 0000000000..21acecba7e --- /dev/null +++ b/confirmationui/1.0/Android.bp @@ -0,0 +1,27 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.confirmationui@1.0", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "IConfirmationResultCallback.hal", + "IConfirmationUI.hal", + ], + interfaces: [ + "android.hardware.keymaster@4.0", + "android.hidl.base@1.0", + ], + types: [ + "MessageSize", + "ResponseCode", + "TestKeyBits", + "TestModeCommands", + "UIOption", + ], + gen_java: false, +} + diff --git a/confirmationui/1.0/IConfirmationResultCallback.hal b/confirmationui/1.0/IConfirmationResultCallback.hal new file mode 100644 index 0000000000..03a10cfe6c --- /dev/null +++ b/confirmationui/1.0/IConfirmationResultCallback.hal @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package android.hardware.confirmationui@1.0; + +/** + * Callback interface passed to IConfirmationUI::promptUserConfirmation(). + * Informs the caller about the result of the prompt operation. + */ +interface IConfirmationResultCallback { + /** + * This callback is called by the confirmation provider when it stops prompting the user. + * Iff the user has confirmed the prompted text, error is ErrorCode::OK and the + * parameters formattedMessage and confirmationToken hold the values needed to request + * a signature from keymaster. + * In all other cases formattedMessage and confirmationToken must be of length 0. + * + * @param error - OK: IFF the user has confirmed the prompt. + * - Canceled: If the user has pressed the cancel button. + * - Aborted: If IConfirmationUI::abort() was called. + * - SystemError: If an unexpected System error occurred that prevented the TUI + * from being shut down gracefully. + * @param formattedMessage holds the prompt text and extra data. + * The message is CBOR (RFC 7049) encoded and has the following format: + * CBOR_MAP{ "prompt", , "extra", } + * The message is a CBOR encoded map (type 5) with the keys + * "prompt" and "extra". The keys are encoded as CBOR text string + * (type 3). The value is encoded as CBOR text string + * (type 3), and the value is encoded as CBOR byte string + * (type 2). The map must have exactly one key value pair for each of + * the keys "prompt" and "extra". Other keys are not allowed. + * The value of "prompt" is given by the proptText argument to + * IConfirmationUI::promptUserConfirmation and must not be modified + * by the implementation. + * The value of "extra" is given by the extraData argument to + * IConfirmationUI::promptUserConfirmation and must not be modified + * or interpreted by the implementation. + * + * @param confirmationToken a 32-byte HMAC-SHA256 value, computed over + * "confirmation token" || + * i.e. the literal UTF-8 encoded string "confirmation token", without + * the "", concatenated with the formatted message as returned in the + * formattedMessage argument. The HMAC is keyed with a 256-bit secret + * which is shared with Keymaster. In test mode the test key MUST be + * used (see types.hal TestModeCommands and TestKeyBits). + */ + result(ResponseCode error, vec formattedMessage, vec confirmationToken); +}; diff --git a/confirmationui/1.0/IConfirmationUI.hal b/confirmationui/1.0/IConfirmationUI.hal new file mode 100644 index 0000000000..db8055d16c --- /dev/null +++ b/confirmationui/1.0/IConfirmationUI.hal @@ -0,0 +1,81 @@ +/* + * 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. + */ + +package android.hardware.confirmationui@1.0; + +import android.hardware.keymaster@4.0::HardwareAuthToken; +import IConfirmationResultCallback; + +interface IConfirmationUI { + /** + * Asynchronously initiates a confirmation UI dialog prompting the user to confirm a given text. + * The TUI prompt must be implemented in such a way that a positive response indicates with + * high confidence that a user has seen the given prompt text even if the Android framework + * including the kernel was compromised. + * + * @param resultCB Implementation of IResultCallback. Used by the implementation to report + * the result of the current pending user prompt. + * + * @param promptText UTF-8 encoded string which is to be presented to the user. + * + * @param extraData A binary blob that must be included in the formatted output message as is. + * It is opaque to the implementation. Implementations must neither interpret + * nor modify the content. + * + * @param locale String specifying the locale that must be used by the TUI dialog. The string + * is an IETF BCP 47 tag. + * + * @param uiOptions A set of uiOptions manipulating how the confirmation prompt is displayed. + * Refer to UIOption in types.hal for possible options. + * + * @return error - OK: IFF the dialog was successfully started. In this case, and only in this + * case, the implementation must, eventually, call the callback to + * indicate completion. + * - OperationPending: Is returned when the confirmation provider is currently + * in use. + * - SystemError: An error occurred trying to communicate with the confirmation + * provider (e.g. trusted app). + * - UIError: The confirmation provider encountered an issue with displaying + * the prompt text to the user. + */ + promptUserConfirmation(IConfirmationResultCallback resultCB, string promptText, + vec extraData, string locale, vec uiOptions) + generates(ResponseCode error); + + /** + * DeliverSecureInput is used by the framework to deliver a secure input event to the + * confirmation provider. + * + * VTS test mode: + * This function can be used to test certain code paths non-interactively. See TestModeCommands + * in types.hal for details. + * + * @param secureInputToken An authentication token as generated by Android authentication + * providers. + * + * @return error - Ignored: Unless used for testing (See TestModeCommands). + */ + deliverSecureInputEvent(HardwareAuthToken secureInputToken) + generates(ResponseCode error); + + /** + * Aborts a pending user prompt. This allows the framework to gracefully end a TUI dialog. + * If a TUI operation was pending the corresponding call back is informed with + * ErrorCode::Aborted. + */ + abort(); +}; + diff --git a/confirmationui/1.0/types.hal b/confirmationui/1.0/types.hal new file mode 100644 index 0000000000..fd7ae6afdd --- /dev/null +++ b/confirmationui/1.0/types.hal @@ -0,0 +1,104 @@ +/* + * 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. + */ + +package android.hardware.confirmationui@1.0; + +/** + * UI modification options. + */ +enum UIOption : uint32_t { + /** Accessibility: Requests color inverted style. */ + AccessibilityInverted = 0, + /** Accessibility: Requests magnified style. */ + AccessibilityMagnified = 1, +}; + +/** + * Codes returned by ConfirmationUI API calls. + */ +enum ResponseCode : uint32_t { + /** API call succeeded or the user gave approval (result callback). */ + OK = 0, + /** The user canceled the TUI (result callback). */ + Canceled = 1, + /** IConfirmationUI::abort() was called. (result callback). */ + Aborted = 2, + /** Cannot start another prompt. */ + OperationPending = 3, + /** IConfirmationUI::deliverSecureInputEvent call was ingored. */ + Ignored = 4, + /** An unexpected system error occured. */ + SystemError = 5, + /** Returned by an unimplemented API call. */ + Unimplemented = 6, + /** + * This is returned when an error is diagnosed that should have been + * caught by earlier input sanitization. Should never be seen in production. + */ + Unexpected = 7, + /** General UI error. */ + UIError = 0x10000, + UIErrorMissingGlyph, + /** + * The implementation must return this error code on promptUserConfirmation if the + * resulting formatted message does not fit into MessageSize::MAX bytes. It is + * advised that the implementation formats the message upon receiving this API call to + * be able to diagnose this syndrome. + */ + UIErrorMessageTooLong, + UIErrorMalformedUTF8Encoding, +}; + +/** + * This defines the maximum message size. This indirectly limits the size of the prompt text + * and the extra data that can be passed to the confirmation UI. The prompt text and extra data + * must fit in to this size including CBOR header information. + */ +enum MessageSize : uint32_t { MAX = 0x1800 }; + +/** + * The test key is 32byte word with all bytes set to TestKeyBits::BYTE. + */ +enum TestKeyBits: uint8_t { BYTE = 0xA5 }; + +/** + * Test mode commands. + * + * IConfirmationUI::deliverSecureInputEvent can be used to test certain code paths. + * To that end, the caller passes an auth token that has an HMAC keyed with the test key + * (see TestKeyBits in types.hal). Implementations first check the HMAC against test key. + * If the test key produces a matching HMAC, the implementation evaluates the challenge field + * of the auth token against the values defined in TestModeCommand. + * If the command indicates that a confirmation token is to be generated the test key MUST be used + * to generate this confirmation token. + * + * See command code for individual test command descriptions. + */ +enum TestModeCommands: uint64_t { + /** + * Simulates the user pressing the OK button on the UI. If no operation is pending + * ResponseCode::Ignored must be returned. A pending operation is finalized successfully + * see IConfirmationResultCallback::result, however, the test key (see TestKeyBits) MUST be + * used to generate the confirmation token. + */ + OK_EVENT = 0, + /** + * Simulates the user pressing the CANCEL button on the UI. If no operation is pending + * Result::Ignored must be returned. A pending operation is finalized as specified in + * IConfirmationResultCallback.hal. + */ + CANCEL_EVENT = 1, +}; From a9f0fb0db1e62168534c5a27e123e1413b818586 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Fri, 3 Nov 2017 12:18:34 -0700 Subject: [PATCH 2/3] 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 --- confirmationui/support/Android.bp | 51 ++ confirmationui/support/OWNERS | 2 + .../1.0/generic/GenericOperation.h | 188 +++++++ .../hardware/confirmationui/support/cbor.h | 335 +++++++++++++ .../support/confirmationui_utils.h | 213 ++++++++ .../confirmationui/support/msg_formatting.h | 471 ++++++++++++++++++ confirmationui/support/src/cbor.cpp | 111 +++++ .../support/src/confirmationui_utils.cpp | 39 ++ .../support/test/android_cbor_test.cpp | 197 ++++++++ confirmationui/support/test/gtest_main.cpp | 23 + .../support/test/msg_formatting_test.cpp | 128 +++++ 11 files changed, 1758 insertions(+) create mode 100644 confirmationui/support/Android.bp create mode 100644 confirmationui/support/OWNERS create mode 100644 confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h create mode 100644 confirmationui/support/include/android/hardware/confirmationui/support/cbor.h create mode 100644 confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h create mode 100644 confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h create mode 100644 confirmationui/support/src/cbor.cpp create mode 100644 confirmationui/support/src/confirmationui_utils.cpp create mode 100644 confirmationui/support/test/android_cbor_test.cpp create mode 100644 confirmationui/support/test/gtest_main.cpp create mode 100644 confirmationui/support/test/msg_formatting_test.cpp diff --git a/confirmationui/support/Android.bp b/confirmationui/support/Android.bp new file mode 100644 index 0000000000..62156b3419 --- /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 0000000000..335660da3b --- /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 0000000000..a88cd40e32 --- /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 +#include +#include +#include + +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& uiOptions) { + for (auto& o : uiOptions) { + if (o == option) return true; + } + return false; +} + +template +class Operation { + using HMacer = support::HMac; + + public: + Operation() : error_(ResponseCode::Ignored), formattedMessageLength_(0) {} + + ResponseCode init(const Callback& resultCB, const hidl_string& promptText, + const hidl_vec& extraData, const hidl_string& locale, + const hidl_vec& 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(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(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 getMessage() { + hidl_vec result; + if (error_ != ResponseCode::OK) return {}; + result.setToExternal(formattedMessageBuffer_, formattedMessageLength_); + return result; + } + hidl_vec 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 result; + result.setToExternal(confirmationTokenScratchpad_->data(), + confirmationTokenScratchpad_->size()); + return result; + } + + ResponseCode error_; + uint8_t formattedMessageBuffer_[uint32_t(MessageSize::MAX)]; + size_t formattedMessageLength_; + NullOr> confirmationTokenScratchpad_; + Callback resultCB_; + typename TimeStamper::TimeStamp startTime_; + NullOr> 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 0000000000..f5814d4f05 --- /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 +#include +#include + +namespace android { +namespace hardware { +namespace confirmationui { +namespace support { + +template +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 +struct MapElement { + const Key& key_; + const Value& value_; + MapElement(const Key& key, const Value& value) : key_(key), value_(value) {} +}; + +template +struct Array; + +template +struct Array { + const Head& head_; + Array 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 +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 +StringBuffer text(const char (&str)[size]) { + if (size > 0) return StringBuffer(str, size - 1); + return StringBuffer(str, size); +} + +/** + * As opposed to text(const char (&str)[size] this function does not strips the last character. + */ +template +StringBuffer text_keep_last(const char (&str)[size]) { + return StringBuffer(str, size); +} + +template +auto getData(const T& v) -> decltype(v.data()) { + return v.data(); +} + +template +auto getData(const T& v) -> decltype(v.c_str()) { + return v.c_str(); +} + +template +auto text(const T& str) -> StringBuffer, TextStr> { + return StringBuffer, TextStr>(getData(str), str.size()); +} + +inline StringBuffer text(const char* str, size_t size) { + return StringBuffer(str, size); +} + +template +StringBuffer bytes(const T (&str)[size]) { + return StringBuffer(str, size); +} + +template +StringBuffer bytes(const T* str, size_t size) { + return StringBuffer(str, size); +} + +template +auto bytes(const T& str) -> StringBuffer, ByteStr> { + return StringBuffer, ByteStr>(getData(str), str.size()); +} + +template +struct Map; + +template +struct Map, Tail...> { + const MapElement& head_; + Map tail_; + Map(const MapElement& head, const Tail&... tail) + : head_(head), tail_(tail...) {} + constexpr size_t size() const { return sizeof...(Tail) + 1; }; +}; + +template <> +struct Map<> {}; + +template +Map...> map(const MapElement&... elements) { + return Map...>(elements...); +} + +template +Array arr(const Elements&... elements) { + return Array(elements...); +} + +template +MapElement pair(const Key& k, const Value& v) { + return MapElement(k, v); +} + +template +struct getUnsignedType; + +template <> +struct getUnsignedType { + typedef uint8_t type; +}; +template <> +struct getUnsignedType { + typedef uint16_t type; +}; +template <> +struct getUnsignedType { + typedef uint32_t type; +}; +template <> +struct getUnsignedType { + typedef uint64_t type; +}; + +template +using Unsigned = typename getUnsignedType::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 + 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 +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 +WriteState write(WriteState wState, const StringBuffer& 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 +WriteState write(WriteState wState, const StringBuffer& 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