blob: bb9ef79a48150d0db5d10714a7e3a368b0063d92 [file]
// Copyright 2026 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 "testing/cbor_utils.h"
#include <sys/types.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "cppbor/cppbor.h"
#include "cppbor/cppbor_parse.h"
#include "nlohmann/json.hpp"
namespace cbor {
namespace {
using Json = ::nlohmann::json;
cppbor::Map B64ToBstr(cppbor::Map* map);
cppbor::Map BstrToB64(cppbor::Map* map);
constexpr absl::string_view kBase64Prefix = "b64'";
constexpr absl::string_view kBase64Suffix = "'";
class NoExceptionParser
: public nlohmann::detail::json_sax_dom_parser<
Json, nlohmann::detail::iterator_input_adapter<const char*>> {
public:
explicit NoExceptionParser(Json& j)
: nlohmann::detail::json_sax_dom_parser<
Json, nlohmann::detail::iterator_input_adapter<const char*>>(
j, false) {}
bool parse_error(std::size_t position, absl::string_view last_token,
const Json::exception& ex) {
error_ = ex.what();
return false; // Stop processing.
}
std::string error() { return error_; }
private:
std::string error_;
};
std::string DecodePrefixedBase64(absl::string_view encoded) {
auto stripped = absl::StripPrefix(encoded, kBase64Prefix);
if (!absl::EndsWith(stripped, kBase64Suffix)) {
LOG(FATAL) << "The base64-encoded string " << encoded
<< " does not end with the suffix " << kBase64Suffix;
}
stripped = absl::StripSuffix(stripped, kBase64Suffix);
std::string decoded;
if (!absl::Base64Unescape(stripped, &decoded)) {
LOG(FATAL) << "Failed to decode base64: " << stripped;
}
return decoded;
}
std::string EncodePrefixedBase64(absl::string_view raw) {
return absl::StrCat(kBase64Prefix, absl::Base64Escape(raw), kBase64Suffix);
}
std::string EncodeCbor(cppbor::Item* item) {
std::vector<uint8_t> buf;
buf.resize(item->encodedSize());
item->encode(buf.data(), buf.data() + buf.size());
return std::string(reinterpret_cast<const char*>(buf.data()), buf.size());
}
// Recursively replaces all base64-encoded Tstr with their decoded values as
// Bstr.
cppbor::Array B64ToBstr(cppbor::Array* array) {
cppbor::Array new_array;
for (auto& item : *array) {
if (auto s = item->asTstr();
s != nullptr && s->value().starts_with(kBase64Prefix)) {
new_array.add(cppbor::Bstr(DecodePrefixedBase64(s->value())));
} else if (auto m = item->asMap(); m != nullptr) {
new_array.add(B64ToBstr(m));
} else if (auto a = item->asArray(); a != nullptr) {
new_array.add(B64ToBstr(a));
} else {
new_array.add(std::move(item));
}
}
return new_array;
}
// Recursively replaces all base64-encoded Tstr with their decoded values as
// Bstr.
cppbor::Map B64ToBstr(cppbor::Map* map) {
cppbor::Map new_map;
for (auto& [key, value] : *map) {
if (auto v = value->asTstr();
v != nullptr && v->value().starts_with(kBase64Prefix)) {
new_map.add(std::move(key),
cppbor::Bstr(DecodePrefixedBase64(v->value())));
} else if (auto m = value->asMap(); m != nullptr) {
new_map.add(std::move(key), B64ToBstr(m));
} else if (auto v = value->asArray(); v != nullptr) {
cppbor::Array new_array = B64ToBstr(v);
new_map.add(std::move(key), std::move(new_array));
} else {
new_map.add(std::move(key), std::move(value));
}
}
return new_map;
}
// Recursively replaces all base64-encoded Tstr with their decoded values as
// Bstr. Supports only arrays and maps.
std::string B64ToBstr(const uint8_t* begin, const uint8_t* end) {
auto [item, new_position, error] = cppbor::parse(begin, end);
if (!error.empty()) {
LOG(FATAL) << "CBOR parsing failed: " << error;
}
if (new_position != end) {
LOG(FATAL) << "Trailing bytes after the parsed CBOR item";
}
if (auto map = item->asMap(); map != nullptr) {
auto new_map = B64ToBstr(map);
return EncodeCbor(&new_map);
} else if (auto array = item->asArray(); array != nullptr) {
auto new_array = B64ToBstr(array);
return EncodeCbor(&new_array);
}
LOG(FATAL) << "Input CBOR item is " << item->type()
<< "; expected array (128) or map (160).";
}
std::unique_ptr<cppbor::Item> UnwrapTag(std::unique_ptr<cppbor::Item> item) {
while (item->asSemanticTag() != nullptr) {
if (auto t = item->asTstr(); t != nullptr) return t->clone();
if (auto u = item->asUint(); u != nullptr) return u->clone();
if (auto i = item->asInt(); i != nullptr) return i->clone();
if (auto b = item->asBool(); b != nullptr) return b->clone();
if (auto f = item->asFloat(); f != nullptr) return f->clone();
if (auto d = item->asDouble(); d != nullptr) return d->clone();
if (auto s = item->asSimple(); s != nullptr) return s->clone();
break;
}
return item;
}
// Recursively replaces all Bstr with base64-encoded Tstr.
cppbor::Array BstrToB64(cppbor::Array* array) {
cppbor::Array new_array;
for (auto& item_ref : *array) {
auto item = UnwrapTag(std::move(item_ref));
if (auto s = item->asBstr(); s != nullptr) {
absl::string_view sv(reinterpret_cast<const char*>(s->value().data()),
s->value().size());
new_array.add(cppbor::Tstr(EncodePrefixedBase64(sv)));
} else if (auto m = item->asMap(); m != nullptr) {
new_array.add(BstrToB64(m));
} else if (auto a = item->asArray(); a != nullptr) {
new_array.add(BstrToB64(a));
} else {
new_array.add(std::move(item));
}
}
return new_array;
}
// Recursively replaces all Bstr with base64-encoded Tstr.
cppbor::Map BstrToB64(cppbor::Map* map) {
cppbor::Map new_map;
for (auto& [key_ref, value_ref] : *map) {
auto key = UnwrapTag(std::move(key_ref));
auto value = UnwrapTag(std::move(value_ref));
if (auto s = value->asBstr(); s != nullptr) {
absl::string_view sv(reinterpret_cast<const char*>(s->value().data()),
s->value().size());
new_map.add(std::move(key), cppbor::Tstr(EncodePrefixedBase64(sv)));
} else if (auto m = value->asMap(); m != nullptr) {
new_map.add(std::move(key), BstrToB64(m));
} else if (auto v = value->asArray(); v != nullptr) {
cppbor::Array new_array = BstrToB64(v);
new_map.add(std::move(key), std::move(new_array));
} else {
new_map.add(std::move(key), std::move(value));
}
}
return new_map;
}
// Recursively replaces all Bstr with base64-encoded Tstr. Supports only arrays
// and maps.
std::string BstrToB64(absl::string_view raw) {
auto [item, new_position, error] =
cppbor::parse(reinterpret_cast<const uint8_t*>(raw.data()), raw.length());
if (!error.empty()) {
LOG(FATAL) << "CBOR parsing failed: " << error;
}
if (new_position !=
reinterpret_cast<const uint8_t*>(raw.data() + raw.length())) {
LOG(FATAL) << "Trailing bytes after the parsed CBOR item";
}
if (auto map = item->asMap(); map != nullptr) {
auto new_map = BstrToB64(map);
return EncodeCbor(&new_map);
} else if (auto array = item->asArray(); array != nullptr) {
auto new_array = BstrToB64(array);
return EncodeCbor(&new_array);
}
LOG(FATAL) << "Input CBOR item is " << item->type()
<< "; expected array (128) or map (160).";
}
} // namespace
std::string FromJson(absl::string_view json) {
Json result;
NoExceptionParser parser(result);
if (!Json::sax_parse(json, &parser, Json::input_format_t::json)) {
LOG(FATAL) << "JSON parse error: " << parser.error();
}
std::vector<uint8_t> cbor = Json::to_cbor(result);
return B64ToBstr(cbor.data(), cbor.data() + cbor.size());
}
std::string ToJson(absl::string_view cbor) {
std::string encoded = BstrToB64(cbor);
Json result;
NoExceptionParser parser(result);
auto ia = nlohmann::detail::input_adapter(encoded);
nlohmann::detail::binary_reader<Json, decltype(ia), NoExceptionParser> reader(
std::move(ia), Json::input_format_t::cbor);
if (!reader.sax_parse(Json::input_format_t::cbor, &parser, /*strict=*/true,
/*tag_handler=*/Json::cbor_tag_handler_t::ignore)) {
LOG(FATAL) << "CBOR parse error: " << parser.error();
}
return result.dump();
}
} // namespace cbor