Improvements to cppbor

1. Add pretty-printing (moved from IdentityCredentialSupport)
2. Add EncodedItem to make it easy to add already-encoded CBOR.
3. Add Map canonicalization.
4. Add support for adding enums as integers.

Test: cppbor_test_external
Change-Id: I641c87567a11de4641b2fcadebe72dd832fceb51
diff --git a/src/cppbor.cpp b/src/cppbor.cpp
index 5a45dd9..7cd0f30 100644
--- a/src/cppbor.cpp
+++ b/src/cppbor.cpp
@@ -16,6 +16,14 @@
 
 #include "cppbor.h"
 
+#include <inttypes.h>
+#include <openssl/sha.h>
+
+#include "cppbor_parse.h"
+
+using std::string;
+using std::vector;
+
 #ifndef __TRUSTY__
 #include <android-base/logging.h>
 #define LOG_TAG "CppBor"
@@ -44,6 +52,187 @@
     }
 }
 
+bool cborAreAllElementsNonCompound(const CompoundItem* compoundItem) {
+    if (compoundItem->type() == ARRAY) {
+        const Array* array = compoundItem->asArray();
+        for (size_t n = 0; n < array->size(); n++) {
+            const Item* entry = (*array)[n].get();
+            switch (entry->type()) {
+                case ARRAY:
+                case MAP:
+                    return false;
+                default:
+                    break;
+            }
+        }
+    } else {
+        const Map* map = compoundItem->asMap();
+        for (size_t n = 0; n < map->size(); n++) {
+            auto [keyEntry, valueEntry] = (*map)[n];
+            switch (keyEntry->type()) {
+                case ARRAY:
+                case MAP:
+                    return false;
+                default:
+                    break;
+            }
+            switch (valueEntry->type()) {
+                case ARRAY:
+                case MAP:
+                    return false;
+                default:
+                    break;
+            }
+        }
+    }
+    return true;
+}
+
+bool prettyPrintInternal(const Item* item, string& out, size_t indent, size_t maxBStrSize,
+                         const vector<string>& mapKeysToNotPrint) {
+    if (!item) {
+        out.append("<NULL>");
+        return false;
+    }
+
+    char buf[80];
+
+    string indentString(indent, ' ');
+
+    switch (item->type()) {
+        case UINT:
+            snprintf(buf, sizeof(buf), "%" PRIu64, item->asUint()->unsignedValue());
+            out.append(buf);
+            break;
+
+        case NINT:
+            snprintf(buf, sizeof(buf), "%" PRId64, item->asNint()->value());
+            out.append(buf);
+            break;
+
+        case BSTR: {
+            const Bstr* bstr = item->asBstr();
+            const vector<uint8_t>& value = bstr->value();
+            if (value.size() > maxBStrSize) {
+                unsigned char digest[SHA_DIGEST_LENGTH];
+                SHA_CTX ctx;
+                SHA1_Init(&ctx);
+                SHA1_Update(&ctx, value.data(), value.size());
+                SHA1_Final(digest, &ctx);
+                char buf2[SHA_DIGEST_LENGTH * 2 + 1];
+                for (size_t n = 0; n < SHA_DIGEST_LENGTH; n++) {
+                    snprintf(buf2 + n * 2, 3, "%02x", digest[n]);
+                }
+                snprintf(buf, sizeof(buf), "<bstr size=%zd sha1=%s>", value.size(), buf2);
+                out.append(buf);
+            } else {
+                out.append("{");
+                for (size_t n = 0; n < value.size(); n++) {
+                    if (n > 0) {
+                        out.append(", ");
+                    }
+                    snprintf(buf, sizeof(buf), "0x%02x", value[n]);
+                    out.append(buf);
+                }
+                out.append("}");
+            }
+        } break;
+
+        case TSTR:
+            out.append("'");
+            {
+                // TODO: escape "'" characters
+                out.append(item->asTstr()->value().c_str());
+            }
+            out.append("'");
+            break;
+
+        case ARRAY: {
+            const Array* array = item->asArray();
+            if (array->size() == 0) {
+                out.append("[]");
+            } else if (cborAreAllElementsNonCompound(array)) {
+                out.append("[");
+                for (size_t n = 0; n < array->size(); n++) {
+                    if (!prettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize,
+                                             mapKeysToNotPrint)) {
+                        return false;
+                    }
+                    out.append(", ");
+                }
+                out.append("]");
+            } else {
+                out.append("[\n" + indentString);
+                for (size_t n = 0; n < array->size(); n++) {
+                    out.append("  ");
+                    if (!prettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize,
+                                             mapKeysToNotPrint)) {
+                        return false;
+                    }
+                    out.append(",\n" + indentString);
+                }
+                out.append("]");
+            }
+        } break;
+
+        case MAP: {
+            const Map* map = item->asMap();
+
+            if (map->size() == 0) {
+                out.append("{}");
+            } else {
+                out.append("{\n" + indentString);
+                for (size_t n = 0; n < map->size(); n++) {
+                    out.append("  ");
+
+                    auto [map_key, map_value] = (*map)[n];
+
+                    if (!prettyPrintInternal(map_key.get(), out, indent + 2, maxBStrSize,
+                                             mapKeysToNotPrint)) {
+                        return false;
+                    }
+                    out.append(" : ");
+                    if (map_key->type() == TSTR &&
+                        std::find(mapKeysToNotPrint.begin(), mapKeysToNotPrint.end(),
+                                  map_key->asTstr()->value()) != mapKeysToNotPrint.end()) {
+                        out.append("<not printed>");
+                    } else {
+                        if (!prettyPrintInternal(map_value.get(), out, indent + 2, maxBStrSize,
+                                                 mapKeysToNotPrint)) {
+                            return false;
+                        }
+                    }
+                    out.append(",\n" + indentString);
+                }
+                out.append("}");
+            }
+        } break;
+
+        case SEMANTIC: {
+            const Semantic* semantic = item->asSemantic();
+            snprintf(buf, sizeof(buf), "tag %" PRIu64 " ", semantic->value());
+            out.append(buf);
+            prettyPrintInternal(semantic->child().get(), out, indent, maxBStrSize,
+                                mapKeysToNotPrint);
+        } break;
+
+        case SIMPLE:
+            const Bool* asBool = item->asSimple()->asBool();
+            const Null* asNull = item->asSimple()->asNull();
+            if (asBool != nullptr) {
+                out.append(asBool->value() ? "true" : "false");
+            } else if (asNull != nullptr) {
+                out.append("null");
+            } else {
+                LOG(ERROR) << "Only boolean/null is implemented for SIMPLE";
+                return false;
+            }
+            break;
+    }
+
+    return true;
+}
+
 }  // namespace
 
 size_t headerSize(uint64_t addlInfo) {
@@ -204,6 +393,53 @@
     CHECK(mEntries.size() % 2 == 0);
 }
 
+bool mapKeyLess(const std::pair<std::unique_ptr<Item>&, std::unique_ptr<Item>&>& a,
+                const std::pair<std::unique_ptr<Item>&, std::unique_ptr<Item>&>& b) {
+    auto keyA = a.first->encode();
+    auto keyB = b.first->encode();
+
+    // CBOR map canonicalization rules are:
+
+    // 1. If two keys have different lengths, the shorter one sorts earlier.
+    if (keyA.size() < keyB.size()) return true;
+    if (keyA.size() > keyB.size()) return false;
+
+    // 2. If two keys have the same length, the one with the lower value in
+    // (byte-wise) lexical order sorts earlier.
+    return std::lexicographical_compare(keyA.begin(), keyA.end(), keyB.begin(), keyB.end());
+}
+
+Map& Map::canonicalize() & {
+    assertInvariant();
+
+    if (size() < 2) {
+        // Empty or single-entry map; no need to reorder.
+        return *this;
+    }
+
+    // The entries of a Map are stored in a flat vector.  We can't easily apply
+    // std::sort on that, so instead we move all of the entries into a vector of
+    // std::pair, sort that, then move all of the entries back into the original
+    // flat vector.
+    vector<std::pair<std::unique_ptr<Item>, std::unique_ptr<Item>>> temp;
+    temp.reserve(size());
+
+    for (size_t i = 0; i < mEntries.size() - 1; i += 2) {
+        temp.push_back({std::move(mEntries[i]), std::move(mEntries[i + 1])});
+    }
+
+    std::sort(temp.begin(), temp.end(), mapKeyLess);
+
+    mEntries.resize(0);
+    mEntries.reserve(temp.size() * 2);  // Should be a NOP since capacity should be unchanged.
+    for (auto& entry : temp) {
+        mEntries.push_back(std::move(entry.first));
+        mEntries.push_back(std::move(entry.second));
+    }
+
+    return *this;
+}
+
 std::unique_ptr<Item> Map::clone() const {
     assertInvariant();
     auto res = std::make_unique<Map>();
@@ -225,4 +461,20 @@
     CHECK(mEntries.size() == 1);
 }
 
+string prettyPrint(const Item* item, size_t maxBStrSize, const vector<string>& mapKeysToNotPrint) {
+    string out;
+    prettyPrintInternal(item, out, 0, maxBStrSize, mapKeysToNotPrint);
+    return out;
+}
+string prettyPrint(const vector<uint8_t>& encodedCbor, size_t maxBStrSize,
+                   const vector<string>& mapKeysToNotPrint) {
+    auto [item, _, message] = parse(encodedCbor);
+    if (item == nullptr) {
+        LOG(ERROR) << "Data to pretty print is not valid CBOR: " << message;
+        return "";
+    }
+
+    return prettyPrint(item.get(), maxBStrSize, mapKeysToNotPrint);
+}
+
 }  // namespace cppbor
diff --git a/src/cppbor_parse.cpp b/src/cppbor_parse.cpp
index 4715152..357b9ee 100644
--- a/src/cppbor_parse.cpp
+++ b/src/cppbor_parse.cpp
@@ -32,7 +32,7 @@
 std::string insufficientLengthString(size_t bytesNeeded, size_t bytesAvail,
                                      const std::string& type) {
     char buf[1024];
-    snprintf(buf, sizeof(buf), "Need %zu byte(s) for %s, have %zu", bytesNeeded, type.c_str(),
+    snprintf(buf, sizeof(buf), "Need %zu byte(s) for %s, have %zu.", bytesNeeded, type.c_str(),
              bytesAvail);
     return std::string(buf);
 }