Commit b67cafe7 authored by Johannes Henkel's avatar Johannes Henkel Committed by Commit Bot

[DevTools] Roll inspector_protocol (v8) (file split)

This decomposes the crdtp library into multiple files.
Since it wasn't previously rolled
it's a bit more than just that.

Upstream review: https://chromium-review.googlesource.com/c/deps/inspector_protocol/+/1907115

New Revision: d020a9e614d4a5116a7c71f288c0340e282e1a6e

Change-Id: I5c588469654bec3e933804ac706fa967c6fe57bc
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1907973
Auto-Submit: Johannes Henkel <johannes@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Yang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64902}
parent 36e812be
......@@ -65,9 +65,7 @@ config("inspector_config") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
configs = [ "../../:internal_config" ]
include_dirs = [
"../../include",
]
include_dirs = [ "../../include" ]
}
v8_header_set("inspector_test_headers") {
......@@ -97,8 +95,7 @@ v8_source_set("inspector") {
deps = [
":inspector_string_conversions",
"../..:v8_version",
"../../third_party/inspector_protocol:encoding",
"../../third_party/inspector_protocol:bindings",
"../../third_party/inspector_protocol:crdtp",
]
public_deps = [
......
......@@ -18,8 +18,6 @@ include_rules = [
"+src/debug/debug-interface.h",
"+src/debug/interface-types.h",
"+src/utils/vector.h",
"+third_party/inspector_protocol/encoding/encoding.h",
"+third_party/inspector_protocol/encoding/encoding.cc",
"+../../third_party/inspector_protocol/encoding/encoding.h",
"+../../third_party/inspector_protocol/encoding/encoding.cc",
"+third_party/inspector_protocol/crdtp",
"+../../third_party/inspector_protocol/crdtp",
]
......@@ -46,11 +46,7 @@
"string_header": "src/inspector/string-util.h"
},
"encoding_lib": {
"namespace": "v8_inspector_protocol_encoding"
},
"bindings_lib": {
"namespace": "v8_inspector_protocol_bindings"
"crdtp": {
"namespace": "v8_crdtp"
}
}
......@@ -5,21 +5,24 @@
#include "src/inspector/v8-inspector-protocol-encoding.h"
#include <cmath>
#include "../../third_party/inspector_protocol/encoding/encoding.h"
#include "../../third_party/inspector_protocol/crdtp/json.h"
#include "src/numbers/conversions.h"
#include "src/utils/vector.h"
namespace v8_inspector {
namespace {
using IPEStatus = ::v8_inspector_protocol_encoding::Status;
using ::v8_inspector_protocol_encoding::span;
using v8_crdtp::span;
using v8_crdtp::Status;
class Platform : public ::v8_inspector_protocol_encoding::json::Platform {
public:
class Platform : public v8_crdtp::json::Platform {
// Parses |str| into |result|. Returns false iff there are
// leftover characters or parsing errors.
bool StrToD(const char* str, double* result) const override {
*result = v8::internal::StringToDouble(str, v8::internal::NO_FLAGS);
return !std::isnan(*result);
}
// Prints |value| in a format suitable for JSON.
std::unique_ptr<char[]> DToStr(double value) const override {
v8::internal::ScopedVector<char> buffer(
v8::internal::kDoubleToCStringMinBufferSize);
......@@ -33,19 +36,18 @@ class Platform : public ::v8_inspector_protocol_encoding::json::Platform {
};
} // namespace
IPEStatus ConvertCBORToJSON(span<uint8_t> cbor, std::vector<uint8_t>* json) {
Status ConvertCBORToJSON(span<uint8_t> cbor, std::vector<uint8_t>* json) {
Platform platform;
return ConvertCBORToJSON(platform, cbor, json);
return v8_crdtp::json::ConvertCBORToJSON(platform, cbor, json);
}
IPEStatus ConvertJSONToCBOR(span<uint8_t> json, std::vector<uint8_t>* cbor) {
Status ConvertJSONToCBOR(span<uint8_t> json, std::vector<uint8_t>* cbor) {
Platform platform;
return ConvertJSONToCBOR(platform, json, cbor);
return v8_crdtp::json::ConvertJSONToCBOR(platform, json, cbor);
}
IPEStatus ConvertJSONToCBOR(span<uint16_t> json, std::vector<uint8_t>* cbor) {
Status ConvertJSONToCBOR(span<uint16_t> json, std::vector<uint8_t>* cbor) {
Platform platform;
return ConvertJSONToCBOR(platform, json, cbor);
return v8_crdtp::json::ConvertJSONToCBOR(platform, json, cbor);
}
} // namespace v8_inspector
......@@ -5,21 +5,19 @@
#ifndef V8_INSPECTOR_V8_INSPECTOR_PROTOCOL_ENCODING_H_
#define V8_INSPECTOR_V8_INSPECTOR_PROTOCOL_ENCODING_H_
#include "../../third_party/inspector_protocol/encoding/encoding.h"
#include "../../third_party/inspector_protocol/crdtp/span.h"
#include "../../third_party/inspector_protocol/crdtp/status.h"
namespace v8_inspector {
::v8_inspector_protocol_encoding::Status ConvertCBORToJSON(
::v8_inspector_protocol_encoding::span<uint8_t> cbor,
std::vector<uint8_t>* json);
v8_crdtp::Status ConvertCBORToJSON(v8_crdtp::span<uint8_t> cbor,
std::vector<uint8_t>* json);
::v8_inspector_protocol_encoding::Status ConvertJSONToCBOR(
::v8_inspector_protocol_encoding::span<uint8_t> json,
std::vector<uint8_t>* cbor);
v8_crdtp::Status ConvertJSONToCBOR(v8_crdtp::span<uint8_t> json,
std::vector<uint8_t>* cbor);
::v8_inspector_protocol_encoding::Status ConvertJSONToCBOR(
::v8_inspector_protocol_encoding::span<uint16_t> json,
std::vector<uint8_t>* cbor);
v8_crdtp::Status ConvertJSONToCBOR(v8_crdtp::span<uint16_t> json,
std::vector<uint8_t>* cbor);
} // namespace v8_inspector
......
......@@ -24,9 +24,9 @@
namespace v8_inspector {
namespace {
using ::v8_inspector_protocol_encoding::span;
using ::v8_inspector_protocol_encoding::SpanFrom;
using IPEStatus = ::v8_inspector_protocol_encoding::Status;
using v8_crdtp::span;
using v8_crdtp::SpanFrom;
using IPEStatus = v8_crdtp::Status;
bool IsCBORMessage(const StringView& msg) {
return msg.is8Bit() && msg.length() >= 2 && msg.characters8()[0] == 0xd8 &&
......@@ -332,8 +332,8 @@ void V8InspectorSessionImpl::reportAllContexts(V8RuntimeAgentImpl* agent) {
void V8InspectorSessionImpl::dispatchProtocolMessage(
const StringView& message) {
using ::v8_inspector_protocol_encoding::span;
using ::v8_inspector_protocol_encoding::SpanFrom;
using v8_crdtp::span;
using v8_crdtp::SpanFrom;
span<uint8_t> cbor;
std::vector<uint8_t> converted_cbor;
if (IsCBORMessage(message)) {
......
......@@ -295,8 +295,7 @@ v8_source_set("unittests_sources") {
"../..:v8_for_testing",
"../..:v8_libbase",
"../..:v8_libplatform",
"../../third_party/inspector_protocol:bindings_test",
"../../third_party/inspector_protocol:encoding_test",
"../../third_party/inspector_protocol:crdtp_test",
"//build/win:default_exe_manifest",
"//testing/gmock",
"//testing/gtest",
......
......@@ -2,60 +2,47 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
static_library("encoding") {
sources = [
"encoding/encoding.cc",
"encoding/encoding.h",
]
}
import("../../gni/v8.gni")
static_library("bindings") {
v8_source_set("crdtp") {
sources = [
"bindings/bindings.cc",
"bindings/bindings.h",
]
"crdtp/cbor.cc",
"crdtp/cbor.h",
"crdtp/export.h",
"crdtp/glue.h",
"crdtp/json.cc",
"crdtp/json.h",
"crdtp/json_platform.h",
"crdtp/parser_handler.h",
"crdtp/span.h",
"crdtp/status.cc",
"crdtp/status.h",
]
configs = [ "../../:internal_config" ]
}
# encoding_test is part of the unittests, defined in
# crdtp_test is part of the unittests, defined in
# test/unittests/BUILD.gn.
import("../../gni/v8.gni")
v8_source_set("encoding_test") {
sources = [
"encoding/encoding_test.cc",
"encoding/encoding_test_helper.h",
]
configs = [
"../..:external_config",
"../..:internal_config_base",
]
deps = [
":encoding",
"../..:v8_libbase",
"../../src/inspector:inspector_string_conversions",
"//testing/gmock",
"//testing/gtest",
]
v8_source_set("crdtp_test") {
testonly = true
}
v8_source_set("bindings_test") {
sources = [
"bindings/bindings_test.cc",
"bindings/bindings_test_helper.h",
"crdtp/cbor_test.cc",
"crdtp/glue_test.cc",
"crdtp/json_test.cc",
"crdtp/span_test.cc",
"crdtp/status_test.cc",
"crdtp/test_platform.cc",
"crdtp/test_platform.h",
]
configs = [
"../..:external_config",
"../..:internal_config_base",
]
deps = [
":bindings",
":crdtp",
"../..:v8_libbase",
"../../src/inspector:inspector_string_conversions",
"//testing/gmock",
"//testing/gtest",
]
testonly = true
}
......@@ -2,7 +2,7 @@ Name: inspector protocol
Short Name: inspector_protocol
URL: https://chromium.googlesource.com/deps/inspector_protocol/
Version: 0
Revision: d2fc9b958e1eeb1e956f3e2208afa9923bdc9b67
Revision: d020a9e614d4a5116a7c71f288c0340e282e1a6e
License: BSD
License File: LICENSE
Security Critical: no
......
......@@ -112,16 +112,9 @@ def read_config():
".lib": False,
".lib.export_macro": "",
".lib.export_header": False,
# The encoding lib consists of encoding/encoding.h and
# encoding/encoding.cc in its subdirectory, which binaries
# must link / depend on.
".encoding_lib.header": os.path.join(inspector_protocol_dir,
"encoding/encoding.h"),
".encoding_lib.namespace": "",
# Ditto for bindings, see bindings/bindings.h.
".bindings_lib.header": os.path.join(inspector_protocol_dir,
"bindings/bindings.h"),
".bindings_lib.namespace": ""
".crdtp": False,
".crdtp.dir": os.path.join(inspector_protocol_dir, "crdtp"),
".crdtp.namespace": "inspector_protocol",
}
for key_value in config_values:
parts = key_value.split("=")
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "encoding.h"
#include "cbor.h"
#include <algorithm>
#include <cassert>
......@@ -11,108 +11,7 @@
#include <limits>
#include <stack>
namespace v8_inspector_protocol_encoding {
// =============================================================================
// Status and Error codes
// =============================================================================
std::string Status::ToASCIIString() const {
switch (error) {
case Error::OK:
return "OK";
case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS:
return ToASCIIString("JSON: unprocessed input remains");
case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED:
return ToASCIIString("JSON: stack limit exceeded");
case Error::JSON_PARSER_NO_INPUT:
return ToASCIIString("JSON: no input");
case Error::JSON_PARSER_INVALID_TOKEN:
return ToASCIIString("JSON: invalid token");
case Error::JSON_PARSER_INVALID_NUMBER:
return ToASCIIString("JSON: invalid number");
case Error::JSON_PARSER_INVALID_STRING:
return ToASCIIString("JSON: invalid string");
case Error::JSON_PARSER_UNEXPECTED_ARRAY_END:
return ToASCIIString("JSON: unexpected array end");
case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED:
return ToASCIIString("JSON: comma or array end expected");
case Error::JSON_PARSER_STRING_LITERAL_EXPECTED:
return ToASCIIString("JSON: string literal expected");
case Error::JSON_PARSER_COLON_EXPECTED:
return ToASCIIString("JSON: colon expected");
case Error::JSON_PARSER_UNEXPECTED_MAP_END:
return ToASCIIString("JSON: unexpected map end");
case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED:
return ToASCIIString("JSON: comma or map end expected");
case Error::JSON_PARSER_VALUE_EXPECTED:
return ToASCIIString("JSON: value expected");
case Error::CBOR_INVALID_INT32:
return ToASCIIString("CBOR: invalid int32");
case Error::CBOR_INVALID_DOUBLE:
return ToASCIIString("CBOR: invalid double");
case Error::CBOR_INVALID_ENVELOPE:
return ToASCIIString("CBOR: invalid envelope");
case Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH:
return ToASCIIString("CBOR: envelope contents length mismatch");
case Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE:
return ToASCIIString("CBOR: map or array expected in envelope");
case Error::CBOR_INVALID_STRING8:
return ToASCIIString("CBOR: invalid string8");
case Error::CBOR_INVALID_STRING16:
return ToASCIIString("CBOR: invalid string16");
case Error::CBOR_INVALID_BINARY:
return ToASCIIString("CBOR: invalid binary");
case Error::CBOR_UNSUPPORTED_VALUE:
return ToASCIIString("CBOR: unsupported value");
case Error::CBOR_NO_INPUT:
return ToASCIIString("CBOR: no input");
case Error::CBOR_INVALID_START_BYTE:
return ToASCIIString("CBOR: invalid start byte");
case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE:
return ToASCIIString("CBOR: unexpected eof expected value");
case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY:
return ToASCIIString("CBOR: unexpected eof in array");
case Error::CBOR_UNEXPECTED_EOF_IN_MAP:
return ToASCIIString("CBOR: unexpected eof in map");
case Error::CBOR_INVALID_MAP_KEY:
return ToASCIIString("CBOR: invalid map key");
case Error::CBOR_STACK_LIMIT_EXCEEDED:
return ToASCIIString("CBOR: stack limit exceeded");
case Error::CBOR_TRAILING_JUNK:
return ToASCIIString("CBOR: trailing junk");
case Error::CBOR_MAP_START_EXPECTED:
return ToASCIIString("CBOR: map start expected");
case Error::CBOR_MAP_STOP_EXPECTED:
return ToASCIIString("CBOR: map stop expected");
case Error::CBOR_ARRAY_START_EXPECTED:
return ToASCIIString("CBOR: array start expected");
case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED:
return ToASCIIString("CBOR: envelope size limit exceeded");
case Error::BINDINGS_MANDATORY_FIELD_MISSING:
return ToASCIIString("BINDINGS: mandatory field missing");
case Error::BINDINGS_BOOL_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: bool value expected");
case Error::BINDINGS_INT32_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: int32 value expected");
case Error::BINDINGS_DOUBLE_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: double value expected");
case Error::BINDINGS_STRING_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: string value expected");
case Error::BINDINGS_STRING8_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: string8 value expected");
case Error::BINDINGS_BINARY_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: binary value expected");
}
// Some compilers can't figure out that we can't get here.
return "INVALID ERROR CODE";
}
std::string Status::ToASCIIString(const char* msg) const {
return std::string(msg) + " at position " + std::to_string(pos);
}
namespace v8_crdtp {
namespace cbor {
namespace {
// Indicates the number of bits the "initial byte" needs to be shifted to the
......@@ -284,11 +183,13 @@ void WriteTokenStartTmpl(MajorType type, uint64_t value, C* encoded) {
encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes));
WriteBytesMostSignificantByteFirst<uint64_t>(value, encoded);
}
void WriteTokenStart(MajorType type,
uint64_t value,
std::vector<uint8_t>* encoded) {
WriteTokenStartTmpl(type, value, encoded);
}
void WriteTokenStart(MajorType type, uint64_t value, std::string* encoded) {
WriteTokenStartTmpl(type, value, encoded);
}
......@@ -301,9 +202,11 @@ void WriteTokenStart(MajorType type, uint64_t value, std::string* encoded) {
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();
......@@ -316,9 +219,11 @@ bool IsCBORMessage(span<uint8_t> msg) {
uint8_t EncodeTrue() {
return kEncodedTrue;
}
uint8_t EncodeFalse() {
return kEncodedFalse;
}
uint8_t EncodeNull() {
return kEncodedNull;
}
......@@ -344,9 +249,11 @@ void EncodeInt32Tmpl(int32_t value, C* out) {
internals::WriteTokenStart(MajorType::NEGATIVE, representation, out);
}
}
void EncodeInt32(int32_t value, std::vector<uint8_t>* out) {
EncodeInt32Tmpl(value, out);
}
void EncodeInt32(int32_t value, std::string* out) {
EncodeInt32Tmpl(value, out);
}
......@@ -369,9 +276,11 @@ void EncodeString16Tmpl(span<uint16_t> in, C* out) {
out->push_back(two_bytes >> 8);
}
}
void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
EncodeString16Tmpl(in, out);
}
void EncodeString16(span<uint16_t> in, std::string* out) {
EncodeString16Tmpl(in, out);
}
......@@ -382,9 +291,11 @@ void EncodeString8Tmpl(span<uint8_t> in, C* out) {
static_cast<uint64_t>(in.size_bytes()), out);
out->insert(out->end(), in.begin(), in.end());
}
void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out) {
EncodeString8Tmpl(in, out);
}
void EncodeString8(span<uint8_t> in, std::string* out) {
EncodeString8Tmpl(in, out);
}
......@@ -410,9 +321,11 @@ void EncodeFromLatin1Tmpl(span<uint8_t> latin1, C* out) {
}
EncodeString8(latin1, out);
}
void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
EncodeFromLatin1Tmpl(latin1, out);
}
void EncodeFromLatin1(span<uint8_t> latin1, std::string* out) {
EncodeFromLatin1Tmpl(latin1, out);
}
......@@ -431,9 +344,11 @@ void EncodeFromUTF16Tmpl(span<uint16_t> utf16, C* out) {
static_cast<uint64_t>(utf16.size()), out);
out->insert(out->end(), utf16.begin(), utf16.end());
}
void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out) {
EncodeFromUTF16Tmpl(utf16, out);
}
void EncodeFromUTF16(span<uint16_t> utf16, std::string* out) {
EncodeFromUTF16Tmpl(utf16, out);
}
......@@ -445,9 +360,11 @@ void EncodeBinaryTmpl(span<uint8_t> in, C* out) {
internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
out->insert(out->end(), in.begin(), in.end());
}
void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
EncodeBinaryTmpl(in, out);
}
void EncodeBinary(span<uint8_t> in, std::string* out) {
EncodeBinaryTmpl(in, out);
}
......@@ -473,9 +390,11 @@ void EncodeDoubleTmpl(double value, C* out) {
reinterpret.from_double = value;
WriteBytesMostSignificantByteFirst<uint64_t>(reinterpret.to_uint64, out);
}
void EncodeDouble(double value, std::vector<uint8_t>* out) {
EncodeDoubleTmpl(value, out);
}
void EncodeDouble(double value, std::string* out) {
EncodeDoubleTmpl(value, out);
}
......@@ -644,6 +563,7 @@ std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
return std::unique_ptr<StreamingParserHandler>(
new CBOREncoder<std::vector<uint8_t>>(out, status));
}
std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
Status* status) {
return std::unique_ptr<StreamingParserHandler>(
......@@ -657,6 +577,7 @@ std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
CBORTokenizer::CBORTokenizer(span<uint8_t> bytes) : bytes_(bytes) {
ReadNextToken(/*enter_envelope=*/false);
}
CBORTokenizer::~CBORTokenizer() {}
CBORTokenTag CBORTokenizer::TokenTag() const {
......@@ -855,7 +776,7 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
switch (token_start_type_) {
case MajorType::UNSIGNED: // INT32.
// INT32 is a signed int32 (int32 makes sense for the
// inspector_protocol, it's not a CBOR limitation), so we check
// inspector protocol, it's not a CBOR limitation), so we check
// against the signed max, so that the allowable values are
// 0, 1, 2, ... 2^31 - 1.
if (!bytes_read || std::numeric_limits<int32_t>::max() <
......@@ -867,7 +788,7 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
return;
case MajorType::NEGATIVE: { // INT32.
// INT32 is a signed int32 (int32 makes sense for the
// inspector_protocol, it's not a CBOR limitation); in CBOR, the
// inspector protocol, it's not a CBOR limitation); in CBOR, the
// negative values for INT32 are represented as NEGATIVE, that is, -1
// INT32 is represented as 1 << 5 | 0 (major type 1, additional info
// value 0).
......@@ -950,12 +871,15 @@ static constexpr int kStackLimit = 300;
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);
bool ParseEnvelope(int32_t stack_depth,
CBORTokenizer* tokenizer,
StreamingParserHandler* out);
......@@ -1218,1040 +1142,17 @@ Status AppendString8EntryToCBORMapTmpl(span<uint8_t> string8_key,
*(out) = new_envelope_size & 0xff;
return Status();
}
Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
span<uint8_t> string8_value,
std::vector<uint8_t>* cbor) {
return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor);
}
Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
span<uint8_t> string8_value,
std::string* cbor) {
return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor);
}
} // 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.
template <typename C>
void PrintHex(uint16_t value, C* out) {
for (int ii = 3; ii >= 0; --ii) {
int four_bits = 0xf & (value >> (4 * ii));
out->push_back(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::vector<uint8_t>* out) { StartElementTmpl(out); }
void StartElement(std::string* out) { StartElementTmpl(out); }
Container container() const { return container_; }
private:
template <typename C>
void StartElementTmpl(C* out) {
assert(container_ != Container::NONE || size_ == 0);
if (size_ != 0) {
char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':';
out->push_back(delim);
}
++size_;
}
Container container_ = Container::NONE;
int size_ = 0;
};
constexpr char kBase64Table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789+/";
template <typename C>
void Base64Encode(const span<uint8_t>& in, C* 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.
size_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.
template <typename C>
class JSONEncoder : public StreamingParserHandler {
public:
JSONEncoder(const Platform* platform, C* 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);
Emit('{');
}
void HandleMapEnd() override {
if (!status_->ok())
return;
assert(state_.size() >= 2 && state_.top().container() == Container::MAP);
state_.pop();
Emit('}');
}
void HandleArrayBegin() override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
state_.emplace(Container::ARRAY);
Emit('[');
}
void HandleArrayEnd() override {
if (!status_->ok())
return;
assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY);
state_.pop();
Emit(']');
}
void HandleString16(span<uint16_t> chars) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit('"');
for (const uint16_t ch : chars) {
if (ch == '"') {
Emit("\\\"");
} else if (ch == '\\') {
Emit("\\\\");
} else if (ch == '\b') {
Emit("\\b");
} else if (ch == '\f') {
Emit("\\f");
} else if (ch == '\n') {
Emit("\\n");
} else if (ch == '\r') {
Emit("\\r");
} else if (ch == '\t') {
Emit("\\t");
} else if (ch >= 32 && ch <= 126) {
Emit(ch);
} else {
Emit("\\u");
PrintHex(ch, out_);
}
}
Emit('"');
}
void HandleString8(span<uint8_t> chars) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit('"');
for (size_t ii = 0; ii < chars.size(); ++ii) {
uint8_t c = chars[ii];
if (c == '"') {
Emit("\\\"");
} else if (c == '\\') {
Emit("\\\\");
} else if (c == '\b') {
Emit("\\b");
} else if (c == '\f') {
Emit("\\f");
} else if (c == '\n') {
Emit("\\n");
} else if (c == '\r') {
Emit("\\r");
} else if (c == '\t') {
Emit("\\t");
} else if (c >= 32 && c <= 126) {
Emit(c);
} else if (c < 32) {
Emit("\\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) {
Emit("\\u");
PrintHex(static_cast<uint16_t>(codepoint), out_);
continue;
}
codepoint -= 0x10000;
// high surrogate
Emit("\\u");
PrintHex(static_cast<uint16_t>((codepoint >> 10) + 0xd800), out_);
// low surrogate
Emit("\\u");
PrintHex(static_cast<uint16_t>((codepoint & 0x3ff) + 0xdc00), out_);
}
}
Emit('"');
}
void HandleBinary(span<uint8_t> bytes) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit('"');
Base64Encode(bytes, out_);
Emit('"');
}
void HandleDouble(double value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
// JSON cannot represent NaN or Infinity. So, for compatibility,
// we behave like the JSON object in web browsers: emit 'null'.
if (!std::isfinite(value)) {
Emit("null");
return;
}
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] == '.') {
Emit('0');
} else if (chars[0] == '-' && chars[1] == '.') {
Emit("-0");
++chars;
}
Emit(chars);
}
void HandleInt32(int32_t value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit(std::to_string(value));
}
void HandleBool(bool value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit(value ? "true" : "false");
}
void HandleNull() override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit("null");
}
void HandleError(Status error) override {
assert(!error.ok());
*status_ = error;
out_->clear();
}
private:
void Emit(char c) { out_->push_back(c); }
void Emit(const char* str) {
out_->insert(out_->end(), str, str + strlen(str));
}
void Emit(const std::string& str) {
out_->insert(out_->end(), str.begin(), str.end());
}
const Platform* platform_;
C* out_;
Status* status_;
std::stack<State> state_;
};
} // namespace
std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
const Platform* platform,
std::vector<uint8_t>* out,
Status* status) {
return std::unique_ptr<StreamingParserHandler>(
new JSONEncoder<std::vector<uint8_t>>(platform, out, status));
}
std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
std::string* out,
Status* status) {
return std::unique_ptr<StreamingParserHandler>(
new JSONEncoder<std::string>(platform, out, status));
}
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON.
// =============================================================================
namespace {
const int kStackLimit = 300;
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, size_t length) {
start_pos_ = start;
const Char* end = start + length;
const Char* tokenEnd = nullptr;
ParseValue(start, end, &tokenEnd, 0);
if (error_)
return;
if (tokenEnd != end) {
HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd);
}
}
private:
bool CharsToDouble(const uint16_t* chars, size_t length, double* result) {
std::string buffer;
buffer.reserve(length + 1);
for (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, 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 = nullptr;
if (!SkipComment(start, end, &comment_end))
break;
start = comment_end;
} else {
break;
}
}
*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 't':
if (ParseConstToken(start, end, token_end, kTrueString))
return BoolTrue;
break;
case 'f':
if (ParseConstToken(start, end, token_end, kFalseString))
return BoolFalse;
break;
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 '"':
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;
}
if ('\\' != c) {
output->push_back(c);
continue;
}
if (start == end)
return false;
c = *start++;
if (c == 'x') {
// \x is not supported.
return false;
}
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 = nullptr;
const Char* token_end = nullptr;
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 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 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 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;
}
default:
// We got a token that's not a value.
HandleError(Error::JSON_PARSER_VALUE_EXPECTED, token_start);
return;
}
SkipWhitespaceAndComments(token_end, end, value_token_end);
}
void HandleError(Error error, const Char* pos) {
assert(error != Error::OK);
if (!error_) {
handler_->HandleError(
Status{error, static_cast<size_t>(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());
}
void ParseJSON(const Platform& platform,
span<uint16_t> chars,
StreamingParserHandler* handler) {
JsonParser<uint16_t> parser(&platform, handler);
parser.Parse(chars.data(), chars.size());
}
// =============================================================================
// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
// =============================================================================
template <typename C>
Status ConvertCBORToJSONTmpl(const Platform& platform,
span<uint8_t> cbor,
C* json) {
Status status;
std::unique_ptr<StreamingParserHandler> json_writer =
NewJSONEncoder(&platform, json, &status);
cbor::ParseCBOR(cbor, json_writer.get());
return status;
}
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::vector<uint8_t>* json) {
return ConvertCBORToJSONTmpl(platform, cbor, json);
}
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::string* json) {
return ConvertCBORToJSONTmpl(platform, cbor, json);
}
template <typename T, typename C>
Status ConvertJSONToCBORTmpl(const Platform& platform, span<T> json, C* cbor) {
Status status;
std::unique_ptr<StreamingParserHandler> encoder =
cbor::NewCBOREncoder(cbor, &status);
ParseJSON(platform, json, encoder.get());
return status;
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::string* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::string* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::vector<uint8_t>* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::vector<uint8_t>* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
} // namespace json
} // namespace v8_inspector_protocol_encoding
} // namespace v8_crdtp
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_
#define V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_
#ifndef V8_CRDTP_CBOR_H_
#define V8_CRDTP_CBOR_H_
#include <algorithm>
#include <cstddef>
......@@ -14,190 +14,11 @@
#include <string>
#include <vector>
namespace v8_inspector_protocol_encoding {
// This library is designed to be portable. The only allowed dependency
// are the C/C++ standard libraries, up to C++11. We support both 32 bit
// and 64 architectures.
//
// Types used below:
// uint8_t: a byte, e.g. for raw bytes or UTF8 characters
// uint16_t: two bytes, e.g. for UTF16 characters
// For input parameters:
// span<uint8_t>: pointer to bytes and length
// span<uint16_t>: pointer to UTF16 chars and length
// For output parameters:
// std::vector<uint8_t> - Owned segment of bytes / utf8 characters and length.
// std::string - Same, for compatibility, even though char is signed.
// =============================================================================
// span - sequence of bytes
// =============================================================================
// This template is similar to std::span, which will be included in C++20.
template <typename T>
class span {
public:
using index_type = size_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> SpanFrom(const std::vector<T>& v) {
return span<T>(v.data(), v.size());
}
template <size_t N>
span<uint8_t> SpanFrom(const char (&str)[N]) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(str), N - 1);
}
inline span<uint8_t> SpanFrom(const char* str) {
return str ? span<uint8_t>(reinterpret_cast<const uint8_t*>(str), strlen(str))
: span<uint8_t>();
}
inline span<uint8_t> SpanFrom(const std::string& v) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(v.data()), v.size());
}
// Less than / equality comparison functions for sorting / searching for byte
// spans. These are similar to absl::string_view's < and == operators.
inline bool SpanLessThan(span<uint8_t> x, span<uint8_t> y) noexcept {
auto min_size = std::min(x.size(), y.size());
const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size);
return (r < 0) || (r == 0 && x.size() < y.size());
}
inline bool SpanEquals(span<uint8_t> x, span<uint8_t> y) noexcept {
auto len = x.size();
if (len != y.size())
return false;
return x.data() == y.data() || len == 0 ||
std::memcmp(x.data(), y.data(), len) == 0;
}
// =============================================================================
// Status and Error codes
// =============================================================================
enum class Error {
OK = 0,
// JSON parsing errors - json_parser.{h,cc}.
JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01,
JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02,
JSON_PARSER_NO_INPUT = 0x03,
JSON_PARSER_INVALID_TOKEN = 0x04,
JSON_PARSER_INVALID_NUMBER = 0x05,
JSON_PARSER_INVALID_STRING = 0x06,
JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07,
JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08,
JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09,
JSON_PARSER_COLON_EXPECTED = 0x0a,
JSON_PARSER_UNEXPECTED_MAP_END = 0x0b,
JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c,
JSON_PARSER_VALUE_EXPECTED = 0x0d,
CBOR_INVALID_INT32 = 0x0e,
CBOR_INVALID_DOUBLE = 0x0f,
CBOR_INVALID_ENVELOPE = 0x10,
CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH = 0x11,
CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE = 0x12,
CBOR_INVALID_STRING8 = 0x13,
CBOR_INVALID_STRING16 = 0x14,
CBOR_INVALID_BINARY = 0x15,
CBOR_UNSUPPORTED_VALUE = 0x16,
CBOR_NO_INPUT = 0x17,
CBOR_INVALID_START_BYTE = 0x18,
CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19,
CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a,
CBOR_UNEXPECTED_EOF_IN_MAP = 0x1b,
CBOR_INVALID_MAP_KEY = 0x1c,
CBOR_STACK_LIMIT_EXCEEDED = 0x1d,
CBOR_TRAILING_JUNK = 0x1e,
CBOR_MAP_START_EXPECTED = 0x1f,
CBOR_MAP_STOP_EXPECTED = 0x20,
CBOR_ARRAY_START_EXPECTED = 0x21,
CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x22,
BINDINGS_MANDATORY_FIELD_MISSING = 0x23,
BINDINGS_BOOL_VALUE_EXPECTED = 0x24,
BINDINGS_INT32_VALUE_EXPECTED = 0x25,
BINDINGS_DOUBLE_VALUE_EXPECTED = 0x26,
BINDINGS_STRING_VALUE_EXPECTED = 0x27,
BINDINGS_STRING8_VALUE_EXPECTED = 0x28,
BINDINGS_BINARY_VALUE_EXPECTED = 0x29,
};
// A status value with position that can be copied. The default status
// is OK. Usually, error status values should come with a valid position.
struct Status {
static constexpr size_t npos() { return std::numeric_limits<size_t>::max(); }
bool ok() const { return error == Error::OK; }
Error error = Error::OK;
size_t pos = npos();
Status(Error error, size_t pos) : error(error), pos(pos) {}
Status() = default;
// Returns a 7 bit US-ASCII string, either "OK" or an error message
// that includes the position.
std::string ToASCIIString() const;
private:
std::string ToASCIIString(const char* msg) const;
};
// Handler interface for parser events emitted by a streaming parser.
// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder,
// json::ParseJSON.
class StreamingParserHandler {
public:
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(span<uint8_t> bytes) = 0;
virtual void HandleDouble(double value) = 0;
virtual void HandleInt32(int32_t value) = 0;
virtual void HandleBool(bool value) = 0;
virtual void HandleNull() = 0;
// The parser may send one error even after other events have already
// been received. Client code is reponsible to then discard the
// already processed events.
// |error| must be an eror, as in, |error.is_ok()| can't be true.
virtual void HandleError(Status error) = 0;
};
#include "export.h"
#include "parser_handler.h"
#include "span.h"
namespace v8_crdtp {
namespace cbor {
// The binary encoding for the inspector protocol follows the CBOR specification
// (RFC 7049). Additional constraints:
......@@ -491,72 +312,6 @@ void WriteTokenStart(cbor::MajorType type,
std::string* encoded);
} // namespace internals
} // namespace cbor
} // namespace v8_crdtp
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::vector<uint8_t>* out,
Status* status);
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);
// =============================================================================
// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
// =============================================================================
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::string* json);
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::vector<uint8_t>* json);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::vector<uint8_t>* cbor);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::vector<uint8_t>* cbor);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::string* cbor);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::string* cbor);
} // namespace json
} // namespace v8_inspector_protocol_encoding
#endif // V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_
#endif // V8_CRDTP_CBOR_H_
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "encoding.h"
#include "cbor.h"
#include <array>
#include <clocale>
......@@ -14,12 +14,13 @@
#include <sstream>
#include <string>
#include "encoding_test_helper.h"
#include "json.h"
#include "test_platform.h"
using testing::ElementsAreArray;
namespace v8_inspector_protocol_encoding {
namespace v8_crdtp {
namespace {
class TestPlatform : public json::Platform {
bool StrToD(const char* str, double* result) const override {
// This is not thread-safe
......@@ -53,108 +54,7 @@ const json::Platform& GetTestPlatform() {
static TestPlatform* platform = new TestPlatform;
return *platform;
}
// =============================================================================
// span - sequence of bytes
// =============================================================================
template <typename T>
class SpanTest : public ::testing::Test {};
using TestTypes = ::testing::Types<uint8_t, uint16_t>;
TYPED_TEST_SUITE(SpanTest, TestTypes);
TYPED_TEST(SpanTest, Empty) {
span<TypeParam> empty;
EXPECT_TRUE(empty.empty());
EXPECT_EQ(0u, empty.size());
EXPECT_EQ(0u, empty.size_bytes());
EXPECT_EQ(empty.begin(), empty.end());
}
TYPED_TEST(SpanTest, SingleItem) {
TypeParam single_item = 42;
span<TypeParam> singular(&single_item, 1);
EXPECT_FALSE(singular.empty());
EXPECT_EQ(1u, singular.size());
EXPECT_EQ(sizeof(TypeParam), singular.size_bytes());
EXPECT_EQ(singular.begin() + 1, singular.end());
EXPECT_EQ(42, singular[0]);
}
TYPED_TEST(SpanTest, FiveItems) {
std::vector<TypeParam> test_input = {31, 32, 33, 34, 35};
span<TypeParam> five_items(test_input.data(), 5);
EXPECT_FALSE(five_items.empty());
EXPECT_EQ(5u, five_items.size());
EXPECT_EQ(sizeof(TypeParam) * 5, five_items.size_bytes());
EXPECT_EQ(five_items.begin() + 5, five_items.end());
EXPECT_EQ(31, five_items[0]);
EXPECT_EQ(32, five_items[1]);
EXPECT_EQ(33, five_items[2]);
EXPECT_EQ(34, five_items[3]);
EXPECT_EQ(35, five_items[4]);
span<TypeParam> three_items = five_items.subspan(2);
EXPECT_EQ(3u, three_items.size());
EXPECT_EQ(33, three_items[0]);
EXPECT_EQ(34, three_items[1]);
EXPECT_EQ(35, three_items[2]);
span<TypeParam> two_items = five_items.subspan(2, 2);
EXPECT_EQ(2u, two_items.size());
EXPECT_EQ(33, two_items[0]);
EXPECT_EQ(34, two_items[1]);
}
TEST(SpanFromTest, FromConstCharAndLiteral) {
// Testing this is useful because strlen(nullptr) is undefined.
EXPECT_EQ(nullptr, SpanFrom(nullptr).data());
EXPECT_EQ(0u, SpanFrom(nullptr).size());
const char* kEmpty = "";
EXPECT_EQ(kEmpty, reinterpret_cast<const char*>(SpanFrom(kEmpty).data()));
EXPECT_EQ(0u, SpanFrom(kEmpty).size());
const char* kFoo = "foo";
EXPECT_EQ(kFoo, reinterpret_cast<const char*>(SpanFrom(kFoo).data()));
EXPECT_EQ(3u, SpanFrom(kFoo).size());
EXPECT_EQ(3u, SpanFrom("foo").size());
}
TEST(SpanComparisons, ByteWiseLexicographicalOrder) {
// Compare the empty span.
EXPECT_FALSE(SpanLessThan(span<uint8_t>(), span<uint8_t>()));
EXPECT_TRUE(SpanEquals(span<uint8_t>(), span<uint8_t>()));
// Compare message with itself.
std::string msg = "Hello, world";
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(msg)));
EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(msg)));
// Compare message and copy.
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(std::string(msg))));
EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(std::string(msg))));
// Compare two messages. |lesser_msg| < |msg| because of the first
// byte ('A' < 'H').
std::string lesser_msg = "A lesser message.";
EXPECT_TRUE(SpanLessThan(SpanFrom(lesser_msg), SpanFrom(msg)));
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(lesser_msg)));
EXPECT_FALSE(SpanEquals(SpanFrom(msg), SpanFrom(lesser_msg)));
}
// =============================================================================
// Status and Error codes
// =============================================================================
TEST(StatusTest, StatusToASCIIString) {
Status ok_status;
EXPECT_EQ("OK", ok_status.ToASCIIString());
Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42);
EXPECT_EQ("JSON: colon expected at position 42", json_error.ToASCIIString());
Status cbor_error(Error::CBOR_TRAILING_JUNK, 21);
EXPECT_EQ("CBOR: trailing junk at position 21", cbor_error.ToASCIIString());
}
} // namespace
namespace cbor {
......@@ -923,8 +823,8 @@ TEST(ParseCBORTest, ParseEmptyCBORMessage) {
TEST(ParseCBORTest, ParseCBORHelloWorld) {
const uint8_t kPayloadLen = 27;
std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen};
bytes.push_back(0xbf); // start indef length map.
EncodeString8(SpanFrom("msg"), &bytes); // key: msg
bytes.push_back(0xbf); // start indef length map.
EncodeString8(SpanFrom("msg"), &bytes); // key: msg
// Now write the value, the familiar "Hello, 🌎." where the globe is expressed
// as two utf16 chars.
bytes.push_back(/*major type=*/2 << 5 | /*additional info=*/20);
......@@ -1490,623 +1390,4 @@ TYPED_TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
}
}
} // namespace cbor
namespace json {
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
void WriteUTF8AsUTF16(StreamingParserHandler* writer, const std::string& utf8) {
writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8))));
}
TEST(JsonEncoder, OverlongEncodings) {
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
// We encode 0x7f, which is the DEL ascii character, as a 4 byte UTF8
// sequence. This is called an overlong encoding, because only 1 byte
// is needed to represent 0x7f as UTF8.
std::vector<uint8_t> chars = {
0xf0, // Starts 4 byte utf8 sequence
0x80, // continuation byte
0x81, // continuation byte w/ payload bit 7 set to 1.
0xbf, // continuation byte w/ payload bits 0-6 set to 11111.
};
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("\"\"", out); // Empty string means that 0x7f was rejected (good).
}
TEST(JsonEncoder, IncompleteUtf8Sequence) {
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleArrayBegin(); // This emits [, which starts an array.
{ // 🌎 takes four bytes to encode in UTF-8. We test with the first three;
// This means we're trying to emit a string that consists solely of an
// incomplete UTF-8 sequence. So the string in the JSON output is empty.
std::string world_utf8 = "🌎";
ASSERT_EQ(4u, world_utf8.size());
std::vector<uint8_t> chars(world_utf8.begin(), world_utf8.begin() + 3);
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\"", out); // Incomplete sequence rejected: empty string.
}
{ // This time, the incomplete sequence is at the end of the string.
std::string msg = "Hello, \xF0\x9F\x8C";
std::vector<uint8_t> chars(msg.begin(), msg.end());
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\",\"Hello, \"", out); // Incomplete sequence dropped at end.
}
}
TEST(JsonStdStringWriterTest, HelloWorld) {
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
WriteUTF8AsUTF16(writer.get(), "Hello, 🌎.");
std::string key = "msg1-as-utf8";
std::string value = "Hello, 🌎.";
writer->HandleString8(SpanFrom(key));
writer->HandleString8(SpanFrom(value));
WriteUTF8AsUTF16(writer.get(), "msg2");
WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\"");
WriteUTF8AsUTF16(writer.get(), "nested");
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "double");
writer->HandleDouble(3.1415);
WriteUTF8AsUTF16(writer.get(), "int");
writer->HandleInt32(-42);
WriteUTF8AsUTF16(writer.get(), "bool");
writer->HandleBool(false);
WriteUTF8AsUTF16(writer.get(), "null");
writer->HandleNull();
writer->HandleMapEnd();
WriteUTF8AsUTF16(writer.get(), "array");
writer->HandleArrayBegin();
writer->HandleInt32(1);
writer->HandleInt32(2);
writer->HandleInt32(3);
writer->HandleArrayEnd();
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ(
"{\"msg1\":\"Hello, \\ud83c\\udf0e.\","
"\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\","
"\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\","
"\"nested\":{\"double\":3.1415,\"int\":-42,"
"\"bool\":false,\"null\":null},\"array\":[1,2,3]}",
out);
}
TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) {
// JSON can't represent +Infinity, -Infinity, or NaN.
// So in practice it's mapped to null.
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleMapBegin();
writer->HandleString8(SpanFrom("Infinity"));
writer->HandleDouble(std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("-Infinity"));
writer->HandleDouble(-std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("NaN"));
writer->HandleDouble(std::numeric_limits<double>::quiet_NaN());
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out);
}
TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) {
// The encoder emits binary submitted to StreamingParserHandler::HandleBinary
// as base64. The following three examples are taken from
// https://en.wikipedia.org/wiki/Base64.
{
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a', 'n'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWFu\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWE=\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TQ==\"", out);
}
{ // "Hello, world.", verified with base64decode.org.
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>(
{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out);
}
}
TEST(JsonStdStringWriterTest, HandlesErrors) {
// When an error is sent via HandleError, it saves it in the provided
// status and clears the output.
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42});
EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, status.error);
EXPECT_EQ(42u, status.pos);
EXPECT_EQ("", out);
}
// We'd use Gmock but unfortunately it only handles copyable return types.
class MockPlatform : public Platform {
public:
// Not implemented.
bool StrToD(const char* str, double* result) const override { return false; }
// A map with pre-registered responses for DToSTr.
std::map<double, std::string> dtostr_responses_;
std::unique_ptr<char[]> DToStr(double value) const override {
auto it = dtostr_responses_.find(value);
CHECK(it != dtostr_responses_.end());
const std::string& str = it->second;
std::unique_ptr<char[]> response(new char[str.size() + 1]);
memcpy(response.get(), str.c_str(), str.size() + 1);
return response;
}
};
TEST(JsonStdStringWriterTest, DoubleToString) {
// This "broken" platform responds without the leading 0 before the
// decimal dot, so it'd be invalid JSON.
MockPlatform platform;
platform.dtostr_responses_[.1] = ".1";
platform.dtostr_responses_[-.7] = "-.7";
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&platform, &out, &status);
writer->HandleArrayBegin();
writer->HandleDouble(.1);
writer->HandleDouble(-.7);
writer->HandleArrayEnd();
EXPECT_EQ("[0.1,-0.7]", out);
}
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON
// =============================================================================
class Log : public StreamingParserHandler {
public:
void HandleMapBegin() override { log_ << "map begin\n"; }
void HandleMapEnd() override { log_ << "map end\n"; }
void HandleArrayBegin() override { log_ << "array begin\n"; }
void HandleArrayEnd() override { log_ << "array end\n"; }
void HandleString8(span<uint8_t> chars) override {
log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n";
}
void HandleString16(span<uint16_t> chars) override {
log_ << "string16: " << UTF16ToUTF8(chars) << "\n";
}
void HandleBinary(span<uint8_t> bytes) override {
// JSON doesn't have native support for arbitrary bytes, so our parser will
// never call this.
CHECK(false);
}
void HandleDouble(double value) override {
log_ << "double: " << value << "\n";
}
void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; }
void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; }
void HandleNull() override { log_ << "null\n"; }
void HandleError(Status status) override { status_ = status; }
std::string str() const { return status_.ok() ? log_.str() : ""; }
Status status() const { return status_; }
private:
std::ostringstream log_;
Status status_;
};
class JsonParserTest : public ::testing::Test {
protected:
Log log_;
};
TEST_F(JsonParserTest, SimpleDictionary) {
std::string json = "{\"foo\": 42}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UsAsciiDelCornerCase) {
// DEL (0x7f) is a 7 bit US-ASCII character, and while it is a control
// character according to Unicode, it's not considered a control
// character in https://tools.ietf.org/html/rfc7159#section-7, so
// it can be placed directly into the JSON string, without JSON escaping.
std::string json = "{\"foo\": \"a\x7f\"}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"string16: a\x7f\n"
"map end\n",
log_.str());
// We've seen an implementation of UTF16ToUTF8 which would replace the DEL
// character with ' ', so this simple roundtrip tests the routines in
// encoding_test_helper.h, to make test failures of the above easier to
// diagnose.
std::vector<uint16_t> utf16 = UTF8ToUTF16(SpanFrom(json));
EXPECT_EQ(json, UTF16ToUTF8(SpanFrom(utf16)));
}
TEST_F(JsonParserTest, Whitespace) {
std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, world.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, NestedDictionary) {
std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: bar\n"
"map begin\n"
"string16: baz\n"
"int: 1\n"
"map end\n"
"string16: bar2\n"
"int: 2\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Doubles) {
std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"double: 3.1415\n"
"string16: bar\n"
"double: 3.1415\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode) {
// Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16.
std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, 🌎.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf16) {
// Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf16 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::vector<uint16_t> json =
UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}"));
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: space\n"
"string16: 🌎 🌙.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf8) {
// Used below:
// гласность - example for 2 byte utf8, Russian word "glasnost"
// 屋 - example for 3 byte utf8, Chinese word for "house"
// 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// 🌙 - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf8 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::string json =
"{"
"\"escapes\": \"\\uD83C\\uDF19\","
"\"2 byte\":\"гласность\","
"\"3 byte\":\"\","
"\"4 byte\":\"🌎\""
"}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: escapes\n"
"string16: 🌙\n"
"string16: 2 byte\n"
"string16: гласность\n"
"string16: 3 byte\n"
"string16: 屋\n"
"string16: 4 byte\n"
"string16: 🌎\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UnprocessedInputRemainsError) {
// Trailing junk after the valid JSON.
std::string json = "{\"foo\": 3.1415} junk";
size_t junk_idx = json.find("junk");
EXPECT_NE(junk_idx, std::string::npos);
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, log_.status().error);
EXPECT_EQ(junk_idx, log_.status().pos);
EXPECT_EQ("", log_.str());
}
std::string MakeNestedJson(int depth) {
std::string json;
for (int ii = 0; ii < depth; ++ii)
json += "{\"foo\":";
json += "42";
for (int ii = 0; ii < depth; ++ii)
json += "}";
return json;
}
TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) {
// kStackLimit is 300 (see json_parser.cc). First let's
// try with a small nested example.
std::string json_3 = MakeNestedJson(3);
ParseJSON(GetTestPlatform(), SpanFrom(json_3), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) {
// Now with kStackLimit (300).
std::string json_limit = MakeNestedJson(300);
ParseJSON(GetTestPlatform(),
span<uint8_t>(reinterpret_cast<const uint8_t*>(json_limit.data()),
json_limit.size()),
&log_);
EXPECT_TRUE(log_.status().ok());
}
TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) {
// Now with kStackLimit + 1 (301) - it exceeds in the innermost instance.
std::string exceeded = MakeNestedJson(301);
ParseJSON(GetTestPlatform(), SpanFrom(exceeded), &log_);
EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
}
TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) {
// Now way past the limit. Still, the point of exceeding is 301.
std::string far_out = MakeNestedJson(320);
ParseJSON(GetTestPlatform(), SpanFrom(far_out), &log_);
EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
}
TEST_F(JsonParserTest, NoInputError) {
std::string json = "";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_NO_INPUT, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidTokenError) {
std::string json = "|";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_TOKEN, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidNumberError) {
// Mantissa exceeds max (the constant used here is int64_t max).
std::string json = "1E9223372036854775807";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_NUMBER, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidStringError) {
// \x22 is an unsupported escape sequence
std::string json = "\"foo\\x22\"";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_STRING, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedArrayEndError) {
std::string json = "[1,2,]";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, log_.status().error);
EXPECT_EQ(5u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) {
std::string json = "[1,2 2";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED,
log_.status().error);
EXPECT_EQ(5u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, StringLiteralExpectedError) {
// There's an error because the key bar, a string, is not terminated.
std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, log_.status().error);
EXPECT_EQ(16u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ColonExpectedError) {
std::string json = "{\"foo\", 42}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COLON_EXPECTED, log_.status().error);
EXPECT_EQ(6u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedMapEndError) {
std::string json = "{\"foo\": 42, }";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_MAP_END, log_.status().error);
EXPECT_EQ(12u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrMapEndExpectedError) {
// The second separator should be a comma.
std::string json = "{\"foo\": 3.1415: \"bar\": 0}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, log_.status().error);
EXPECT_EQ(14u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ValueExpectedError) {
std::string json = "}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
template <typename T>
class ConvertJSONToCBORTest : public ::testing::Test {};
using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes);
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) {
std::string json_in = "{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}";
TypeParam json(json_in.begin(), json_in.end());
TypeParam cbor;
{
Status status = ConvertJSONToCBOR(GetTestPlatform(), SpanFrom(json), &cbor);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
TypeParam roundtrip_json;
{
Status status =
ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
EXPECT_EQ(json, roundtrip_json);
}
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) {
std::vector<uint16_t> json16 = {
'{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l',
'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't',
'"', ':', '[', '1', ',', '2', ',', '3', ']', '}'};
TypeParam cbor;
{
Status status = ConvertJSONToCBOR(
GetTestPlatform(), span<uint16_t>(json16.data(), json16.size()), &cbor);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
TypeParam roundtrip_json;
{
Status status =
ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}";
TypeParam expected_json(json.begin(), json.end());
EXPECT_EQ(expected_json, roundtrip_json);
}
} // namespace json
} // namespace v8_inspector_protocol_encoding
} // namespace v8_crdtp
......@@ -2,4 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bindings.h"
// This file is V8 specific.
// It is not rolled from the upstream project.
// CRDTP doesn't export symbols from V8, so it's empty.
......@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_H_
#define V8_INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_H_
#ifndef V8_CRDTP_GLUE_H_
#define V8_CRDTP_GLUE_H_
#include <cassert>
#include <memory>
namespace v8_inspector_protocol_bindings {
namespace v8_crdtp {
namespace glue {
// =============================================================================
// glue::detail::PtrMaybe, glue::detail::ValueMaybe, templates for optional
......@@ -70,11 +70,11 @@ class ValueMaybe {
};
} // namespace detail
} // namespace glue
} // namespace v8_inspector_protocol_bindings
} // namespace v8_crdtp
#define PROTOCOL_DISALLOW_COPY(ClassName) \
private: \
ClassName(const ClassName&) = delete; \
ClassName& operator=(const ClassName&) = delete
private: \
ClassName(const ClassName&) = delete; \
ClassName& operator=(const ClassName&) = delete
#endif // V8_INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_H_
#endif // V8_CRDTP_GLUE_H_
......@@ -2,14 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bindings.h"
#include "glue.h"
#include <string>
#include <vector>
#include "bindings_test_helper.h"
#include "test_platform.h"
namespace v8_inspector_protocol_bindings {
namespace v8_crdtp {
namespace glue {
// =============================================================================
// glue::detail::PtrMaybe, glue::detail::ValueMaybe, templates for optional
......@@ -41,4 +41,4 @@ TEST(PtrValueTest, SmokeTest) {
EXPECT_EQ(out, 42);
}
} // namespace glue
} // namespace v8_inspector_protocol_bindings
} // namespace v8_crdtp
// 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.
#include "json.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstring>
#include <limits>
#include <stack>
#include "cbor.h"
namespace v8_crdtp {
namespace json {
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
namespace {
// Prints |value| to |out| with 4 hex digits, most significant chunk first.
template <typename C>
void PrintHex(uint16_t value, C* out) {
for (int ii = 3; ii >= 0; --ii) {
int four_bits = 0xf & (value >> (4 * ii));
out->push_back(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::vector<uint8_t>* out) { StartElementTmpl(out); }
void StartElement(std::string* out) { StartElementTmpl(out); }
Container container() const { return container_; }
private:
template <typename C>
void StartElementTmpl(C* out) {
assert(container_ != Container::NONE || size_ == 0);
if (size_ != 0) {
char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':';
out->push_back(delim);
}
++size_;
}
Container container_ = Container::NONE;
int size_ = 0;
};
constexpr char kBase64Table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789+/";
template <typename C>
void Base64Encode(const span<uint8_t>& in, C* 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.
size_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.
template <typename C>
class JSONEncoder : public StreamingParserHandler {
public:
JSONEncoder(const Platform* platform, C* 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);
Emit('{');
}
void HandleMapEnd() override {
if (!status_->ok())
return;
assert(state_.size() >= 2 && state_.top().container() == Container::MAP);
state_.pop();
Emit('}');
}
void HandleArrayBegin() override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
state_.emplace(Container::ARRAY);
Emit('[');
}
void HandleArrayEnd() override {
if (!status_->ok())
return;
assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY);
state_.pop();
Emit(']');
}
void HandleString16(span<uint16_t> chars) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit('"');
for (const uint16_t ch : chars) {
if (ch == '"') {
Emit("\\\"");
} else if (ch == '\\') {
Emit("\\\\");
} else if (ch == '\b') {
Emit("\\b");
} else if (ch == '\f') {
Emit("\\f");
} else if (ch == '\n') {
Emit("\\n");
} else if (ch == '\r') {
Emit("\\r");
} else if (ch == '\t') {
Emit("\\t");
} else if (ch >= 32 && ch <= 126) {
Emit(ch);
} else {
Emit("\\u");
PrintHex(ch, out_);
}
}
Emit('"');
}
void HandleString8(span<uint8_t> chars) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit('"');
for (size_t ii = 0; ii < chars.size(); ++ii) {
uint8_t c = chars[ii];
if (c == '"') {
Emit("\\\"");
} else if (c == '\\') {
Emit("\\\\");
} else if (c == '\b') {
Emit("\\b");
} else if (c == '\f') {
Emit("\\f");
} else if (c == '\n') {
Emit("\\n");
} else if (c == '\r') {
Emit("\\r");
} else if (c == '\t') {
Emit("\\t");
} else if (c >= 32 && c <= 126) {
Emit(c);
} else if (c < 32) {
Emit("\\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) {
Emit("\\u");
PrintHex(static_cast<uint16_t>(codepoint), out_);
continue;
}
codepoint -= 0x10000;
// high surrogate
Emit("\\u");
PrintHex(static_cast<uint16_t>((codepoint >> 10) + 0xd800), out_);
// low surrogate
Emit("\\u");
PrintHex(static_cast<uint16_t>((codepoint & 0x3ff) + 0xdc00), out_);
}
}
Emit('"');
}
void HandleBinary(span<uint8_t> bytes) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit('"');
Base64Encode(bytes, out_);
Emit('"');
}
void HandleDouble(double value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
// JSON cannot represent NaN or Infinity. So, for compatibility,
// we behave like the JSON object in web browsers: emit 'null'.
if (!std::isfinite(value)) {
Emit("null");
return;
}
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] == '.') {
Emit('0');
} else if (chars[0] == '-' && chars[1] == '.') {
Emit("-0");
++chars;
}
Emit(chars);
}
void HandleInt32(int32_t value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit(std::to_string(value));
}
void HandleBool(bool value) override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit(value ? "true" : "false");
}
void HandleNull() override {
if (!status_->ok())
return;
state_.top().StartElement(out_);
Emit("null");
}
void HandleError(Status error) override {
assert(!error.ok());
*status_ = error;
out_->clear();
}
private:
void Emit(char c) { out_->push_back(c); }
void Emit(const char* str) {
out_->insert(out_->end(), str, str + strlen(str));
}
void Emit(const std::string& str) {
out_->insert(out_->end(), str.begin(), str.end());
}
const Platform* platform_;
C* out_;
Status* status_;
std::stack<State> state_;
};
} // namespace
std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
const Platform* platform,
std::vector<uint8_t>* out,
Status* status) {
return std::unique_ptr<StreamingParserHandler>(
new JSONEncoder<std::vector<uint8_t>>(platform, out, status));
}
std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
std::string* out,
Status* status) {
return std::unique_ptr<StreamingParserHandler>(
new JSONEncoder<std::string>(platform, out, status));
}
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON.
// =============================================================================
namespace {
const int kStackLimit = 300;
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, size_t length) {
start_pos_ = start;
const Char* end = start + length;
const Char* tokenEnd = nullptr;
ParseValue(start, end, &tokenEnd, 0);
if (error_)
return;
if (tokenEnd != end) {
HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd);
}
}
private:
bool CharsToDouble(const uint16_t* chars, size_t length, double* result) {
std::string buffer;
buffer.reserve(length + 1);
for (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, 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 = nullptr;
if (!SkipComment(start, end, &comment_end))
break;
start = comment_end;
} else {
break;
}
}
*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 't':
if (ParseConstToken(start, end, token_end, kTrueString))
return BoolTrue;
break;
case 'f':
if (ParseConstToken(start, end, token_end, kFalseString))
return BoolFalse;
break;
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 '"':
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;
}
if ('\\' != c) {
output->push_back(c);
continue;
}
if (start == end)
return false;
c = *start++;
if (c == 'x') {
// \x is not supported.
return false;
}
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 = nullptr;
const Char* token_end = nullptr;
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 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 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 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;
}
default:
// We got a token that's not a value.
HandleError(Error::JSON_PARSER_VALUE_EXPECTED, token_start);
return;
}
SkipWhitespaceAndComments(token_end, end, value_token_end);
}
void HandleError(Error error, const Char* pos) {
assert(error != Error::OK);
if (!error_) {
handler_->HandleError(
Status{error, static_cast<size_t>(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());
}
void ParseJSON(const Platform& platform,
span<uint16_t> chars,
StreamingParserHandler* handler) {
JsonParser<uint16_t> parser(&platform, handler);
parser.Parse(chars.data(), chars.size());
}
// =============================================================================
// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
// =============================================================================
template <typename C>
Status ConvertCBORToJSONTmpl(const Platform& platform,
span<uint8_t> cbor,
C* json) {
Status status;
std::unique_ptr<StreamingParserHandler> json_writer =
NewJSONEncoder(&platform, json, &status);
cbor::ParseCBOR(cbor, json_writer.get());
return status;
}
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::vector<uint8_t>* json) {
return ConvertCBORToJSONTmpl(platform, cbor, json);
}
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::string* json) {
return ConvertCBORToJSONTmpl(platform, cbor, json);
}
template <typename T, typename C>
Status ConvertJSONToCBORTmpl(const Platform& platform, span<T> json, C* cbor) {
Status status;
std::unique_ptr<StreamingParserHandler> encoder =
cbor::NewCBOREncoder(cbor, &status);
ParseJSON(platform, json, encoder.get());
return status;
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::string* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::string* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::vector<uint8_t>* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::vector<uint8_t>* cbor) {
return ConvertJSONToCBORTmpl(platform, json, cbor);
}
} // namespace json
} // namespace v8_crdtp
// 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 V8_CRDTP_JSON_H_
#define V8_CRDTP_JSON_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "export.h"
#include "json_platform.h"
#include "parser_handler.h"
namespace v8_crdtp {
namespace json {
// =============================================================================
// 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::vector<uint8_t>* out,
Status* status);
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);
// =============================================================================
// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
// =============================================================================
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::string* json);
Status ConvertCBORToJSON(const Platform& platform,
span<uint8_t> cbor,
std::vector<uint8_t>* json);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::vector<uint8_t>* cbor);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::vector<uint8_t>* cbor);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint8_t> json,
std::string* cbor);
Status ConvertJSONToCBOR(const Platform& platform,
span<uint16_t> json,
std::string* cbor);
} // namespace json
} // namespace v8_crdtp
#endif // V8_CRDTP_JSON_H_
// 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 V8_CRDTP_JSON_PLATFORM_H_
#define V8_CRDTP_JSON_PLATFORM_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "export.h"
namespace v8_crdtp {
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;
};
} // namespace json
} // namespace v8_crdtp
#endif // V8_CRDTP_JSON_PLATFORM_H_
// Copyright 2018 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.
#include "json.h"
#include <array>
#include <clocale>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include "test_platform.h"
using testing::ElementsAreArray;
namespace v8_crdtp {
namespace {
class TestPlatform : public json::Platform {
bool StrToD(const char* str, double* result) const override {
// This is not thread-safe
// (see https://en.cppreference.com/w/cpp/locale/setlocale)
// but good enough for a unittest.
const char* saved_locale = std::setlocale(LC_NUMERIC, nullptr);
char* end;
*result = std::strtod(str, &end);
std::setlocale(LC_NUMERIC, saved_locale);
if (errno == ERANGE) {
// errno must be reset, e.g. see the example here:
// https://en.cppreference.com/w/cpp/string/byte/strtof
errno = 0;
return false;
}
return end == str + strlen(str);
}
std::unique_ptr<char[]> DToStr(double value) const override {
std::stringstream ss;
ss.imbue(std::locale("C"));
ss << value;
std::string str = ss.str();
std::unique_ptr<char[]> result(new char[str.size() + 1]);
memcpy(result.get(), str.c_str(), str.size() + 1);
return result;
}
};
const json::Platform& GetTestPlatform() {
static TestPlatform* platform = new TestPlatform;
return *platform;
}
} // namespace
namespace json {
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
void WriteUTF8AsUTF16(StreamingParserHandler* writer, const std::string& utf8) {
writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8))));
}
TEST(JsonEncoder, OverlongEncodings) {
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
// We encode 0x7f, which is the DEL ascii character, as a 4 byte UTF8
// sequence. This is called an overlong encoding, because only 1 byte
// is needed to represent 0x7f as UTF8.
std::vector<uint8_t> chars = {
0xf0, // Starts 4 byte utf8 sequence
0x80, // continuation byte
0x81, // continuation byte w/ payload bit 7 set to 1.
0xbf, // continuation byte w/ payload bits 0-6 set to 11111.
};
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("\"\"", out); // Empty string means that 0x7f was rejected (good).
}
TEST(JsonEncoder, IncompleteUtf8Sequence) {
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleArrayBegin(); // This emits [, which starts an array.
{ // 🌎 takes four bytes to encode in UTF-8. We test with the first three;
// This means we're trying to emit a string that consists solely of an
// incomplete UTF-8 sequence. So the string in the JSON output is empty.
std::string world_utf8 = "🌎";
ASSERT_EQ(4u, world_utf8.size());
std::vector<uint8_t> chars(world_utf8.begin(), world_utf8.begin() + 3);
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\"", out); // Incomplete sequence rejected: empty string.
}
{ // This time, the incomplete sequence is at the end of the string.
std::string msg = "Hello, \xF0\x9F\x8C";
std::vector<uint8_t> chars(msg.begin(), msg.end());
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\",\"Hello, \"", out); // Incomplete sequence dropped at end.
}
}
TEST(JsonStdStringWriterTest, HelloWorld) {
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
WriteUTF8AsUTF16(writer.get(), "Hello, 🌎.");
std::string key = "msg1-as-utf8";
std::string value = "Hello, 🌎.";
writer->HandleString8(SpanFrom(key));
writer->HandleString8(SpanFrom(value));
WriteUTF8AsUTF16(writer.get(), "msg2");
WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\"");
WriteUTF8AsUTF16(writer.get(), "nested");
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "double");
writer->HandleDouble(3.1415);
WriteUTF8AsUTF16(writer.get(), "int");
writer->HandleInt32(-42);
WriteUTF8AsUTF16(writer.get(), "bool");
writer->HandleBool(false);
WriteUTF8AsUTF16(writer.get(), "null");
writer->HandleNull();
writer->HandleMapEnd();
WriteUTF8AsUTF16(writer.get(), "array");
writer->HandleArrayBegin();
writer->HandleInt32(1);
writer->HandleInt32(2);
writer->HandleInt32(3);
writer->HandleArrayEnd();
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ(
"{\"msg1\":\"Hello, \\ud83c\\udf0e.\","
"\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\","
"\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\","
"\"nested\":{\"double\":3.1415,\"int\":-42,"
"\"bool\":false,\"null\":null},\"array\":[1,2,3]}",
out);
}
TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) {
// JSON can't represent +Infinity, -Infinity, or NaN.
// So in practice it's mapped to null.
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleMapBegin();
writer->HandleString8(SpanFrom("Infinity"));
writer->HandleDouble(std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("-Infinity"));
writer->HandleDouble(-std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("NaN"));
writer->HandleDouble(std::numeric_limits<double>::quiet_NaN());
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out);
}
TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) {
// The encoder emits binary submitted to StreamingParserHandler::HandleBinary
// as base64. The following three examples are taken from
// https://en.wikipedia.org/wiki/Base64.
{
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a', 'n'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWFu\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWE=\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TQ==\"", out);
}
{ // "Hello, world.", verified with base64decode.org.
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>(
{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out);
}
}
TEST(JsonStdStringWriterTest, HandlesErrors) {
// When an error is sent via HandleError, it saves it in the provided
// status and clears the output.
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&GetTestPlatform(), &out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42});
EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, status.error);
EXPECT_EQ(42u, status.pos);
EXPECT_EQ("", out);
}
// We'd use Gmock but unfortunately it only handles copyable return types.
class MockPlatform : public Platform {
public:
// Not implemented.
bool StrToD(const char* str, double* result) const override { return false; }
// A map with pre-registered responses for DToSTr.
std::map<double, std::string> dtostr_responses_;
std::unique_ptr<char[]> DToStr(double value) const override {
auto it = dtostr_responses_.find(value);
CHECK(it != dtostr_responses_.end());
const std::string& str = it->second;
std::unique_ptr<char[]> response(new char[str.size() + 1]);
memcpy(response.get(), str.c_str(), str.size() + 1);
return response;
}
};
TEST(JsonStdStringWriterTest, DoubleToString) {
// This "broken" platform responds without the leading 0 before the
// decimal dot, so it'd be invalid JSON.
MockPlatform platform;
platform.dtostr_responses_[.1] = ".1";
platform.dtostr_responses_[-.7] = "-.7";
std::string out;
Status status;
std::unique_ptr<StreamingParserHandler> writer =
NewJSONEncoder(&platform, &out, &status);
writer->HandleArrayBegin();
writer->HandleDouble(.1);
writer->HandleDouble(-.7);
writer->HandleArrayEnd();
EXPECT_EQ("[0.1,-0.7]", out);
}
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON
// =============================================================================
class Log : public StreamingParserHandler {
public:
void HandleMapBegin() override { log_ << "map begin\n"; }
void HandleMapEnd() override { log_ << "map end\n"; }
void HandleArrayBegin() override { log_ << "array begin\n"; }
void HandleArrayEnd() override { log_ << "array end\n"; }
void HandleString8(span<uint8_t> chars) override {
log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n";
}
void HandleString16(span<uint16_t> chars) override {
log_ << "string16: " << UTF16ToUTF8(chars) << "\n";
}
void HandleBinary(span<uint8_t> bytes) override {
// JSON doesn't have native support for arbitrary bytes, so our parser will
// never call this.
CHECK(false);
}
void HandleDouble(double value) override {
log_ << "double: " << value << "\n";
}
void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; }
void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; }
void HandleNull() override { log_ << "null\n"; }
void HandleError(Status status) override { status_ = status; }
std::string str() const { return status_.ok() ? log_.str() : ""; }
Status status() const { return status_; }
private:
std::ostringstream log_;
Status status_;
};
class JsonParserTest : public ::testing::Test {
protected:
Log log_;
};
TEST_F(JsonParserTest, SimpleDictionary) {
std::string json = "{\"foo\": 42}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UsAsciiDelCornerCase) {
// DEL (0x7f) is a 7 bit US-ASCII character, and while it is a control
// character according to Unicode, it's not considered a control
// character in https://tools.ietf.org/html/rfc7159#section-7, so
// it can be placed directly into the JSON string, without JSON escaping.
std::string json = "{\"foo\": \"a\x7f\"}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"string16: a\x7f\n"
"map end\n",
log_.str());
// We've seen an implementation of UTF16ToUTF8 which would replace the DEL
// character with ' ', so this simple roundtrip tests the routines in
// encoding_test_helper.h, to make test failures of the above easier to
// diagnose.
std::vector<uint16_t> utf16 = UTF8ToUTF16(SpanFrom(json));
EXPECT_EQ(json, UTF16ToUTF8(SpanFrom(utf16)));
}
TEST_F(JsonParserTest, Whitespace) {
std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, world.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, NestedDictionary) {
std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: bar\n"
"map begin\n"
"string16: baz\n"
"int: 1\n"
"map end\n"
"string16: bar2\n"
"int: 2\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Doubles) {
std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"double: 3.1415\n"
"string16: bar\n"
"double: 3.1415\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode) {
// Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16.
std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, 🌎.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf16) {
// Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf16 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::vector<uint16_t> json =
UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}"));
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: space\n"
"string16: 🌎 🌙.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf8) {
// Used below:
// гласность - example for 2 byte utf8, Russian word "glasnost"
// 屋 - example for 3 byte utf8, Chinese word for "house"
// 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// 🌙 - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf8 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::string json =
"{"
"\"escapes\": \"\\uD83C\\uDF19\","
"\"2 byte\":\"гласность\","
"\"3 byte\":\"\","
"\"4 byte\":\"🌎\""
"}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: escapes\n"
"string16: 🌙\n"
"string16: 2 byte\n"
"string16: гласность\n"
"string16: 3 byte\n"
"string16: 屋\n"
"string16: 4 byte\n"
"string16: 🌎\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UnprocessedInputRemainsError) {
// Trailing junk after the valid JSON.
std::string json = "{\"foo\": 3.1415} junk";
size_t junk_idx = json.find("junk");
EXPECT_NE(junk_idx, std::string::npos);
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, log_.status().error);
EXPECT_EQ(junk_idx, log_.status().pos);
EXPECT_EQ("", log_.str());
}
std::string MakeNestedJson(int depth) {
std::string json;
for (int ii = 0; ii < depth; ++ii)
json += "{\"foo\":";
json += "42";
for (int ii = 0; ii < depth; ++ii)
json += "}";
return json;
}
TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) {
// kStackLimit is 300 (see json_parser.cc). First let's
// try with a small nested example.
std::string json_3 = MakeNestedJson(3);
ParseJSON(GetTestPlatform(), SpanFrom(json_3), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) {
// Now with kStackLimit (300).
std::string json_limit = MakeNestedJson(300);
ParseJSON(GetTestPlatform(),
span<uint8_t>(reinterpret_cast<const uint8_t*>(json_limit.data()),
json_limit.size()),
&log_);
EXPECT_TRUE(log_.status().ok());
}
TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) {
// Now with kStackLimit + 1 (301) - it exceeds in the innermost instance.
std::string exceeded = MakeNestedJson(301);
ParseJSON(GetTestPlatform(), SpanFrom(exceeded), &log_);
EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
}
TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) {
// Now way past the limit. Still, the point of exceeding is 301.
std::string far_out = MakeNestedJson(320);
ParseJSON(GetTestPlatform(), SpanFrom(far_out), &log_);
EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
}
TEST_F(JsonParserTest, NoInputError) {
std::string json = "";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_NO_INPUT, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidTokenError) {
std::string json = "|";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_TOKEN, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidNumberError) {
// Mantissa exceeds max (the constant used here is int64_t max).
std::string json = "1E9223372036854775807";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_NUMBER, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidStringError) {
// \x22 is an unsupported escape sequence
std::string json = "\"foo\\x22\"";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_INVALID_STRING, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedArrayEndError) {
std::string json = "[1,2,]";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, log_.status().error);
EXPECT_EQ(5u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) {
std::string json = "[1,2 2";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED,
log_.status().error);
EXPECT_EQ(5u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, StringLiteralExpectedError) {
// There's an error because the key bar, a string, is not terminated.
std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, log_.status().error);
EXPECT_EQ(16u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ColonExpectedError) {
std::string json = "{\"foo\", 42}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COLON_EXPECTED, log_.status().error);
EXPECT_EQ(6u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedMapEndError) {
std::string json = "{\"foo\": 42, }";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_MAP_END, log_.status().error);
EXPECT_EQ(12u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrMapEndExpectedError) {
// The second separator should be a comma.
std::string json = "{\"foo\": 3.1415: \"bar\": 0}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, log_.status().error);
EXPECT_EQ(14u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ValueExpectedError) {
std::string json = "}";
ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, log_.status().error);
EXPECT_EQ(0u, log_.status().pos);
EXPECT_EQ("", log_.str());
}
template <typename T>
class ConvertJSONToCBORTest : public ::testing::Test {};
using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes);
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) {
std::string json_in = "{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}";
TypeParam json(json_in.begin(), json_in.end());
TypeParam cbor;
{
Status status = ConvertJSONToCBOR(GetTestPlatform(), SpanFrom(json), &cbor);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
TypeParam roundtrip_json;
{
Status status =
ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
EXPECT_EQ(json, roundtrip_json);
}
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) {
std::vector<uint16_t> json16 = {
'{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l',
'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't',
'"', ':', '[', '1', ',', '2', ',', '3', ']', '}'};
TypeParam cbor;
{
Status status = ConvertJSONToCBOR(
GetTestPlatform(), span<uint16_t>(json16.data(), json16.size()), &cbor);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
TypeParam roundtrip_json;
{
Status status =
ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
EXPECT_EQ(Error::OK, status.error);
EXPECT_EQ(Status::npos(), status.pos);
}
std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}";
TypeParam expected_json(json.begin(), json.end());
EXPECT_EQ(expected_json, roundtrip_json);
}
} // namespace json
} // namespace v8_crdtp
// 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 V8_CRDTP_PARSER_HANDLER_H_
#define V8_CRDTP_PARSER_HANDLER_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "export.h"
#include "span.h"
#include "status.h"
namespace v8_crdtp {
// Handler interface for parser events emitted by a streaming parser.
// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder,
// json::ParseJSON.
class StreamingParserHandler {
public:
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(span<uint8_t> bytes) = 0;
virtual void HandleDouble(double value) = 0;
virtual void HandleInt32(int32_t value) = 0;
virtual void HandleBool(bool value) = 0;
virtual void HandleNull() = 0;
// The parser may send one error even after other events have already
// been received. Client code is reponsible to then discard the
// already processed events.
// |error| must be an eror, as in, |error.is_ok()| can't be true.
virtual void HandleError(Status error) = 0;
};
} // namespace v8_crdtp
#endif // V8_CRDTP_PARSER_HANDLER_H_
// 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 V8_CRDTP_SPAN_H_
#define V8_CRDTP_SPAN_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "export.h"
namespace v8_crdtp {
// =============================================================================
// span - sequence of bytes
// =============================================================================
// This template is similar to std::span, which will be included in C++20.
template <typename T>
class span {
public:
using index_type = size_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> SpanFrom(const std::vector<T>& v) {
return span<T>(v.data(), v.size());
}
template <size_t N>
span<uint8_t> SpanFrom(const char (&str)[N]) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(str), N - 1);
}
inline span<uint8_t> SpanFrom(const char* str) {
return str ? span<uint8_t>(reinterpret_cast<const uint8_t*>(str), strlen(str))
: span<uint8_t>();
}
inline span<uint8_t> SpanFrom(const std::string& v) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(v.data()), v.size());
}
// Less than / equality comparison functions for sorting / searching for byte
// spans. These are similar to absl::string_view's < and == operators.
inline bool SpanLessThan(span<uint8_t> x, span<uint8_t> y) noexcept {
auto min_size = std::min(x.size(), y.size());
const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size);
return (r < 0) || (r == 0 && x.size() < y.size());
}
inline bool SpanEquals(span<uint8_t> x, span<uint8_t> y) noexcept {
auto len = x.size();
if (len != y.size())
return false;
return x.data() == y.data() || len == 0 ||
std::memcmp(x.data(), y.data(), len) == 0;
}
} // namespace v8_crdtp
#endif // V8_CRDTP_SPAN_H_
// Copyright 2018 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.
#include "span.h"
#include <array>
#include <clocale>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include "test_platform.h"
using testing::ElementsAreArray;
namespace v8_crdtp {
// =============================================================================
// span - sequence of bytes
// =============================================================================
template <typename T>
class SpanTest : public ::testing::Test {};
using TestTypes = ::testing::Types<uint8_t, uint16_t>;
TYPED_TEST_SUITE(SpanTest, TestTypes);
TYPED_TEST(SpanTest, Empty) {
span<TypeParam> empty;
EXPECT_TRUE(empty.empty());
EXPECT_EQ(0u, empty.size());
EXPECT_EQ(0u, empty.size_bytes());
EXPECT_EQ(empty.begin(), empty.end());
}
TYPED_TEST(SpanTest, SingleItem) {
TypeParam single_item = 42;
span<TypeParam> singular(&single_item, 1);
EXPECT_FALSE(singular.empty());
EXPECT_EQ(1u, singular.size());
EXPECT_EQ(sizeof(TypeParam), singular.size_bytes());
EXPECT_EQ(singular.begin() + 1, singular.end());
EXPECT_EQ(42, singular[0]);
}
TYPED_TEST(SpanTest, FiveItems) {
std::vector<TypeParam> test_input = {31, 32, 33, 34, 35};
span<TypeParam> five_items(test_input.data(), 5);
EXPECT_FALSE(five_items.empty());
EXPECT_EQ(5u, five_items.size());
EXPECT_EQ(sizeof(TypeParam) * 5, five_items.size_bytes());
EXPECT_EQ(five_items.begin() + 5, five_items.end());
EXPECT_EQ(31, five_items[0]);
EXPECT_EQ(32, five_items[1]);
EXPECT_EQ(33, five_items[2]);
EXPECT_EQ(34, five_items[3]);
EXPECT_EQ(35, five_items[4]);
span<TypeParam> three_items = five_items.subspan(2);
EXPECT_EQ(3u, three_items.size());
EXPECT_EQ(33, three_items[0]);
EXPECT_EQ(34, three_items[1]);
EXPECT_EQ(35, three_items[2]);
span<TypeParam> two_items = five_items.subspan(2, 2);
EXPECT_EQ(2u, two_items.size());
EXPECT_EQ(33, two_items[0]);
EXPECT_EQ(34, two_items[1]);
}
TEST(SpanFromTest, FromConstCharAndLiteral) {
// Testing this is useful because strlen(nullptr) is undefined.
EXPECT_EQ(nullptr, SpanFrom(nullptr).data());
EXPECT_EQ(0u, SpanFrom(nullptr).size());
const char* kEmpty = "";
EXPECT_EQ(kEmpty, reinterpret_cast<const char*>(SpanFrom(kEmpty).data()));
EXPECT_EQ(0u, SpanFrom(kEmpty).size());
const char* kFoo = "foo";
EXPECT_EQ(kFoo, reinterpret_cast<const char*>(SpanFrom(kFoo).data()));
EXPECT_EQ(3u, SpanFrom(kFoo).size());
EXPECT_EQ(3u, SpanFrom("foo").size());
}
TEST(SpanComparisons, ByteWiseLexicographicalOrder) {
// Compare the empty span.
EXPECT_FALSE(SpanLessThan(span<uint8_t>(), span<uint8_t>()));
EXPECT_TRUE(SpanEquals(span<uint8_t>(), span<uint8_t>()));
// Compare message with itself.
std::string msg = "Hello, world";
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(msg)));
EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(msg)));
// Compare message and copy.
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(std::string(msg))));
EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(std::string(msg))));
// Compare two messages. |lesser_msg| < |msg| because of the first
// byte ('A' < 'H').
std::string lesser_msg = "A lesser message.";
EXPECT_TRUE(SpanLessThan(SpanFrom(lesser_msg), SpanFrom(msg)));
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(lesser_msg)));
EXPECT_FALSE(SpanEquals(SpanFrom(msg), SpanFrom(lesser_msg)));
}
} // namespace v8_crdtp
// 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.
#include "status.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstring>
#include <limits>
#include <stack>
namespace v8_crdtp {
// =============================================================================
// Status and Error codes
// =============================================================================
std::string Status::ToASCIIString() const {
switch (error) {
case Error::OK:
return "OK";
case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS:
return ToASCIIString("JSON: unprocessed input remains");
case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED:
return ToASCIIString("JSON: stack limit exceeded");
case Error::JSON_PARSER_NO_INPUT:
return ToASCIIString("JSON: no input");
case Error::JSON_PARSER_INVALID_TOKEN:
return ToASCIIString("JSON: invalid token");
case Error::JSON_PARSER_INVALID_NUMBER:
return ToASCIIString("JSON: invalid number");
case Error::JSON_PARSER_INVALID_STRING:
return ToASCIIString("JSON: invalid string");
case Error::JSON_PARSER_UNEXPECTED_ARRAY_END:
return ToASCIIString("JSON: unexpected array end");
case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED:
return ToASCIIString("JSON: comma or array end expected");
case Error::JSON_PARSER_STRING_LITERAL_EXPECTED:
return ToASCIIString("JSON: string literal expected");
case Error::JSON_PARSER_COLON_EXPECTED:
return ToASCIIString("JSON: colon expected");
case Error::JSON_PARSER_UNEXPECTED_MAP_END:
return ToASCIIString("JSON: unexpected map end");
case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED:
return ToASCIIString("JSON: comma or map end expected");
case Error::JSON_PARSER_VALUE_EXPECTED:
return ToASCIIString("JSON: value expected");
case Error::CBOR_INVALID_INT32:
return ToASCIIString("CBOR: invalid int32");
case Error::CBOR_INVALID_DOUBLE:
return ToASCIIString("CBOR: invalid double");
case Error::CBOR_INVALID_ENVELOPE:
return ToASCIIString("CBOR: invalid envelope");
case Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH:
return ToASCIIString("CBOR: envelope contents length mismatch");
case Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE:
return ToASCIIString("CBOR: map or array expected in envelope");
case Error::CBOR_INVALID_STRING8:
return ToASCIIString("CBOR: invalid string8");
case Error::CBOR_INVALID_STRING16:
return ToASCIIString("CBOR: invalid string16");
case Error::CBOR_INVALID_BINARY:
return ToASCIIString("CBOR: invalid binary");
case Error::CBOR_UNSUPPORTED_VALUE:
return ToASCIIString("CBOR: unsupported value");
case Error::CBOR_NO_INPUT:
return ToASCIIString("CBOR: no input");
case Error::CBOR_INVALID_START_BYTE:
return ToASCIIString("CBOR: invalid start byte");
case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE:
return ToASCIIString("CBOR: unexpected eof expected value");
case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY:
return ToASCIIString("CBOR: unexpected eof in array");
case Error::CBOR_UNEXPECTED_EOF_IN_MAP:
return ToASCIIString("CBOR: unexpected eof in map");
case Error::CBOR_INVALID_MAP_KEY:
return ToASCIIString("CBOR: invalid map key");
case Error::CBOR_STACK_LIMIT_EXCEEDED:
return ToASCIIString("CBOR: stack limit exceeded");
case Error::CBOR_TRAILING_JUNK:
return ToASCIIString("CBOR: trailing junk");
case Error::CBOR_MAP_START_EXPECTED:
return ToASCIIString("CBOR: map start expected");
case Error::CBOR_MAP_STOP_EXPECTED:
return ToASCIIString("CBOR: map stop expected");
case Error::CBOR_ARRAY_START_EXPECTED:
return ToASCIIString("CBOR: array start expected");
case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED:
return ToASCIIString("CBOR: envelope size limit exceeded");
case Error::BINDINGS_MANDATORY_FIELD_MISSING:
return ToASCIIString("BINDINGS: mandatory field missing");
case Error::BINDINGS_BOOL_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: bool value expected");
case Error::BINDINGS_INT32_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: int32 value expected");
case Error::BINDINGS_DOUBLE_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: double value expected");
case Error::BINDINGS_STRING_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: string value expected");
case Error::BINDINGS_STRING8_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: string8 value expected");
case Error::BINDINGS_BINARY_VALUE_EXPECTED:
return ToASCIIString("BINDINGS: binary value expected");
}
// Some compilers can't figure out that we can't get here.
return "INVALID ERROR CODE";
}
std::string Status::ToASCIIString(const char* msg) const {
return std::string(msg) + " at position " + std::to_string(pos);
}
} // namespace v8_crdtp
// 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 V8_CRDTP_ENCODING_H_
#define V8_CRDTP_ENCODING_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "export.h"
namespace v8_crdtp {
// =============================================================================
// Status and Error codes
// =============================================================================
enum class Error {
OK = 0,
// JSON parsing errors - json_parser.{h,cc}.
JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01,
JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02,
JSON_PARSER_NO_INPUT = 0x03,
JSON_PARSER_INVALID_TOKEN = 0x04,
JSON_PARSER_INVALID_NUMBER = 0x05,
JSON_PARSER_INVALID_STRING = 0x06,
JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07,
JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08,
JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09,
JSON_PARSER_COLON_EXPECTED = 0x0a,
JSON_PARSER_UNEXPECTED_MAP_END = 0x0b,
JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c,
JSON_PARSER_VALUE_EXPECTED = 0x0d,
CBOR_INVALID_INT32 = 0x0e,
CBOR_INVALID_DOUBLE = 0x0f,
CBOR_INVALID_ENVELOPE = 0x10,
CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH = 0x11,
CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE = 0x12,
CBOR_INVALID_STRING8 = 0x13,
CBOR_INVALID_STRING16 = 0x14,
CBOR_INVALID_BINARY = 0x15,
CBOR_UNSUPPORTED_VALUE = 0x16,
CBOR_NO_INPUT = 0x17,
CBOR_INVALID_START_BYTE = 0x18,
CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19,
CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a,
CBOR_UNEXPECTED_EOF_IN_MAP = 0x1b,
CBOR_INVALID_MAP_KEY = 0x1c,
CBOR_STACK_LIMIT_EXCEEDED = 0x1d,
CBOR_TRAILING_JUNK = 0x1e,
CBOR_MAP_START_EXPECTED = 0x1f,
CBOR_MAP_STOP_EXPECTED = 0x20,
CBOR_ARRAY_START_EXPECTED = 0x21,
CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x22,
BINDINGS_MANDATORY_FIELD_MISSING = 0x23,
BINDINGS_BOOL_VALUE_EXPECTED = 0x24,
BINDINGS_INT32_VALUE_EXPECTED = 0x25,
BINDINGS_DOUBLE_VALUE_EXPECTED = 0x26,
BINDINGS_STRING_VALUE_EXPECTED = 0x27,
BINDINGS_STRING8_VALUE_EXPECTED = 0x28,
BINDINGS_BINARY_VALUE_EXPECTED = 0x29,
};
// A status value with position that can be copied. The default status
// is OK. Usually, error status values should come with a valid position.
struct Status {
static constexpr size_t npos() { return std::numeric_limits<size_t>::max(); }
bool ok() const { return error == Error::OK; }
Error error = Error::OK;
size_t pos = npos();
Status(Error error, size_t pos) : error(error), pos(pos) {}
Status() = default;
// Returns a 7 bit US-ASCII string, either "OK" or an error message
// that includes the position.
std::string ToASCIIString() const;
private:
std::string ToASCIIString(const char* msg) const;
};
} // namespace v8_crdtp
#endif // V8_CRDTP_ENCODING_H_
// Copyright 2018 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.
#include "status.h"
#include <array>
#include <clocale>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include "test_platform.h"
using testing::ElementsAreArray;
namespace v8_crdtp {
// =============================================================================
// Status and Error codes
// =============================================================================
TEST(StatusTest, StatusToASCIIString) {
Status ok_status;
EXPECT_EQ("OK", ok_status.ToASCIIString());
Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42);
EXPECT_EQ("JSON: colon expected at position 42", json_error.ToASCIIString());
Status cbor_error(Error::CBOR_TRAILING_JUNK, 21);
EXPECT_EQ("CBOR: trailing junk at position 21", cbor_error.ToASCIIString());
}
} // namespace v8_crdtp
......@@ -5,18 +5,11 @@
// This file is V8 specific, to make encoding_test.cc work.
// It is not rolled from the upstream project.
#ifndef V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_
#define V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_
#include "test_platform.h"
#include <string>
#include <vector>
#include "src/base/logging.h"
#include "src/inspector/v8-string-conversions.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8_inspector_protocol_encoding {
namespace v8_crdtp {
std::string UTF16ToUTF8(span<uint16_t> in) {
return v8_inspector::UTF16ToUTF8(in.data(), in.size());
......@@ -28,6 +21,4 @@ std::vector<uint16_t> UTF8ToUTF16(span<uint8_t> in) {
return std::vector<uint16_t>(utf16.begin(), utf16.end());
}
} // namespace v8_inspector_protocol_encoding
#endif // V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_
} // namespace v8_crdtp
......@@ -2,17 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is V8 specific, to make bindings_test.cc work.
// This file is V8 specific, to make encoding_test.cc work.
// It is not rolled from the upstream project.
#ifndef V8_INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_TEST_HELPER_H_
#define V8_INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_TEST_HELPER_H_
#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_
#define V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_
#include <string>
#include <vector>
#include "span.h"
#include "src/base/logging.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#endif // V8_INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_TEST_HELPER_H_
namespace v8_crdtp {
std::string UTF16ToUTF8(span<uint16_t> in);
std::vector<uint16_t> UTF8ToUTF16(span<uint8_t> in);
} // namespace v8_crdtp
#endif // V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_
......@@ -316,10 +316,10 @@ std::unique_ptr<Serializable> InternalResponse::createErrorResponse(int callId,
void InternalResponse::AppendSerialized(std::vector<uint8_t>* out) const
{
using {{config.encoding_lib.namespace}}::cbor::NewCBOREncoder;
using {{config.encoding_lib.namespace}}::StreamingParserHandler;
using {{config.encoding_lib.namespace}}::Status;
using {{config.encoding_lib.namespace}}::SpanFrom;
using {{config.crdtp.namespace}}::cbor::NewCBOREncoder;
using {{config.crdtp.namespace}}::StreamingParserHandler;
using {{config.crdtp.namespace}}::Status;
using {{config.crdtp.namespace}}::SpanFrom;
Status status;
std::unique_ptr<StreamingParserHandler> encoder =
......
......@@ -18,7 +18,7 @@
#include <unordered_map>
#include <unordered_set>
#include "{{config.bindings_lib.header}}"
#include "{{config.crdtp.dir}}/glue.h"
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
......@@ -57,8 +57,8 @@ template <typename T>
using Array = typename detail::ArrayTypedef<T>::type;
namespace detail {
using {{config.bindings_lib.namespace}}::glue::detail::PtrMaybe;
using {{config.bindings_lib.namespace}}::glue::detail::ValueMaybe;
using {{config.crdtp.namespace}}::glue::detail::PtrMaybe;
using {{config.crdtp.namespace}}::glue::detail::ValueMaybe;
template <typename T>
struct MaybeTypedef { typedef PtrMaybe<T> type; };
......
......@@ -6,7 +6,7 @@
//#include "Values.h"
#include "{{config.encoding_lib.header}}"
#include "{{config.crdtp.dir}}/cbor.h"
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
......@@ -66,26 +66,26 @@ void escapeStringForJSONInternal(const Char* str, unsigned len,
// to this constant.
static constexpr int kStackLimitValues = 1000;
using {{config.encoding_lib.namespace}}::Error;
using {{config.encoding_lib.namespace}}::Status;
using {{config.encoding_lib.namespace}}::span;
using {{config.crdtp.namespace}}::Error;
using {{config.crdtp.namespace}}::Status;
using {{config.crdtp.namespace}}::span;
namespace cbor {
using {{config.encoding_lib.namespace}}::cbor::CBORTokenTag;
using {{config.encoding_lib.namespace}}::cbor::CBORTokenizer;
using {{config.encoding_lib.namespace}}::cbor::EncodeBinary;
using {{config.encoding_lib.namespace}}::cbor::EncodeDouble;
using {{config.encoding_lib.namespace}}::cbor::EncodeFalse;
using {{config.encoding_lib.namespace}}::cbor::EncodeFromLatin1;
using {{config.encoding_lib.namespace}}::cbor::EncodeFromUTF16;
using {{config.encoding_lib.namespace}}::cbor::EncodeIndefiniteLengthArrayStart;
using {{config.encoding_lib.namespace}}::cbor::EncodeIndefiniteLengthMapStart;
using {{config.encoding_lib.namespace}}::cbor::EncodeInt32;
using {{config.encoding_lib.namespace}}::cbor::EncodeNull;
using {{config.encoding_lib.namespace}}::cbor::EncodeStop;
using {{config.encoding_lib.namespace}}::cbor::EncodeString8;
using {{config.encoding_lib.namespace}}::cbor::EncodeTrue;
using {{config.encoding_lib.namespace}}::cbor::EnvelopeEncoder;
using {{config.encoding_lib.namespace}}::cbor::InitialByteForEnvelope;
using {{config.crdtp.namespace}}::cbor::CBORTokenTag;
using {{config.crdtp.namespace}}::cbor::CBORTokenizer;
using {{config.crdtp.namespace}}::cbor::EncodeBinary;
using {{config.crdtp.namespace}}::cbor::EncodeDouble;
using {{config.crdtp.namespace}}::cbor::EncodeFalse;
using {{config.crdtp.namespace}}::cbor::EncodeFromLatin1;
using {{config.crdtp.namespace}}::cbor::EncodeFromUTF16;
using {{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthArrayStart;
using {{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart;
using {{config.crdtp.namespace}}::cbor::EncodeInt32;
using {{config.crdtp.namespace}}::cbor::EncodeNull;
using {{config.crdtp.namespace}}::cbor::EncodeStop;
using {{config.crdtp.namespace}}::cbor::EncodeString8;
using {{config.crdtp.namespace}}::cbor::EncodeTrue;
using {{config.crdtp.namespace}}::cbor::EnvelopeEncoder;
using {{config.crdtp.namespace}}::cbor::InitialByteForEnvelope;
} // namespace cbor
// Below are three parsing routines for CBOR, which cover enough
......
......@@ -18,12 +18,21 @@ FILES_TO_SYNC = [
'code_generator.py',
'concatenate_protocols.py',
'convert_protocol_to_json.py',
'encoding/encoding.h',
'encoding/encoding.cc',
'encoding/encoding_test.cc',
'bindings/bindings.h',
'bindings/bindings.cc',
'bindings/bindings_test.cc',
'crdtp/cbor.cc',
'crdtp/cbor.h',
'crdtp/cbor_test.cc',
'crdtp/glue.h',
'crdtp/glue_test.cc',
'crdtp/json.cc',
'crdtp/json.h',
'crdtp/json_platform.h',
'crdtp/json_test.cc',
'crdtp/parser_handler.h',
'crdtp/span.h',
'crdtp/span_test.cc',
'crdtp/status.cc',
'crdtp/status.h',
'crdtp/status_test.cc',
'inspector_protocol.gni',
'inspector_protocol.gypi',
'lib/*',
......@@ -140,18 +149,13 @@ def main(argv):
print('You said --force ... as you wish, modifying the destination.')
for f in to_add + to_copy:
contents = open(os.path.join(src_dir, f)).read()
contents = contents.replace('CRDTP_EXPORT ', '')
contents = contents.replace(
'INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_',
'V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_')
'CRDTP_',
'V8_CRDTP_')
contents = contents.replace(
'namespace inspector_protocol_encoding',
'namespace v8_inspector_protocol_encoding')
contents = contents.replace(
'INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_H_',
'V8_INSPECTOR_PROTOCOL_BINDINGS_BINDINGS_H_')
contents = contents.replace(
'namespace inspector_protocol_bindings',
'namespace v8_inspector_protocol_bindings')
'namespace crdtp',
'namespace v8_crdtp')
open(os.path.join(dest_dir, f), 'w').write(contents)
shutil.copymode(os.path.join(src_dir, f), os.path.join(dest_dir, f))
for f in to_delete:
......
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