diff options
author | Max Bires <jbires@google.com> | 2020-04-13 23:38:10 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-04-13 23:38:10 +0000 |
commit | fc33ae7c91f087ac78ecb082ba8605eab4c1d552 (patch) | |
tree | 1cd94a5a1a2ed76645790441635ca97f5f8ae667 | |
parent | befe16d3512525f9f1557b348918ca90e09c7fd6 (diff) | |
parent | a7b145a6a5d05b89e9769ffea84d1068ef32bd4e (diff) | |
download | libcppbor-fc33ae7c91f087ac78ecb082ba8605eab4c1d552.tar.gz |
Adding the initial commit for libcppbor am: a7b145a6a5
Change-Id: I983e47204c26a3d76e317447be9f33e749a7e9ed
-rw-r--r-- | Android.bp | 59 | ||||
-rw-r--r-- | CONTRIBUTING.md | 28 | ||||
-rw-r--r-- | LICENSE | 202 | ||||
-rw-r--r-- | README.md | 219 | ||||
-rw-r--r-- | include/cppbor/cppbor.h | 827 | ||||
-rw-r--r-- | include/cppbor/cppbor_parse.h | 133 | ||||
-rw-r--r-- | src/cppbor.cpp | 224 | ||||
-rw-r--r-- | src/cppbor_parse.cpp | 351 | ||||
-rw-r--r-- | tests/cppbor_test.cpp | 1459 |
9 files changed, 3502 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..8a049a1 --- /dev/null +++ b/Android.bp @@ -0,0 +1,59 @@ +// Copyright 2019 Google LLC +// +// 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 +// +// https://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: "libcppbor", + vendor_available: true, + host_supported: true, + srcs: [ + "src/cppbor.cpp", + "src/cppbor_parse.cpp", + ], + export_include_dirs: [ + "include/cppbor", + ], + shared_libs: [ + "libbase", + ] +} + +cc_test { + name: "cppbor_test", + srcs: [ + "tests/cppbor_test.cpp" + ], + shared_libs: [ + "libcppbor", + "libbase", + ], + static_libs: [ + "libgmock", + ], + test_suites: ["general-tests"], +} + +cc_test_host { + name: "cppbor_host_test", + srcs: [ + "tests/cppbor_test.cpp" + ], + shared_libs: [ + "libcppbor", + "libbase", + ], + static_libs: [ + "libgmock", + ], + test_suites: ["general-tests"], +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..db177d4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to <https://cla.developers.google.com/> to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b23cc0f --- /dev/null +++ b/README.md @@ -0,0 +1,219 @@ +LibCppBor: A Modern C++ CBOR Parser and Generator +============================================== + +LibCppBor provides a natural and easy-to-use syntax for constructing and +parsing CBOR messages. It does not (yet) support all features of +CBOR, nor (yet) support validation against CDDL schemata, though both +are planned. CBOR features that aren't supported include: + +* Indefinite length values +* Semantic tagging +* Floating point + +LibCppBor requires C++-17. + +## CBOR representation + +LibCppBor represents CBOR data items as instances of the `Item` class or, +more precisely, as instances of subclasses of `Item`, since `Item` is a +pure interface. The subclasses of `Item` correspond almost one-to-one +with CBOR major types, and are named to match the CDDL names to which +they correspond. They are: + +* `Uint` corresponds to major type 0, and can hold unsigned integers + up through (2^64 - 1). +* `Nint` corresponds to major type 1. It can only hold values from -1 + to -(2^63 - 1), since it's internal representation is an int64_t. + This can be fixed, but it seems unlikely that applications will need + the omitted range from -(2^63) to (2^64 - 1), since it's + inconvenient to represent them in many programming languages. +* `Int` is an abstract base of `Uint` and `Nint` that facilitates + working with all signed integers representable with int64_t. +* `Bstr` corresponds to major type 2, a byte string. +* `Tstr` corresponds to major type 3, a text string. +* `Array` corresponds to major type 4, an Array. It holds a + variable-length array of `Item`s. +* `Map` corresponds to major type 5, a Map. It holds a + variable-length array of pairs of `Item`s. +* `Simple` corresponds to major type 7. It's an abstract class since + items require more specific type. +* `Bool` is the only currently-implemented subclass of `Simple`. + +Note that major type 6, semantic tag, is not yet implemented. + +In practice, users of LibCppBor will rarely use most of these classes +when generating CBOR encodings. This is because LibCppBor provides +straightforward conversions from the obvious normal C++ types. +Specifically, the following conversions are provided in appropriate +contexts: + +* Signed and unsigned integers convert to `Uint` or `Nint`, as + appropriate. +* `std::string`, `std::string_view`, `const char*` and + `std::pair<char iterator, char iterator>` convert to `Tstr`. +* `std::vector<uint8_t>`, `std::pair<uint8_t iterator, uint8_t + iterator>` and `std::pair<uint8_t*, size_t>` convert to `Bstr`. +* `bool` converts to `Bool`. + +## CBOR generation + +### Complete tree generation + +The set of `encode` methods in `Item` provide the interface for +producing encoded CBOR. The basic process for "complete tree" +generation (as opposed to "incremental" generation, which is discussed +below) is to construct an `Item` which models the data to be encoded, +and then call one of the `encode` methods, whichever is convenient for +the encoding destination. A trivial example: + +``` +cppbor::Uint val(0); +std::vector<uint8_t> encoding = val.encode(); +``` + + It's relatively rare that single values are encoded as above. More often, the + "root" data item will be an `Array` or `Map` which contains a more complex structure.For example + : + +``` using cppbor::Map; +using cppbor::Array; + +std::vector<uint8_t> vec = // ... + Map val("key1", Array(Map("key_a", 99 "key_b", vec), "foo"), "key2", true); +std::vector<uint8_t> encoding = val.encode(); +``` + +This creates a map with two entries, with `Tstr` keys "Outer1" and +"Outer2", respectively. The "Outer1" entry has as its value an +`Array` containing a `Map` and a `Tstr`. The "Outer2" entry has a +`Bool` value. + +This example demonstrates how automatic conversion of C++ types to +LibCppBor `Item` subclass instances is done. Where the caller provides a +C++ or C string, a `Tstr` entry is added. Where the caller provides +an integer literal or variable, a `Uint` or `Nint` is added, depending +on whether the value is positive or negative. + +As an alternative, a more fluent-style API is provided for building up +structures. For example: + +``` +using cppbor::Map; +using cppbor::Array; + +std::vector<uint8_t> vec = // ... + Map val(); +val.add("key1", Array().add(Map().add("key_a", 99).add("key_b", vec)).add("foo")).add("key2", true); +std::vector<uint8_t> encoding = val.encode(); +``` + + An advantage of this interface over the constructor - + based creation approach above is that it need not be done all at once. + The `add` methods return a reference to the object added to to allow calls to be chained, + but chaining is not necessary; calls can be made + sequentially, as the data to add is available. + +#### `encode` methods + +There are several variations of `Item::encode`, all of which +accomplish the same task but output the encoded data in different +ways, and with somewhat different performance characteristics. The +provided options are: + +* `bool encode(uint8\_t** pos, const uint8\_t* end)` encodes into the + buffer referenced by the range [`*pos`, end). `*pos` is moved. If + the encoding runs out of buffer space before finishing, the method + returns false. This is the most efficient way to encode, into an + already-allocated buffer. +* `void encode(EncodeCallback encodeCallback)` calls `encodeCallback` + for each encoded byte. It's the responsibility of the implementor + of the callback to behave safely in the event that the output buffer + (if applicable) is exhausted. This is less efficient than the prior + method because it imposes an additional function call for each byte. +* `template </*...*/> void encode(OutputIterator i)` + encodes into the provided iterator. SFINAE ensures that the + template doesn't match for non-iterators. The implementation + actually uses the callback-based method, plus has whatever overhead + the iterator adds. +* `std::vector<uint8_t> encode()` creates a new std::vector, reserves + sufficient capacity to hold the encoding, and inserts the encoded + bytes with a std::pushback_iterator and the previous method. +* `std::string toString()` does the same as the previous method, but + returns a string instead of a vector. + +### Incremental generation + +Incremental generation requires deeper understanding of CBOR, because +the library can't do as much to ensure that the output is valid. The +basic tool for intcremental generation is the `encodeHeader` +function. There are two variations, one which writes into a buffer, +and one which uses a callback. Both simply write out the bytes of a +header. To construct the same map as in the above examples, +incrementally, one might write: + +``` +using namespace cppbor; // For example brevity + +std::vector encoding; +auto iter = std::back_inserter(result); +encodeHeader(MAP, 2 /* # of map entries */, iter); +std::string s = "key1"; +encodeHeader(TSTR, s.size(), iter); +std::copy(s.begin(), s.end(), iter); +encodeHeader(ARRAY, 2 /* # of array entries */, iter); +Map().add("key_a", 99).add("key_b", vec).encode(iter) +s = "foo"; +encodeHeader(TSTR, foo.size(), iter); +std::copy(s.begin(), s.end(), iter); +s = "key2"; +encodeHeader(TSTR, foo.size(), iter); +std::copy(s.begin(), s.end(), iter); +encodeHeader(SIMPLE, TRUE, iter); +``` + +As the above example demonstrates, the styles can be mixed -- Note the +creation and encoding of the inner Map using the fluent style. + +## Parsing + +LibCppBor also supports parsing of encoded CBOR data, with the same +feature set as encoding. There are two basic approaches to parsing, +"full" and "stream" + +### Full parsing + +Full parsing means completely parsing a (possibly-compound) data +item from a byte buffer. The `parse` functions that do not take a +`ParseClient` pointer do this. They return a `ParseResult` which is a +tuple of three values: + +* std::unique_ptr<Item> that points to the parsed item, or is nullptr + if there was a parse error. +* const uint8_t* that points to the byte after the end of the decoded + item, or to the first unparseable byte in the event of an error. +* std::string that is empty on success or contains an error message if + a parse error occurred. + +Assuming a successful parse, you can then use `Item::type()` to +discover the type of the parsed item (e.g. MAP), and then use the +appropriate `Item::as*()` method (e.g. `Item::asMap()`) to get a +pointer to an interface which allows you to retrieve specific values. + +### Stream parsing + +Stream parsing is more complex, but more flexible. To use +StreamParsing, you must create your own subclass of `ParseClient` and +call one of the `parse` functions that accepts it. See the +`ParseClient` methods docstrings for details. + +One unusual feature of stream parsing is that the `ParseClient` +callback methods not only provide the parsed Item, but also pointers +to the portion of the buffer that encode that Item. This is useful +if, for example, you want to find an element inside of a structure, +and then copy the encoding of that sub-structure, without bothering to +parse the rest. + +The full parser is implemented with the stream parser. + +### Disclaimer +This is not an officially supported Google product diff --git a/include/cppbor/cppbor.h b/include/cppbor/cppbor.h new file mode 100644 index 0000000..8fb7cc6 --- /dev/null +++ b/include/cppbor/cppbor.h @@ -0,0 +1,827 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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 <cstdint> +#include <functional> +#include <iterator> +#include <memory> +#include <numeric> +#include <string> +#include <vector> + +namespace cppbor { + +enum MajorType : uint8_t { + UINT = 0 << 5, + NINT = 1 << 5, + BSTR = 2 << 5, + TSTR = 3 << 5, + ARRAY = 4 << 5, + MAP = 5 << 5, + SEMANTIC = 6 << 5, + SIMPLE = 7 << 5, +}; + +enum SimpleType { + BOOLEAN, + NULL_T, // Only two supported, as yet. +}; + +enum SpecialAddlInfoValues : uint8_t { + FALSE = 20, + TRUE = 21, + NULL_V = 22, + ONE_BYTE_LENGTH = 24, + TWO_BYTE_LENGTH = 25, + FOUR_BYTE_LENGTH = 26, + EIGHT_BYTE_LENGTH = 27, +}; + +class Item; +class Uint; +class Nint; +class Int; +class Tstr; +class Bstr; +class Simple; +class Bool; +class Array; +class Map; +class Null; +class Semantic; + +/** + * Returns the size of a CBOR header that contains the additional info value addlInfo. + */ +size_t headerSize(uint64_t addlInfo); + +/** + * Encodes a CBOR header with the specified type and additional info into the range [pos, end). + * Returns a pointer to one past the last byte written, or nullptr if there isn't sufficient space + * to write the header. + */ +uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end); + +using EncodeCallback = std::function<void(uint8_t)>; + +/** + * Encodes a CBOR header with the specified type and additional info, passing each byte in turn to + * encodeCallback. + */ +void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback); + +/** + * Encodes a CBOR header witht he specified type and additional info, writing each byte to the + * provided OutputIterator. + */ +template <typename OutputIterator, + typename = std::enable_if_t<std::is_base_of_v< + std::output_iterator_tag, + typename std::iterator_traits<OutputIterator>::iterator_category>>> +void encodeHeader(MajorType type, uint64_t addlInfo, OutputIterator iter) { + return encodeHeader(type, addlInfo, [&](uint8_t v) { *iter++ = v; }); +} + +/** + * Item represents a CBOR-encodeable data item. Item is an abstract interface with a set of virtual + * methods that allow encoding of the item or conversion to the appropriate derived type. + */ +class Item { + public: + virtual ~Item() {} + + /** + * Returns the CBOR type of the item. + */ + virtual MajorType type() const = 0; + + // These methods safely downcast an Item to the appropriate subclass. + virtual const Int* asInt() const { return nullptr; } + virtual const Uint* asUint() const { return nullptr; } + virtual const Nint* asNint() const { return nullptr; } + virtual const Tstr* asTstr() const { return nullptr; } + virtual const Bstr* asBstr() const { return nullptr; } + virtual const Simple* asSimple() const { return nullptr; } + virtual const Map* asMap() const { return nullptr; } + virtual const Array* asArray() const { return nullptr; } + virtual const Semantic* asSemantic() const { return nullptr; } + + /** + * Returns true if this is a "compound" item, i.e. one that contains one or more other items. + */ + virtual bool isCompound() const { return false; } + + bool operator==(const Item& other) const&; + bool operator!=(const Item& other) const& { return !(*this == other); } + + /** + * Returns the number of bytes required to encode this Item into CBOR. Note that if this is a + * complex Item, calling this method will require walking the whole tree. + */ + virtual size_t encodedSize() const = 0; + + /** + * Encodes the Item into buffer referenced by range [*pos, end). Returns a pointer to one past + * the last position written. Returns nullptr if there isn't enough space to encode. + */ + virtual uint8_t* encode(uint8_t* pos, const uint8_t* end) const = 0; + + /** + * Encodes the Item by passing each encoded byte to encodeCallback. + */ + virtual void encode(EncodeCallback encodeCallback) const = 0; + + /** + * Clones the Item + */ + virtual std::unique_ptr<Item> clone() const = 0; + + /** + * Encodes the Item into the provided OutputIterator. + */ + template <typename OutputIterator, + typename = typename std::iterator_traits<OutputIterator>::iterator_category> + void encode(OutputIterator i) const { + return encode([&](uint8_t v) { *i++ = v; }); + } + + /** + * Encodes the Item into a new std::vector<uint8_t>. + */ + std::vector<uint8_t> encode() const { + std::vector<uint8_t> retval; + retval.reserve(encodedSize()); + encode(std::back_inserter(retval)); + return retval; + } + + /** + * Encodes the Item into a new std::string. + */ + std::string toString() const { + std::string retval; + retval.reserve(encodedSize()); + encode([&](uint8_t v) { retval.push_back(v); }); + return retval; + } + + /** + * Encodes only the header of the Item. + */ + inline uint8_t* encodeHeader(uint64_t addlInfo, uint8_t* pos, const uint8_t* end) const { + return ::cppbor::encodeHeader(type(), addlInfo, pos, end); + } + + /** + * Encodes only the header of the Item. + */ + inline void encodeHeader(uint64_t addlInfo, EncodeCallback encodeCallback) const { + ::cppbor::encodeHeader(type(), addlInfo, encodeCallback); + } +}; + +/** + * Int is an abstraction that allows Uint and Nint objects to be manipulated without caring about + * the sign. + */ +class Int : public Item { + public: + bool operator==(const Int& other) const& { return value() == other.value(); } + + virtual int64_t value() const = 0; + + const Int* asInt() const override { return this; } +}; + +/** + * Uint is a concrete Item that implements CBOR major type 0. + */ +class Uint : public Int { + public: + static constexpr MajorType kMajorType = UINT; + + explicit Uint(uint64_t v) : mValue(v) {} + + bool operator==(const Uint& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + const Uint* asUint() const override { return this; } + + size_t encodedSize() const override { return headerSize(mValue); } + + int64_t value() const override { return mValue; } + uint64_t unsignedValue() const { return mValue; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(mValue, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue, encodeCallback); + } + + virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Uint>(mValue); } + + private: + uint64_t mValue; +}; + +/** + * Nint is a concrete Item that implements CBOR major type 1. + + * Note that it is incapable of expressing the full range of major type 1 values, becaue it can only + * express values that fall into the range [std::numeric_limits<int64_t>::min(), -1]. It cannot + * express values in the range [std::numeric_limits<int64_t>::min() - 1, + * -std::numeric_limits<uint64_t>::max()]. + */ +class Nint : public Int { + public: + static constexpr MajorType kMajorType = NINT; + + explicit Nint(int64_t v); + + bool operator==(const Nint& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + const Nint* asNint() const override { return this; } + size_t encodedSize() const override { return headerSize(addlInfo()); } + + int64_t value() const override { return mValue; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(addlInfo(), pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(addlInfo(), encodeCallback); + } + + virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Nint>(mValue); } + + private: + uint64_t addlInfo() const { return -1ll - mValue; } + + int64_t mValue; +}; + +/** + * Bstr is a concrete Item that implements major type 2. + */ +class Bstr : public Item { + public: + static constexpr MajorType kMajorType = BSTR; + + // Construct from a vector + explicit Bstr(std::vector<uint8_t> v) : mValue(std::move(v)) {} + + // Construct from a string + explicit Bstr(const std::string& v) + : mValue(reinterpret_cast<const uint8_t*>(v.data()), + reinterpret_cast<const uint8_t*>(v.data()) + v.size()) {} + + // Construct from a pointer/size pair + explicit Bstr(const std::pair<const uint8_t*, size_t>& buf) + : mValue(buf.first, buf.first + buf.second) {} + + // Construct from a pair of iterators + template <typename I1, typename I2, + typename = typename std::iterator_traits<I1>::iterator_category, + typename = typename std::iterator_traits<I2>::iterator_category> + explicit Bstr(const std::pair<I1, I2>& pair) : mValue(pair.first, pair.second) {} + + // Construct from an iterator range. + template <typename I1, typename I2, + typename = typename std::iterator_traits<I1>::iterator_category, + typename = typename std::iterator_traits<I2>::iterator_category> + Bstr(I1 begin, I2 end) : mValue(begin, end) {} + + bool operator==(const Bstr& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + const Bstr* asBstr() const override { return this; } + size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::vector<uint8_t>& value() const { return mValue; } + + virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Bstr>(mValue); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::vector<uint8_t> mValue; +}; + +/** + * Bstr is a concrete Item that implements major type 3. + */ +class Tstr : public Item { + public: + static constexpr MajorType kMajorType = TSTR; + + // Construct from a string + explicit Tstr(std::string v) : mValue(std::move(v)) {} + + // Construct from a string_view + explicit Tstr(const std::string_view& v) : mValue(v) {} + + // Construct from a C string + explicit Tstr(const char* v) : mValue(std::string(v)) {} + + // Construct from a pair of iterators + template <typename I1, typename I2, + typename = typename std::iterator_traits<I1>::iterator_category, + typename = typename std::iterator_traits<I2>::iterator_category> + explicit Tstr(const std::pair<I1, I2>& pair) : mValue(pair.first, pair.second) {} + + // Construct from an iterator range + template <typename I1, typename I2, + typename = typename std::iterator_traits<I1>::iterator_category, + typename = typename std::iterator_traits<I2>::iterator_category> + Tstr(I1 begin, I2 end) : mValue(begin, end) {} + + bool operator==(const Tstr& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + const Tstr* asTstr() const override { return this; } + size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::string& value() const { return mValue; } + + virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Tstr>(mValue); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::string mValue; +}; + +/** + * CompoundItem is an abstract Item that provides common functionality for Items that contain other + * items, i.e. Arrays (CBOR type 4) and Maps (CBOR type 5). + */ +class CompoundItem : public Item { + public: + bool operator==(const CompoundItem& other) const&; + + virtual size_t size() const { return mEntries.size(); } + + bool isCompound() const override { return true; } + + size_t encodedSize() const override { + return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(size()), + [](size_t sum, auto& entry) { return sum + entry->encodedSize(); }); + } + + using Item::encode; // Make base versions visible. + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override; + + virtual uint64_t addlInfo() const = 0; + + protected: + std::vector<std::unique_ptr<Item>> mEntries; +}; + +/* + * Array is a concrete Item that implements CBOR major type 4. + * + * Note that Arrays are not copyable. This is because copying them is expensive and making them + * move-only ensures that they're never copied accidentally. If you actually want to copy an Array, + * use the clone() method. + */ +class Array : public CompoundItem { + public: + static constexpr MajorType kMajorType = ARRAY; + + Array() = default; + Array(const Array& other) = delete; + Array(Array&&) = default; + Array& operator=(const Array&) = delete; + Array& operator=(Array&&) = default; + + /** + * Construct an Array from a variable number of arguments of different types. See + * details::makeItem below for details on what types may be provided. In general, this accepts + * all of the types you'd expect and doest the things you'd expect (integral values are addes as + * Uint or Nint, std::string and char* are added as Tstr, bools are added as Bool, etc.). + */ + template <typename... Args, typename Enable> + Array(Args&&... args); + + /** + * Append a single element to the Array, of any compatible type. + */ + template <typename T> + Array& add(T&& v) &; + template <typename T> + Array&& add(T&& v) &&; + + const std::unique_ptr<Item>& operator[](size_t index) const { return mEntries[index]; } + std::unique_ptr<Item>& operator[](size_t index) { return mEntries[index]; } + + MajorType type() const override { return kMajorType; } + const Array* asArray() const override { return this; } + + virtual std::unique_ptr<Item> clone() const override; + + uint64_t addlInfo() const override { return size(); } +}; + +/* + * Map is a concrete Item that implements CBOR major type 5. + * + * Note that Maps are not copyable. This is because copying them is expensive and making them + * move-only ensures that they're never copied accidentally. If you actually want to copy a + * Map, use the clone() method. + */ +class Map : public CompoundItem { + public: + static constexpr MajorType kMajorType = MAP; + + Map() = default; + Map(const Map& other) = delete; + Map(Map&&) = default; + Map& operator=(const Map& other) = delete; + Map& operator=(Map&&) = default; + + /** + * Construct a Map from a variable number of arguments of different types. An even number of + * arguments must be provided (this is verified statically). See details::makeItem below for + * details on what types may be provided. In general, this accepts all of the types you'd + * expect and doest the things you'd expect (integral values are addes as Uint or Nint, + * std::string and char* are added as Tstr, bools are added as Bool, etc.). + */ + template <typename... Args, typename Enable> + Map(Args&&... args); + + /** + * Append a key/value pair to the Map, of any compatible types. + */ + template <typename Key, typename Value> + Map& add(Key&& key, Value&& value) &; + template <typename Key, typename Value> + Map&& add(Key&& key, Value&& value) &&; + + size_t size() const override { + assertInvariant(); + return mEntries.size() / 2; + } + + template <typename Key, typename Enable> + std::pair<std::unique_ptr<Item>&, bool> get(Key key); + + std::pair<const std::unique_ptr<Item>&, const std::unique_ptr<Item>&> operator[]( + size_t index) const { + assertInvariant(); + return {mEntries[index * 2], mEntries[index * 2 + 1]}; + } + + std::pair<std::unique_ptr<Item>&, std::unique_ptr<Item>&> operator[](size_t index) { + assertInvariant(); + return {mEntries[index * 2], mEntries[index * 2 + 1]}; + } + + MajorType type() const override { return kMajorType; } + const Map* asMap() const override { return this; } + + virtual std::unique_ptr<Item> clone() const override; + + uint64_t addlInfo() const override { return size(); } + + private: + void assertInvariant() const; +}; + +class Semantic : public CompoundItem { + public: + static constexpr MajorType kMajorType = SEMANTIC; + + template <typename T> + explicit Semantic(uint64_t value, T&& child); + + Semantic(const Semantic& other) = delete; + Semantic(Semantic&&) = default; + Semantic& operator=(const Semantic& other) = delete; + Semantic& operator=(Semantic&&) = default; + + size_t size() const override { + assertInvariant(); + return 1; + } + + size_t encodedSize() const override { + return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(mValue), + [](size_t sum, auto& entry) { return sum + entry->encodedSize(); }); + } + + MajorType type() const override { return kMajorType; } + const Semantic* asSemantic() const override { return this; } + + const std::unique_ptr<Item>& child() const { + assertInvariant(); + return mEntries[0]; + } + + std::unique_ptr<Item>& child() { + assertInvariant(); + return mEntries[0]; + } + + uint64_t value() const { return mValue; } + + uint64_t addlInfo() const override { return value(); } + + virtual std::unique_ptr<Item> clone() const override { + assertInvariant(); + return std::make_unique<Semantic>(mValue, mEntries[0]->clone()); + } + + protected: + Semantic() = default; + Semantic(uint64_t value) : mValue(value) {} + uint64_t mValue; + + private: + void assertInvariant() const; +}; + +/** + * Simple is abstract Item that implements CBOR major type 7. It is intended to be subclassed to + * create concrete Simple types. At present only Bool is provided. + */ +class Simple : public Item { + public: + static constexpr MajorType kMajorType = SIMPLE; + + bool operator==(const Simple& other) const&; + + virtual SimpleType simpleType() const = 0; + MajorType type() const override { return kMajorType; } + + const Simple* asSimple() const override { return this; } + + virtual const Bool* asBool() const { return nullptr; }; + virtual const Null* asNull() const { return nullptr; }; +}; + +/** + * Bool is a concrete type that implements CBOR major type 7, with additional item values for TRUE + * and FALSE. + */ +class Bool : public Simple { + public: + static constexpr SimpleType kSimpleType = BOOLEAN; + + explicit Bool(bool v) : mValue(v) {} + + bool operator==(const Bool& other) const& { return mValue == other.mValue; } + + SimpleType simpleType() const override { return kSimpleType; } + const Bool* asBool() const override { return this; } + + size_t encodedSize() const override { return 1; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(mValue ? TRUE : FALSE, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue ? TRUE : FALSE, encodeCallback); + } + + bool value() const { return mValue; } + + virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Bool>(mValue); } + + private: + bool mValue; +}; + +/** + * Null is a concrete type that implements CBOR major type 7, with additional item value for NULL + */ +class Null : public Simple { + public: + static constexpr SimpleType kSimpleType = NULL_T; + + explicit Null() {} + + SimpleType simpleType() const override { return kSimpleType; } + const Null* asNull() const override { return this; } + + size_t encodedSize() const override { return 1; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(NULL_V, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(NULL_V, encodeCallback); + } + + virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Null>(); } +}; + +template <typename T> +std::unique_ptr<T> downcastItem(std::unique_ptr<Item>&& v) { + static_assert(std::is_base_of_v<Item, T> && !std::is_abstract_v<T>, + "returned type is not an Item or is an abstract class"); + if (v && T::kMajorType == v->type()) { + if constexpr (std::is_base_of_v<Simple, T>) { + if (T::kSimpleType != v->asSimple()->simpleType()) { + return nullptr; + } + } + return std::unique_ptr<T>(static_cast<T*>(v.release())); + } else { + return nullptr; + } +} + +/** + * Details. Mostly you shouldn't have to look below, except perhaps at the docstring for makeItem. + */ +namespace details { + +template <typename T, typename V, typename Enable = void> +struct is_iterator_pair_over : public std::false_type {}; + +template <typename I1, typename I2, typename V> +struct is_iterator_pair_over< + std::pair<I1, I2>, V, + typename std::enable_if_t<std::is_same_v<V, typename std::iterator_traits<I1>::value_type>>> + : public std::true_type {}; + +template <typename T, typename V, typename Enable = void> +struct is_unique_ptr_of_subclass_of_v : public std::false_type {}; + +template <typename T, typename P> +struct is_unique_ptr_of_subclass_of_v<T, std::unique_ptr<P>, + typename std::enable_if_t<std::is_base_of_v<T, P>>> + : public std::true_type {}; + +/* check if type is one of std::string (1), std::string_view (2), null-terminated char* (3) or pair + * of iterators (4)*/ +template <typename T, typename Enable = void> +struct is_text_type_v : public std::false_type {}; + +template <typename T> +struct is_text_type_v< + T, typename std::enable_if_t< + /* case 1 */ // + std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string> + /* case 2 */ // + || std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string_view> + /* case 3 */ // + || std::is_same_v<std::remove_cv_t<std::decay_t<T>>, char*> // + || std::is_same_v<std::remove_cv_t<std::decay_t<T>>, const char*> + /* case 4 */ + || details::is_iterator_pair_over<T, char>::value>> : public std::true_type {}; + +/** + * Construct a unique_ptr<Item> from many argument types. Accepts: + * + * (a) booleans; + * (b) integers, all sizes and signs; + * (c) text strings, as defined by is_text_type_v above; + * (d) byte strings, as std::vector<uint8_t>(d1), pair of iterators (d2) or pair<uint8_t*, size_T> + * (d3); and + * (e) Item subclass instances, including Array and Map. Items may be provided by naked pointer + * (e1), unique_ptr (e2), reference (e3) or value (e3). If provided by reference or value, will + * be moved if possible. If provided by pointer, ownership is taken. + * (f) null pointer; + */ +template <typename T> +std::unique_ptr<Item> makeItem(T v) { + Item* p = nullptr; + if constexpr (/* case a */ std::is_same_v<T, bool>) { + p = new Bool(v); + } else if constexpr (/* case b */ std::is_integral_v<T>) { // b + if (v < 0) { + p = new Nint(v); + } else { + p = new Uint(static_cast<uint64_t>(v)); + } + } else if constexpr (/* case c */ // + details::is_text_type_v<T>::value) { + p = new Tstr(v); + } else if constexpr (/* case d1 */ // + std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, + std::vector<uint8_t>> + /* case d2 */ // + || details::is_iterator_pair_over<T, uint8_t>::value + /* case d3 */ // + || std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, + std::pair<uint8_t*, size_t>>) { + p = new Bstr(v); + } else if constexpr (/* case e1 */ // + std::is_pointer_v<T> && + std::is_base_of_v<Item, std::remove_pointer_t<T>>) { + p = v; + } else if constexpr (/* case e2 */ // + details::is_unique_ptr_of_subclass_of_v<Item, T>::value) { + p = v.release(); + } else if constexpr (/* case e3 */ // + std::is_base_of_v<Item, T>) { + p = new T(std::move(v)); + } else if constexpr (/* case f */ std::is_null_pointer_v<T>) { + p = new Null(); + } else { + // It's odd that this can't be static_assert(false), since it shouldn't be evaluated if one + // of the above ifs matches. But static_assert(false) always triggers. + static_assert(std::is_same_v<T, bool>, "makeItem called with unsupported type"); + } + return std::unique_ptr<Item>(p); +} + +} // namespace details + +template <typename... Args, + /* Prevent use as copy ctor */ typename = std::enable_if_t< + (sizeof...(Args)) != 1 || + !(std::is_same_v<Array, std::remove_cv_t<std::remove_reference_t<Args>>> || ...)>> +Array::Array(Args&&... args) { + mEntries.reserve(sizeof...(args)); + (mEntries.push_back(details::makeItem(std::forward<Args>(args))), ...); +} + +template <typename T> +Array& Array::add(T&& v) & { + mEntries.push_back(details::makeItem(std::forward<T>(v))); + return *this; +} + +template <typename T> +Array&& Array::add(T&& v) && { + mEntries.push_back(details::makeItem(std::forward<T>(v))); + return std::move(*this); +} + +template <typename... Args, + /* Prevent use as copy ctor */ typename = std::enable_if_t<(sizeof...(Args)) != 1>> +Map::Map(Args&&... args) { + static_assert((sizeof...(Args)) % 2 == 0, "Map must have an even number of entries"); + mEntries.reserve(sizeof...(args)); + (mEntries.push_back(details::makeItem(std::forward<Args>(args))), ...); +} + +template <typename Key, typename Value> +Map& Map::add(Key&& key, Value&& value) & { + mEntries.push_back(details::makeItem(std::forward<Key>(key))); + mEntries.push_back(details::makeItem(std::forward<Value>(value))); + return *this; +} + +template <typename Key, typename Value> +Map&& Map::add(Key&& key, Value&& value) && { + this->add(std::forward<Key>(key), std::forward<Value>(value)); + return std::move(*this); +} + +template <typename Key, typename = std::enable_if_t<std::is_integral_v<Key> || + details::is_text_type_v<Key>::value>> +std::pair<std::unique_ptr<Item>&, bool> Map::get(Key key) { + assertInvariant(); + auto keyItem = details::makeItem(key); + for (size_t i = 0; i < mEntries.size(); i += 2) { + if (*keyItem == *mEntries[i]) { + return {mEntries[i + 1], true}; + } + } + return {keyItem, false}; +} + +template <typename T> +Semantic::Semantic(uint64_t value, T&& child) : mValue(value) { + mEntries.reserve(1); + mEntries.push_back(details::makeItem(std::forward<T>(child))); +} + +} // namespace cppbor diff --git a/include/cppbor/cppbor_parse.h b/include/cppbor/cppbor_parse.h new file mode 100644 index 0000000..1b10ce1 --- /dev/null +++ b/include/cppbor/cppbor_parse.h @@ -0,0 +1,133 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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 "cppbor.h" + +namespace cppbor { + +using ParseResult = std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */>; + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, end). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +ParseResult parse(const uint8_t* begin, const uint8_t* end); + +/** + * Parse the first CBOR data item (possibly compound) from the byte vector. + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const std::vector<uint8_t>& encoding) { + return parse(encoding.data(), encoding.data() + encoding.size()); +} + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const uint8_t* begin, size_t size) { + return parse(begin, begin + size); +} + +class ParseClient; + +/** + * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the + * provided ParseClient when elements are found. + */ +void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient); + +/** + * Parse the CBOR data in the vector in streaming fashion, calling methods on the + * provided ParseClient when elements are found. + */ +inline void parse(const std::vector<uint8_t>& encoding, ParseClient* parseClient) { + return parse(encoding.data(), encoding.data() + encoding.size(), parseClient); +} + +/** + * A pure interface that callers of the streaming parse functions must implement. + */ +class ParseClient { + public: + virtual ~ParseClient() {} + + /** + * Called when an item is found. The Item pointer points to the found item; use type() and + * the appropriate as*() method to examine the value. hdrBegin points to the first byte of the + * header, valueBegin points to the first byte of the value and end points one past the end of + * the item. In the case of header-only items, such as integers, and compound items (ARRAY, + * MAP or SEMANTIC) whose end has not yet been found, valueBegin and end are equal and point to + * the byte past the header. + * + * Note that for compound types (ARRAY, MAP, and SEMANTIC), the Item will have no content. For + * Map and Array items, the size() method will return a correct value, but the index operators + * are unsafe, and the object cannot be safely compared with another Array/Map. + * + * The method returns a ParseClient*. In most cases "return this;" will be the right answer, + * but a different ParseClient may be returned, which the parser will begin using. If the method + * returns nullptr, parsing will be aborted immediately. + */ + virtual ParseClient* item(std::unique_ptr<Item>& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end) = 0; + + /** + * Called when the end of a compound item (MAP or ARRAY) is found. The item argument will be + * the same one passed to the item() call -- and may be empty if item() moved its value out. + * hdrBegin, valueBegin and end point to the beginning of the item header, the beginning of the + * first contained value, and one past the end of the last contained value, respectively. + * + * Note that the Item will have no content. + * + * As with item(), itemEnd() can change the ParseClient by returning a different one, or end the + * parsing by returning nullptr; + */ + virtual ParseClient* itemEnd(std::unique_ptr<Item>& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end) = 0; + + /** + * Called when parsing encounters an error. position is set to the first unparsed byte (one + * past the last successfully-parsed byte) and errorMessage contains an message explaining what + * sort of error occurred. + */ + virtual void error(const uint8_t* position, const std::string& errorMessage) = 0; +}; + +} // namespace cppbor diff --git a/src/cppbor.cpp b/src/cppbor.cpp new file mode 100644 index 0000000..25c2da1 --- /dev/null +++ b/src/cppbor.cpp @@ -0,0 +1,224 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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 "cppbor.h" + +#define LOG_TAG "CppBor" +#include <android-base/logging.h> + +namespace cppbor { + +namespace { + +template <typename T, typename Iterator, typename = std::enable_if<std::is_unsigned<T>::value>> +Iterator writeBigEndian(T value, Iterator pos) { + for (unsigned i = 0; i < sizeof(value); ++i) { + *pos++ = static_cast<uint8_t>(value >> (8 * (sizeof(value) - 1))); + value = static_cast<T>(value << 8); + } + return pos; +} + +template <typename T, typename = std::enable_if<std::is_unsigned<T>::value>> +void writeBigEndian(T value, std::function<void(uint8_t)>& cb) { + for (unsigned i = 0; i < sizeof(value); ++i) { + cb(static_cast<uint8_t>(value >> (8 * (sizeof(value) - 1)))); + value = static_cast<T>(value << 8); + } +} + +} // namespace + +size_t headerSize(uint64_t addlInfo) { + if (addlInfo < ONE_BYTE_LENGTH) return 1; + if (addlInfo <= std::numeric_limits<uint8_t>::max()) return 2; + if (addlInfo <= std::numeric_limits<uint16_t>::max()) return 3; + if (addlInfo <= std::numeric_limits<uint32_t>::max()) return 5; + return 9; +} + +uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end) { + size_t sz = headerSize(addlInfo); + if (end - pos < static_cast<ssize_t>(sz)) return nullptr; + switch (sz) { + case 1: + *pos++ = type | static_cast<uint8_t>(addlInfo); + return pos; + case 2: + *pos++ = type | ONE_BYTE_LENGTH; + *pos++ = static_cast<uint8_t>(addlInfo); + return pos; + case 3: + *pos++ = type | TWO_BYTE_LENGTH; + return writeBigEndian(static_cast<uint16_t>(addlInfo), pos); + case 5: + *pos++ = type | FOUR_BYTE_LENGTH; + return writeBigEndian(static_cast<uint32_t>(addlInfo), pos); + case 9: + *pos++ = type | EIGHT_BYTE_LENGTH; + return writeBigEndian(addlInfo, pos); + default: + CHECK(false); // Impossible to get here. + return nullptr; + } +} + +void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback) { + size_t sz = headerSize(addlInfo); + switch (sz) { + case 1: + encodeCallback(type | static_cast<uint8_t>(addlInfo)); + break; + case 2: + encodeCallback(type | ONE_BYTE_LENGTH); + encodeCallback(static_cast<uint8_t>(addlInfo)); + break; + case 3: + encodeCallback(type | TWO_BYTE_LENGTH); + writeBigEndian(static_cast<uint16_t>(addlInfo), encodeCallback); + break; + case 5: + encodeCallback(type | FOUR_BYTE_LENGTH); + writeBigEndian(static_cast<uint32_t>(addlInfo), encodeCallback); + break; + case 9: + encodeCallback(type | EIGHT_BYTE_LENGTH); + writeBigEndian(addlInfo, encodeCallback); + break; + default: + CHECK(false); // Impossible to get here. + } +} + +bool Item::operator==(const Item& other) const& { + if (type() != other.type()) return false; + switch (type()) { + case UINT: + return *asUint() == *(other.asUint()); + case NINT: + return *asNint() == *(other.asNint()); + case BSTR: + return *asBstr() == *(other.asBstr()); + case TSTR: + return *asTstr() == *(other.asTstr()); + case ARRAY: + return *asArray() == *(other.asArray()); + case MAP: + return *asMap() == *(other.asMap()); + case SIMPLE: + return *asSimple() == *(other.asSimple()); + case SEMANTIC: + return *asSemantic() == *(other.asSemantic()); + default: + CHECK(false); // Impossible to get here. + return false; + } +} + +Nint::Nint(int64_t v) : mValue(v) { + CHECK(v < 0) << "Only negative values allowed"; +} + +bool Simple::operator==(const Simple& other) const& { + if (simpleType() != other.simpleType()) return false; + + switch (simpleType()) { + case BOOLEAN: + return *asBool() == *(other.asBool()); + case NULL_T: + return true; + default: + CHECK(false); // Impossible to get here. + return false; + } +} + +uint8_t* Bstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mValue.size(), pos, end); + if (!pos || end - pos < static_cast<ptrdiff_t>(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); +} + +void Bstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mValue) { + encodeCallback(c); + } +} + +uint8_t* Tstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mValue.size(), pos, end); + if (!pos || end - pos < static_cast<ptrdiff_t>(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); +} + +void Tstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mValue) { + encodeCallback(static_cast<uint8_t>(c)); + } +} + +bool CompoundItem::operator==(const CompoundItem& other) const& { + return type() == other.type() // + && addlInfo() == other.addlInfo() // + // Can't use vector::operator== because the contents are pointers. std::equal lets us + // provide a predicate that does the dereferencing. + && std::equal(mEntries.begin(), mEntries.end(), other.mEntries.begin(), + [](auto& a, auto& b) -> bool { return *a == *b; }); +} + +uint8_t* CompoundItem::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(addlInfo(), pos, end); + if (!pos) return nullptr; + for (auto& entry : mEntries) { + pos = entry->encode(pos, end); + if (!pos) return nullptr; + } + return pos; +} + +void CompoundItem::encode(EncodeCallback encodeCallback) const { + encodeHeader(addlInfo(), encodeCallback); + for (auto& entry : mEntries) { + entry->encode(encodeCallback); + } +} + +void Map::assertInvariant() const { + CHECK(mEntries.size() % 2 == 0); +} + +std::unique_ptr<Item> Map::clone() const { + assertInvariant(); + auto res = std::make_unique<Map>(); + for (size_t i = 0; i < mEntries.size(); i += 2) { + res->add(mEntries[i]->clone(), mEntries[i + 1]->clone()); + } + return res; +} + +std::unique_ptr<Item> Array::clone() const { + auto res = std::make_unique<Array>(); + for (size_t i = 0; i < mEntries.size(); i++) { + res->add(mEntries[i]->clone()); + } + return res; +} + +void Semantic::assertInvariant() const { + CHECK(mEntries.size() == 1); +} + +} // namespace cppbor diff --git a/src/cppbor_parse.cpp b/src/cppbor_parse.cpp new file mode 100644 index 0000000..6da0036 --- /dev/null +++ b/src/cppbor_parse.cpp @@ -0,0 +1,351 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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 "cppbor_parse.h" + +#include <sstream> +#include <stack> + +#define LOG_TAG "CppBor" +#include <android-base/logging.h> + +namespace cppbor { + +namespace { + +std::string insufficientLengthString(size_t bytesNeeded, size_t bytesAvail, + const std::string& type) { + std::stringstream errStream; + errStream << "Need " << bytesNeeded << " byte(s) for " << type << ", have " << bytesAvail + << "."; + return errStream.str(); +} + +template <typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>> +std::tuple<bool, uint64_t, const uint8_t*> parseLength(const uint8_t* pos, const uint8_t* end, + ParseClient* parseClient) { + if (pos + sizeof(T) > end) { + parseClient->error(pos - 1, insufficientLengthString(sizeof(T), end - pos, "length field")); + return {false, 0, pos}; + } + + const uint8_t* intEnd = pos + sizeof(T); + T result = 0; + do { + result = static_cast<T>((result << 8) | *pos++); + } while (pos < intEnd); + return {true, result, pos}; +} + +std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end, + ParseClient* parseClient); + +std::tuple<const uint8_t*, ParseClient*> handleUint(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr<Item> item = std::make_unique<Uint>(value); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple<const uint8_t*, ParseClient*> handleNint(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + if (value > std::numeric_limits<int64_t>::max()) { + parseClient->error(hdrBegin, "NINT values that don't fit in int64_t are not supported."); + return {hdrBegin, nullptr /* end parsing */}; + } + std::unique_ptr<Item> item = std::make_unique<Nint>(-1 - static_cast<uint64_t>(value)); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple<const uint8_t*, ParseClient*> handleBool(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr<Item> item = std::make_unique<Bool>(value == TRUE); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple<const uint8_t*, ParseClient*> handleNull(const uint8_t* hdrBegin, const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr<Item> item = std::make_unique<Null>(); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +template <typename T> +std::tuple<const uint8_t*, ParseClient*> handleString(uint64_t length, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end, + const std::string& errLabel, + ParseClient* parseClient) { + if (end - valueBegin < static_cast<ssize_t>(length)) { + parseClient->error(hdrBegin, insufficientLengthString(length, end - valueBegin, errLabel)); + return {hdrBegin, nullptr /* end parsing */}; + } + + std::unique_ptr<Item> item = std::make_unique<T>(valueBegin, valueBegin + length); + return {valueBegin + length, + parseClient->item(item, hdrBegin, valueBegin, valueBegin + length)}; +} + +class IncompleteItem { + public: + virtual ~IncompleteItem() {} + virtual void add(std::unique_ptr<Item> item) = 0; +}; + +class IncompleteArray : public Array, public IncompleteItem { + public: + IncompleteArray(size_t size) : mSize(size) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return mSize; } + + void add(std::unique_ptr<Item> item) override { + mEntries.reserve(mSize); + mEntries.push_back(std::move(item)); + } + + private: + size_t mSize; +}; + +class IncompleteMap : public Map, public IncompleteItem { + public: + IncompleteMap(size_t size) : mSize(size) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return mSize; } + + void add(std::unique_ptr<Item> item) override { + mEntries.reserve(mSize * 2); + mEntries.push_back(std::move(item)); + } + + private: + size_t mSize; +}; + +class IncompleteSemantic : public Semantic, public IncompleteItem { + public: + IncompleteSemantic(uint64_t value) : Semantic(value) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return 1; } + + void add(std::unique_ptr<Item> item) override { + mEntries.reserve(1); + mEntries.push_back(std::move(item)); + } +}; + +std::tuple<const uint8_t*, ParseClient*> handleEntries(size_t entryCount, const uint8_t* hdrBegin, + const uint8_t* pos, const uint8_t* end, + const std::string& typeName, + ParseClient* parseClient) { + while (entryCount > 0) { + --entryCount; + if (pos == end) { + parseClient->error(hdrBegin, "Not enough entries for " + typeName + "."); + return {hdrBegin, nullptr /* end parsing */}; + } + std::tie(pos, parseClient) = parseRecursively(pos, end, parseClient); + if (!parseClient) return {hdrBegin, nullptr}; + } + return {pos, parseClient}; +} + +std::tuple<const uint8_t*, ParseClient*> handleCompound( + std::unique_ptr<Item> item, uint64_t entryCount, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end, const std::string& typeName, + ParseClient* parseClient) { + parseClient = + parseClient->item(item, hdrBegin, valueBegin, valueBegin /* don't know the end yet */); + if (!parseClient) return {hdrBegin, nullptr}; + + const uint8_t* pos; + std::tie(pos, parseClient) = + handleEntries(entryCount, hdrBegin, valueBegin, end, typeName, parseClient); + if (!parseClient) return {hdrBegin, nullptr}; + + return {pos, parseClient->itemEnd(item, hdrBegin, valueBegin, pos)}; +} + +std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end, + ParseClient* parseClient) { + const uint8_t* pos = begin; + + MajorType type = static_cast<MajorType>(*pos & 0xE0); + uint8_t tagInt = *pos & 0x1F; + ++pos; + + bool success = true; + uint64_t addlData; + if (tagInt < ONE_BYTE_LENGTH || tagInt > EIGHT_BYTE_LENGTH) { + addlData = tagInt; + } else { + switch (tagInt) { + case ONE_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength<uint8_t>(pos, end, parseClient); + break; + + case TWO_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength<uint16_t>(pos, end, parseClient); + break; + + case FOUR_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength<uint32_t>(pos, end, parseClient); + break; + + case EIGHT_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength<uint64_t>(pos, end, parseClient); + break; + + default: + CHECK(false); // It's impossible to get here + break; + } + } + + if (!success) return {begin, nullptr}; + + switch (type) { + case UINT: + return handleUint(addlData, begin, pos, parseClient); + + case NINT: + return handleNint(addlData, begin, pos, parseClient); + + case BSTR: + return handleString<Bstr>(addlData, begin, pos, end, "byte string", parseClient); + + case TSTR: + return handleString<Tstr>(addlData, begin, pos, end, "text string", parseClient); + + case ARRAY: + return handleCompound(std::make_unique<IncompleteArray>(addlData), addlData, begin, pos, + end, "array", parseClient); + + case MAP: + return handleCompound(std::make_unique<IncompleteMap>(addlData), addlData * 2, begin, + pos, end, "map", parseClient); + + case SEMANTIC: + return handleCompound(std::make_unique<IncompleteSemantic>(addlData), 1, begin, pos, + end, "semantic", parseClient); + + case SIMPLE: + switch (addlData) { + case TRUE: + case FALSE: + return handleBool(addlData, begin, pos, parseClient); + case NULL_V: + return handleNull(begin, pos, parseClient); + } + } + CHECK(false); // Impossible to get here. + return {}; +} + +class FullParseClient : public ParseClient { + public: + virtual ParseClient* item(std::unique_ptr<Item>& item, const uint8_t*, const uint8_t*, + const uint8_t* end) override { + if (mParentStack.empty() && !item->isCompound()) { + // This is the first and only item. + mTheItem = std::move(item); + mPosition = end; + return nullptr; // We're done. + } + + if (item->isCompound()) { + // Starting a new compound data item, i.e. a new parent. Save it on the parent stack. + // It's safe to save a raw pointer because the unique_ptr is guaranteed to stay in + // existence until the corresponding itemEnd() call. + assert(dynamic_cast<CompoundItem*>(item.get())); + mParentStack.push(static_cast<CompoundItem*>(item.get())); + return this; + } else { + appendToLastParent(std::move(item)); + return this; + } + } + + virtual ParseClient* itemEnd(std::unique_ptr<Item>& item, const uint8_t*, const uint8_t*, + const uint8_t* end) override { + CHECK(item->isCompound() && item.get() == mParentStack.top()); + mParentStack.pop(); + + if (mParentStack.empty()) { + mTheItem = std::move(item); + mPosition = end; + return nullptr; // We're done + } else { + appendToLastParent(std::move(item)); + return this; + } + } + + virtual void error(const uint8_t* position, const std::string& errorMessage) override { + mPosition = position; + mErrorMessage = errorMessage; + } + + std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> + parseResult() { + std::unique_ptr<Item> p = std::move(mTheItem); + return {std::move(p), mPosition, std::move(mErrorMessage)}; + } + + private: + void appendToLastParent(std::unique_ptr<Item> item) { + auto parent = mParentStack.top(); + assert(dynamic_cast<IncompleteItem*>(parent)); + if (parent->type() == ARRAY) { + static_cast<IncompleteArray*>(parent)->add(std::move(item)); + } else if (parent->type() == MAP) { + static_cast<IncompleteMap*>(parent)->add(std::move(item)); + } else if (parent->type() == SEMANTIC) { + static_cast<IncompleteSemantic*>(parent)->add(std::move(item)); + } else { + CHECK(false); // Impossible to get here. + } + } + + std::unique_ptr<Item> mTheItem; + std::stack<CompoundItem*> mParentStack; + const uint8_t* mPosition = nullptr; + std::string mErrorMessage; +}; + +} // anonymous namespace + +void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) { + parseRecursively(begin, end, parseClient); +} + +std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> +parse(const uint8_t* begin, const uint8_t* end) { + FullParseClient parseClient; + parse(begin, end, &parseClient); + return parseClient.parseResult(); +} + +} // namespace cppbor diff --git a/tests/cppbor_test.cpp b/tests/cppbor_test.cpp new file mode 100644 index 0000000..baa7c3b --- /dev/null +++ b/tests/cppbor_test.cpp @@ -0,0 +1,1459 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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 <iomanip> +#include <sstream> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "cppbor.h" +#include "cppbor_parse.h" + +using namespace cppbor; +using namespace std; + +using ::testing::_; +using ::testing::AllOf; +using ::testing::ByRef; +using ::testing::InSequence; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::Truly; +using ::testing::Unused; + +string hexDump(const string& str) { + stringstream s; + for (auto c : str) { + s << setfill('0') << setw(2) << hex << (static_cast<unsigned>(c) & 0xff); + } + return s.str(); +} + +TEST(SimpleValueTest, UnsignedValueSizes) { + // Check that unsigned integers encode to correct lengths, and that encodedSize() is correct. + vector<pair<uint64_t /* value */, size_t /* expected encoded size */>> testCases{ + {0, 1}, + {1, 1}, + {23, 1}, + {24, 2}, + {255, 2}, + {256, 3}, + {65535, 3}, + {65536, 5}, + {4294967295, 5}, + {4294967296, 9}, + {std::numeric_limits<uint64_t>::max(), 9}, + }; + for (auto& testCase : testCases) { + Uint val(testCase.first); + EXPECT_EQ(testCase.second, val.encodedSize()) << "Wrong size for value " << testCase.first; + EXPECT_EQ(val.encodedSize(), val.toString().size()) + << "encodedSize and encoding disagree for value " << testCase.first; + } +} + +TEST(SimpleValueTest, UnsignedValueEncodings) { + EXPECT_EQ("\x00"s, Uint(0u).toString()); + EXPECT_EQ("\x01"s, Uint(1u).toString()); + EXPECT_EQ("\x0a"s, Uint(10u).toString()); + EXPECT_EQ("\x17"s, Uint(23u).toString()); + EXPECT_EQ("\x18\x18"s, Uint(24u).toString()); + EXPECT_EQ("\x18\x19"s, Uint(25u).toString()); + EXPECT_EQ("\x18\x64"s, Uint(100u).toString()); + EXPECT_EQ("\x19\x03\xe8"s, Uint(1000u).toString()); + EXPECT_EQ("\x1a\x00\x0f\x42\x40"s, Uint(1000000u).toString()); + EXPECT_EQ("\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00"s, Uint(1000000000000u).toString()); + EXPECT_EQ("\x1B\x7f\xff\xff\xff\xff\xff\xff\xff"s, + Uint(std::numeric_limits<int64_t>::max()).toString()); +} + +TEST(SimpleValueTest, NegativeValueEncodings) { + EXPECT_EQ("\x20"s, Nint(-1).toString()); + EXPECT_EQ("\x28"s, Nint(-9).toString()); + EXPECT_EQ("\x29"s, Nint(-10).toString()); + EXPECT_EQ("\x36"s, Nint(-23).toString()); + EXPECT_EQ("\x37"s, Nint(-24).toString()); + EXPECT_EQ("\x38\x18"s, Nint(-25).toString()); + EXPECT_EQ("\x38\x62"s, Nint(-99).toString()); + EXPECT_EQ("\x38\x63"s, Nint(-100).toString()); + EXPECT_EQ("\x39\x03\xe6"s, Nint(-999).toString()); + EXPECT_EQ("\x39\x03\xe7"s, Nint(-1000).toString()); + EXPECT_EQ("\x3a\x00\x0f\x42\x3F"s, Nint(-1000000).toString()); + EXPECT_EQ("\x3b\x00\x00\x00\xe8\xd4\xa5\x0f\xff"s, Nint(-1000000000000).toString()); + EXPECT_EQ("\x3B\x7f\xff\xff\xff\xff\xff\xff\xff"s, + Nint(std::numeric_limits<int64_t>::min()).toString()); +} + +TEST(SimpleValueDeathTest, NegativeValueEncodings) { + EXPECT_DEATH(Nint(0), ""); + EXPECT_DEATH(Nint(1), ""); +} + +TEST(SimpleValueTest, BooleanEncodings) { + EXPECT_EQ("\xf4"s, Bool(false).toString()); + EXPECT_EQ("\xf5"s, Bool(true).toString()); +} + +TEST(SimpleValueTest, NullEncodings) { + EXPECT_EQ("\xf6"s, Null().toString()); +} + +TEST(SimpleValueTest, ByteStringEncodings) { + EXPECT_EQ("\x40", Bstr("").toString()); + EXPECT_EQ("\x41\x61", Bstr("a").toString()); + EXPECT_EQ("\x41\x41", Bstr("A").toString()); + EXPECT_EQ("\x44\x49\x45\x54\x46", Bstr("IETF").toString()); + EXPECT_EQ("\x42\x22\x5c", Bstr("\"\\").toString()); + EXPECT_EQ("\x42\xc3\xbc", Bstr("\xc3\xbc").toString()); + EXPECT_EQ("\x43\xe6\xb0\xb4", Bstr("\xe6\xb0\xb4").toString()); + EXPECT_EQ("\x44\xf0\x90\x85\x91", Bstr("\xf0\x90\x85\x91").toString()); + EXPECT_EQ("\x44\x01\x02\x03\x04", Bstr("\x01\x02\x03\x04").toString()); + EXPECT_EQ("\x44\x40\x40\x40\x40", Bstr("@@@@").toString()); +} + +TEST(SimpleValueTest, TextStringEncodings) { + EXPECT_EQ("\x60"s, Tstr("").toString()); + EXPECT_EQ("\x61\x61"s, Tstr("a").toString()); + EXPECT_EQ("\x61\x41"s, Tstr("A").toString()); + EXPECT_EQ("\x64\x49\x45\x54\x46"s, Tstr("IETF").toString()); + EXPECT_EQ("\x62\x22\x5c"s, Tstr("\"\\").toString()); + EXPECT_EQ("\x62\xc3\xbc"s, Tstr("\xc3\xbc").toString()); + EXPECT_EQ("\x63\xe6\xb0\xb4"s, Tstr("\xe6\xb0\xb4").toString()); + EXPECT_EQ("\x64\xf0\x90\x85\x91"s, Tstr("\xf0\x90\x85\x91").toString()); + EXPECT_EQ("\x64\x01\x02\x03\x04"s, Tstr("\x01\x02\x03\x04").toString()); +} + +TEST(SimpleValueTest, SemanticTagEncoding) { + EXPECT_EQ("\xDB\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x63\x41\x45\x53"s, + Semantic(std::numeric_limits<uint64_t>::max(), "AES").toString()); +} + +TEST(IsIteratorPairOverTest, All) { + EXPECT_TRUE(( + details::is_iterator_pair_over<pair<string::iterator, string::iterator>, char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<string::const_iterator, string::iterator>, + char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<string::iterator, string::const_iterator>, + char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<char*, char*>, char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<const char*, char*>, char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<char*, const char*>, char>::value)); + EXPECT_FALSE((details::is_iterator_pair_over<pair<string::iterator, string::iterator>, + uint8_t>::value)); + EXPECT_FALSE((details::is_iterator_pair_over<pair<char*, char*>, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over< + pair<vector<uint8_t>::iterator, vector<uint8_t>::iterator>, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over< + pair<vector<uint8_t>::const_iterator, vector<uint8_t>::iterator>, + uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over< + pair<vector<uint8_t>::iterator, vector<uint8_t>::const_iterator>, + uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<uint8_t*, uint8_t*>, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<const uint8_t*, uint8_t*>, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over<pair<uint8_t*, const uint8_t*>, uint8_t>::value)); + EXPECT_FALSE((details::is_iterator_pair_over< + pair<vector<uint8_t>::iterator, vector<uint8_t>::iterator>, char>::value)); + EXPECT_FALSE((details::is_iterator_pair_over<pair<uint8_t*, const uint8_t*>, char>::value)); +} + +TEST(IsUniquePtrSubclassOf, All) { + EXPECT_TRUE((details::is_unique_ptr_of_subclass_of_v<Item, std::unique_ptr<Bool>>::value)); + EXPECT_TRUE((details::is_unique_ptr_of_subclass_of_v<Item, std::unique_ptr<Map>>::value)); + EXPECT_TRUE((details::is_unique_ptr_of_subclass_of_v<Item, std::unique_ptr<Array>>::value)); + EXPECT_TRUE((details::is_unique_ptr_of_subclass_of_v<Item, std::unique_ptr<Semantic>>::value)); + EXPECT_FALSE( + (details::is_unique_ptr_of_subclass_of_v<std::string, std::unique_ptr<Bool>>::value)); + EXPECT_FALSE(( + details::is_unique_ptr_of_subclass_of_v<uint8_t, std::unique_ptr<std::string>>::value)); +} + +TEST(MakeEntryTest, Boolean) { + EXPECT_EQ("\xf4"s, details::makeItem(false)->toString()); +} + +TEST(MakeEntryTest, Null) { + EXPECT_EQ("\xf6"s, details::makeItem(nullptr)->toString()); +} + +TEST(MakeEntryTest, Integers) { + EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint8_t>(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint16_t>(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint32_t>(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint64_t>(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast<int8_t>(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast<int16_t>(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast<int32_t>(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast<int64_t>(0))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast<int8_t>(-1))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast<int16_t>(-1))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast<int32_t>(-1))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast<int64_t>(-1))->toString()); + + EXPECT_EQ("\x1b\xff\xff\xff\xff\xff\xff\xff\xff"s, + details::makeItem(static_cast<uint64_t>(std::numeric_limits<uint64_t>::max())) + ->toString()); +} + +TEST(MakeEntryTest, StdStrings) { + string s1("hello"); + const string s2("hello"); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString()); // copy of string + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, + details::makeItem(s2)->toString()); // copy of const string + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, + details::makeItem(std::move(s1))->toString()); // move string + EXPECT_EQ(0U, s1.size()); // Prove string was moved, not copied. +} + +TEST(MakeEntryTest, StdStringViews) { + string_view s1("hello"); + const string_view s2("hello"); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s2)->toString()); +} + +TEST(MakeEntryTest, CStrings) { + char s1[] = "hello"; + const char s2[] = "hello"; + const char* s3 = "hello"; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s2)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s3)->toString()); +} + +TEST(MakeEntryTest, StringIteratorPairs) { + // Use iterators from string to prove that "real" iterators work + string s1 = "hello"s; + pair<string::iterator, string::iterator> p1 = make_pair(s1.begin(), s1.end()); + + const pair<string::iterator, string::iterator> p2 = p1; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p1)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p2)->toString()); + + // Use char*s as iterators + const char* s2 = "hello"; + pair p3 = make_pair(s2, s2 + 5); + const pair p4 = p3; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p3)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p4)->toString()); +} + +TEST(MakeEntryTest, ByteStrings) { + vector<uint8_t> v1 = {0x00, 0x01, 0x02}; + const vector<uint8_t> v2 = {0x00, 0x01, 0x02}; + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v1)->toString()); // copy of vector + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v2)->toString()); // copy of const vector + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(std::move(v1))->toString()); // move vector + EXPECT_EQ(0U, v1.size()); // Prove vector was moved, not copied. +} + +TEST(MakeEntryTest, ByteStringIteratorPairs) { + using vec = vector<uint8_t>; + using iter = vec::iterator; + vec v1 = {0x00, 0x01, 0x02}; + pair<iter, iter> p1 = make_pair(v1.begin(), v1.end()); + const pair<iter, iter> p2 = make_pair(v1.begin(), v1.end()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p1)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p2)->toString()); + + // Use uint8_t*s as iterators + uint8_t v2[] = {0x00, 0x01, 0x02}; + uint8_t* v3 = v2; + pair<uint8_t*, uint8_t*> p3 = make_pair(v2, v2 + 3); + const pair<uint8_t*, uint8_t*> p4 = make_pair(v2, v2 + 3); + pair<uint8_t*, uint8_t*> p5 = make_pair(v3, v3 + 3); + const pair<uint8_t*, uint8_t*> p6 = make_pair(v3, v3 + 3); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p3)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p4)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p5)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p6)->toString()); +} + +TEST(MakeEntryTest, ByteStringBuffers) { + uint8_t v1[] = {0x00, 0x01, 0x02}; + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(make_pair(v1, 3))->toString()); +} + +TEST(MakeEntryTest, ItemPointer) { + Uint* p1 = new Uint(0); + EXPECT_EQ("\x00"s, details::makeItem(p1)->toString()); + EXPECT_EQ("\x60"s, details::makeItem(new Tstr(string()))->toString()); +} + +TEST(MakeEntryTest, ItemReference) { + Tstr str("hello"s); + Tstr& strRef = str; + const Tstr& strConstRef = str; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(str)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strRef)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strConstRef)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(std::move(str))->toString()); + EXPECT_EQ("\x60"s, details::makeItem(str)->toString()); // Prove that it moved + + EXPECT_EQ("\x00"s, details::makeItem(Uint(0))->toString()); + + EXPECT_EQ("\x43\x00\x01\x02"s, + details::makeItem(Bstr(vector<uint8_t>{0x00, 0x01, 0x02}))->toString()); + + EXPECT_EQ("\x80"s, details::makeItem(Array())->toString()); + EXPECT_EQ("\xa0"s, details::makeItem(Map())->toString()); +} + +TEST(CompoundValueTest, ArrayOfInts) { + EXPECT_EQ("\x80"s, Array().toString()); + Array(Uint(0)).toString(); + + EXPECT_EQ("\x81\x00"s, Array(Uint(0U)).toString()); + EXPECT_EQ("\x82\x00\x01"s, Array(Uint(0), Uint(1)).toString()); + EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(Uint(0), Uint(1), Nint(-99)).toString()); + + EXPECT_EQ("\x81\x00"s, Array(0).toString()); + EXPECT_EQ("\x82\x00\x01"s, Array(0, 1).toString()); + EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(0, 1, -99).toString()); +} + +TEST(CompoundValueTest, MapOfInts) { + EXPECT_EQ("\xA0"s, Map().toString()); + EXPECT_EQ("\xA1\x00\x01"s, Map(Uint(0), Uint(1)).toString()); + // Maps with an odd number of arguments will fail to compile. Uncomment the next lines to test. + // EXPECT_EQ("\xA1\x00"s, Map(Int(0)).toString()); + // EXPECT_EQ("\xA1\x00\x01\x02"s, Map(Int(0), Int(1), Int(2)).toString()); +} + +TEST(CompoundValueTest, MixedArray) { + vector<uint8_t> vec = {3, 2, 1}; + EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Array(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString()); + + EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Array(1, -1, vec, "hello").toString()); +} + +TEST(CompoundValueTest, MixedMap) { + vector<uint8_t> vec = {3, 2, 1}; + EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Map(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString()); + + EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Map(1, -1, vec, "hello").toString()); +} + +TEST(CompoundValueTest, NestedStructures) { + vector<uint8_t> vec = {3, 2, 1}; + + string expectedEncoding = + "\xA2\x66\x4F\x75\x74\x65\x72\x31\x82\xA2\x66\x49\x6E\x6E\x65\x72\x31\x18\x63\x66\x49" + "\x6E" + "\x6E\x65\x72\x32\x43\x03\x02\x01\x63\x66\x6F\x6F\x66\x4F\x75\x74\x65\x72\x32\x0A"s; + + // Do it with explicity-created Items + EXPECT_EQ(expectedEncoding, + Map(Tstr("Outer1"), + Array( // + Map(Tstr("Inner1"), Uint(99), Tstr("Inner2"), Bstr(vec)), Tstr("foo")), + Tstr("Outer2"), // + Uint(10)) + .toString()); + EXPECT_EQ(3U, vec.size()); + + // Now just use convertible types + EXPECT_EQ(expectedEncoding, Map("Outer1", + Array(Map("Inner1", 99, // + "Inner2", vec), + "foo"), + "Outer2", 10) + .toString()); + EXPECT_EQ(3U, vec.size()); + + // Finally, do it with the .add() method. This is slightly less efficient, but has the + // advantage you can build a structure up incrementally, or somewhat fluently if you like. + // First, fluently. + EXPECT_EQ(expectedEncoding, Map().add("Outer1", Array().add(Map() // + .add("Inner1", 99) + .add("Inner2", vec)) + .add("foo")) + .add("Outer2", 10) + .toString()); + EXPECT_EQ(3U, vec.size()); + + // Next, more incrementally + Array arr; + arr.add(Map() // + .add("Inner1", 99) + .add("Inner2", vec)) + .add("foo"); + EXPECT_EQ(3U, vec.size()); + + Map m; + m.add("Outer1", std::move(arr)); // Moving is necessary; Map and Array cannot be copied. + m.add("Outer2", 10); + auto s = m.toString(); + EXPECT_EQ(expectedEncoding, s); +} + +TEST(EncodingMethodsTest, AllVariants) { + Map map; + map.add("key1", Array().add(Map() // + .add("key_a", 9999999) + .add("key_b", std::vector<uint8_t>{0x01, 0x02, 0x03}) + .add("key_c", std::numeric_limits<uint64_t>::max()) + .add("key_d", std::numeric_limits<int16_t>::min())) + .add("foo")) + .add("key2", true) + .add("key3", Semantic(987654321, "Zhai gana test")); + + std::vector<uint8_t> buf; + buf.resize(map.encodedSize()); + + EXPECT_EQ(buf.data() + buf.size(), map.encode(buf.data(), buf.data() + buf.size())); + + EXPECT_EQ(buf, map.encode()); + + std::vector<uint8_t> buf2; + map.encode(std::back_inserter(buf2)); + EXPECT_EQ(buf, buf2); + + auto iter = buf.begin(); + map.encode([&](uint8_t c) { EXPECT_EQ(c, *iter++); }); +} + +TEST(EncodingMethodsTest, UintWithTooShortBuf) { + Uint val(100000); + vector<uint8_t> buf(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, TstrWithTooShortBuf) { + Tstr val("01234567890123456789012345"s); + vector<uint8_t> buf(1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); + + buf.resize(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, BstrWithTooShortBuf) { + Bstr val("01234567890123456789012345"s); + vector<uint8_t> buf(1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); + + buf.resize(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, ArrayWithTooShortBuf) { + Array val("a", 5, -100); + + std::vector<uint8_t> buf(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, MapWithTooShortBuf) { + Map map; + map.add("key1", Array().add(Map() // + .add("key_a", 99) + .add("key_b", std::vector<uint8_t>{0x01, 0x02, 0x03})) + .add("foo")) + .add("key2", true); + + std::vector<uint8_t> buf(map.encodedSize() - 1); + EXPECT_EQ(nullptr, map.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, SemanticWithTooShortBuf) { + Semantic tag(4321, Array().add(Array().add("Qaiyrly kesh!").add("Kesh zharyq!").add("431")) + .add(Map().add("kilt_1", 777).add("kilt_2", 999))); + std::vector<uint8_t> buf(tag.encodedSize() - 1); + EXPECT_EQ(nullptr, tag.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EqualityTest, Uint) { + Uint val(99); + EXPECT_EQ(val, Uint(99)); + + EXPECT_NE(val, Uint(98)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Nint) { + Nint val(-1); + EXPECT_EQ(val, Nint(-1)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Tstr) { + Tstr val("99"); + EXPECT_EQ(val, Tstr("99")); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("98")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Bstr) { + Bstr val("99"); + EXPECT_EQ(val, Bstr("99")); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Bool) { + Bool val(false); + EXPECT_EQ(val, Bool(false)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Array) { + Array val(99, 1); + EXPECT_EQ(val, Array(99, 1)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 2)); + EXPECT_NE(val, Array(98, 1)); + EXPECT_NE(val, Array(99, 1, 2)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Map) { + Map val(99, 1); + EXPECT_EQ(val, Map(99, 1)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 2)); + EXPECT_NE(val, Map(99, 1, 99, 2)); +} + +TEST(EqualityTest, Null) { + Null val; + EXPECT_EQ(val, Null()); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 2)); + EXPECT_NE(val, Map(99, 1, 99, 2)); +} + +TEST(EqualityTest, Semantic) { + Semantic val(215, Bstr("asd")); + EXPECT_EQ(val, Semantic(215, Bstr("asd"))); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 2)); + EXPECT_NE(val, Null()); +} + +TEST(ConvertTest, Uint) { + unique_ptr<Item> item = details::makeItem(10); + + EXPECT_EQ(UINT, item->type()); + EXPECT_NE(nullptr, item->asInt()); + EXPECT_NE(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(10, item->asInt()->value()); + EXPECT_EQ(10, item->asUint()->value()); +} + +TEST(ConvertTest, Nint) { + unique_ptr<Item> item = details::makeItem(-10); + + EXPECT_EQ(NINT, item->type()); + EXPECT_NE(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_NE(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(-10, item->asInt()->value()); + EXPECT_EQ(-10, item->asNint()->value()); +} + +TEST(ConvertTest, Tstr) { + unique_ptr<Item> item = details::makeItem("hello"); + + EXPECT_EQ(TSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_NE(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ("hello"s, item->asTstr()->value()); +} + +TEST(ConvertTest, Bstr) { + vector<uint8_t> vec{0x23, 0x24, 0x22}; + unique_ptr<Item> item = details::makeItem(vec); + + EXPECT_EQ(BSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_NE(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(vec, item->asBstr()->value()); +} + +TEST(ConvertTest, Bool) { + unique_ptr<Item> item = details::makeItem(false); + + EXPECT_EQ(SIMPLE, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_NE(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(BOOLEAN, item->asSimple()->simpleType()); + EXPECT_NE(nullptr, item->asSimple()->asBool()); + EXPECT_EQ(nullptr, item->asSimple()->asNull()); + + EXPECT_FALSE(item->asSimple()->asBool()->value()); +} + +TEST(ConvertTest, Map) { + unique_ptr<Item> item(new Map); + + EXPECT_EQ(MAP, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_NE(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(0U, item->asMap()->size()); +} + +TEST(ConvertTest, Array) { + unique_ptr<Item> item(new Array); + + EXPECT_EQ(ARRAY, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_NE(nullptr, item->asArray()); + + EXPECT_EQ(0U, item->asArray()->size()); +} + +TEST(ConvertTest, Semantic) { + unique_ptr<Item> item(new Semantic(1, "DSA")); + + EXPECT_EQ(SEMANTIC, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + EXPECT_NE(nullptr, item->asSemantic()); + + EXPECT_EQ(1U, item->asSemantic()->size()); +} + +TEST(ConvertTest, Null) { + unique_ptr<Item> item(new Null); + + EXPECT_EQ(SIMPLE, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_NE(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(NULL_T, item->asSimple()->simpleType()); + EXPECT_EQ(nullptr, item->asSimple()->asBool()); + EXPECT_NE(nullptr, item->asSimple()->asNull()); +} + +TEST(CloningTest, Uint) { + Uint item(10); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), UINT); + EXPECT_NE(clone->asUint(), nullptr); + EXPECT_EQ(item, *clone->asUint()); + std::move(item); + EXPECT_EQ(*clone->asUint(), Uint(10)); +} + +TEST(CloningTest, Nint) { + Nint item(-1000000); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), NINT); + EXPECT_NE(clone->asNint(), nullptr); + EXPECT_EQ(item, *clone->asNint()); + std::move(item); + EXPECT_EQ(*clone->asNint(), Nint(-1000000)); +} + +TEST(CloningTest, Tstr) { + Tstr item("qwertyasdfgh"); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), TSTR); + EXPECT_NE(clone->asTstr(), nullptr); + EXPECT_EQ(item, *clone->asTstr()); + std::move(item); + EXPECT_EQ(*clone->asTstr(), Tstr("qwertyasdfgh")); +} + +TEST(CloningTest, Bstr) { + Bstr item(std::vector<uint8_t>{1, 2, 3, 255, 0}); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), BSTR); + EXPECT_NE(clone->asBstr(), nullptr); + EXPECT_EQ(item, *clone->asBstr()); + std::move(item); + EXPECT_EQ(*clone->asBstr(), Bstr(std::vector<uint8_t>{1, 2, 3, 255, 0})); +} + +TEST(CloningTest, Array) { + Array item(-1000000, 22222222, "item", Map(1, 2, 4, Array(1, "das", true, nullptr)), + Semantic(16, "DATA")), + copy(-1000000, 22222222, "item", Map(1, 2, 4, Array(1, "das", true, nullptr)), + Semantic(16, "DATA")); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), ARRAY); + EXPECT_NE(clone->asArray(), nullptr); + EXPECT_EQ(item, *clone->asArray()); + std::move(item[0]); + std::move(item[1]); + std::move(item[3]); + std::move(item); + EXPECT_EQ(*clone->asArray(), copy); +} + +TEST(CloningTest, Map) { + Map item("key", Array("value1", "value2", 3), 15, Null(), -5, 45), + copy("key", Array("value1", "value2", 3), 15, Null(), -5, 45); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), MAP); + EXPECT_NE(clone->asMap(), nullptr); + EXPECT_EQ(item, *clone->asMap()); + auto [key, value] = item[0]; + std::move(key); + std::move(value); + std::move(item); + EXPECT_EQ(*clone->asMap(), copy); +} + +TEST(CloningTest, Bool) { + Bool item(true); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), SIMPLE); + EXPECT_NE(clone->asSimple(), nullptr); + EXPECT_EQ(clone->asSimple()->simpleType(), BOOLEAN); + EXPECT_NE(clone->asSimple()->asBool(), nullptr); + EXPECT_EQ(item, *clone->asSimple()->asBool()); + std::move(item); + EXPECT_EQ(*clone->asSimple()->asBool(), Bool(true)); +} + +TEST(CloningTest, Null) { + Null item; + auto clone = item.clone(); + EXPECT_EQ(clone->type(), SIMPLE); + EXPECT_NE(clone->asSimple(), nullptr); + EXPECT_EQ(clone->asSimple()->simpleType(), NULL_T); + EXPECT_NE(clone->asSimple()->asNull(), nullptr); + EXPECT_EQ(item, *clone->asSimple()->asNull()); + std::move(item); + EXPECT_EQ(*clone->asSimple()->asNull(), Null()); +} + +TEST(CloningTest, Semantic) { + Semantic item(96, Array(1, 2, 3, "entry", Map("key", "value"))), + copy(96, Array(1, 2, 3, "entry", Map("key", "value"))); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), SEMANTIC); + EXPECT_NE(clone->asSemantic(), nullptr); + EXPECT_EQ(item, *clone->asSemantic()); + std::move(item); + EXPECT_EQ(*clone->asSemantic(), copy); +} + +class MockParseClient : public ParseClient { + public: + MOCK_METHOD4(item, ParseClient*(std::unique_ptr<Item>& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end)); + MOCK_METHOD4(itemEnd, ParseClient*(std::unique_ptr<Item>& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end)); + MOCK_METHOD2(error, void(const uint8_t* position, const std::string& errorMessage)); +}; + +MATCHER_P(IsType, value, std::string("Type ") + (negation ? "doesn't match" : "matches")) { + return arg->type() == value; +} + +MATCHER_P(MatchesItem, value, "") { + return arg && *arg == value; +} + +MATCHER_P(IsArrayOfSize, value, "") { + return arg->type() == ARRAY && arg->asArray()->size() == value; +} + +MATCHER_P(IsSemanticTagOfValue, value, "") { + return arg->type() == SEMANTIC && arg->asSemantic()->value() == value; +} + +MATCHER_P(IsMapOfSize, value, "") { + return arg->type() == MAP && arg->asMap()->size() == value; +} + +TEST(StreamParseTest, Uint) { + MockParseClient mpc; + + Uint val(100); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Nint) { + MockParseClient mpc; + + Nint val(-10); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Bool) { + MockParseClient mpc; + + Bool val(true); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Null) { + MockParseClient mpc; + + Null val; + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Tstr) { + MockParseClient mpc; + + Tstr val("Hello"); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Bstr) { + MockParseClient mpc; + + Bstr val("Hello"); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Array) { + MockParseClient mpc; + + Array val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits<uint64_t>::max()); + ASSERT_NE(val[2]->asArray(), nullptr); + const Array& interior = *(val[2]->asArray()); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + { + InSequence s; + const uint8_t* pos = encBegin; + EXPECT_CALL(mpc, item(IsArrayOfSize(val.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0])), pos, pos + 1, pos + 6)) + .WillOnce(Return(&mpc)); + pos += 6; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + const uint8_t* innerArrayBegin = pos; + EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8)) + .WillOnce(Return(&mpc)); + pos += 8; + EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin, + innerArrayBegin + 1, pos)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[3])), pos, pos + 9, pos + 9)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(val.size()), encBegin, encBegin + 1, encEnd)) + .WillOnce(Return(&mpc)); + } + + EXPECT_CALL(mpc, error(_, _)) // + .Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Semantic) { + MockParseClient mpc; + Semantic val(15, Array(-5, "Hi")); + auto encoded = val.encode(); + ASSERT_NE(val.child()->asArray(), nullptr); + const Array& array = *(val.child()->asArray()); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + { + InSequence s; + const uint8_t* pos = encBegin; + EXPECT_CALL(mpc, item(IsSemanticTagOfValue(val.value()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + const uint8_t* innerArrayBegin = pos; + EXPECT_CALL(mpc, item(IsArrayOfSize(array.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*array[0])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*array[1])), pos, pos + 1, pos + 3)) + .WillOnce(Return(&mpc)); + pos += 3; + EXPECT_CALL(mpc, + itemEnd(IsArrayOfSize(array.size()), innerArrayBegin, innerArrayBegin + 1, pos)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(IsSemanticTagOfValue(val.value()), encBegin, encBegin + 1, encEnd)) + .WillOnce(Return(&mpc)); + } + + EXPECT_CALL(mpc, error(_, _)) // + .Times(0); + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Map) { + MockParseClient mpc; + + Map val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits<uint64_t>::max()); + ASSERT_NE(val[1].first->asArray(), nullptr); + const Array& interior = *(val[1].first->asArray()); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + { + InSequence s; + const uint8_t* pos = encBegin; + EXPECT_CALL(mpc, item(_, pos, pos + 1, pos + 1)).WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].first)), pos, pos + 1, pos + 6)) + .WillOnce(Return(&mpc)); + pos += 6; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].second)), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + const uint8_t* innerArrayBegin = pos; + EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8)) + .WillOnce(Return(&mpc)); + pos += 8; + EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin, + innerArrayBegin + 1, pos)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1].second)), pos, pos + 9, pos + 9)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(IsMapOfSize(val.size()), encBegin, encBegin + 1, encEnd)) + .WillOnce(Return(&mpc)); + } + + EXPECT_CALL(mpc, error(_, _)) // + .Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(FullParserTest, Uint) { + Uint val(10); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Null) { + Null val; + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Nint) { + Nint val(-10); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); + + vector<uint8_t> minNint = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + std::tie(item, pos, message) = parse(minNint); + EXPECT_THAT(item, NotNull()); + EXPECT_EQ(item->asNint()->value(), std::numeric_limits<int64_t>::min()); +} + +TEST(FullParserTest, NintOutOfRange) { + vector<uint8_t> outOfRangeNint = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + auto [item, pos, message] = parse(outOfRangeNint); + EXPECT_THAT(item, IsNull()); + EXPECT_EQ(pos, outOfRangeNint.data()); + EXPECT_EQ(message, "NINT values that don't fit in int64_t are not supported."); +} + +TEST(FullParserTest, Tstr) { + Tstr val("Hello"); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Bstr) { + Bstr val("\x00\x01\0x02"s); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Array) { + Array val("hello", -4, 3); + + auto encoded = val.encode(); + auto [item, pos, message] = parse(encoded); + EXPECT_THAT(item, MatchesItem(ByRef(val))); + EXPECT_EQ(pos, encoded.data() + encoded.size()); + EXPECT_EQ("", message); + + // We've already checked it all, but walk it just for fun. + ASSERT_NE(nullptr, item->asArray()); + const Array& arr = *(item->asArray()); + ASSERT_EQ(arr[0]->type(), TSTR); + EXPECT_EQ(arr[0]->asTstr()->value(), "hello"); +} + +TEST(FullParserTest, Map) { + Map val("hello", -4, 3, Bstr("hi")); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(ByRef(val))); +} + +TEST(FullParserTest, Semantic) { + Semantic val(99, "Salem"); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(ByRef(val))); +} + +TEST(FullParserTest, Complex) { + vector<uint8_t> vec = {0x01, 0x02, 0x08, 0x03}; + Map val("Outer1", + Array(Map("Inner1", 99, // + "Inner2", vec), + "foo"), + "Outer2", 10); + + std::unique_ptr<Item> item; + const uint8_t* pos; + std::string message; + std::tie(item, pos, message) = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(ByRef(val))); +} + +TEST(FullParserTest, IncompleteUint) { + Uint val(1000); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data(), pos); + EXPECT_EQ("Need 2 byte(s) for length field, have 1.", message); +} + +TEST(FullParserTest, IncompleteString) { + Tstr val("hello"); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data(), pos); + EXPECT_EQ("Need 5 byte(s) for text string, have 3.", message); +} + +TEST(FullParserTest, ArrayWithInsufficientEntries) { + Array val(1, 2, 3, 4); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data(), pos); + EXPECT_EQ("Not enough entries for array.", message); +} + +TEST(FullParserTest, ArrayWithTruncatedEntry) { + Array val(1, 2, 3, 400000); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data() + encoding.size() - 5, pos); + EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message); +} + +TEST(FullParserTest, MapWithTruncatedEntry) { + Map val(1, 2, 300000, 4); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data() + 3, pos); + EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message); +} + +TEST(ItemDowncastingTest, Uint) { + auto item = std::unique_ptr<Item>(new Uint(1)); + EXPECT_NE(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Nint) { + auto item = std::unique_ptr<Item>(new Nint(-211)); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Tstr) { + auto item = std::unique_ptr<Item>(new Tstr("string")); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Bstr) { + auto item = std::unique_ptr<Item>(new Bstr(std::vector<uint8_t>{1, 2, 3, 4, 5, 6})); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Array) { + auto item = std::unique_ptr<Item>(new Array(1, 2, "3", "4", Array(5, "6"))); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Map) { + auto item = std::unique_ptr<Item>(new Map(1, 2, "key", "value")); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Bool) { + auto item = std::unique_ptr<Item>(new Bool(false)); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Null) { + auto item = std::unique_ptr<Item>(new Null()); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(ItemDowncastingTest, Semantic) { + auto item = std::unique_ptr<Item>(new Semantic(11, Map("key", Array(1, 2, 3)))); + EXPECT_EQ(downcastItem<Uint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Nint>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Tstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bstr>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Array>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Map>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Bool>(std::move(item)).get(), nullptr); + EXPECT_EQ(downcastItem<Null>(std::move(item)).get(), nullptr); + EXPECT_NE(downcastItem<Semantic>(std::move(item)).get(), nullptr); + // Uncommenting following lines should not compile + // EXPECT_EQ(downcastItem<Int>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<CompoundItem>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Simple>(std::move(item)).get(), nullptr); + // EXPECT_EQ(downcastItem<Item>(std::move(item)).get(), nullptr); +} + +TEST(MapGetValueByKeyTest, Map) { + Array compoundItem(1, 2, 3, 4, 5, Map(4, 5, "a", "b")); + auto clone = compoundItem.clone(); + Map item(1, 2, "key", "value", "item", std::move(compoundItem)); + auto [value1, found1] = item.get(1); + EXPECT_TRUE(found1); + EXPECT_EQ(*value1, Uint(2)); + auto [value2, found2] = item.get("key"); + EXPECT_TRUE(found2); + EXPECT_EQ(*value2, Tstr("value")); + auto [value3, found3] = item.get("item"); + EXPECT_TRUE(found3); + EXPECT_EQ(*value3, *clone); + auto [value4, found4] = item.get("wrong"); + EXPECT_FALSE(found4); +} + +TEST(EmptyBstrTest, Bstr) { + Bstr bstr(std::vector<uint8_t>{}); + auto encoding = bstr.encode(); + auto [obj, pos, message] = parse(encoding.data(), encoding.size()); + EXPECT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj, bstr); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} |