Commit 1cb390b8 authored by Johannes Henkel's avatar Johannes Henkel Committed by Commit Bot

[DevTools] Roll inspector_protocol to a7423d8ca937e658ab3b85e3b02676bced145ba6.

Change-Id: I270de4fa2970c9e33600453aaecc1c786701dcc3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1521326Reviewed-by: 's avatarAlexei Filippov <alph@chromium.org>
Commit-Queue: Johannes Henkel <johannes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60225}
parent d6c56745
......@@ -2,7 +2,7 @@ Name: inspector protocol
Short Name: inspector_protocol
URL: https://chromium.googlesource.com/deps/inspector_protocol/
Version: 0
Revision: b13e24ccee66d7e0590ce1266db9c906e3648561
Revision: a7423d8ca937e658ab3b85e3b02676bced145ba6
License: BSD
License File: LICENSE
Security Critical: no
......
......@@ -632,7 +632,7 @@ def main():
"Array_h.template",
"DispatcherBase_h.template",
"Parser_h.template",
"CBOR_h.template",
"encoding_h.template",
]
protocol_cpp_templates = [
......@@ -642,7 +642,7 @@ def main():
"Object_cpp.template",
"DispatcherBase_cpp.template",
"Parser_cpp.template",
"CBOR_cpp.template",
"encoding_cpp.template",
]
forward_h_templates = [
......
......@@ -33,10 +33,10 @@ template("inspector_protocol_generate") {
invoker.config_file,
"$inspector_protocol_dir/lib/base_string_adapter_cc.template",
"$inspector_protocol_dir/lib/base_string_adapter_h.template",
"$inspector_protocol_dir/lib/encoding_h.template",
"$inspector_protocol_dir/lib/encoding_cpp.template",
"$inspector_protocol_dir/lib/Allocator_h.template",
"$inspector_protocol_dir/lib/Array_h.template",
"$inspector_protocol_dir/lib/CBOR_h.template",
"$inspector_protocol_dir/lib/CBOR_cpp.template",
"$inspector_protocol_dir/lib/DispatcherBase_cpp.template",
"$inspector_protocol_dir/lib/DispatcherBase_h.template",
"$inspector_protocol_dir/lib/ErrorSupport_cpp.template",
......
......@@ -5,10 +5,10 @@
{
'variables': {
'inspector_protocol_files': [
'lib/encoding_h.template',
'lib/encoding_cpp.template',
'lib/Allocator_h.template',
'lib/Array_h.template',
'lib/CBOR_h.template',
'lib/CBOR_cpp.template',
'lib/DispatcherBase_cpp.template',
'lib/DispatcherBase_h.template',
'lib/ErrorSupport_cpp.template',
......
......@@ -66,21 +66,21 @@ static constexpr int kStackLimitValues = 1000;
// Below are three parsing routines for CBOR, which cover enough
// to roundtrip JSON messages.
std::unique_ptr<DictionaryValue> parseMap(int32_t stack_depth, CBORTokenizer* tokenizer);
std::unique_ptr<ListValue> parseArray(int32_t stack_depth, CBORTokenizer* tokenizer);
std::unique_ptr<Value> parseValue(int32_t stack_depth, CBORTokenizer* tokenizer);
std::unique_ptr<DictionaryValue> parseMap(int32_t stack_depth, cbor::CBORTokenizer* tokenizer);
std::unique_ptr<ListValue> parseArray(int32_t stack_depth, cbor::CBORTokenizer* tokenizer);
std::unique_ptr<Value> parseValue(int32_t stack_depth, cbor::CBORTokenizer* tokenizer);
// |bytes| must start with the indefinite length array byte, so basically,
// ParseArray may only be called after an indefinite length array has been
// detected.
std::unique_ptr<ListValue> parseArray(int32_t stack_depth, CBORTokenizer* tokenizer) {
DCHECK(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START);
std::unique_ptr<ListValue> parseArray(int32_t stack_depth, cbor::CBORTokenizer* tokenizer) {
DCHECK(tokenizer->TokenTag() == cbor::CBORTokenTag::ARRAY_START);
tokenizer->Next();
auto list = ListValue::create();
while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
while (tokenizer->TokenTag() != cbor::CBORTokenTag::STOP) {
// Error::CBOR_UNEXPECTED_EOF_IN_ARRAY
if (tokenizer->TokenTag() == CBORTokenTag::DONE) return nullptr;
if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr;
if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE) return nullptr;
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// Parse value.
auto value = parseValue(stack_depth, tokenizer);
if (!value) return nullptr;
......@@ -91,51 +91,51 @@ std::unique_ptr<ListValue> parseArray(int32_t stack_depth, CBORTokenizer* tokeni
}
std::unique_ptr<Value> parseValue(
int32_t stack_depth, CBORTokenizer* tokenizer) {
int32_t stack_depth, cbor::CBORTokenizer* tokenizer) {
// Error::CBOR_STACK_LIMIT_EXCEEDED
if (stack_depth > kStackLimitValues) return nullptr;
// Skip past the envelope to get to what's inside.
if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE)
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
switch (tokenizer->TokenTag()) {
case CBORTokenTag::ERROR_VALUE:
case cbor::CBORTokenTag::ERROR_VALUE:
return nullptr;
case CBORTokenTag::DONE:
case cbor::CBORTokenTag::DONE:
// Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE
return nullptr;
case CBORTokenTag::TRUE_VALUE: {
case cbor::CBORTokenTag::TRUE_VALUE: {
std::unique_ptr<Value> value = FundamentalValue::create(true);
tokenizer->Next();
return value;
}
case CBORTokenTag::FALSE_VALUE: {
case cbor::CBORTokenTag::FALSE_VALUE: {
std::unique_ptr<Value> value = FundamentalValue::create(false);
tokenizer->Next();
return value;
}
case CBORTokenTag::NULL_VALUE: {
case cbor::CBORTokenTag::NULL_VALUE: {
std::unique_ptr<Value> value = FundamentalValue::null();
tokenizer->Next();
return value;
}
case CBORTokenTag::INT32: {
case cbor::CBORTokenTag::INT32: {
std::unique_ptr<Value> value = FundamentalValue::create(tokenizer->GetInt32());
tokenizer->Next();
return value;
}
case CBORTokenTag::DOUBLE: {
case cbor::CBORTokenTag::DOUBLE: {
std::unique_ptr<Value> value = FundamentalValue::create(tokenizer->GetDouble());
tokenizer->Next();
return value;
}
case CBORTokenTag::STRING8: {
case cbor::CBORTokenTag::STRING8: {
span<uint8_t> str = tokenizer->GetString8();
std::unique_ptr<Value> value =
StringValue::create(StringUtil::fromUTF8(str.data(), str.size()));
tokenizer->Next();
return value;
}
case CBORTokenTag::STRING16: {
case cbor::CBORTokenTag::STRING16: {
span<uint8_t> wire = tokenizer->GetString16WireRep();
DCHECK_EQ(wire.size() & 1, 0);
std::unique_ptr<Value> value = StringValue::create(StringUtil::fromUTF16(
......@@ -143,14 +143,14 @@ std::unique_ptr<Value> parseValue(
tokenizer->Next();
return value;
}
case CBORTokenTag::BINARY: {
case cbor::CBORTokenTag::BINARY: {
span<uint8_t> payload = tokenizer->GetBinary();
tokenizer->Next();
return BinaryValue::create(Binary::fromSpan(payload.data(), payload.size()));
}
case CBORTokenTag::MAP_START:
case cbor::CBORTokenTag::MAP_START:
return parseMap(stack_depth + 1, tokenizer);
case CBORTokenTag::ARRAY_START:
case cbor::CBORTokenTag::ARRAY_START:
return parseArray(stack_depth + 1, tokenizer);
default:
// Error::CBOR_UNSUPPORTED_VALUE
......@@ -162,22 +162,22 @@ std::unique_ptr<Value> parseValue(
// ParseArray may only be called after an indefinite length array has been
// detected.
std::unique_ptr<DictionaryValue> parseMap(
int32_t stack_depth, CBORTokenizer* tokenizer) {
int32_t stack_depth, cbor::CBORTokenizer* tokenizer) {
auto dict = DictionaryValue::create();
tokenizer->Next();
while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
if (tokenizer->TokenTag() == CBORTokenTag::DONE) {
while (tokenizer->TokenTag() != cbor::CBORTokenTag::STOP) {
if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE) {
// Error::CBOR_UNEXPECTED_EOF_IN_MAP
return nullptr;
}
if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr;
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// Parse key.
String key;
if (tokenizer->TokenTag() == CBORTokenTag::STRING8) {
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) {
span<uint8_t> key_span = tokenizer->GetString8();
key = StringUtil::fromUTF8(key_span.data(), key_span.size());
tokenizer->Next();
} else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) {
} else if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) {
return nullptr; // STRING16 not supported yet.
} else {
// Error::CBOR_INVALID_MAP_KEY
......@@ -202,22 +202,21 @@ std::unique_ptr<Value> Value::parseBinary(const uint8_t* data, size_t size) {
if (bytes.empty()) return nullptr;
// Error::CBOR_INVALID_START_BYTE
// TODO(johannes): EncodeInitialByteForEnvelope() method.
if (bytes[0] != 0xd8) return nullptr;
if (bytes[0] != cbor::InitialByteForEnvelope()) return nullptr;
CBORTokenizer tokenizer(bytes);
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr;
cbor::CBORTokenizer tokenizer(bytes);
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// We checked for the envelope start byte above, so the tokenizer
// must agree here, since it's not an error.
DCHECK(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE);
DCHECK(tokenizer.TokenTag() == cbor::CBORTokenTag::ENVELOPE);
tokenizer.EnterEnvelope();
// Error::MAP_START_EXPECTED
if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) return nullptr;
if (tokenizer.TokenTag() != cbor::CBORTokenTag::MAP_START) return nullptr;
std::unique_ptr<Value> result = parseMap(/*stack_depth=*/1, &tokenizer);
if (!result) return nullptr;
if (tokenizer.TokenTag() == CBORTokenTag::DONE) return result;
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr;
if (tokenizer.TokenTag() == cbor::CBORTokenTag::DONE) return result;
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr;
// Error::CBOR_TRAILING_JUNK
return nullptr;
}
......@@ -255,7 +254,7 @@ void Value::writeJSON(StringBuilder* output) const
void Value::writeBinary(std::vector<uint8_t>* bytes) const {
DCHECK(m_type == TypeNull);
bytes->push_back(EncodeNull());
bytes->push_back(cbor::EncodeNull());
}
std::unique_ptr<Value> Value::clone() const
......@@ -332,13 +331,13 @@ void FundamentalValue::writeJSON(StringBuilder* output) const
void FundamentalValue::writeBinary(std::vector<uint8_t>* bytes) const {
switch (type()) {
case TypeDouble:
EncodeDouble(m_doubleValue, bytes);
cbor::EncodeDouble(m_doubleValue, bytes);
return;
case TypeInteger:
EncodeInt32(m_integerValue, bytes);
cbor::EncodeInt32(m_integerValue, bytes);
return;
case TypeBoolean:
bytes->push_back(m_boolValue ? EncodeTrue() : EncodeFalse());
bytes->push_back(m_boolValue ? cbor::EncodeTrue() : cbor::EncodeFalse());
return;
default:
DCHECK(false);
......@@ -380,20 +379,20 @@ namespace {
// have LATIN1 on the wire, so we call EncodeFromLatin1 which
// transcodes to UTF8 if needed.
void EncodeString(const String& s, std::vector<uint8_t>* out) {
if (StringUtil::CharactersLatin1(s)) {
EncodeFromLatin1(span<uint8_t>(StringUtil::CharactersLatin1(s),
StringUtil::CharacterCount(s)),
out);
if (StringUtil::CharacterCount(s) == 0) {
cbor::EncodeString8(span<uint8_t>(nullptr, 0), out); // Empty string.
} else if (StringUtil::CharactersLatin1(s)) {
cbor::EncodeFromLatin1(span<uint8_t>(StringUtil::CharactersLatin1(s),
StringUtil::CharacterCount(s)),
out);
} else if (StringUtil::CharactersUTF16(s)) {
EncodeFromUTF16(span<uint16_t>(StringUtil::CharactersUTF16(s),
StringUtil::CharacterCount(s)),
out);
cbor::EncodeFromUTF16(span<uint16_t>(StringUtil::CharactersUTF16(s),
StringUtil::CharacterCount(s)),
out);
} else if (StringUtil::CharactersUTF8(s)) {
EncodeString8(span<uint8_t>(StringUtil::CharactersUTF8(s),
StringUtil::CharacterCount(s)),
out);
} else {
EncodeString8(span<uint8_t>(nullptr, 0), out); // Empty string.
cbor::EncodeString8(span<uint8_t>(StringUtil::CharactersUTF8(s),
StringUtil::CharacterCount(s)),
out);
}
}
} // namespace
......@@ -420,7 +419,8 @@ void BinaryValue::writeJSON(StringBuilder* output) const
}
void BinaryValue::writeBinary(std::vector<uint8_t>* bytes) const {
EncodeBinary(span<uint8_t>(m_binaryValue.data(), m_binaryValue.size()), bytes);
cbor::EncodeBinary(span<uint8_t>(m_binaryValue.data(),
m_binaryValue.size()), bytes);
}
std::unique_ptr<Value> BinaryValue::clone() const
......@@ -583,9 +583,9 @@ void DictionaryValue::writeJSON(StringBuilder* output) const
}
void DictionaryValue::writeBinary(std::vector<uint8_t>* bytes) const {
EnvelopeEncoder encoder;
cbor::EnvelopeEncoder encoder;
encoder.EncodeStart(bytes);
bytes->push_back(EncodeIndefiniteLengthMapStart());
bytes->push_back(cbor::EncodeIndefiniteLengthMapStart());
for (size_t i = 0; i < m_order.size(); ++i) {
const String& key = m_order[i];
Dictionary::const_iterator value = m_data.find(key);
......@@ -593,7 +593,7 @@ void DictionaryValue::writeBinary(std::vector<uint8_t>* bytes) const {
EncodeString(key, bytes);
value->second->writeBinary(bytes);
}
bytes->push_back(EncodeStop());
bytes->push_back(cbor::EncodeStop());
encoder.EncodeStop(bytes);
}
......@@ -632,13 +632,13 @@ void ListValue::writeJSON(StringBuilder* output) const
}
void ListValue::writeBinary(std::vector<uint8_t>* bytes) const {
EnvelopeEncoder encoder;
cbor::EnvelopeEncoder encoder;
encoder.EncodeStart(bytes);
bytes->push_back(EncodeIndefiniteLengthArrayStart());
bytes->push_back(cbor::EncodeIndefiniteLengthArrayStart());
for (size_t i = 0; i < m_data.size(); ++i) {
m_data[i]->writeBinary(bytes);
}
bytes->push_back(EncodeStop());
bytes->push_back(cbor::EncodeStop());
encoder.EncodeStop(bytes);
}
......
......@@ -256,34 +256,34 @@ bool AppendStringValueToMapBinary(base::StringPiece in,
if (in.size() < 1 + 1 + 4 + 1 + 1)
return false;
const uint8_t* envelope = reinterpret_cast<const uint8_t*>(in.data());
if (cbor::kInitialByteForEnvelope != envelope[0])
if (cbor::InitialByteForEnvelope() != envelope[0])
return false;
if (cbor::kInitialByteFor32BitLengthByteString != envelope[1])
if (cbor::InitialByteFor32BitLengthByteString() != envelope[1])
return false;
if (cbor::kInitialByteIndefiniteLengthMap != envelope[6])
if (cbor::EncodeIndefiniteLengthMapStart() != envelope[6])
return false;
uint32_t envelope_size = ReadEnvelopeSize(envelope + 2);
if (envelope_size + 2 + 4 != in.size())
return false;
if (cbor::kStopByte != static_cast<uint8_t>(*in.rbegin()))
if (cbor::EncodeStop() != static_cast<uint8_t>(*in.rbegin()))
return false;
std::vector<uint8_t> encoded_entry;
encoded_entry.reserve(1 + 4 + key.size() + 1 + 4 + value.size());
span<uint8_t> key_span(
reinterpret_cast<const uint8_t*>(key.data()), key.size());
EncodeString8(key_span, &encoded_entry);
cbor::EncodeString8(key_span, &encoded_entry);
span<uint8_t> value_span(
reinterpret_cast<const uint8_t*>(value.data()), value.size());
EncodeString8(value_span, &encoded_entry);
cbor::EncodeString8(value_span, &encoded_entry);
out->clear();
out->reserve(in.size() + encoded_entry.size());
out->append(in.begin(), in.end() - 1);
out->append(reinterpret_cast<const char*>(encoded_entry.data()),
encoded_entry.size());
out->append(1, static_cast<char>(cbor::kStopByte));
out->append(1, static_cast<char>(cbor::EncodeStop()));
std::size_t new_size = envelope_size + out->size() - in.size();
if (new_size > static_cast<std::size_t>(
std::numeric_limits<uint32_t>::max())) {
......
{# This template is generated by gen_cbor_templates.py. #}
// Generated by lib/CBOR_cpp.template.
// Generated by lib/encoding_cpp.template.
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
......@@ -7,17 +7,67 @@
#include <cassert>
#include <cstring>
#include <limits>
#include <stack>
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}
// ===== encoding/cbor.cc =====
using namespace cbor;
// ===== encoding/encoding.cc =====
namespace cbor {
namespace {
// Indicates the number of bits the "initial byte" needs to be shifted to the
// right after applying |kMajorTypeMask| to produce the major type in the
// lowermost bits.
static constexpr uint8_t kMajorTypeBitShift = 5u;
// Mask selecting the low-order 5 bits of the "initial byte", which is where
// the additional information is encoded.
static constexpr uint8_t kAdditionalInformationMask = 0x1f;
// Mask selecting the high-order 3 bits of the "initial byte", which indicates
// the major type of the encoded value.
static constexpr uint8_t kMajorTypeMask = 0xe0;
// Indicates the integer is in the following byte.
static constexpr uint8_t kAdditionalInformation1Byte = 24u;
// Indicates the integer is in the next 2 bytes.
static constexpr uint8_t kAdditionalInformation2Bytes = 25u;
// Indicates the integer is in the next 4 bytes.
static constexpr uint8_t kAdditionalInformation4Bytes = 26u;
// Indicates the integer is in the next 8 bytes.
static constexpr uint8_t kAdditionalInformation8Bytes = 27u;
// Encodes the initial byte, consisting of the |type| in the first 3 bits
// followed by 5 bits of |additional_info|.
constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) {
return (static_cast<uint8_t>(type) << kMajorTypeBitShift) |
(additional_info & kAdditionalInformationMask);
}
// TAG 24 indicates that what follows is a byte string which is
// encoded in CBOR format. We use this as a wrapper for
// maps and arrays, allowing us to skip them, because the
// byte string carries its size (byte length).
// https://tools.ietf.org/html/rfc7049#section-2.4.4.1
static constexpr uint8_t kInitialByteForEnvelope =
EncodeInitialByte(MajorType::TAG, 24);
// The initial byte for a byte string with at most 2^32 bytes
// of payload. This is used for envelope encoding, even if
// the byte string is shorter.
static constexpr uint8_t kInitialByteFor32BitLengthByteString =
EncodeInitialByte(MajorType::BYTE_STRING, 26);
// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional
// info = 31.
static constexpr uint8_t kInitialByteIndefiniteLengthArray =
EncodeInitialByte(MajorType::ARRAY, 31);
static constexpr uint8_t kInitialByteIndefiniteLengthMap =
EncodeInitialByte(MajorType::MAP, 31);
// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite
// length maps / arrays.
static constexpr uint8_t kStopByte =
EncodeInitialByte(MajorType::SIMPLE_VALUE, 31);
// See RFC 7049 Section 2.3, Table 2.
static constexpr uint8_t kEncodedTrue =
......@@ -29,32 +79,11 @@ static constexpr uint8_t kEncodedNull =
static constexpr uint8_t kInitialByteForDouble =
EncodeInitialByte(MajorType::SIMPLE_VALUE, 27);
} // namespace
uint8_t EncodeTrue() { return kEncodedTrue; }
uint8_t EncodeFalse() { return kEncodedFalse; }
uint8_t EncodeNull() { return kEncodedNull; }
uint8_t EncodeIndefiniteLengthArrayStart() {
return kInitialByteIndefiniteLengthArray;
}
uint8_t EncodeIndefiniteLengthMapStart() {
return kInitialByteIndefiniteLengthMap;
}
uint8_t EncodeStop() { return kStopByte; }
namespace {
// See RFC 7049 Table 3 and Section 2.4.4.2. This is used as a prefix for
// arbitrary binary data encoded as BYTE_STRING.
static constexpr uint8_t kExpectedConversionToBase64Tag =
EncodeInitialByte(MajorType::TAG, 22);
// When parsing CBOR, we limit recursion depth for objects and arrays
// to this constant.
static constexpr int kStackLimit = 1000;
// Writes the bytes for |v| to |out|, starting with the most significant byte.
// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
template <typename T>
......@@ -62,45 +91,7 @@ void WriteBytesMostSignificantByteFirst(T v, std::vector<uint8_t>* out) {
for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes)
out->push_back(0xff & (v >> (shift_bytes * 8)));
}
} // namespace
namespace cbor_internals {
// Writes the start of a token with |type|. The |value| may indicate the size,
// or it may be the payload if the value is an unsigned integer.
void WriteTokenStart(MajorType type, uint64_t value,
std::vector<uint8_t>* encoded) {
if (value < 24) {
// Values 0-23 are encoded directly into the additional info of the
// initial byte.
encoded->push_back(EncodeInitialByte(type, /*additional_info=*/value));
return;
}
if (value <= std::numeric_limits<uint8_t>::max()) {
// Values 24-255 are encoded with one initial byte, followed by the value.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation1Byte));
encoded->push_back(value);
return;
}
if (value <= std::numeric_limits<uint16_t>::max()) {
// Values 256-65535: 1 initial byte + 2 bytes payload.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation2Bytes));
WriteBytesMostSignificantByteFirst<uint16_t>(value, encoded);
return;
}
if (value <= std::numeric_limits<uint32_t>::max()) {
// 32 bit uint: 1 initial byte + 4 bytes payload.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation4Bytes));
WriteBytesMostSignificantByteFirst<uint32_t>(static_cast<uint32_t>(value),
encoded);
return;
}
// 64 bit uint: 1 initial byte + 8 bytes payload.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes));
WriteBytesMostSignificantByteFirst<uint64_t>(value, encoded);
}
} // namespace cbor_internals
namespace {
// Extracts sizeof(T) bytes from |in| to extract a value of type T
// (e.g. uint64_t, uint32_t, ...), most significant byte first.
// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
......@@ -114,9 +105,15 @@ T ReadBytesMostSignificantByteFirst(span<uint8_t> in) {
}
} // namespace
namespace cbor_internals {
namespace internals {
// Reads the start of a token with definitive size from |bytes|.
// |type| is the major type as specified in RFC 7049 Section 2.1.
// |value| is the payload (e.g. for MajorType::UNSIGNED) or is the size
// (e.g. for BYTE_STRING).
// If successful, returns the number of bytes read. Otherwise returns -1.
int8_t ReadTokenStart(span<uint8_t> bytes, MajorType* type, uint64_t* value) {
if (bytes.empty()) return -1;
if (bytes.empty())
return -1;
uint8_t initial_byte = bytes[0];
*type = MajorType((initial_byte & kMajorTypeMask) >> kMajorTypeBitShift);
......@@ -129,7 +126,8 @@ int8_t ReadTokenStart(span<uint8_t> bytes, MajorType* type, uint64_t* value) {
}
if (additional_information == kAdditionalInformation1Byte) {
// Values 24-255 are encoded with one initial byte, followed by the value.
if (bytes.size() < 2) return -1;
if (bytes.size() < 2)
return -1;
*value = ReadBytesMostSignificantByteFirst<uint8_t>(bytes.subspan(1));
return 2;
}
......@@ -156,23 +154,96 @@ int8_t ReadTokenStart(span<uint8_t> bytes, MajorType* type, uint64_t* value) {
}
return -1;
}
} // namespace cbor_internals
using cbor_internals::WriteTokenStart;
using cbor_internals::ReadTokenStart;
// Writes the start of a token with |type|. The |value| may indicate the size,
// or it may be the payload if the value is an unsigned integer.
void WriteTokenStart(MajorType type,
uint64_t value,
std::vector<uint8_t>* encoded) {
if (value < 24) {
// Values 0-23 are encoded directly into the additional info of the
// initial byte.
encoded->push_back(EncodeInitialByte(type, /*additional_info=*/value));
return;
}
if (value <= std::numeric_limits<uint8_t>::max()) {
// Values 24-255 are encoded with one initial byte, followed by the value.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation1Byte));
encoded->push_back(value);
return;
}
if (value <= std::numeric_limits<uint16_t>::max()) {
// Values 256-65535: 1 initial byte + 2 bytes payload.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation2Bytes));
WriteBytesMostSignificantByteFirst<uint16_t>(value, encoded);
return;
}
if (value <= std::numeric_limits<uint32_t>::max()) {
// 32 bit uint: 1 initial byte + 4 bytes payload.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation4Bytes));
WriteBytesMostSignificantByteFirst<uint32_t>(static_cast<uint32_t>(value),
encoded);
return;
}
// 64 bit uint: 1 initial byte + 8 bytes payload.
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes));
WriteBytesMostSignificantByteFirst<uint64_t>(value, encoded);
}
} // namespace internals
// =============================================================================
// Detecting CBOR content
// =============================================================================
uint8_t InitialByteForEnvelope() {
return kInitialByteForEnvelope;
}
uint8_t InitialByteFor32BitLengthByteString() {
return kInitialByteFor32BitLengthByteString;
}
bool IsCBORMessage(span<uint8_t> msg) {
return msg.size() >= 6 && msg[0] == InitialByteForEnvelope() &&
msg[1] == InitialByteFor32BitLengthByteString();
}
// =============================================================================
// Encoding invidiual CBOR items
// =============================================================================
uint8_t EncodeTrue() {
return kEncodedTrue;
}
uint8_t EncodeFalse() {
return kEncodedFalse;
}
uint8_t EncodeNull() {
return kEncodedNull;
}
uint8_t EncodeIndefiniteLengthArrayStart() {
return kInitialByteIndefiniteLengthArray;
}
uint8_t EncodeIndefiniteLengthMapStart() {
return kInitialByteIndefiniteLengthMap;
}
uint8_t EncodeStop() {
return kStopByte;
}
void EncodeInt32(int32_t value, std::vector<uint8_t>* out) {
if (value >= 0) {
WriteTokenStart(MajorType::UNSIGNED, value, out);
internals::WriteTokenStart(MajorType::UNSIGNED, value, out);
} else {
uint64_t representation = static_cast<uint64_t>(-(value + 1));
WriteTokenStart(MajorType::NEGATIVE, representation, out);
internals::WriteTokenStart(MajorType::NEGATIVE, representation, out);
}
}
void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
// When emitting UTF16 characters, we always write the least significant byte
// first; this is because it's the native representation for X86.
// TODO(johannes): Implement a more efficient thing here later, e.g.
......@@ -189,14 +260,15 @@ void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
}
void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out) {
WriteTokenStart(MajorType::STRING, static_cast<uint64_t>(in.size_bytes()),
out);
internals::WriteTokenStart(MajorType::STRING,
static_cast<uint64_t>(in.size_bytes()), out);
out->insert(out->end(), in.begin(), in.end());
}
void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
for (std::ptrdiff_t ii = 0; ii < latin1.size(); ++ii) {
if (latin1[ii] <= 127) continue;
if (latin1[ii] <= 127)
continue;
// If there's at least one non-ASCII char, convert to UTF8.
std::vector<uint8_t> utf8(latin1.begin(), latin1.begin() + ii);
for (; ii < latin1.size(); ++ii) {
......@@ -208,7 +280,7 @@ void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
utf8.push_back((latin1[ii] | 0x80) & 0xbf);
}
}
EncodeString8(span<uint8_t>(utf8.data(), utf8.size()), out);
EncodeString8(SpanFromVector(utf8), out);
return;
}
EncodeString8(latin1, out);
......@@ -217,19 +289,21 @@ void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out) {
// If there's at least one non-ASCII char, encode as STRING16 (UTF16).
for (uint16_t ch : utf16) {
if (ch <= 127) continue;
if (ch <= 127)
continue;
EncodeString16(utf16, out);
return;
}
// It's all US-ASCII, strip out every second byte and encode as UTF8.
WriteTokenStart(MajorType::STRING, static_cast<uint64_t>(utf16.size()), out);
internals::WriteTokenStart(MajorType::STRING,
static_cast<uint64_t>(utf16.size()), out);
out->insert(out->end(), utf16.begin(), utf16.end());
}
void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
out->push_back(kExpectedConversionToBase64Tag);
uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
out->insert(out->end(), in.begin(), in.end());
}
......@@ -254,6 +328,10 @@ void EncodeDouble(double value, std::vector<uint8_t>* out) {
WriteBytesMostSignificantByteFirst<uint64_t>(reinterpret.to_uint64, out);
}
// =============================================================================
// cbor::EnvelopeEncoder - for wrapping submessages
// =============================================================================
void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
assert(byte_size_pos_ == 0);
out->push_back(kInitialByteForEnvelope);
......@@ -269,7 +347,8 @@ bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
uint64_t byte_size = out->size() - (byte_size_pos_ + sizeof(uint32_t));
// We store exactly 4 bytes, so at most INT32MAX, with most significant
// byte first.
if (byte_size > std::numeric_limits<uint32_t>::max()) return false;
if (byte_size > std::numeric_limits<uint32_t>::max())
return false;
for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0;
--shift_bytes) {
(*out)[byte_size_pos_++] = 0xff & (byte_size >> (shift_bytes * 8));
......@@ -277,21 +356,25 @@ bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
return true;
}
// =============================================================================
// cbor::NewCBOREncoder - for encoding from a streaming parser
// =============================================================================
namespace {
class JSONToCBOREncoder : public JSONParserHandler {
class CBOREncoder : public StreamingParserHandler {
public:
JSONToCBOREncoder(std::vector<uint8_t>* out, Status* status)
CBOREncoder(std::vector<uint8_t>* out, Status* status)
: out_(out), status_(status) {
*status_ = Status();
}
void HandleObjectBegin() override {
void HandleMapBegin() override {
envelopes_.emplace_back();
envelopes_.back().EncodeStart(out_);
out_->push_back(kInitialByteIndefiniteLengthMap);
}
void HandleObjectEnd() override {
void HandleMapEnd() override {
out_->push_back(kStopByte);
assert(!envelopes_.empty());
envelopes_.back().EncodeStop(out_);
......@@ -316,21 +399,10 @@ class JSONToCBOREncoder : public JSONParserHandler {
}
void HandleString16(span<uint16_t> chars) override {
for (uint16_t ch : chars) {
if (ch >= 0x7f) {
// If there's at least one non-7bit character, we encode as UTF16.
EncodeString16(chars, out_);
return;
}
}
std::vector<uint8_t> sevenbit_chars(chars.begin(), chars.end());
EncodeString8(span<uint8_t>(sevenbit_chars.data(), sevenbit_chars.size()),
out_);
EncodeFromUTF16(chars, out_);
}
void HandleBinary(std::vector<uint8_t> bytes) override {
EncodeBinary(span<uint8_t>(bytes.data(), bytes.size()), out_);
}
void HandleBinary(span<uint8_t> bytes) override { EncodeBinary(bytes, out_); }
void HandleDouble(double value) override { EncodeDouble(value, out_); }
......@@ -359,212 +431,40 @@ class JSONToCBOREncoder : public JSONParserHandler {
};
} // namespace
std::unique_ptr<JSONParserHandler> NewJSONToCBOREncoder(
std::vector<uint8_t>* out, Status* status) {
return std::unique_ptr<JSONParserHandler>(new JSONToCBOREncoder(out, status));
std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
std::vector<uint8_t>* out,
Status* status) {
return std::unique_ptr<StreamingParserHandler>(new CBOREncoder(out, status));
}
namespace {
// Below are three parsing routines for CBOR, which cover enough
// to roundtrip JSON messages.
bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer,
JSONParserHandler* out);
bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer,
JSONParserHandler* out);
bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer,
JSONParserHandler* out);
void ParseUTF16String(CBORTokenizer* tokenizer, JSONParserHandler* out) {
std::vector<uint16_t> value;
span<uint8_t> rep = tokenizer->GetString16WireRep();
for (std::ptrdiff_t ii = 0; ii < rep.size(); ii += 2)
value.push_back((rep[ii + 1] << 8) | rep[ii]);
out->HandleString16(span<uint16_t>(value.data(), value.size()));
tokenizer->Next();
// =============================================================================
// cbor::CBORTokenizer - for parsing individual CBOR items
// =============================================================================
CBORTokenizer::CBORTokenizer(span<uint8_t> bytes) : bytes_(bytes) {
ReadNextToken(/*enter_envelope=*/false);
}
CBORTokenizer::~CBORTokenizer() {}
bool ParseUTF8String(CBORTokenizer* tokenizer, JSONParserHandler* out) {
assert(tokenizer->TokenTag() == CBORTokenTag::STRING8);
out->HandleString8(tokenizer->GetString8());
tokenizer->Next();
return true;
CBORTokenTag CBORTokenizer::TokenTag() const {
return token_tag_;
}
bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer,
JSONParserHandler* out) {
if (stack_depth > kStackLimit) {
out->HandleError(
Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos});
return false;
}
// Skip past the envelope to get to what's inside.
if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
switch (tokenizer->TokenTag()) {
case CBORTokenTag::ERROR_VALUE:
out->HandleError(tokenizer->Status());
return false;
case CBORTokenTag::DONE:
out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE,
tokenizer->Status().pos});
return false;
case CBORTokenTag::TRUE_VALUE:
out->HandleBool(true);
tokenizer->Next();
return true;
case CBORTokenTag::FALSE_VALUE:
out->HandleBool(false);
tokenizer->Next();
return true;
case CBORTokenTag::NULL_VALUE:
out->HandleNull();
tokenizer->Next();
return true;
case CBORTokenTag::INT32:
out->HandleInt32(tokenizer->GetInt32());
tokenizer->Next();
return true;
case CBORTokenTag::DOUBLE:
out->HandleDouble(tokenizer->GetDouble());
tokenizer->Next();
return true;
case CBORTokenTag::STRING8:
return ParseUTF8String(tokenizer, out);
case CBORTokenTag::STRING16:
ParseUTF16String(tokenizer, out);
return true;
case CBORTokenTag::BINARY: {
span<uint8_t> binary = tokenizer->GetBinary();
out->HandleBinary(std::vector<uint8_t>(binary.begin(), binary.end()));
tokenizer->Next();
return true;
}
case CBORTokenTag::MAP_START:
return ParseMap(stack_depth + 1, tokenizer, out);
case CBORTokenTag::ARRAY_START:
return ParseArray(stack_depth + 1, tokenizer, out);
default:
out->HandleError(
Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos});
return false;
}
void CBORTokenizer::Next() {
if (token_tag_ == CBORTokenTag::ERROR_VALUE ||
token_tag_ == CBORTokenTag::DONE)
return;
ReadNextToken(/*enter_envelope=*/false);
}
// |bytes| must start with the indefinite length array byte, so basically,
// ParseArray may only be called after an indefinite length array has been
// detected.
bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer,
JSONParserHandler* out) {
assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START);
tokenizer->Next();
out->HandleArrayBegin();
while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
if (tokenizer->TokenTag() == CBORTokenTag::DONE) {
out->HandleError(
Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos});
return false;
}
if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) {
out->HandleError(tokenizer->Status());
return false;
}
// Parse value.
if (!ParseValue(stack_depth, tokenizer, out)) return false;
}
out->HandleArrayEnd();
tokenizer->Next();
return true;
void CBORTokenizer::EnterEnvelope() {
assert(token_tag_ == CBORTokenTag::ENVELOPE);
ReadNextToken(/*enter_envelope=*/true);
}
// |bytes| must start with the indefinite length array byte, so basically,
// ParseArray may only be called after an indefinite length array has been
// detected.
bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer,
JSONParserHandler* out) {
assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START);
out->HandleObjectBegin();
tokenizer->Next();
while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
if (tokenizer->TokenTag() == CBORTokenTag::DONE) {
out->HandleError(
Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos});
return false;
}
if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) {
out->HandleError(tokenizer->Status());
return false;
}
// Parse key.
if (tokenizer->TokenTag() == CBORTokenTag::STRING8) {
if (!ParseUTF8String(tokenizer, out)) return false;
} else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) {
ParseUTF16String(tokenizer, out);
} else {
out->HandleError(
Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos});
return false;
}
// Parse value.
if (!ParseValue(stack_depth, tokenizer, out)) return false;
}
out->HandleObjectEnd();
tokenizer->Next();
return true;
Status CBORTokenizer::Status() const {
return status_;
}
} // namespace
void ParseCBOR(span<uint8_t> bytes, JSONParserHandler* json_out) {
if (bytes.empty()) {
json_out->HandleError(Status{Error::CBOR_NO_INPUT, 0});
return;
}
if (bytes[0] != kInitialByteForEnvelope) {
json_out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0});
return;
}
CBORTokenizer tokenizer(bytes);
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) {
json_out->HandleError(tokenizer.Status());
return;
}
// We checked for the envelope start byte above, so the tokenizer
// must agree here, since it's not an error.
assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE);
tokenizer.EnterEnvelope();
if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) {
json_out->HandleError(
Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos});
return;
}
if (!ParseMap(/*stack_depth=*/1, &tokenizer, json_out)) return;
if (tokenizer.TokenTag() == CBORTokenTag::DONE) return;
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) {
json_out->HandleError(tokenizer.Status());
return;
}
json_out->HandleError(
Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos});
}
CBORTokenizer::CBORTokenizer(span<uint8_t> bytes) : bytes_(bytes) {
ReadNextToken(/*enter_envelope=*/false);
}
CBORTokenizer::~CBORTokenizer() {}
CBORTokenTag CBORTokenizer::TokenTag() const { return token_tag_; }
void CBORTokenizer::Next() {
if (token_tag_ == CBORTokenTag::ERROR_VALUE || token_tag_ == CBORTokenTag::DONE)
return;
ReadNextToken(/*enter_envelope=*/false);
}
void CBORTokenizer::EnterEnvelope() {
assert(token_tag_ == CBORTokenTag::ENVELOPE);
ReadNextToken(/*enter_envelope=*/true);
}
Status CBORTokenizer::Status() const { return status_; }
int32_t CBORTokenizer::GetInt32() const {
assert(token_tag_ == CBORTokenTag::INT32);
......@@ -604,6 +504,12 @@ span<uint8_t> CBORTokenizer::GetBinary() const {
return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
}
span<uint8_t> CBORTokenizer::GetEnvelopeContents() const {
assert(token_tag_ == CBORTokenTag::ENVELOPE);
auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length);
}
void CBORTokenizer::ReadNextToken(bool enter_envelope) {
if (enter_envelope) {
status_.pos += kEncodedEnvelopeHeaderSize;
......@@ -636,9 +542,9 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
SetToken(CBORTokenTag::NULL_VALUE, 1);
return;
case kExpectedConversionToBase64Tag: { // BINARY
int8_t bytes_read =
ReadTokenStart(bytes_.subspan(status_.pos + 1), &token_start_type_,
&token_start_internal_value_);
int8_t bytes_read = internals::ReadTokenStart(
bytes_.subspan(status_.pos + 1), &token_start_type_,
&token_start_internal_value_);
int64_t token_byte_length = 1 + bytes_read + token_start_internal_value_;
if (-1 == bytes_read || token_start_type_ != MajorType::BYTE_STRING ||
status_.pos + token_byte_length > bytes_.size()) {
......@@ -678,16 +584,15 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
return;
}
auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
SetToken(CBORTokenTag::ENVELOPE,
kEncodedEnvelopeHeaderSize + length);
SetToken(CBORTokenTag::ENVELOPE, kEncodedEnvelopeHeaderSize + length);
return;
}
default: {
span<uint8_t> remainder =
bytes_.subspan(status_.pos, bytes_.size() - status_.pos);
assert(!remainder.empty());
int8_t token_start_length = ReadTokenStart(remainder, &token_start_type_,
&token_start_internal_value_);
int8_t token_start_length = internals::ReadTokenStart(
remainder, &token_start_type_, &token_start_internal_value_);
bool success = token_start_length != -1;
switch (token_start_type_) {
case MajorType::UNSIGNED: // INT32.
......@@ -713,7 +618,8 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
SetError(Error::CBOR_INVALID_STRING8);
return;
}
auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
auto length =
static_cast<std::ptrdiff_t>(token_start_internal_value_);
SetToken(CBORTokenTag::STRING8, token_start_length + length);
return;
}
......@@ -726,7 +632,8 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
SetError(Error::CBOR_INVALID_STRING16);
return;
}
auto length = static_cast<std::ptrdiff_t>(token_start_internal_value_);
auto length =
static_cast<std::ptrdiff_t>(token_start_internal_value_);
SetToken(CBORTokenTag::STRING16, token_start_length + length);
return;
}
......@@ -752,75 +659,1147 @@ void CBORTokenizer::SetError(Error error) {
status_.error = error;
}
#if 0
void DumpCBOR(span<uint8_t> cbor) {
std::string indent;
CBORTokenizer tokenizer(cbor);
while (true) {
fprintf(stderr, "%s", indent.c_str());
switch (tokenizer.TokenTag()) {
case CBORTokenTag::ERROR_VALUE:
fprintf(stderr, "ERROR {status.error=%d, status.pos=%ld}\n",
tokenizer.Status().error, tokenizer.Status().pos);
return;
case CBORTokenTag::DONE:
fprintf(stderr, "DONE\n");
return;
case CBORTokenTag::TRUE_VALUE:
fprintf(stderr, "TRUE_VALUE\n");
// =============================================================================
// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages
// =============================================================================
namespace {
// When parsing CBOR, we limit recursion depth for objects and arrays
// to this constant.
static constexpr int kStackLimit = 1000;
// Below are three parsing routines for CBOR, which cover enough
// to roundtrip JSON messages.
bool ParseMap(int32_t stack_depth,
CBORTokenizer* tokenizer,
StreamingParserHandler* out);
bool ParseArray(int32_t stack_depth,
CBORTokenizer* tokenizer,
StreamingParserHandler* out);
bool ParseValue(int32_t stack_depth,
CBORTokenizer* tokenizer,
StreamingParserHandler* out);
void ParseUTF16String(CBORTokenizer* tokenizer, StreamingParserHandler* out) {
std::vector<uint16_t> value;
span<uint8_t> rep = tokenizer->GetString16WireRep();
for (std::ptrdiff_t ii = 0; ii < rep.size(); ii += 2)
value.push_back((rep[ii + 1] << 8) | rep[ii]);
out->HandleString16(span<uint16_t>(value.data(), value.size()));
tokenizer->Next();
}
bool ParseUTF8String(CBORTokenizer* tokenizer, StreamingParserHandler* out) {
assert(tokenizer->TokenTag() == CBORTokenTag::STRING8);
out->HandleString8(tokenizer->GetString8());
tokenizer->Next();
return true;
}
bool ParseValue(int32_t stack_depth,
CBORTokenizer* tokenizer,
StreamingParserHandler* out) {
if (stack_depth > kStackLimit) {
out->HandleError(
Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos});
return false;
}
// Skip past the envelope to get to what's inside.
if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
switch (tokenizer->TokenTag()) {
case CBORTokenTag::ERROR_VALUE:
out->HandleError(tokenizer->Status());
return false;
case CBORTokenTag::DONE:
out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE,
tokenizer->Status().pos});
return false;
case CBORTokenTag::TRUE_VALUE:
out->HandleBool(true);
tokenizer->Next();
return true;
case CBORTokenTag::FALSE_VALUE:
out->HandleBool(false);
tokenizer->Next();
return true;
case CBORTokenTag::NULL_VALUE:
out->HandleNull();
tokenizer->Next();
return true;
case CBORTokenTag::INT32:
out->HandleInt32(tokenizer->GetInt32());
tokenizer->Next();
return true;
case CBORTokenTag::DOUBLE:
out->HandleDouble(tokenizer->GetDouble());
tokenizer->Next();
return true;
case CBORTokenTag::STRING8:
return ParseUTF8String(tokenizer, out);
case CBORTokenTag::STRING16:
ParseUTF16String(tokenizer, out);
return true;
case CBORTokenTag::BINARY: {
out->HandleBinary(tokenizer->GetBinary());
tokenizer->Next();
return true;
}
case CBORTokenTag::MAP_START:
return ParseMap(stack_depth + 1, tokenizer, out);
case CBORTokenTag::ARRAY_START:
return ParseArray(stack_depth + 1, tokenizer, out);
default:
out->HandleError(
Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos});
return false;
}
}
// |bytes| must start with the indefinite length array byte, so basically,
// ParseArray may only be called after an indefinite length array has been
// detected.
bool ParseArray(int32_t stack_depth,
CBORTokenizer* tokenizer,
StreamingParserHandler* out) {
assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START);
tokenizer->Next();
out->HandleArrayBegin();
while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
if (tokenizer->TokenTag() == CBORTokenTag::DONE) {
out->HandleError(
Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos});
return false;
}
if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) {
out->HandleError(tokenizer->Status());
return false;
}
// Parse value.
if (!ParseValue(stack_depth, tokenizer, out))
return false;
}
out->HandleArrayEnd();
tokenizer->Next();
return true;
}
// |bytes| must start with the indefinite length array byte, so basically,
// ParseArray may only be called after an indefinite length array has been
// detected.
bool ParseMap(int32_t stack_depth,
CBORTokenizer* tokenizer,
StreamingParserHandler* out) {
assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START);
out->HandleMapBegin();
tokenizer->Next();
while (tokenizer->TokenTag() != CBORTokenTag::STOP) {
if (tokenizer->TokenTag() == CBORTokenTag::DONE) {
out->HandleError(
Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos});
return false;
}
if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) {
out->HandleError(tokenizer->Status());
return false;
}
// Parse key.
if (tokenizer->TokenTag() == CBORTokenTag::STRING8) {
if (!ParseUTF8String(tokenizer, out))
return false;
} else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) {
ParseUTF16String(tokenizer, out);
} else {
out->HandleError(
Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos});
return false;
}
// Parse value.
if (!ParseValue(stack_depth, tokenizer, out))
return false;
}
out->HandleMapEnd();
tokenizer->Next();
return true;
}
} // namespace
void ParseCBOR(span<uint8_t> bytes, StreamingParserHandler* out) {
if (bytes.empty()) {
out->HandleError(Status{Error::CBOR_NO_INPUT, 0});
return;
}
if (bytes[0] != kInitialByteForEnvelope) {
out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0});
return;
}
CBORTokenizer tokenizer(bytes);
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) {
out->HandleError(tokenizer.Status());
return;
}
// We checked for the envelope start byte above, so the tokenizer
// must agree here, since it's not an error.
assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE);
tokenizer.EnterEnvelope();
if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) {
out->HandleError(
Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos});
return;
}
if (!ParseMap(/*stack_depth=*/1, &tokenizer, out))
return;
if (tokenizer.TokenTag() == CBORTokenTag::DONE)
return;
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) {
out->HandleError(tokenizer.Status());
return;
}
out->HandleError(Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos});
}
} // namespace cbor
namespace json {
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
namespace {
// Prints |value| to |out| with 4 hex digits, most significant chunk first.
void PrintHex(uint16_t value, std::string* out) {
for (int ii = 3; ii >= 0; --ii) {
int four_bits = 0xf & (value >> (4 * ii));
out->append(1, four_bits + ((four_bits <= 9) ? '0' : ('a' - 10)));
}
}
// In the writer below, we maintain a stack of State instances.
// It is just enough to emit the appropriate delimiters and brackets
// in JSON.
enum class Container {
// Used for the top-level, initial state.
NONE,
// Inside a JSON object.
MAP,
// Inside a JSON array.
ARRAY
};
class State {
public:
explicit State(Container container) : container_(container) {}
void StartElement(std::string* out) {
assert(container_ != Container::NONE || size_ == 0);
if (size_ != 0) {
char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':';
out->append(1, delim);
}
++size_;
}
Container container() const { return container_; }
private:
Container container_ = Container::NONE;
int size_ = 0;
};
constexpr char kBase64Table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789+/";
void Base64Encode(const span<uint8_t>& in, std::string* out) {
// The following three cases are based on the tables in the example
// section in https://en.wikipedia.org/wiki/Base64. We process three
// input bytes at a time, emitting 4 output bytes at a time.
std::ptrdiff_t ii = 0;
// While possible, process three input bytes.
for (; ii + 3 <= in.size(); ii += 3) {
uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8) | in[ii + 2];
out->push_back(kBase64Table[(twentyfour_bits >> 18)]);
out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]);
out->push_back(kBase64Table[twentyfour_bits & 0x3f]);
}
if (ii + 2 <= in.size()) { // Process two input bytes.
uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8);
out->push_back(kBase64Table[(twentyfour_bits >> 18)]);
out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]);
out->push_back('='); // Emit padding.
return;
}
if (ii + 1 <= in.size()) { // Process a single input byte.
uint32_t twentyfour_bits = (in[ii] << 16);
out->push_back(kBase64Table[(twentyfour_bits >> 18)]);
out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
out->push_back('='); // Emit padding.
out->push_back('='); // Emit padding.
}
}
// Implements a handler for JSON parser events to emit a JSON string.
class JSONEncoder : public StreamingParserHandler {
public:
JSONEncoder(const Platform* platform, std::string* out, Status* status)
: platform_(platform), out_(out), status_(status) {
*status_ = Status();
state_.emplace(Container::NONE);
}
void HandleMapBegin() override {
if (!status_->ok())
return;
assert(!state_.empty());
state_.top().StartElement(out_);
state_.emplace(Container::MAP);
out_->append("{");
}
void HandleMapEnd() override {
if (!status_->ok())
return;
assert(state_.size() >= 2 && state_.top().container() == Container::MAP);
state_.pop();
out_->append("}");
}
void HandleArrayBegin() override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
state_.emplace(Container::ARRAY);
out_->append("[");
}
void HandleArrayEnd() override {
if (!status_->ok())
return;
assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY);
state_.pop();
out_->append("]");
}
void HandleString16(span<uint16_t> chars) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
out_->append("\"");
for (const uint16_t ch : chars) {
if (ch == '"') {
out_->append("\\\"");
} else if (ch == '\\') {
out_->append("\\\\");
} else if (ch == '\b') {
out_->append("\\b");
} else if (ch == '\f') {
out_->append("\\f");
} else if (ch == '\n') {
out_->append("\\n");
} else if (ch == '\r') {
out_->append("\\r");
} else if (ch == '\t') {
out_->append("\\t");
} else if (ch >= 32 && ch <= 126) {
out_->append(1, ch);
} else {
out_->append("\\u");
PrintHex(ch, out_);
}
}
out_->append("\"");
}
void HandleString8(span<uint8_t> chars) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
out_->append("\"");
for (std::ptrdiff_t ii = 0; ii < chars.size(); ++ii) {
uint8_t c = chars[ii];
if (c == '"') {
out_->append("\\\"");
} else if (c == '\\') {
out_->append("\\\\");
} else if (c == '\b') {
out_->append("\\b");
} else if (c == '\f') {
out_->append("\\f");
} else if (c == '\n') {
out_->append("\\n");
} else if (c == '\r') {
out_->append("\\r");
} else if (c == '\t') {
out_->append("\\t");
} else if (c >= 32 && c <= 126) {
out_->append(1, c);
} else if (c < 32) {
out_->append("\\u");
PrintHex(static_cast<uint16_t>(c), out_);
} else {
// Inspect the leading byte to figure out how long the utf8
// byte sequence is; while doing this initialize |codepoint|
// with the first few bits.
// See table in: https://en.wikipedia.org/wiki/UTF-8
// byte one is 110x xxxx -> 2 byte utf8 sequence
// byte one is 1110 xxxx -> 3 byte utf8 sequence
// byte one is 1111 0xxx -> 4 byte utf8 sequence
uint32_t codepoint;
int num_bytes_left;
if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence
num_bytes_left = 1;
codepoint = c & 0x1f;
} else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence
num_bytes_left = 2;
codepoint = c & 0x0f;
} else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence
codepoint = c & 0x07;
num_bytes_left = 3;
} else {
continue; // invalid leading byte
}
// If we have enough bytes in our input, decode the remaining ones
// belonging to this Unicode character into |codepoint|.
if (ii + num_bytes_left > chars.size())
continue;
while (num_bytes_left > 0) {
c = chars[++ii];
--num_bytes_left;
// Check the next byte is a continuation byte, that is 10xx xxxx.
if ((c & 0xc0) != 0x80)
continue;
codepoint = (codepoint << 6) | (c & 0x3f);
}
// Disallow overlong encodings for ascii characters, as these
// would include " and other characters significant to JSON
// string termination / control.
if (codepoint < 0x7f)
continue;
// Invalid in UTF8, and can't be represented in UTF16 anyway.
if (codepoint > 0x10ffff)
continue;
// So, now we transcode to UTF16,
// using the math described at https://en.wikipedia.org/wiki/UTF-16,
// for either one or two 16 bit characters.
if (codepoint < 0xffff) {
out_->append("\\u");
PrintHex(static_cast<uint16_t>(codepoint), out_);
continue;
}
codepoint -= 0x10000;
// high surrogate
out_->append("\\u");
PrintHex(static_cast<uint16_t>((codepoint >> 10) + 0xd800), out_);
// low surrogate
out_->append("\\u");
PrintHex(static_cast<uint16_t>((codepoint & 0x3ff) + 0xdc00), out_);
}
}
out_->append("\"");
}
void HandleBinary(span<uint8_t> bytes) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
out_->append("\"");
Base64Encode(bytes, out_);
out_->append("\"");
}
void HandleDouble(double value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
std::unique_ptr<char[]> str_value = platform_->DToStr(value);
// DToStr may fail to emit a 0 before the decimal dot. E.g. this is
// the case in base::NumberToString in Chromium (which is based on
// dmg_fp). So, much like
// https://cs.chromium.org/chromium/src/base/json/json_writer.cc
// we probe for this and emit the leading 0 anyway if necessary.
const char* chars = str_value.get();
if (chars[0] == '.') {
out_->append("0");
} else if (chars[0] == '-' && chars[1] == '.') {
out_->append("-0");
++chars;
}
out_->append(chars);
}
void HandleInt32(int32_t value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
out_->append(std::to_string(value));
}
void HandleBool(bool value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
out_->append(value ? "true" : "false");
}
void HandleNull() override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
out_->append("null");
}
void HandleError(Status error) override {
assert(!error.ok());
*status_ = error;
out_->clear();
}
private:
const Platform* platform_;
std::string* out_;
Status* status_;
std::stack<State> state_;
};
} // namespace
std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
std::string* out,
Status* status) {
return std::unique_ptr<StreamingParserHandler>(
new JSONEncoder(platform, out, status));
}
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON.
// =============================================================================
namespace {
const int kStackLimit = 1000;
enum Token {
ObjectBegin,
ObjectEnd,
ArrayBegin,
ArrayEnd,
StringLiteral,
Number,
BoolTrue,
BoolFalse,
NullToken,
ListSeparator,
ObjectPairSeparator,
InvalidToken,
NoInput
};
const char* const kNullString = "null";
const char* const kTrueString = "true";
const char* const kFalseString = "false";
template <typename Char>
class JsonParser {
public:
JsonParser(const Platform* platform, StreamingParserHandler* handler)
: platform_(platform), handler_(handler) {}
void Parse(const Char* start, std::size_t length) {
start_pos_ = start;
const Char* end = start + length;
const Char* tokenEnd;
ParseValue(start, end, &tokenEnd, 0);
if (tokenEnd != end) {
HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd);
}
}
private:
bool CharsToDouble(const uint16_t* chars,
std::size_t length,
double* result) {
std::string buffer;
buffer.reserve(length + 1);
for (std::size_t ii = 0; ii < length; ++ii) {
bool is_ascii = !(chars[ii] & ~0x7F);
if (!is_ascii)
return false;
buffer.push_back(static_cast<char>(chars[ii]));
}
return platform_->StrToD(buffer.c_str(), result);
}
bool CharsToDouble(const uint8_t* chars, std::size_t length, double* result) {
std::string buffer(reinterpret_cast<const char*>(chars), length);
return platform_->StrToD(buffer.c_str(), result);
}
static bool ParseConstToken(const Char* start,
const Char* end,
const Char** token_end,
const char* token) {
// |token| is \0 terminated, it's one of the constants at top of the file.
while (start < end && *token != '\0' && *start++ == *token++) {
}
if (*token != '\0')
return false;
*token_end = start;
return true;
}
static bool ReadInt(const Char* start,
const Char* end,
const Char** token_end,
bool allow_leading_zeros) {
if (start == end)
return false;
bool has_leading_zero = '0' == *start;
int length = 0;
while (start < end && '0' <= *start && *start <= '9') {
++start;
++length;
}
if (!length)
return false;
if (!allow_leading_zeros && length > 1 && has_leading_zero)
return false;
*token_end = start;
return true;
}
static bool ParseNumberToken(const Char* start,
const Char* end,
const Char** token_end) {
// We just grab the number here. We validate the size in DecodeNumber.
// According to RFC4627, a valid number is: [minus] int [frac] [exp]
if (start == end)
return false;
Char c = *start;
if ('-' == c)
++start;
if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/false))
return false;
if (start == end) {
*token_end = start;
return true;
}
// Optional fraction part
c = *start;
if ('.' == c) {
++start;
if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true))
return false;
if (start == end) {
*token_end = start;
return true;
}
c = *start;
}
// Optional exponent part
if ('e' == c || 'E' == c) {
++start;
if (start == end)
return false;
c = *start;
if ('-' == c || '+' == c) {
++start;
if (start == end)
return false;
}
if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true))
return false;
}
*token_end = start;
return true;
}
static bool ReadHexDigits(const Char* start,
const Char* end,
const Char** token_end,
int digits) {
if (end - start < digits)
return false;
for (int i = 0; i < digits; ++i) {
Char c = *start++;
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') ||
('A' <= c && c <= 'F')))
return false;
}
*token_end = start;
return true;
}
static bool ParseStringToken(const Char* start,
const Char* end,
const Char** token_end) {
while (start < end) {
Char c = *start++;
if ('\\' == c) {
if (start == end)
return false;
c = *start++;
// Make sure the escaped char is valid.
switch (c) {
case 'x':
if (!ReadHexDigits(start, end, &start, 2))
return false;
break;
case 'u':
if (!ReadHexDigits(start, end, &start, 4))
return false;
break;
case '\\':
case '/':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case '"':
break;
default:
return false;
}
} else if ('"' == c) {
*token_end = start;
return true;
}
}
return false;
}
static bool SkipComment(const Char* start,
const Char* end,
const Char** comment_end) {
if (start == end)
return false;
if (*start != '/' || start + 1 >= end)
return false;
++start;
if (*start == '/') {
// Single line comment, read to newline.
for (++start; start < end; ++start) {
if (*start == '\n' || *start == '\r') {
*comment_end = start + 1;
return true;
}
}
*comment_end = end;
// Comment reaches end-of-input, which is fine.
return true;
}
if (*start == '*') {
Char previous = '\0';
// Block comment, read until end marker.
for (++start; start < end; previous = *start++) {
if (previous == '*' && *start == '/') {
*comment_end = start + 1;
return true;
}
}
// Block comment must close before end-of-input.
return false;
}
return false;
}
static bool IsSpaceOrNewLine(Char c) {
// \v = vertial tab; \f = form feed page break.
return c == ' ' || c == '\n' || c == '\v' || c == '\f' || c == '\r' ||
c == '\t';
}
static void SkipWhitespaceAndComments(const Char* start,
const Char* end,
const Char** whitespace_end) {
while (start < end) {
if (IsSpaceOrNewLine(*start)) {
++start;
} else if (*start == '/') {
const Char* comment_end;
if (!SkipComment(start, end, &comment_end))
break;
start = comment_end;
} else {
break;
case CBORTokenTag::FALSE_VALUE:
fprintf(stderr, "FALSE_VALUE\n");
}
}
*whitespace_end = start;
}
static Token ParseToken(const Char* start,
const Char* end,
const Char** tokenStart,
const Char** token_end) {
SkipWhitespaceAndComments(start, end, tokenStart);
start = *tokenStart;
if (start == end)
return NoInput;
switch (*start) {
case 'n':
if (ParseConstToken(start, end, token_end, kNullString))
return NullToken;
break;
case CBORTokenTag::NULL_VALUE:
fprintf(stderr, "NULL_VALUE\n");
case 't':
if (ParseConstToken(start, end, token_end, kTrueString))
return BoolTrue;
break;
case CBORTokenTag::INT32:
fprintf(stderr, "INT32 [%d]\n", tokenizer.GetInt32());
case 'f':
if (ParseConstToken(start, end, token_end, kFalseString))
return BoolFalse;
break;
case CBORTokenTag::DOUBLE:
fprintf(stderr, "DOUBLE [%lf]\n", tokenizer.GetDouble());
case '[':
*token_end = start + 1;
return ArrayBegin;
case ']':
*token_end = start + 1;
return ArrayEnd;
case ',':
*token_end = start + 1;
return ListSeparator;
case '{':
*token_end = start + 1;
return ObjectBegin;
case '}':
*token_end = start + 1;
return ObjectEnd;
case ':':
*token_end = start + 1;
return ObjectPairSeparator;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
if (ParseNumberToken(start, end, token_end))
return Number;
break;
case CBORTokenTag::STRING8: {
span<uint8_t> v = tokenizer.GetString8();
std::string t(v.begin(), v.end());
fprintf(stderr, "STRING8 [%s]\n", t.c_str());
case '"':
if (ParseStringToken(start + 1, end, token_end))
return StringLiteral;
break;
}
return InvalidToken;
}
static int HexToInt(Char c) {
if ('0' <= c && c <= '9')
return c - '0';
if ('A' <= c && c <= 'F')
return c - 'A' + 10;
if ('a' <= c && c <= 'f')
return c - 'a' + 10;
assert(false); // Unreachable.
return 0;
}
static bool DecodeString(const Char* start,
const Char* end,
std::vector<uint16_t>* output) {
if (start == end)
return true;
if (start > end)
return false;
output->reserve(end - start);
while (start < end) {
uint16_t c = *start++;
// If the |Char| we're dealing with is really a byte, then
// we have utf8 here, and we need to check for multibyte characters
// and transcode them to utf16 (either one or two utf16 chars).
if (sizeof(Char) == sizeof(uint8_t) && c >= 0x7f) {
// Inspect the leading byte to figure out how long the utf8
// byte sequence is; while doing this initialize |codepoint|
// with the first few bits.
// See table in: https://en.wikipedia.org/wiki/UTF-8
// byte one is 110x xxxx -> 2 byte utf8 sequence
// byte one is 1110 xxxx -> 3 byte utf8 sequence
// byte one is 1111 0xxx -> 4 byte utf8 sequence
uint32_t codepoint;
int num_bytes_left;
if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence
num_bytes_left = 1;
codepoint = c & 0x1f;
} else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence
num_bytes_left = 2;
codepoint = c & 0x0f;
} else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence
codepoint = c & 0x07;
num_bytes_left = 3;
} else {
return false; // invalid leading byte
}
// If we have enough bytes in our inpput, decode the remaining ones
// belonging to this Unicode character into |codepoint|.
if (start + num_bytes_left > end)
return false;
while (num_bytes_left > 0) {
c = *start++;
--num_bytes_left;
// Check the next byte is a continuation byte, that is 10xx xxxx.
if ((c & 0xc0) != 0x80)
return false;
codepoint = (codepoint << 6) | (c & 0x3f);
}
// Disallow overlong encodings for ascii characters, as these
// would include " and other characters significant to JSON
// string termination / control.
if (codepoint < 0x7f)
return false;
// Invalid in UTF8, and can't be represented in UTF16 anyway.
if (codepoint > 0x10ffff)
return false;
// So, now we transcode to UTF16,
// using the math described at https://en.wikipedia.org/wiki/UTF-16,
// for either one or two 16 bit characters.
if (codepoint < 0xffff) {
output->push_back(codepoint);
continue;
}
codepoint -= 0x10000;
output->push_back((codepoint >> 10) + 0xd800); // high surrogate
output->push_back((codepoint & 0x3ff) + 0xdc00); // low surrogate
continue;
}
case CBORTokenTag::STRING16: {
span<uint8_t> v = tokenizer.GetString16WireRep();
std::string t(v.begin(), v.end());
fprintf(stderr, "STRING16 [%s]\n", t.c_str());
break;
if ('\\' != c) {
output->push_back(c);
continue;
}
if (start == end)
return false;
c = *start++;
if (c == 'x') {
// \x is not supported.
return false;
}
case CBORTokenTag::BINARY: {
span<uint8_t> v = tokenizer.GetBinary();
std::string t(v.begin(), v.end());
fprintf(stderr, "BINARY [%s]\n", t.c_str());
switch (c) {
case '"':
case '/':
case '\\':
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'v':
c = '\v';
break;
case 'u':
c = (HexToInt(*start) << 12) + (HexToInt(*(start + 1)) << 8) +
(HexToInt(*(start + 2)) << 4) + HexToInt(*(start + 3));
start += 4;
break;
default:
return false;
}
output->push_back(c);
}
return true;
}
void ParseValue(const Char* start,
const Char* end,
const Char** value_token_end,
int depth) {
if (depth > kStackLimit) {
HandleError(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, start);
return;
}
const Char* token_start;
const Char* token_end;
Token token = ParseToken(start, end, &token_start, &token_end);
switch (token) {
case NoInput:
HandleError(Error::JSON_PARSER_NO_INPUT, token_start);
return;
case InvalidToken:
HandleError(Error::JSON_PARSER_INVALID_TOKEN, token_start);
return;
case NullToken:
handler_->HandleNull();
break;
case BoolTrue:
handler_->HandleBool(true);
break;
case BoolFalse:
handler_->HandleBool(false);
break;
case Number: {
double value;
if (!CharsToDouble(token_start, token_end - token_start, &value)) {
HandleError(Error::JSON_PARSER_INVALID_NUMBER, token_start);
return;
}
if (value >= std::numeric_limits<int32_t>::min() &&
value <= std::numeric_limits<int32_t>::max() &&
static_cast<int32_t>(value) == value)
handler_->HandleInt32(static_cast<int32_t>(value));
else
handler_->HandleDouble(value);
break;
}
case CBORTokenTag::MAP_START:
fprintf(stderr, "MAP_START\n");
indent += " ";
case StringLiteral: {
std::vector<uint16_t> value;
bool ok = DecodeString(token_start + 1, token_end - 1, &value);
if (!ok) {
HandleError(Error::JSON_PARSER_INVALID_STRING, token_start);
return;
}
handler_->HandleString16(span<uint16_t>(value.data(), value.size()));
break;
case CBORTokenTag::ARRAY_START:
fprintf(stderr, "ARRAY_START\n");
indent += " ";
}
case ArrayBegin: {
handler_->HandleArrayBegin();
start = token_end;
token = ParseToken(start, end, &token_start, &token_end);
while (token != ArrayEnd) {
ParseValue(start, end, &token_end, depth + 1);
if (error_)
return;
// After a list value, we expect a comma or the end of the list.
start = token_end;
token = ParseToken(start, end, &token_start, &token_end);
if (token == ListSeparator) {
start = token_end;
token = ParseToken(start, end, &token_start, &token_end);
if (token == ArrayEnd) {
HandleError(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, token_start);
return;
}
} else if (token != ArrayEnd) {
// Unexpected value after list value. Bail out.
HandleError(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED,
token_start);
return;
}
}
handler_->HandleArrayEnd();
break;
case CBORTokenTag::STOP:
fprintf(stderr, "STOP\n");
indent.erase(0, 2);
}
case ObjectBegin: {
handler_->HandleMapBegin();
start = token_end;
token = ParseToken(start, end, &token_start, &token_end);
while (token != ObjectEnd) {
if (token != StringLiteral) {
HandleError(Error::JSON_PARSER_STRING_LITERAL_EXPECTED,
token_start);
return;
}
std::vector<uint16_t> key;
if (!DecodeString(token_start + 1, token_end - 1, &key)) {
HandleError(Error::JSON_PARSER_INVALID_STRING, token_start);
return;
}
handler_->HandleString16(span<uint16_t>(key.data(), key.size()));
start = token_end;
token = ParseToken(start, end, &token_start, &token_end);
if (token != ObjectPairSeparator) {
HandleError(Error::JSON_PARSER_COLON_EXPECTED, token_start);
return;
}
start = token_end;
ParseValue(start, end, &token_end, depth + 1);
if (error_)
return;
start = token_end;
// After a key/value pair, we expect a comma or the end of the
// object.
token = ParseToken(start, end, &token_start, &token_end);
if (token == ListSeparator) {
start = token_end;
token = ParseToken(start, end, &token_start, &token_end);
if (token == ObjectEnd) {
HandleError(Error::JSON_PARSER_UNEXPECTED_MAP_END, token_start);
return;
}
} else if (token != ObjectEnd) {
// Unexpected value after last object value. Bail out.
HandleError(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED,
token_start);
return;
}
}
handler_->HandleMapEnd();
break;
case CBORTokenTag::ENVELOPE:
fprintf(stderr, "ENVELOPE\n");
tokenizer.EnterEnvelope();
continue;
}
default:
// We got a token that's not a value.
HandleError(Error::JSON_PARSER_VALUE_EXPECTED, token_start);
return;
}
tokenizer.Next();
SkipWhitespaceAndComments(token_end, end, value_token_end);
}
void HandleError(Error error, const Char* pos) {
assert(error != Error::OK);
if (!error_) {
handler_->HandleError(Status{error, pos - start_pos_});
error_ = true;
}
}
const Char* start_pos_ = nullptr;
bool error_ = false;
const Platform* platform_;
StreamingParserHandler* handler_;
};
} // namespace
void ParseJSON(const Platform* platform,
span<uint8_t> chars,
StreamingParserHandler* handler) {
JsonParser<uint8_t> parser(platform, handler);
parser.Parse(chars.data(), chars.size());
}
#endif
void ParseJSON(const Platform* platform,
span<uint16_t> chars,
StreamingParserHandler* handler) {
JsonParser<uint16_t> parser(platform, handler);
parser.Parse(chars.data(), chars.size());
}
} // namespace json
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
......
{# This template is generated by gen_cbor_templates.py. #}
// Generated by lib/CBOR_h.template.
// Generated by lib/encoding_h.template.
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef {{"_".join(config.protocol.namespace)}}_CBOR_h
#define {{"_".join(config.protocol.namespace)}}_CBOR_h
#ifndef {{"_".join(config.protocol.namespace)}}_encoding_h
#define {{"_".join(config.protocol.namespace)}}_encoding_h
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}
// ===== encoding/status.h =====
// ===== encoding/encoding.h =====
// =============================================================================
// span - sequence of bytes
// =============================================================================
// This template is similar to std::span, which will be included in C++20. Like
// std::span it uses ptrdiff_t, which is signed (and thus a bit annoying
// sometimes when comparing with size_t), but other than this it's much simpler.
template <typename T>
class span {
public:
using index_type = std::ptrdiff_t;
span() : data_(nullptr), size_(0) {}
span(const T* data, index_type size) : data_(data), size_(size) {}
const T* data() const { return data_; }
const T* begin() const { return data_; }
const T* end() const { return data_ + size_; }
const T& operator[](index_type idx) const { return data_[idx]; }
span<T> subspan(index_type offset, index_type count) const {
return span(data_ + offset, count);
}
span<T> subspan(index_type offset) const {
return span(data_ + offset, size_ - offset);
}
bool empty() const { return size_ == 0; }
index_type size() const { return size_; }
index_type size_bytes() const { return size_ * sizeof(T); }
private:
const T* data_;
index_type size_;
};
template <typename T>
span<T> SpanFromVector(const std::vector<T>& v) {
return span<T>(v.data(), v.size());
}
inline span<uint8_t> SpanFromStdString(const std::string& v) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(v.data()), v.size());
}
// Error codes.
enum class Error {
......@@ -33,8 +84,8 @@ enum class Error {
JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08,
JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09,
JSON_PARSER_COLON_EXPECTED = 0x0a,
JSON_PARSER_UNEXPECTED_OBJECT_END = 0x0b,
JSON_PARSER_COMMA_OR_OBJECT_END_EXPECTED = 0x0c,
JSON_PARSER_UNEXPECTED_MAP_END = 0x0b,
JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c,
JSON_PARSER_VALUE_EXPECTED = 0x0d,
CBOR_INVALID_INT32 = 0x0e,
......@@ -69,57 +120,19 @@ struct Status {
Status() = default;
};
// ===== encoding/span.h =====
// This template is similar to std::span, which will be included in C++20. Like
// std::span it uses ptrdiff_t, which is signed (and thus a bit annoying
// sometimes when comparing with size_t), but other than this it's much simpler.
template <typename T>
class span {
// Handler interface for parser events emitted by a streaming parser.
// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder,
// json::ParseJSON.
class StreamingParserHandler {
public:
using index_type = std::ptrdiff_t;
span() : data_(nullptr), size_(0) {}
span(const T* data, index_type size) : data_(data), size_(size) {}
const T* data() const { return data_; }
const T* begin() const { return data_; }
const T* end() const { return data_ + size_; }
const T& operator[](index_type idx) const { return data_[idx]; }
span<T> subspan(index_type offset, index_type count) const {
return span(data_ + offset, count);
}
span<T> subspan(index_type offset) const {
return span(data_ + offset, size_ - offset);
}
bool empty() const { return size_ == 0; }
index_type size() const { return size_; }
index_type size_bytes() const { return size_ * sizeof(T); }
private:
const T* data_;
index_type size_;
};
// ===== encoding/json_parser_handler.h =====
// Handler interface for JSON parser events. See also json_parser.h.
class JSONParserHandler {
public:
virtual ~JSONParserHandler() = default;
virtual void HandleObjectBegin() = 0;
virtual void HandleObjectEnd() = 0;
virtual ~StreamingParserHandler() = default;
virtual void HandleMapBegin() = 0;
virtual void HandleMapEnd() = 0;
virtual void HandleArrayBegin() = 0;
virtual void HandleArrayEnd() = 0;
virtual void HandleString8(span<uint8_t> chars) = 0;
virtual void HandleString16(span<uint16_t> chars) = 0;
virtual void HandleBinary(std::vector<uint8_t> bytes) = 0;
virtual void HandleBinary(span<uint8_t> bytes) = 0;
virtual void HandleDouble(double value) = 0;
virtual void HandleInt32(int32_t value) = 0;
virtual void HandleBool(bool value) = 0;
......@@ -132,97 +145,7 @@ class JSONParserHandler {
virtual void HandleError(Status error) = 0;
};
// ===== encoding/cbor_internals.h =====
namespace cbor {
enum class MajorType;
}
namespace cbor_internals {
// Reads the start of a token with definitive size from |bytes|.
// |type| is the major type as specified in RFC 7049 Section 2.1.
// |value| is the payload (e.g. for MajorType::UNSIGNED) or is the size
// (e.g. for BYTE_STRING).
// If successful, returns the number of bytes read. Otherwise returns -1.
int8_t ReadTokenStart(span<uint8_t> bytes, cbor::MajorType* type,
uint64_t* value);
// Writes the start of a token with |type|. The |value| may indicate the size,
// or it may be the payload if the value is an unsigned integer.
void WriteTokenStart(cbor::MajorType type, uint64_t value,
std::vector<uint8_t>* encoded);
} // namespace cbor_internals
// ===== encoding/cbor.h =====
namespace cbor {
// The major types from RFC 7049 Section 2.1.
enum class MajorType {
UNSIGNED = 0,
NEGATIVE = 1,
BYTE_STRING = 2,
STRING = 3,
ARRAY = 4,
MAP = 5,
TAG = 6,
SIMPLE_VALUE = 7
};
// Indicates the number of bits the "initial byte" needs to be shifted to the
// right after applying |kMajorTypeMask| to produce the major type in the
// lowermost bits.
static constexpr uint8_t kMajorTypeBitShift = 5u;
// Mask selecting the low-order 5 bits of the "initial byte", which is where
// the additional information is encoded.
static constexpr uint8_t kAdditionalInformationMask = 0x1f;
// Mask selecting the high-order 3 bits of the "initial byte", which indicates
// the major type of the encoded value.
static constexpr uint8_t kMajorTypeMask = 0xe0;
// Indicates the integer is in the following byte.
static constexpr uint8_t kAdditionalInformation1Byte = 24u;
// Indicates the integer is in the next 2 bytes.
static constexpr uint8_t kAdditionalInformation2Bytes = 25u;
// Indicates the integer is in the next 4 bytes.
static constexpr uint8_t kAdditionalInformation4Bytes = 26u;
// Indicates the integer is in the next 8 bytes.
static constexpr uint8_t kAdditionalInformation8Bytes = 27u;
// Encodes the initial byte, consisting of the |type| in the first 3 bits
// followed by 5 bits of |additional_info|.
constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) {
return (static_cast<uint8_t>(type) << kMajorTypeBitShift) |
(additional_info & kAdditionalInformationMask);
}
// TAG 24 indicates that what follows is a byte string which is
// encoded in CBOR format. We use this as a wrapper for
// maps and arrays, allowing us to skip them, because the
// byte string carries its size (byte length).
// https://tools.ietf.org/html/rfc7049#section-2.4.4.1
static constexpr uint8_t kInitialByteForEnvelope =
EncodeInitialByte(MajorType::TAG, 24);
// The initial byte for a byte string with at most 2^32 bytes
// of payload. This is used for envelope encoding, even if
// the byte string is shorter.
static constexpr uint8_t kInitialByteFor32BitLengthByteString =
EncodeInitialByte(MajorType::BYTE_STRING, 26);
// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional
// info = 31.
static constexpr uint8_t kInitialByteIndefiniteLengthArray =
EncodeInitialByte(MajorType::ARRAY, 31);
static constexpr uint8_t kInitialByteIndefiniteLengthMap =
EncodeInitialByte(MajorType::MAP, 31);
// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite
// length maps / arrays.
static constexpr uint8_t kStopByte =
EncodeInitialByte(MajorType::SIMPLE_VALUE, 31);
} // namespace cbor
// The binary encoding for the inspector protocol follows the CBOR specification
// (RFC 7049). Additional constraints:
// - Only indefinite length maps and arrays are supported.
......@@ -239,12 +162,38 @@ static constexpr uint8_t kStopByte =
// as CBOR BYTE_STRING (major type 2). For such strings, the number of
// bytes encoded must be even.
// - UTF8 strings (major type 3) are supported.
// - 7 bit US-ASCII strings must always be encoded as UTF8 strings, not
// - 7 bit US-ASCII strings must always be encoded as UTF8 strings, never
// as UTF16 strings.
// - Arbitrary byte arrays, in the inspector protocol called 'binary',
// are encoded as BYTE_STRING (major type 2), prefixed with a byte
// indicating base64 when rendered as JSON.
// =============================================================================
// Detecting CBOR content
// =============================================================================
// The first byte for an envelope, which we use for wrapping dictionaries
// and arrays; and the byte that indicates a byte string with 32 bit length.
// These two bytes start an envelope, and thereby also any CBOR message
// produced or consumed by this protocol. See also |EnvelopeEncoder| below.
uint8_t InitialByteForEnvelope();
uint8_t InitialByteFor32BitLengthByteString();
// Checks whether |msg| is a cbor message.
bool IsCBORMessage(span<uint8_t> msg);
// =============================================================================
// Encoding individual CBOR items
// =============================================================================
// Some constants for CBOR tokens that only take a single byte on the wire.
uint8_t EncodeTrue();
uint8_t EncodeFalse();
uint8_t EncodeNull();
uint8_t EncodeIndefiniteLengthArrayStart();
uint8_t EncodeIndefiniteLengthMapStart();
uint8_t EncodeStop();
// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE|
// (major type 1) iff < 0.
void EncodeInt32(int32_t value, std::vector<uint8_t>* out);
......@@ -275,13 +224,9 @@ void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out);
// with additional info = 27, followed by 8 bytes in big endian.
void EncodeDouble(double value, std::vector<uint8_t>* out);
// Some constants for CBOR tokens that only take a single byte on the wire.
uint8_t EncodeTrue();
uint8_t EncodeFalse();
uint8_t EncodeNull();
uint8_t EncodeIndefiniteLengthArrayStart();
uint8_t EncodeIndefiniteLengthMapStart();
uint8_t EncodeStop();
// =============================================================================
// cbor::EnvelopeEncoder - for wrapping submessages
// =============================================================================
// An envelope indicates the byte length of a wrapped item.
// We use this for maps and array, which allows the decoder
......@@ -304,20 +249,23 @@ class EnvelopeEncoder {
std::size_t byte_size_pos_ = 0;
};
// This can be used to convert from JSON to CBOR, by passing the
// return value to the routines in json_parser.h. The handler will encode into
// |out|, and iff an error occurs it will set |status| to an error and clear
// |out|. Otherwise, |status.ok()| will be |true|.
std::unique_ptr<JSONParserHandler> NewJSONToCBOREncoder(
std::vector<uint8_t>* out, Status* status);
// =============================================================================
// cbor::NewCBOREncoder - for encoding from a streaming parser
// =============================================================================
// Parses a CBOR encoded message from |bytes|, sending JSON events to
// |json_out|. If an error occurs, sends |out->HandleError|, and parsing stops.
// The client is responsible for discarding the already received information in
// that case.
void ParseCBOR(span<uint8_t> bytes, JSONParserHandler* json_out);
// This can be used to convert to CBOR, by passing the return value to a parser
// that drives it. The handler will encode into |out|, and iff an error occurs
// it will set |status| to an error and clear |out|. Otherwise, |status.ok()|
// will be |true|.
std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
std::vector<uint8_t>* out,
Status* status);
// Tags for the tokens within a CBOR message that CBORStream understands.
// =============================================================================
// cbor::CBORTokenizer - for parsing individual CBOR items
// =============================================================================
// Tags for the tokens within a CBOR message that CBORTokenizer understands.
// Note that this is not the same terminology as the CBOR spec (RFC 7049),
// but rather, our adaptation. For instance, we lump unsigned and signed
// major type into INT32 here (and disallow values outside the int32_t range).
......@@ -357,6 +305,18 @@ enum class CBORTokenTag {
DONE,
};
// The major types from RFC 7049 Section 2.1.
enum class MajorType {
UNSIGNED = 0,
NEGATIVE = 1,
BYTE_STRING = 2,
STRING = 3,
ARRAY = 4,
MAP = 5,
TAG = 6,
SIMPLE_VALUE = 7
};
// CBORTokenizer segments a CBOR message, presenting the tokens therein as
// numbers, strings, etc. This is not a complete CBOR parser, but makes it much
// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse
......@@ -403,6 +363,9 @@ class CBORTokenizer {
// To be called only if ::TokenTag() == CBORTokenTag::BINARY.
span<uint8_t> GetBinary() const;
// To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
span<uint8_t> GetEnvelopeContents() const;
private:
void ReadNextToken(bool enter_envelope);
void SetToken(CBORTokenTag token, std::ptrdiff_t token_byte_length);
......@@ -412,14 +375,72 @@ class CBORTokenizer {
CBORTokenTag token_tag_;
struct Status status_;
std::ptrdiff_t token_byte_length_;
cbor::MajorType token_start_type_;
MajorType token_start_type_;
uint64_t token_start_internal_value_;
};
void DumpCBOR(span<uint8_t> cbor);
// =============================================================================
// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages
// =============================================================================
// Parses a CBOR encoded message from |bytes|, sending events to
// |out|. If an error occurs, sends |out->HandleError|, and parsing stops.
// The client is responsible for discarding the already received information in
// that case.
void ParseCBOR(span<uint8_t> bytes, StreamingParserHandler* out);
namespace internals { // Exposed only for writing tests.
int8_t ReadTokenStart(span<uint8_t> bytes,
cbor::MajorType* type,
uint64_t* value);
void WriteTokenStart(cbor::MajorType type,
uint64_t value,
std::vector<uint8_t>* encoded);
} // namespace internals
} // namespace cbor
namespace json {
// Client code must provide an instance. Implementation should delegate
// to whatever is appropriate.
class Platform {
public:
virtual ~Platform() = default;
// Parses |str| into |result|. Returns false iff there are
// leftover characters or parsing errors.
virtual bool StrToD(const char* str, double* result) const = 0;
// Prints |value| in a format suitable for JSON.
virtual std::unique_ptr<char[]> DToStr(double value) const = 0;
};
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
// Returns a handler object which will write ascii characters to |out|.
// |status->ok()| will be false iff the handler routine HandleError() is called.
// In that case, we'll stop emitting output.
// Except for calling the HandleError routine at any time, the client
// code must call the Handle* methods in an order in which they'd occur
// in valid JSON; otherwise we may crash (the code uses assert).
std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
std::string* out,
Status* status);
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON
// =============================================================================
void ParseJSON(const Platform* platform,
span<uint8_t> chars,
StreamingParserHandler* handler);
void ParseJSON(const Platform* platform,
span<uint16_t> chars,
StreamingParserHandler* handler);
} // namespace json
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
#endif // !defined({{"_".join(config.protocol.namespace)}}_CBOR_h)
#endif // !defined({{"_".join(config.protocol.namespace)}}_encoding_h)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment