/* * 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" #include #include #include "cppbor_parse.h" using std::string; using std::vector; #ifndef __TRUSTY__ #include #define LOG_TAG "CppBor" #else #define CHECK(x) (void)(x) #endif namespace cppbor { namespace { template ::value>> Iterator writeBigEndian(T value, Iterator pos) { for (unsigned i = 0; i < sizeof(value); ++i) { *pos++ = static_cast(value >> (8 * (sizeof(value) - 1))); value = static_cast(value << 8); } return pos; } template ::value>> void writeBigEndian(T value, std::function& cb) { for (unsigned i = 0; i < sizeof(value); ++i) { cb(static_cast(value >> (8 * (sizeof(value) - 1)))); value = static_cast(value << 8); } } bool cborAreAllElementsNonCompound(const Item* 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 (auto& [keyEntry, valueEntry] : *map) { 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& mapKeysToNotPrint) { if (!item) { out.append(""); return false; } char buf[80]; string indentString(indent, ' '); size_t tagCount = item->semanticTagCount(); while (tagCount > 0) { --tagCount; snprintf(buf, sizeof(buf), "tag %" PRIu64 " ", item->semanticTag(tagCount)); out.append(buf); } switch (item->type()) { case SEMANTIC: // Handled above. break; 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 uint8_t* valueData; size_t valueSize; const Bstr* bstr = item->asBstr(); if (bstr != nullptr) { const vector& value = bstr->value(); valueData = value.data(); valueSize = value.size(); } else { const ViewBstr* viewBstr = item->asViewBstr(); assert(viewBstr != nullptr); std::basic_string_view view = viewBstr->view(); valueData = view.data(); valueSize = view.size(); } if (valueSize > maxBStrSize) { unsigned char digest[SHA_DIGEST_LENGTH]; SHA_CTX ctx; SHA1_Init(&ctx); SHA1_Update(&ctx, valueData, valueSize); 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), "", valueSize, buf2); out.append(buf); } else { out.append("{"); for (size_t n = 0; n < valueSize; n++) { if (n > 0) { out.append(", "); } snprintf(buf, sizeof(buf), "0x%02x", valueData[n]); out.append(buf); } out.append("}"); } } break; case TSTR: out.append("'"); { // TODO: escape "'" characters if (item->asTstr() != nullptr) { out.append(item->asTstr()->value().c_str()); } else { const ViewTstr* viewTstr = item->asViewTstr(); assert(viewTstr != nullptr); out.append(viewTstr->view()); } } 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 (auto& [map_key, map_value] : *map) { out.append(" "); 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(""); } else { if (!prettyPrintInternal(map_value.get(), out, indent + 2, maxBStrSize, mapKeysToNotPrint)) { return false; } } out.append(",\n" + indentString); } out.append("}"); } } 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 { #ifndef __TRUSTY__ LOG(ERROR) << "Only boolean/null is implemented for SIMPLE"; #endif // __TRUSTY__ return false; } break; } return true; } } // namespace size_t headerSize(uint64_t addlInfo) { if (addlInfo < ONE_BYTE_LENGTH) return 1; if (addlInfo <= std::numeric_limits::max()) return 2; if (addlInfo <= std::numeric_limits::max()) return 3; if (addlInfo <= std::numeric_limits::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(sz)) return nullptr; switch (sz) { case 1: *pos++ = type | static_cast(addlInfo); return pos; case 2: *pos++ = type | ONE_BYTE_LENGTH; *pos++ = static_cast(addlInfo); return pos; case 3: *pos++ = type | TWO_BYTE_LENGTH; return writeBigEndian(static_cast(addlInfo), pos); case 5: *pos++ = type | FOUR_BYTE_LENGTH; return writeBigEndian(static_cast(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(addlInfo)); break; case 2: encodeCallback(type | ONE_BYTE_LENGTH); encodeCallback(static_cast(addlInfo)); break; case 3: encodeCallback(type | TWO_BYTE_LENGTH); writeBigEndian(static_cast(addlInfo), encodeCallback); break; case 5: encodeCallback(type | FOUR_BYTE_LENGTH); writeBigEndian(static_cast(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: if (asBstr() != nullptr && other.asBstr() != nullptr) { return *asBstr() == *(other.asBstr()); } if (asViewBstr() != nullptr && other.asViewBstr() != nullptr) { return *asViewBstr() == *(other.asViewBstr()); } // Interesting corner case: comparing a Bstr and ViewBstr with // identical contents. The function currently returns false for // this case. // TODO: if it should return true, this needs a deep comparison return false; case TSTR: if (asTstr() != nullptr && other.asTstr() != nullptr) { return *asTstr() == *(other.asTstr()); } if (asViewTstr() != nullptr && other.asViewTstr() != nullptr) { return *asViewTstr() == *(other.asViewTstr()); } // Same corner case as Bstr return false; case ARRAY: return *asArray() == *(other.asArray()); case MAP: return *asMap() == *(other.asMap()); case SIMPLE: return *asSimple() == *(other.asSimple()); case SEMANTIC: return *asSemanticTag() == *(other.asSemanticTag()); default: CHECK(false); // Impossible to get here. return false; } } Nint::Nint(int64_t v) : mValue(v) { CHECK(v < 0); } 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(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* ViewBstr::encode(uint8_t* pos, const uint8_t* end) const { pos = encodeHeader(mView.size(), pos, end); if (!pos || end - pos < static_cast(mView.size())) return nullptr; return std::copy(mView.begin(), mView.end(), pos); } void ViewBstr::encodeValue(EncodeCallback encodeCallback) const { for (auto c : mView) { encodeCallback(static_cast(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(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(c)); } } uint8_t* ViewTstr::encode(uint8_t* pos, const uint8_t* end) const { pos = encodeHeader(mView.size(), pos, end); if (!pos || end - pos < static_cast(mView.size())) return nullptr; return std::copy(mView.begin(), mView.end(), pos); } void ViewTstr::encodeValue(EncodeCallback encodeCallback) const { for (auto c : mView) { encodeCallback(static_cast(c)); } } bool Array::operator==(const Array& other) const& { return size() == other.size() // 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* Array::encode(uint8_t* pos, const uint8_t* end) const { pos = encodeHeader(size(), pos, end); if (!pos) return nullptr; for (auto& entry : mEntries) { pos = entry->encode(pos, end); if (!pos) return nullptr; } return pos; } void Array::encode(EncodeCallback encodeCallback) const { encodeHeader(size(), encodeCallback); for (auto& entry : mEntries) { entry->encode(encodeCallback); } } std::unique_ptr Array::clone() const { auto res = std::make_unique(); for (size_t i = 0; i < mEntries.size(); i++) { res->add(mEntries[i]->clone()); } return res; } bool Map::operator==(const Map& other) const& { return size() == other.size() // Can't use vector::operator== because the contents are pairs of pointers. std::equal // lets us provide a predicate that does the dereferencing. && std::equal(begin(), end(), other.begin(), [](auto& a, auto& b) { return *a.first == *b.first && *a.second == *b.second; }); } uint8_t* Map::encode(uint8_t* pos, const uint8_t* end) const { pos = encodeHeader(size(), pos, end); if (!pos) return nullptr; for (auto& entry : mEntries) { pos = entry.first->encode(pos, end); if (!pos) return nullptr; pos = entry.second->encode(pos, end); if (!pos) return nullptr; } return pos; } void Map::encode(EncodeCallback encodeCallback) const { encodeHeader(size(), encodeCallback); for (auto& entry : mEntries) { entry.first->encode(encodeCallback); entry.second->encode(encodeCallback); } } bool Map::keyLess(const Item* a, const Item* b) { // CBOR map canonicalization rules are: // 1. If two keys have different lengths, the shorter one sorts earlier. if (a->encodedSize() < b->encodedSize()) return true; if (a->encodedSize() > b->encodedSize()) return false; // 2. If two keys have the same length, the one with the lower value in (byte-wise) lexical // order sorts earlier. This requires encoding both items. auto encodedA = a->encode(); auto encodedB = b->encode(); return std::lexicographical_compare(encodedA.begin(), encodedA.end(), // encodedB.begin(), encodedB.end()); } void recursivelyCanonicalize(std::unique_ptr& item) { switch (item->type()) { case UINT: case NINT: case BSTR: case TSTR: case SIMPLE: return; case ARRAY: std::for_each(item->asArray()->begin(), item->asArray()->end(), recursivelyCanonicalize); return; case MAP: item->asMap()->canonicalize(true /* recurse */); return; case SEMANTIC: // This can't happen. SemanticTags delegate their type() method to the contained Item's // type. assert(false); return; } } Map& Map::canonicalize(bool recurse) & { if (recurse) { for (auto& entry : mEntries) { recursivelyCanonicalize(entry.first); recursivelyCanonicalize(entry.second); } } if (size() < 2 || mCanonicalized) { // Trivially or already canonical; do nothing. return *this; } std::sort(begin(), end(), [](auto& a, auto& b) { return keyLess(a.first.get(), b.first.get()); }); mCanonicalized = true; return *this; } std::unique_ptr Map::clone() const { auto res = std::make_unique(); for (auto& [key, value] : *this) { res->add(key->clone(), value->clone()); } res->mCanonicalized = mCanonicalized; return res; } std::unique_ptr SemanticTag::clone() const { return std::make_unique(mValue, mTaggedItem->clone()); } uint8_t* SemanticTag::encode(uint8_t* pos, const uint8_t* end) const { // Can't use the encodeHeader() method that calls type() to get the major type, since that will // return the tagged Item's type. pos = ::cppbor::encodeHeader(kMajorType, mValue, pos, end); if (!pos) return nullptr; return mTaggedItem->encode(pos, end); } void SemanticTag::encode(EncodeCallback encodeCallback) const { // Can't use the encodeHeader() method that calls type() to get the major type, since that will // return the tagged Item's type. ::cppbor::encodeHeader(kMajorType, mValue, encodeCallback); mTaggedItem->encode(encodeCallback); } size_t SemanticTag::semanticTagCount() const { size_t levelCount = 1; // Count this level. const SemanticTag* cur = this; while (cur->mTaggedItem && (cur = cur->mTaggedItem->asSemanticTag()) != nullptr) ++levelCount; return levelCount; } uint64_t SemanticTag::semanticTag(size_t nesting) const { // Getting the value of a specific nested tag is a bit tricky, because we start with the outer // tag and don't know how many are inside. We count the number of nesting levels to find out // how many there are in total, then to get the one we want we have to walk down levelCount - // nesting steps. size_t levelCount = semanticTagCount(); if (nesting >= levelCount) return 0; levelCount -= nesting; const SemanticTag* cur = this; while (--levelCount > 0) cur = cur->mTaggedItem->asSemanticTag(); return cur->mValue; } string prettyPrint(const Item* item, size_t maxBStrSize, const vector& mapKeysToNotPrint) { string out; prettyPrintInternal(item, out, 0, maxBStrSize, mapKeysToNotPrint); return out; } string prettyPrint(const vector& encodedCbor, size_t maxBStrSize, const vector& mapKeysToNotPrint) { auto [item, _, message] = parse(encodedCbor); if (item == nullptr) { #ifndef __TRUSTY__ LOG(ERROR) << "Data to pretty print is not valid CBOR: " << message; #endif // __TRUSTY__ return ""; } return prettyPrint(item.get(), maxBStrSize, mapKeysToNotPrint); } } // namespace cppbor