diff --git a/src/inspector/node_inspector.gypi b/src/inspector/node_inspector.gypi index aea4de894fbe99..2c428604708204 100644 --- a/src/inspector/node_inspector.gypi +++ b/src/inspector/node_inspector.gypi @@ -34,7 +34,7 @@ '<(protocol_tool_path)/templates/Imported_h.template', '<(protocol_tool_path)/templates/TypeBuilder_cpp.template', '<(protocol_tool_path)/templates/TypeBuilder_h.template', - '<(protocol_tool_path)/CodeGenerator.py', + '<(protocol_tool_path)/code_generator.py', ] }, 'defines': [ @@ -86,7 +86,7 @@ ], 'action': [ 'python', - 'tools/inspector_protocol/ConvertProtocolToJSON.py', + 'tools/inspector_protocol/convert_protocol_to_json.py', '<@(_inputs)', '<@(_outputs)', ], @@ -105,7 +105,7 @@ 'process_outputs_as_sources': 1, 'action': [ 'python', - 'tools/inspector_protocol/CodeGenerator.py', + 'tools/inspector_protocol/code_generator.py', '--jinja_dir', '<@(protocol_tool_path)/..', '--output_base', '<(SHARED_INTERMEDIATE_DIR)/src/', '--config', '<(SHARED_INTERMEDIATE_DIR)/node_protocol_config.json', @@ -123,7 +123,7 @@ ], 'action': [ 'python', - 'tools/inspector_protocol/ConcatenateProtocols.py', + 'tools/inspector_protocol/concatenate_protocols.py', '<@(_inputs)', '<@(_outputs)', ], diff --git a/src/inspector/node_string.cc b/src/inspector/node_string.cc index cb9e90c20e807a..a79df9e817c049 100644 --- a/src/inspector/node_string.cc +++ b/src/inspector/node_string.cc @@ -85,6 +85,28 @@ double toDouble(const char* buffer, size_t length, bool* ok) { return d; } +std::unique_ptr parseMessage(const std::string& message, bool binary) { + if (binary) { + return Value::parseBinary( + reinterpret_cast(message.data()), + message.length()); + } + return parseJSON(message); +} + +ProtocolMessage jsonToMessage(String message) { + return message; +} + +ProtocolMessage binaryToMessage(std::vector message) { + return std::string(reinterpret_cast(message.data()), + message.size()); +} + +String fromUTF8(const uint8_t* data, size_t length) { + return std::string(reinterpret_cast(data), length); +} + } // namespace StringUtil } // namespace protocol } // namespace inspector diff --git a/src/inspector/node_string.h b/src/inspector/node_string.h index 468aec96b56e79..3496cf9e6904f3 100644 --- a/src/inspector/node_string.h +++ b/src/inspector/node_string.h @@ -18,6 +18,17 @@ class Value; using String = std::string; using StringBuilder = std::ostringstream; +using ProtocolMessage = std::string; + +class StringUTF8Adapter { + public: + explicit StringUTF8Adapter(const std::string& string) : string_(string) { } + const char* Data() const { return string_.data(); } + size_t length() const { return string_.length(); } + + private: + const std::string& string_; +}; namespace StringUtil { // NOLINTNEXTLINE(runtime/references) This is V8 API... @@ -67,8 +78,29 @@ void builderAppendQuotedString(StringBuilder& builder, const String&); std::unique_ptr parseJSON(const String&); std::unique_ptr parseJSON(v8_inspector::StringView view); +std::unique_ptr parseMessage(const std::string& message, bool binary); +ProtocolMessage jsonToMessage(String message); +ProtocolMessage binaryToMessage(std::vector message); +String fromUTF8(const uint8_t* data, size_t length); + extern size_t kNotFound; } // namespace StringUtil + +// A read-only sequence of uninterpreted bytes with reference-counted storage. +// Though the templates for generating the protocol bindings reference +// this type, js_protocol.pdl doesn't have a field of type 'binary', so +// therefore it's unnecessary to provide an implementation here. +class Binary { + public: + const uint8_t* data() const { UNREACHABLE(); } + size_t size() const { UNREACHABLE(); } + String toBase64() const { UNREACHABLE(); } + static Binary fromBase64(const String& base64, bool* success) { + UNREACHABLE(); + } + static Binary fromSpan(const uint8_t* data, size_t size) { UNREACHABLE(); } +}; + } // namespace protocol } // namespace inspector } // namespace node diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 47bcf8cbfb1b89..ef2f01d3690443 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -229,15 +229,19 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, } std::string dispatchProtocolMessage(const StringView& message) { - std::unique_ptr parsed; + std::string raw_message = protocol::StringUtil::StringViewToUtf8(message); + std::unique_ptr value = + protocol::DictionaryValue::cast(protocol::StringUtil::parseMessage( + raw_message, false)); + int call_id; std::string method; - node_dispatcher_->getCommandName( - protocol::StringUtil::StringViewToUtf8(message), &method, &parsed); + node_dispatcher_->parseCommand(value.get(), &call_id, &method); if (v8_inspector::V8InspectorSession::canDispatchMethod( Utf8ToStringView(method)->string())) { session_->dispatchProtocolMessage(message); } else { - node_dispatcher_->dispatch(std::move(parsed)); + node_dispatcher_->dispatch(call_id, method, std::move(value), + raw_message); } return method; } @@ -277,11 +281,17 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, void sendProtocolResponse(int callId, std::unique_ptr message) override { - sendMessageToFrontend(message->serialize()); + sendMessageToFrontend(message->serializeToJSON()); } void sendProtocolNotification( std::unique_ptr message) override { - sendMessageToFrontend(message->serialize()); + sendMessageToFrontend(message->serializeToJSON()); + } + + void fallThrough(int callId, + const std::string& method, + const std::string& message) override { + DCHECK(false); } std::unique_ptr tracing_agent_; diff --git a/tools/inspector_protocol/README.v8 b/tools/inspector_protocol/README.node similarity index 88% rename from tools/inspector_protocol/README.v8 rename to tools/inspector_protocol/README.node index 8a82f2a9c9d691..6f22020a4de3dc 100644 --- a/tools/inspector_protocol/README.v8 +++ b/tools/inspector_protocol/README.node @@ -2,7 +2,7 @@ Name: inspector protocol Short Name: inspector_protocol URL: https://chromium.googlesource.com/deps/inspector_protocol/ Version: 0 -Revision: 752d4abd13119010cf30e454e8ef9b5fb7ef43a3 +Revision: f67ec5180f476830e839226b5ca948e43070fdab License: BSD License File: LICENSE Security Critical: no diff --git a/tools/inspector_protocol/CheckProtocolCompatibility.py b/tools/inspector_protocol/check_protocol_compatibility.py similarity index 97% rename from tools/inspector_protocol/CheckProtocolCompatibility.py rename to tools/inspector_protocol/check_protocol_compatibility.py index c70162a2a44ef0..d2df244fa97154 100755 --- a/tools/inspector_protocol/CheckProtocolCompatibility.py +++ b/tools/inspector_protocol/check_protocol_compatibility.py @@ -45,11 +45,14 @@ # # Adding --show_changes to the command line prints out a list of valid public API changes. +from __future__ import print_function import copy import os.path import optparse import sys +import pdl + try: import json except ImportError: @@ -166,6 +169,11 @@ def compare_types(context, kind, type_1, type_2, types_map_1, types_map_2, depth base_type_1 = type_1["type"] base_type_2 = type_2["type"] + # Binary and string have the same wire representation in JSON. + if ((base_type_1 == "string" and base_type_2 == "binary") or + (base_type_2 == "string" and base_type_1 == "binary")): + return + if base_type_1 != base_type_2: errors.append("%s: %s base type mismatch, '%s' vs '%s'" % (context, kind, base_type_1, base_type_2)) elif base_type_1 == "object": @@ -228,8 +236,8 @@ def load_schema(file_name, domains): if not os.path.isfile(file_name): return input_file = open(file_name, "r") - json_string = input_file.read() - parsed_json = json.loads(json_string) + parsed_json = pdl.loads(input_file.read(), file_name) + input_file.close() domains += parsed_json["domains"] return parsed_json["version"] @@ -422,6 +430,7 @@ def load_domains_and_baselines(file_name, domains, baseline_domains): version = load_schema(os.path.normpath(file_name), domains) suffix = "-%s.%s.json" % (version["major"], version["minor"]) baseline_file = file_name.replace(".json", suffix) + baseline_file = file_name.replace(".pdl", suffix) load_schema(os.path.normpath(baseline_file), baseline_domains) return version @@ -467,9 +476,9 @@ def main(): if arg_options.show_changes: changes = compare_schemas(domains, baseline_domains, True) if len(changes) > 0: - print " Public changes since %s:" % version + print(" Public changes since %s:" % version) for change in changes: - print " %s" % change + print(" %s" % change) if arg_options.stamp: with open(arg_options.stamp, 'a') as _: diff --git a/tools/inspector_protocol/CodeGenerator.py b/tools/inspector_protocol/code_generator.py old mode 100644 new mode 100755 similarity index 90% rename from tools/inspector_protocol/CodeGenerator.py rename to tools/inspector_protocol/code_generator.py index e630b02985710f..fb9959d6082a7b --- a/tools/inspector_protocol/CodeGenerator.py +++ b/tools/inspector_protocol/code_generator.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Copyright 2016 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. @@ -14,6 +15,8 @@ except ImportError: import simplejson as json +import pdl + # Path handling for libraries and templates # Paths have to be normalized because Jinja uses the exact template path to # determine the hash used in the cache filename, and we need a pre-caching step @@ -30,14 +33,14 @@ def json_to_object(data, output_base, config_base): def json_object_hook(object_dict): items = [(k, os.path.join(config_base, v) if k == "path" else v) for (k, v) in object_dict.items()] items = [(k, os.path.join(output_base, v) if k == "output" else v) for (k, v) in items] - keys, values = zip(*items) + keys, values = list(zip(*items)) return collections.namedtuple('X', keys)(*values) return json.loads(data, object_hook=json_object_hook) def init_defaults(config_tuple, path, defaults): keys = list(config_tuple._fields) # pylint: disable=E1101 values = [getattr(config_tuple, k) for k in keys] - for i in xrange(len(keys)): + for i in range(len(keys)): if hasattr(values[i], "_fields"): values[i] = init_defaults(values[i], path + "." + keys[i], defaults) for optional in defaults: @@ -95,6 +98,7 @@ def init_defaults(config_tuple, path, defaults): ".protocol.export_macro": "", ".protocol.export_header": False, ".protocol.options": False, + ".protocol.file_name_prefix": "", ".exported": False, ".exported.export_macro": "", ".exported.export_header": False, @@ -130,7 +134,7 @@ def dash_to_camelcase(word): def to_snake_case(name): - return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxint).lower() + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxsize).lower() def to_method_case(config, name): @@ -156,6 +160,10 @@ def format_include(config, header, file_name=None): return header +def format_domain_include(config, header, file_name): + return format_include(config, header, config.protocol.file_name_prefix + file_name) + + def to_file_name(config, file_name): if config.use_snake_file_names: return to_snake_case(file_name).replace(".cpp", ".cc") @@ -258,6 +266,21 @@ def create_string_type_definition(): } +def create_binary_type_definition(): + # pylint: disable=W0622 + return { + "return_type": "Binary", + "pass_type": "const Binary&", + "to_pass_type": "%s", + "to_raw_type": "%s", + "to_rvalue": "%s", + "type": "Binary", + "raw_type": "Binary", + "raw_pass_type": "const Binary&", + "raw_return_type": "Binary", + } + + def create_primitive_type_definition(type): # pylint: disable=W0622 typedefs = { @@ -330,9 +353,8 @@ def __init__(self, config): def read_protocol_file(self, file_name): input_file = open(file_name, "r") - json_string = input_file.read() + parsed_json = pdl.loads(input_file.read(), file_name) input_file.close() - parsed_json = json.loads(json_string) version = parsed_json["version"]["major"] + "." + parsed_json["version"]["minor"] domains = [] for domain in parsed_json["domains"]: @@ -436,8 +458,10 @@ def create_type_definitions(self): self.type_definitions["boolean"] = create_primitive_type_definition("boolean") self.type_definitions["object"] = create_object_type_definition() self.type_definitions["any"] = create_any_type_definition() + self.type_definitions["binary"] = create_binary_type_definition() for domain in self.json_api["domains"]: self.type_definitions[domain["domain"] + ".string"] = create_string_type_definition() + self.type_definitions[domain["domain"] + ".binary"] = create_binary_type_definition() if not ("types" in domain): continue for type in domain["types"]: @@ -447,10 +471,11 @@ def create_type_definitions(self): elif type["type"] == "object": self.type_definitions[type_name] = create_user_type_definition(domain["domain"], type) elif type["type"] == "array": - items_type = type["items"]["type"] - self.type_definitions[type_name] = wrap_array_definition(self.type_definitions[items_type]) + self.type_definitions[type_name] = self.resolve_type(type) elif type["type"] == domain["domain"] + ".string": self.type_definitions[type_name] = create_string_type_definition() + elif type["type"] == domain["domain"] + ".binary": + self.type_definitions[type_name] = create_binary_type_definition() else: self.type_definitions[type_name] = create_primitive_type_definition(type["type"]) @@ -571,21 +596,23 @@ def main(): for domain in protocol.json_api["domains"]: class_name = domain["domain"] + file_name = config.protocol.file_name_prefix + class_name template_context = { "protocol": protocol, "config": config, "domain": domain, "join_arrays": join_arrays, "format_include": functools.partial(format_include, config), + "format_domain_include": functools.partial(format_domain_include, config), } if domain["domain"] in protocol.generate_domains: - outputs[os.path.join(config.protocol.output, to_file_name(config, class_name + ".h"))] = h_template.render(template_context) - outputs[os.path.join(config.protocol.output, to_file_name(config, class_name + ".cpp"))] = cpp_template.render(template_context) + outputs[os.path.join(config.protocol.output, to_file_name(config, file_name + ".h"))] = h_template.render(template_context) + outputs[os.path.join(config.protocol.output, to_file_name(config, file_name + ".cpp"))] = cpp_template.render(template_context) if domain["domain"] in protocol.exported_domains: - outputs[os.path.join(config.exported.output, to_file_name(config, class_name + ".h"))] = exported_template.render(template_context) + outputs[os.path.join(config.exported.output, to_file_name(config, file_name + ".h"))] = exported_template.render(template_context) if domain["domain"] in protocol.imported_domains: - outputs[os.path.join(config.protocol.output, to_file_name(config, class_name + ".h"))] = imported_template.render(template_context) + outputs[os.path.join(config.protocol.output, to_file_name(config, file_name + ".h"))] = imported_template.render(template_context) if config.lib: template_context = { @@ -596,8 +623,7 @@ def main(): lib_templates_dir = os.path.join(module_path, "lib") # Note these should be sorted in the right order. # TODO(dgozman): sort them programmatically based on commented includes. - lib_h_templates = [ - "Collections_h.template", + protocol_h_templates = [ "ErrorSupport_h.template", "Values_h.template", "Object_h.template", @@ -606,15 +632,17 @@ def main(): "Array_h.template", "DispatcherBase_h.template", "Parser_h.template", + "CBOR_h.template", ] - lib_cpp_templates = [ + protocol_cpp_templates = [ "Protocol_cpp.template", "ErrorSupport_cpp.template", "Values_cpp.template", "Object_cpp.template", "DispatcherBase_cpp.template", "Parser_cpp.template", + "CBOR_cpp.template", ] forward_h_templates = [ @@ -623,6 +651,14 @@ def main(): "FrontendChannel_h.template", ] + base_string_adapter_h_templates = [ + "base_string_adapter_h.template", + ] + + base_string_adapter_cc_templates = [ + "base_string_adapter_cc.template", + ] + def generate_lib_file(file_name, template_files): parts = [] for template_file in template_files: @@ -632,20 +668,22 @@ def generate_lib_file(file_name, template_files): outputs[file_name] = "\n\n".join(parts) generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Forward.h")), forward_h_templates) - generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.h")), lib_h_templates) - generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.cpp")), lib_cpp_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.h")), protocol_h_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.cpp")), protocol_cpp_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "base_string_adapter.h")), base_string_adapter_h_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "base_string_adapter.cc")), base_string_adapter_cc_templates) # Make gyp / make generatos happy, otherwise make rebuilds world. inputs_ts = max(map(os.path.getmtime, inputs)) up_to_date = True - for output_file in outputs.iterkeys(): + for output_file in outputs.keys(): if not os.path.exists(output_file) or os.path.getmtime(output_file) < inputs_ts: up_to_date = False break if up_to_date: sys.exit() - for file_name, content in outputs.iteritems(): + for file_name, content in outputs.items(): out_file = open(file_name, "w") out_file.write(content) out_file.close() diff --git a/tools/inspector_protocol/ConcatenateProtocols.py b/tools/inspector_protocol/concatenate_protocols.py similarity index 92% rename from tools/inspector_protocol/ConcatenateProtocols.py rename to tools/inspector_protocol/concatenate_protocols.py index a7cbc992c76e40..e9f448efe72702 100755 --- a/tools/inspector_protocol/ConcatenateProtocols.py +++ b/tools/inspector_protocol/concatenate_protocols.py @@ -11,6 +11,7 @@ except ImportError: import simplejson as json +import pdl def main(argv): if len(argv) < 1: @@ -25,8 +26,7 @@ def main(argv): sys.stderr.write("Cannot find %s\n" % file_name) return 1 input_file = open(file_name, "r") - json_string = input_file.read() - parsed_json = json.loads(json_string) + parsed_json = pdl.loads(input_file.read(), file_name) domains += parsed_json["domains"] version = parsed_json["version"] diff --git a/tools/inspector_protocol/convert_protocol_to_json.py b/tools/inspector_protocol/convert_protocol_to_json.py new file mode 100755 index 00000000000000..96048f793d85a8 --- /dev/null +++ b/tools/inspector_protocol/convert_protocol_to_json.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# Copyright 2017 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. + +import argparse +import collections +import json +import os.path +import re +import sys + +import pdl + +def main(argv): + parser = argparse.ArgumentParser(description=( + "Converts from .pdl to .json by invoking the pdl Python module.")) + parser.add_argument('--map_binary_to_string', type=bool, + help=('If set, binary in the .pdl is mapped to a ' + 'string in .json. Client code will have to ' + 'base64 decode the string to get the payload.')) + parser.add_argument("pdl_file", help="The .pdl input file to parse.") + parser.add_argument("json_file", help="The .json output file write.") + args = parser.parse_args(argv) + file_name = os.path.normpath(args.pdl_file) + input_file = open(file_name, "r") + pdl_string = input_file.read() + protocol = pdl.loads(pdl_string, file_name, args.map_binary_to_string) + input_file.close() + + output_file = open(os.path.normpath(args.json_file), 'wb') + json.dump(protocol, output_file, indent=4, separators=(',', ': ')) + output_file.close() + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/tools/inspector_protocol/lib/Allocator_h.template b/tools/inspector_protocol/lib/Allocator_h.template index 8f8109d695c597..15eaaaff0236d2 100644 --- a/tools/inspector_protocol/lib/Allocator_h.template +++ b/tools/inspector_protocol/lib/Allocator_h.template @@ -1,3 +1,5 @@ +// This file is generated by Allocator_h.template. + // Copyright 2016 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. @@ -11,13 +13,6 @@ namespace {{namespace}} { enum NotNullTagEnum { NotNullLiteral }; -#define PROTOCOL_DISALLOW_NEW() \ - private: \ - void* operator new(size_t) = delete; \ - void* operator new(size_t, NotNullTagEnum, void*) = delete; \ - void* operator new(size_t, void*) = delete; \ - public: - #define PROTOCOL_DISALLOW_COPY(ClassName) \ private: \ ClassName(const ClassName&) = delete; \ diff --git a/tools/inspector_protocol/lib/Array_h.template b/tools/inspector_protocol/lib/Array_h.template index 3854f6e5cd102e..c420a0f7e9650a 100644 --- a/tools/inspector_protocol/lib/Array_h.template +++ b/tools/inspector_protocol/lib/Array_h.template @@ -1,3 +1,5 @@ +// This file is generated by Array_h.template. + // Copyright 2016 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. diff --git a/tools/inspector_protocol/lib/CBOR_cpp.template b/tools/inspector_protocol/lib/CBOR_cpp.template new file mode 100644 index 00000000000000..36750b19a3c935 --- /dev/null +++ b/tools/inspector_protocol/lib/CBOR_cpp.template @@ -0,0 +1,803 @@ +{# This template is generated by gen_cbor_templates.py. #} +// Generated by lib/CBOR_cpp.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#include +#include + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +// ===== encoding/cbor.cc ===== + +using namespace cbor; + +namespace { + +// See RFC 7049 Section 2.3, Table 2. +static constexpr uint8_t kEncodedTrue = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 21); +static constexpr uint8_t kEncodedFalse = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 20); +static constexpr uint8_t kEncodedNull = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 22); +static constexpr uint8_t kInitialByteForDouble = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 27); + +} // namespace + +uint8_t EncodeTrue() { return kEncodedTrue; } +uint8_t EncodeFalse() { return kEncodedFalse; } +uint8_t EncodeNull() { return kEncodedNull; } + +uint8_t EncodeIndefiniteLengthArrayStart() { + return kInitialByteIndefiniteLengthArray; +} + +uint8_t EncodeIndefiniteLengthMapStart() { + return kInitialByteIndefiniteLengthMap; +} + +uint8_t EncodeStop() { return kStopByte; } + +namespace { +// See RFC 7049 Table 3 and Section 2.4.4.2. This is used as a prefix for +// arbitrary binary data encoded as BYTE_STRING. +static constexpr uint8_t kExpectedConversionToBase64Tag = + EncodeInitialByte(MajorType::TAG, 22); + +// When parsing CBOR, we limit recursion depth for objects and arrays +// to this constant. +static constexpr int kStackLimit = 1000; + +// Writes the bytes for |v| to |out|, starting with the most significant byte. +// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html +template +void WriteBytesMostSignificantByteFirst(T v, std::vector* out) { + for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes) + out->push_back(0xff & (v >> (shift_bytes * 8))); +} +} // namespace + +namespace cbor_internals { +// Writes the start of a token with |type|. The |value| may indicate the size, +// or it may be the payload if the value is an unsigned integer. +void WriteTokenStart(MajorType type, uint64_t value, + std::vector* encoded) { + if (value < 24) { + // Values 0-23 are encoded directly into the additional info of the + // initial byte. + encoded->push_back(EncodeInitialByte(type, /*additional_info=*/value)); + return; + } + if (value <= std::numeric_limits::max()) { + // Values 24-255 are encoded with one initial byte, followed by the value. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation1Byte)); + encoded->push_back(value); + return; + } + if (value <= std::numeric_limits::max()) { + // Values 256-65535: 1 initial byte + 2 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation2Bytes)); + WriteBytesMostSignificantByteFirst(value, encoded); + return; + } + if (value <= std::numeric_limits::max()) { + // 32 bit uint: 1 initial byte + 4 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation4Bytes)); + WriteBytesMostSignificantByteFirst(static_cast(value), + encoded); + return; + } + // 64 bit uint: 1 initial byte + 8 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes)); + WriteBytesMostSignificantByteFirst(value, encoded); +} +} // namespace cbor_internals + +namespace { +// Extracts sizeof(T) bytes from |in| to extract a value of type T +// (e.g. uint64_t, uint32_t, ...), most significant byte first. +// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html +template +T ReadBytesMostSignificantByteFirst(span in) { + assert(static_cast(in.size()) >= sizeof(T)); + T result = 0; + for (std::size_t shift_bytes = 0; shift_bytes < sizeof(T); ++shift_bytes) + result |= T(in[sizeof(T) - 1 - shift_bytes]) << (shift_bytes * 8); + return result; +} +} // namespace + +namespace cbor_internals { +int8_t ReadTokenStart(span bytes, MajorType* type, uint64_t* value) { + if (bytes.empty()) return -1; + uint8_t initial_byte = bytes[0]; + *type = MajorType((initial_byte & kMajorTypeMask) >> kMajorTypeBitShift); + + uint8_t additional_information = initial_byte & kAdditionalInformationMask; + if (additional_information < 24) { + // Values 0-23 are encoded directly into the additional info of the + // initial byte. + *value = additional_information; + return 1; + } + if (additional_information == kAdditionalInformation1Byte) { + // Values 24-255 are encoded with one initial byte, followed by the value. + if (bytes.size() < 2) return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 2; + } + if (additional_information == kAdditionalInformation2Bytes) { + // Values 256-65535: 1 initial byte + 2 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint16_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 3; + } + if (additional_information == kAdditionalInformation4Bytes) { + // 32 bit uint: 1 initial byte + 4 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint32_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 5; + } + if (additional_information == kAdditionalInformation8Bytes) { + // 64 bit uint: 1 initial byte + 8 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint64_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 9; + } + return -1; +} +} // namespace cbor_internals + +using cbor_internals::WriteTokenStart; +using cbor_internals::ReadTokenStart; + +void EncodeInt32(int32_t value, std::vector* out) { + if (value >= 0) { + WriteTokenStart(MajorType::UNSIGNED, value, out); + } else { + uint64_t representation = static_cast(-(value + 1)); + WriteTokenStart(MajorType::NEGATIVE, representation, out); + } +} + +void EncodeString16(span in, std::vector* out) { + uint64_t byte_length = static_cast(in.size_bytes()); + WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); + // When emitting UTF16 characters, we always write the least significant byte + // first; this is because it's the native representation for X86. + // TODO(johannes): Implement a more efficient thing here later, e.g. + // casting *iff* the machine has this byte order. + // The wire format for UTF16 chars will probably remain the same + // (least significant byte first) since this way we can have + // golden files, unittests, etc. that port easily and universally. + // See also: + // https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html + for (const uint16_t two_bytes : in) { + out->push_back(two_bytes); + out->push_back(two_bytes >> 8); + } +} + +void EncodeString8(span in, std::vector* out) { + WriteTokenStart(MajorType::STRING, static_cast(in.size_bytes()), + out); + out->insert(out->end(), in.begin(), in.end()); +} + +void EncodeBinary(span in, std::vector* out) { + out->push_back(kExpectedConversionToBase64Tag); + uint64_t byte_length = static_cast(in.size_bytes()); + WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); + out->insert(out->end(), in.begin(), in.end()); +} + +// A double is encoded with a specific initial byte +// (kInitialByteForDouble) plus the 64 bits of payload for its value. +constexpr std::ptrdiff_t kEncodedDoubleSize = 1 + sizeof(uint64_t); + +// An envelope is encoded with a specific initial byte +// (kInitialByteForEnvelope), plus the start byte for a BYTE_STRING with a 32 +// bit wide length, plus a 32 bit length for that string. +constexpr std::ptrdiff_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t); + +void EncodeDouble(double value, std::vector* out) { + // The additional_info=27 indicates 64 bits for the double follow. + // See RFC 7049 Section 2.3, Table 1. + out->push_back(kInitialByteForDouble); + union { + double from_double; + uint64_t to_uint64; + } reinterpret; + reinterpret.from_double = value; + WriteBytesMostSignificantByteFirst(reinterpret.to_uint64, out); +} + +void EnvelopeEncoder::EncodeStart(std::vector* out) { + assert(byte_size_pos_ == 0); + out->push_back(kInitialByteForEnvelope); + out->push_back(kInitialByteFor32BitLengthByteString); + byte_size_pos_ = out->size(); + out->resize(out->size() + sizeof(uint32_t)); +} + +bool EnvelopeEncoder::EncodeStop(std::vector* out) { + assert(byte_size_pos_ != 0); + // The byte size is the size of the payload, that is, all the + // bytes that were written past the byte size position itself. + uint64_t byte_size = out->size() - (byte_size_pos_ + sizeof(uint32_t)); + // We store exactly 4 bytes, so at most INT32MAX, with most significant + // byte first. + if (byte_size > std::numeric_limits::max()) return false; + for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0; + --shift_bytes) { + (*out)[byte_size_pos_++] = 0xff & (byte_size >> (shift_bytes * 8)); + } + return true; +} + +namespace { +class JSONToCBOREncoder : public JSONParserHandler { + public: + JSONToCBOREncoder(std::vector* out, Status* status) + : out_(out), status_(status) { + *status_ = Status(); + } + + void HandleObjectBegin() override { + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthMap); + } + + void HandleObjectEnd() override { + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + envelopes_.back().EncodeStop(out_); + envelopes_.pop_back(); + } + + void HandleArrayBegin() override { + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthArray); + } + + void HandleArrayEnd() override { + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + envelopes_.back().EncodeStop(out_); + envelopes_.pop_back(); + } + + void HandleString16(std::vector chars) override { + for (uint16_t ch : chars) { + if (ch >= 0x7f) { + // If there's at least one non-7bit character, we encode as UTF16. + EncodeString16(span(chars.data(), chars.size()), out_); + return; + } + } + std::vector sevenbit_chars(chars.begin(), chars.end()); + EncodeString8(span(sevenbit_chars.data(), sevenbit_chars.size()), + out_); + } + + void HandleBinary(std::vector bytes) override { + EncodeBinary(span(bytes.data(), bytes.size()), out_); + } + + void HandleDouble(double value) override { EncodeDouble(value, out_); } + + void HandleInt32(int32_t value) override { EncodeInt32(value, out_); } + + void HandleBool(bool value) override { + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(value ? kEncodedTrue : kEncodedFalse); + } + + void HandleNull() override { + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(kEncodedNull); + } + + void HandleError(Status error) override { + assert(!error.ok()); + *status_ = error; + out_->clear(); + } + + private: + std::vector* out_; + std::vector envelopes_; + Status* status_; +}; +} // namespace + +std::unique_ptr NewJSONToCBOREncoder( + std::vector* out, Status* status) { + return std::unique_ptr(new JSONToCBOREncoder(out, status)); +} + +namespace { +// Below are three parsing routines for CBOR, which cover enough +// to roundtrip JSON messages. +bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); +bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); +bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); + +void ParseUTF16String(CBORTokenizer* tokenizer, JSONParserHandler* out) { + std::vector value; + span rep = tokenizer->GetString16WireRep(); + for (std::ptrdiff_t ii = 0; ii < rep.size(); ii += 2) + value.push_back((rep[ii + 1] << 8) | rep[ii]); + out->HandleString16(std::move(value)); + tokenizer->Next(); +} + +// For now this method only covers US-ASCII. Later, we may allow UTF8. +bool ParseASCIIString(CBORTokenizer* tokenizer, JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::STRING8); + std::vector value16; + for (uint8_t ch : tokenizer->GetString8()) { + // We only accept us-ascii (7 bit) strings here. Other strings must + // be encoded with 16 bit (the BYTE_STRING case). + if (ch >= 0x7f) { + out->HandleError( + Status{Error::CBOR_STRING8_MUST_BE_7BIT, tokenizer->Status().pos}); + return false; + } + value16.push_back(ch); + } + out->HandleString16(std::move(value16)); + tokenizer->Next(); + return true; +} + +bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + if (stack_depth > kStackLimit) { + out->HandleError( + Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos}); + return false; + } + // Skip past the envelope to get to what's inside. + if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + switch (tokenizer->TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + out->HandleError(tokenizer->Status()); + return false; + case CBORTokenTag::DONE: + out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, + tokenizer->Status().pos}); + return false; + case CBORTokenTag::TRUE_VALUE: + out->HandleBool(true); + tokenizer->Next(); + return true; + case CBORTokenTag::FALSE_VALUE: + out->HandleBool(false); + tokenizer->Next(); + return true; + case CBORTokenTag::NULL_VALUE: + out->HandleNull(); + tokenizer->Next(); + return true; + case CBORTokenTag::INT32: + out->HandleInt32(tokenizer->GetInt32()); + tokenizer->Next(); + return true; + case CBORTokenTag::DOUBLE: + out->HandleDouble(tokenizer->GetDouble()); + tokenizer->Next(); + return true; + case CBORTokenTag::STRING8: + return ParseASCIIString(tokenizer, out); + case CBORTokenTag::STRING16: + ParseUTF16String(tokenizer, out); + return true; + case CBORTokenTag::BINARY: { + span binary = tokenizer->GetBinary(); + out->HandleBinary(std::vector(binary.begin(), binary.end())); + tokenizer->Next(); + return true; + } + case CBORTokenTag::MAP_START: + return ParseMap(stack_depth + 1, tokenizer, out); + case CBORTokenTag::ARRAY_START: + return ParseArray(stack_depth + 1, tokenizer, out); + default: + out->HandleError( + Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos}); + return false; + } +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START); + tokenizer->Next(); + out->HandleArrayBegin(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + out->HandleError( + Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos}); + return false; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer->Status()); + return false; + } + // Parse value. + if (!ParseValue(stack_depth, tokenizer, out)) return false; + } + out->HandleArrayEnd(); + tokenizer->Next(); + return true; +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START); + out->HandleObjectBegin(); + tokenizer->Next(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + out->HandleError( + Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos}); + return false; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer->Status()); + return false; + } + // Parse key. + if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { + if (!ParseASCIIString(tokenizer, out)) return false; + } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { + ParseUTF16String(tokenizer, out); + } else { + out->HandleError( + Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos}); + return false; + } + // Parse value. + if (!ParseValue(stack_depth, tokenizer, out)) return false; + } + out->HandleObjectEnd(); + tokenizer->Next(); + return true; +} +} // namespace + +void ParseCBOR(span bytes, JSONParserHandler* json_out) { + if (bytes.empty()) { + json_out->HandleError(Status{Error::CBOR_NO_INPUT, 0}); + return; + } + if (bytes[0] != kInitialByteForEnvelope) { + json_out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0}); + return; + } + CBORTokenizer tokenizer(bytes); + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + json_out->HandleError(tokenizer.Status()); + return; + } + // We checked for the envelope start byte above, so the tokenizer + // must agree here, since it's not an error. + assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE); + tokenizer.EnterEnvelope(); + if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) { + json_out->HandleError( + Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos}); + return; + } + if (!ParseMap(/*stack_depth=*/1, &tokenizer, json_out)) return; + if (tokenizer.TokenTag() == CBORTokenTag::DONE) return; + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + json_out->HandleError(tokenizer.Status()); + return; + } + json_out->HandleError( + Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos}); +} + +CBORTokenizer::CBORTokenizer(span bytes) : bytes_(bytes) { + ReadNextToken(/*enter_envelope=*/false); +} +CBORTokenizer::~CBORTokenizer() {} + +CBORTokenTag CBORTokenizer::TokenTag() const { return token_tag_; } + +void CBORTokenizer::Next() { + if (token_tag_ == CBORTokenTag::ERROR_VALUE || token_tag_ == CBORTokenTag::DONE) + return; + ReadNextToken(/*enter_envelope=*/false); +} + +void CBORTokenizer::EnterEnvelope() { + assert(token_tag_ == CBORTokenTag::ENVELOPE); + ReadNextToken(/*enter_envelope=*/true); +} + +Status CBORTokenizer::Status() const { return status_; } + +int32_t CBORTokenizer::GetInt32() const { + assert(token_tag_ == CBORTokenTag::INT32); + // The range checks happen in ::ReadNextToken(). + return static_cast( + token_start_type_ == MajorType::UNSIGNED + ? token_start_internal_value_ + : -static_cast(token_start_internal_value_) - 1); +} + +double CBORTokenizer::GetDouble() const { + assert(token_tag_ == CBORTokenTag::DOUBLE); + union { + uint64_t from_uint64; + double to_double; + } reinterpret; + reinterpret.from_uint64 = ReadBytesMostSignificantByteFirst( + bytes_.subspan(status_.pos + 1)); + return reinterpret.to_double; +} + +span CBORTokenizer::GetString8() const { + assert(token_tag_ == CBORTokenTag::STRING8); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +span CBORTokenizer::GetString16WireRep() const { + assert(token_tag_ == CBORTokenTag::STRING16); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +span CBORTokenizer::GetBinary() const { + assert(token_tag_ == CBORTokenTag::BINARY); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +void CBORTokenizer::ReadNextToken(bool enter_envelope) { + if (enter_envelope) { + status_.pos += kEncodedEnvelopeHeaderSize; + } else { + status_.pos = + status_.pos == Status::npos() ? 0 : status_.pos + token_byte_length_; + } + status_.error = Error::OK; + if (status_.pos >= bytes_.size()) { + token_tag_ = CBORTokenTag::DONE; + return; + } + switch (bytes_[status_.pos]) { + case kStopByte: + SetToken(CBORTokenTag::STOP, 1); + return; + case kInitialByteIndefiniteLengthMap: + SetToken(CBORTokenTag::MAP_START, 1); + return; + case kInitialByteIndefiniteLengthArray: + SetToken(CBORTokenTag::ARRAY_START, 1); + return; + case kEncodedTrue: + SetToken(CBORTokenTag::TRUE_VALUE, 1); + return; + case kEncodedFalse: + SetToken(CBORTokenTag::FALSE_VALUE, 1); + return; + case kEncodedNull: + SetToken(CBORTokenTag::NULL_VALUE, 1); + return; + case kExpectedConversionToBase64Tag: { // BINARY + int8_t bytes_read = + ReadTokenStart(bytes_.subspan(status_.pos + 1), &token_start_type_, + &token_start_internal_value_); + int64_t token_byte_length = 1 + bytes_read + token_start_internal_value_; + if (-1 == bytes_read || token_start_type_ != MajorType::BYTE_STRING || + status_.pos + token_byte_length > bytes_.size()) { + SetError(Error::CBOR_INVALID_BINARY); + return; + } + SetToken(CBORTokenTag::BINARY, + static_cast(token_byte_length)); + return; + } + case kInitialByteForDouble: { // DOUBLE + if (status_.pos + kEncodedDoubleSize > bytes_.size()) { + SetError(Error::CBOR_INVALID_DOUBLE); + return; + } + SetToken(CBORTokenTag::DOUBLE, kEncodedDoubleSize); + return; + } + case kInitialByteForEnvelope: { // ENVELOPE + if (status_.pos + kEncodedEnvelopeHeaderSize > bytes_.size()) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + // The envelope must be a byte string with 32 bit length. + if (bytes_[status_.pos + 1] != kInitialByteFor32BitLengthByteString) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + // Read the length of the byte string. + token_start_internal_value_ = ReadBytesMostSignificantByteFirst( + bytes_.subspan(status_.pos + 2)); + // Make sure the payload is contained within the message. + if (token_start_internal_value_ + kEncodedEnvelopeHeaderSize + + status_.pos > + static_cast(bytes_.size())) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::ENVELOPE, + kEncodedEnvelopeHeaderSize + length); + return; + } + default: { + span remainder = + bytes_.subspan(status_.pos, bytes_.size() - status_.pos); + assert(!remainder.empty()); + int8_t token_start_length = ReadTokenStart(remainder, &token_start_type_, + &token_start_internal_value_); + bool success = token_start_length != -1; + switch (token_start_type_) { + case MajorType::UNSIGNED: // INT32. + if (!success || std::numeric_limits::max() < + token_start_internal_value_) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, token_start_length); + return; + case MajorType::NEGATIVE: // INT32. + if (!success || + std::numeric_limits::min() > + -static_cast(token_start_internal_value_) - 1) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, token_start_length); + return; + case MajorType::STRING: { // STRING8. + if (!success || remainder.size() < static_cast( + token_start_internal_value_)) { + SetError(Error::CBOR_INVALID_STRING8); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::STRING8, token_start_length + length); + return; + } + case MajorType::BYTE_STRING: { // STRING16. + if (!success || + remainder.size() < + static_cast(token_start_internal_value_) || + // Must be divisible by 2 since UTF16 is 2 bytes per character. + token_start_internal_value_ & 1) { + SetError(Error::CBOR_INVALID_STRING16); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::STRING16, token_start_length + length); + return; + } + case MajorType::ARRAY: + case MajorType::MAP: + case MajorType::TAG: + case MajorType::SIMPLE_VALUE: + SetError(Error::CBOR_UNSUPPORTED_VALUE); + return; + } + } + } +} + +void CBORTokenizer::SetToken(CBORTokenTag token_tag, + std::ptrdiff_t token_byte_length) { + token_tag_ = token_tag; + token_byte_length_ = token_byte_length; +} + +void CBORTokenizer::SetError(Error error) { + token_tag_ = CBORTokenTag::ERROR_VALUE; + status_.error = error; +} + +#if 0 +void DumpCBOR(span cbor) { + std::string indent; + CBORTokenizer tokenizer(cbor); + while (true) { + fprintf(stderr, "%s", indent.c_str()); + switch (tokenizer.TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + fprintf(stderr, "ERROR {status.error=%d, status.pos=%ld}\n", + tokenizer.Status().error, tokenizer.Status().pos); + return; + case CBORTokenTag::DONE: + fprintf(stderr, "DONE\n"); + return; + case CBORTokenTag::TRUE_VALUE: + fprintf(stderr, "TRUE_VALUE\n"); + break; + case CBORTokenTag::FALSE_VALUE: + fprintf(stderr, "FALSE_VALUE\n"); + break; + case CBORTokenTag::NULL_VALUE: + fprintf(stderr, "NULL_VALUE\n"); + break; + case CBORTokenTag::INT32: + fprintf(stderr, "INT32 [%d]\n", tokenizer.GetInt32()); + break; + case CBORTokenTag::DOUBLE: + fprintf(stderr, "DOUBLE [%lf]\n", tokenizer.GetDouble()); + break; + case CBORTokenTag::STRING8: { + span v = tokenizer.GetString8(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "STRING8 [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::STRING16: { + span v = tokenizer.GetString16WireRep(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "STRING16 [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::BINARY: { + span v = tokenizer.GetBinary(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "BINARY [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::MAP_START: + fprintf(stderr, "MAP_START\n"); + indent += " "; + break; + case CBORTokenTag::ARRAY_START: + fprintf(stderr, "ARRAY_START\n"); + indent += " "; + break; + case CBORTokenTag::STOP: + fprintf(stderr, "STOP\n"); + indent.erase(0, 2); + break; + case CBORTokenTag::ENVELOPE: + fprintf(stderr, "ENVELOPE\n"); + tokenizer.EnterEnvelope(); + continue; + } + tokenizer.Next(); + } +} +#endif + + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} diff --git a/tools/inspector_protocol/lib/CBOR_h.template b/tools/inspector_protocol/lib/CBOR_h.template new file mode 100644 index 00000000000000..dd637f19e7d9d9 --- /dev/null +++ b/tools/inspector_protocol/lib/CBOR_h.template @@ -0,0 +1,416 @@ +{# This template is generated by gen_cbor_templates.py. #} +// Generated by lib/CBOR_h.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef {{"_".join(config.protocol.namespace)}}_CBOR_h +#define {{"_".join(config.protocol.namespace)}}_CBOR_h + +#include +#include +#include +#include + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +// ===== encoding/status.h ===== + +// 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_OBJECT_END = 0x0b, + JSON_PARSER_COMMA_OR_OBJECT_END_EXPECTED = 0x0c, + JSON_PARSER_VALUE_EXPECTED = 0x0d, + + CBOR_INVALID_INT32 = 0x0e, + CBOR_INVALID_DOUBLE = 0x0f, + CBOR_INVALID_ENVELOPE = 0x10, + CBOR_INVALID_STRING8 = 0x11, + CBOR_INVALID_STRING16 = 0x12, + CBOR_INVALID_BINARY = 0x13, + CBOR_UNSUPPORTED_VALUE = 0x14, + CBOR_NO_INPUT = 0x15, + CBOR_INVALID_START_BYTE = 0x16, + CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x17, + CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x18, + CBOR_UNEXPECTED_EOF_IN_MAP = 0x19, + CBOR_INVALID_MAP_KEY = 0x1a, + CBOR_STACK_LIMIT_EXCEEDED = 0x1b, + CBOR_STRING8_MUST_BE_7BIT = 0x1c, + CBOR_TRAILING_JUNK = 0x1d, + CBOR_MAP_START_EXPECTED = 0x1e, +}; + +// 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 std::ptrdiff_t npos() { return -1; } + + bool ok() const { return error == Error::OK; } + + Error error = Error::OK; + std::ptrdiff_t pos = npos(); + Status(Error error, std::ptrdiff_t pos) : error(error), pos(pos) {} + Status() = default; +}; + +// ===== encoding/span.h ===== + +// This template is similar to std::span, which will be included in C++20. Like +// std::span it uses ptrdiff_t, which is signed (and thus a bit annoying +// sometimes when comparing with size_t), but other than this it's much simpler. +template +class span { + public: + using index_type = std::ptrdiff_t; + + span() : data_(nullptr), size_(0) {} + span(const T* data, index_type size) : data_(data), size_(size) {} + + const T* data() const { return data_; } + + const T* begin() const { return data_; } + const T* end() const { return data_ + size_; } + + const T& operator[](index_type idx) const { return data_[idx]; } + + span subspan(index_type offset, index_type count) const { + return span(data_ + offset, count); + } + + span subspan(index_type offset) const { + return span(data_ + offset, size_ - offset); + } + + bool empty() const { return size_ == 0; } + + index_type size() const { return size_; } + index_type size_bytes() const { return size_ * sizeof(T); } + + private: + const T* data_; + index_type size_; +}; + +// ===== encoding/json_parser_handler.h ===== + +// Handler interface for JSON parser events. See also json_parser.h. +class JSONParserHandler { + public: + virtual ~JSONParserHandler() = default; + virtual void HandleObjectBegin() = 0; + virtual void HandleObjectEnd() = 0; + virtual void HandleArrayBegin() = 0; + virtual void HandleArrayEnd() = 0; + // TODO(johannes): Support utf8 (requires utf16->utf8 conversion + // internally, including handling mismatched surrogate pairs). + virtual void HandleString16(std::vector chars) = 0; + virtual void HandleBinary(std::vector 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; +}; + +// ===== encoding/cbor_internals.h ===== + +namespace cbor { +enum class MajorType; +} + +namespace cbor_internals { + +// Reads the start of a token with definitive size from |bytes|. +// |type| is the major type as specified in RFC 7049 Section 2.1. +// |value| is the payload (e.g. for MajorType::UNSIGNED) or is the size +// (e.g. for BYTE_STRING). +// If successful, returns the number of bytes read. Otherwise returns -1. +int8_t ReadTokenStart(span bytes, cbor::MajorType* type, + uint64_t* value); + +// Writes the start of a token with |type|. The |value| may indicate the size, +// or it may be the payload if the value is an unsigned integer. +void WriteTokenStart(cbor::MajorType type, uint64_t value, + std::vector* encoded); +} // namespace cbor_internals + +// ===== encoding/cbor.h ===== + + +namespace cbor { + +// The major types from RFC 7049 Section 2.1. +enum class MajorType { + UNSIGNED = 0, + NEGATIVE = 1, + BYTE_STRING = 2, + STRING = 3, + ARRAY = 4, + MAP = 5, + TAG = 6, + SIMPLE_VALUE = 7 +}; + +// Indicates the number of bits the "initial byte" needs to be shifted to the +// right after applying |kMajorTypeMask| to produce the major type in the +// lowermost bits. +static constexpr uint8_t kMajorTypeBitShift = 5u; +// Mask selecting the low-order 5 bits of the "initial byte", which is where +// the additional information is encoded. +static constexpr uint8_t kAdditionalInformationMask = 0x1f; +// Mask selecting the high-order 3 bits of the "initial byte", which indicates +// the major type of the encoded value. +static constexpr uint8_t kMajorTypeMask = 0xe0; +// Indicates the integer is in the following byte. +static constexpr uint8_t kAdditionalInformation1Byte = 24u; +// Indicates the integer is in the next 2 bytes. +static constexpr uint8_t kAdditionalInformation2Bytes = 25u; +// Indicates the integer is in the next 4 bytes. +static constexpr uint8_t kAdditionalInformation4Bytes = 26u; +// Indicates the integer is in the next 8 bytes. +static constexpr uint8_t kAdditionalInformation8Bytes = 27u; + +// Encodes the initial byte, consisting of the |type| in the first 3 bits +// followed by 5 bits of |additional_info|. +constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) { + return (static_cast(type) << kMajorTypeBitShift) | + (additional_info & kAdditionalInformationMask); +} + +// TAG 24 indicates that what follows is a byte string which is +// encoded in CBOR format. We use this as a wrapper for +// maps and arrays, allowing us to skip them, because the +// byte string carries its size (byte length). +// https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +static constexpr uint8_t kInitialByteForEnvelope = + EncodeInitialByte(MajorType::TAG, 24); +// The initial byte for a byte string with at most 2^32 bytes +// of payload. This is used for envelope encoding, even if +// the byte string is shorter. +static constexpr uint8_t kInitialByteFor32BitLengthByteString = + EncodeInitialByte(MajorType::BYTE_STRING, 26); + +// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional +// info = 31. +static constexpr uint8_t kInitialByteIndefiniteLengthArray = + EncodeInitialByte(MajorType::ARRAY, 31); +static constexpr uint8_t kInitialByteIndefiniteLengthMap = + EncodeInitialByte(MajorType::MAP, 31); +// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite +// length maps / arrays. +static constexpr uint8_t kStopByte = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 31); + +} // namespace cbor + +// The binary encoding for the inspector protocol follows the CBOR specification +// (RFC 7049). Additional constraints: +// - Only indefinite length maps and arrays are supported. +// - Maps and arrays are wrapped with an envelope, that is, a +// CBOR tag with value 24 followed by a byte string specifying +// the byte length of the enclosed map / array. The byte string +// must use a 32 bit wide length. +// - At the top level, a message must be an indefinite length map +// wrapped by an envelope. +// - Maximal size for messages is 2^32 (4 GB). +// - For scalars, we support only the int32_t range, encoded as +// UNSIGNED/NEGATIVE (major types 0 / 1). +// - UTF16 strings, including with unbalanced surrogate pairs, are encoded +// as CBOR BYTE_STRING (major type 2). For such strings, the number of +// bytes encoded must be even. +// - UTF8 strings (major type 3) may only have ASCII characters +// (7 bit US-ASCII). +// - Arbitrary byte arrays, in the inspector protocol called 'binary', +// are encoded as BYTE_STRING (major type 2), prefixed with a byte +// indicating base64 when rendered as JSON. + +// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE| +// (major type 1) iff < 0. +void EncodeInt32(int32_t value, std::vector* out); + +// Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16 +// character in |in| is emitted with most significant byte first, +// appending to |out|. +void EncodeString16(span in, std::vector* out); + +// Encodes a UTF8 string |in| as STRING (major type 3). +void EncodeString8(span in, std::vector* out); + +// Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with +// definitive length, prefixed with tag 22 indicating expected conversion to +// base64 (see RFC 7049, Table 3 and Section 2.4.4.2). +void EncodeBinary(span in, std::vector* out); + +// Encodes / decodes a double as Major type 7 (SIMPLE_VALUE), +// with additional info = 27, followed by 8 bytes in big endian. +void EncodeDouble(double value, std::vector* out); + +// Some constants for CBOR tokens that only take a single byte on the wire. +uint8_t EncodeTrue(); +uint8_t EncodeFalse(); +uint8_t EncodeNull(); +uint8_t EncodeIndefiniteLengthArrayStart(); +uint8_t EncodeIndefiniteLengthMapStart(); +uint8_t EncodeStop(); + +// An envelope indicates the byte length of a wrapped item. +// We use this for maps and array, which allows the decoder +// to skip such (nested) values whole sale. +// It's implemented as a CBOR tag (major type 6) with additional +// info = 24, followed by a byte string with a 32 bit length value; +// so the maximal structure that we can wrap is 2^32 bits long. +// See also: https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +class EnvelopeEncoder { + public: + // Emits the envelope start bytes and records the position for the + // byte size in |byte_size_pos_|. Also emits empty bytes for the + // byte sisze so that encoding can continue. + void EncodeStart(std::vector* out); + // This records the current size in |out| at position byte_size_pos_. + // Returns true iff successful. + bool EncodeStop(std::vector* out); + + private: + std::size_t byte_size_pos_ = 0; +}; + +// This can be used to convert from JSON to CBOR, by passing the +// return value to the routines in json_parser.h. The handler will encode into +// |out|, and iff an error occurs it will set |status| to an error and clear +// |out|. Otherwise, |status.ok()| will be |true|. +std::unique_ptr NewJSONToCBOREncoder( + std::vector* out, Status* status); + +// Parses a CBOR encoded message from |bytes|, sending JSON events to +// |json_out|. If an error occurs, sends |out->HandleError|, and parsing stops. +// The client is responsible for discarding the already received information in +// that case. +void ParseCBOR(span bytes, JSONParserHandler* json_out); + +// Tags for the tokens within a CBOR message that CBORStream understands. +// Note that this is not the same terminology as the CBOR spec (RFC 7049), +// but rather, our adaptation. For instance, we lump unsigned and signed +// major type into INT32 here (and disallow values outside the int32_t range). +enum class CBORTokenTag { + // Encountered an error in the structure of the message. Consult + // status() for details. + ERROR_VALUE, + // Booleans and NULL. + TRUE_VALUE, + FALSE_VALUE, + NULL_VALUE, + // An int32_t (signed 32 bit integer). + INT32, + // A double (64 bit floating point). + DOUBLE, + // A UTF8 string. + STRING8, + // A UTF16 string. + STRING16, + // A binary string. + BINARY, + // Starts an indefinite length map; after the map start we expect + // alternating keys and values, followed by STOP. + MAP_START, + // Starts an indefinite length array; after the array start we + // expect values, followed by STOP. + ARRAY_START, + // Ends a map or an array. + STOP, + // An envelope indicator, wrapping a map or array. + // Internally this carries the byte length of the wrapped + // map or array. While CBORTokenizer::Next() will read / skip the entire + // envelope, CBORTokenizer::EnterEnvelope() reads the tokens + // inside of it. + ENVELOPE, + // We've reached the end there is nothing else to read. + DONE, +}; + +// CBORTokenizer segments a CBOR message, presenting the tokens therein as +// numbers, strings, etc. This is not a complete CBOR parser, but makes it much +// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse +// messages partially. +class CBORTokenizer { + public: + explicit CBORTokenizer(span bytes); + ~CBORTokenizer(); + + // Identifies the current token that we're looking at, + // or ERROR_VALUE (in which ase ::Status() has details) + // or DONE (if we're past the last token). + CBORTokenTag TokenTag() const; + + // Advances to the next token. + void Next(); + // Can only be called if TokenTag() == CBORTokenTag::ENVELOPE. + // While Next() would skip past the entire envelope / what it's + // wrapping, EnterEnvelope positions the cursor inside of the envelope, + // letting the client explore the nested structure. + void EnterEnvelope(); + + // If TokenTag() is CBORTokenTag::ERROR_VALUE, then Status().error describes + // the error more precisely; otherwise it'll be set to Error::OK. + // In either case, Status().pos is the current position. + struct Status Status() const; + + // The following methods retrieve the token values. They can only + // be called if TokenTag() matches. + + // To be called only if ::TokenTag() == CBORTokenTag::INT32. + int32_t GetInt32() const; + + // To be called only if ::TokenTag() == CBORTokenTag::DOUBLE. + double GetDouble() const; + + // To be called only if ::TokenTag() == CBORTokenTag::STRING8. + span GetString8() const; + + // Wire representation for STRING16 is low byte first (little endian). + // To be called only if ::TokenTag() == CBORTokenTag::STRING16. + span GetString16WireRep() const; + + // To be called only if ::TokenTag() == CBORTokenTag::BINARY. + span GetBinary() const; + + private: + void ReadNextToken(bool enter_envelope); + void SetToken(CBORTokenTag token, std::ptrdiff_t token_byte_length); + void SetError(Error error); + + span bytes_; + CBORTokenTag token_tag_; + struct Status status_; + std::ptrdiff_t token_byte_length_; + cbor::MajorType token_start_type_; + uint64_t token_start_internal_value_; +}; + +void DumpCBOR(span cbor); + + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} +#endif // !defined({{"_".join(config.protocol.namespace)}}_CBOR_h) diff --git a/tools/inspector_protocol/lib/DispatcherBase_cpp.template b/tools/inspector_protocol/lib/DispatcherBase_cpp.template index cecef743bffcc6..11843f433007fc 100644 --- a/tools/inspector_protocol/lib/DispatcherBase_cpp.template +++ b/tools/inspector_protocol/lib/DispatcherBase_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by DispatcherBase_cpp.template. + // Copyright 2016 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. @@ -68,10 +70,11 @@ DispatcherBase::WeakPtr::~WeakPtr() m_dispatcher->m_weakPtrs.erase(this); } -DispatcherBase::Callback::Callback(std::unique_ptr backendImpl, int callId, int callbackId) +DispatcherBase::Callback::Callback(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message) : m_backendImpl(std::move(backendImpl)) , m_callId(callId) - , m_callbackId(callbackId) { } + , m_method(method) + , m_message(message) { } DispatcherBase::Callback::~Callback() = default; @@ -92,32 +95,18 @@ void DispatcherBase::Callback::fallThroughIfActive() { if (!m_backendImpl || !m_backendImpl->get()) return; - m_backendImpl->get()->markFallThrough(m_callbackId); + m_backendImpl->get()->channel()->fallThrough(m_callId, m_method, m_message); m_backendImpl = nullptr; } DispatcherBase::DispatcherBase(FrontendChannel* frontendChannel) - : m_frontendChannel(frontendChannel) - , m_lastCallbackId(0) - , m_lastCallbackFallThrough(false) { } + : m_frontendChannel(frontendChannel) { } DispatcherBase::~DispatcherBase() { clearFrontend(); } -int DispatcherBase::nextCallbackId() -{ - m_lastCallbackFallThrough = false; - return ++m_lastCallbackId; -} - -void DispatcherBase::markFallThrough(int callbackId) -{ - DCHECK(callbackId == m_lastCallbackId); - m_lastCallbackFallThrough = true; -} - void DispatcherBase::sendResponse(int callId, const DispatchResponse& response, std::unique_ptr result) { if (!m_frontendChannel) @@ -153,18 +142,14 @@ public: return std::unique_ptr(new ProtocolError(code, errorMessage)); } - String serialize() override + String serializeToJSON() override { - std::unique_ptr error = DictionaryValue::create(); - error->setInteger("code", m_code); - error->setString("message", m_errorMessage); - if (m_data.length()) - error->setString("data", m_data); - std::unique_ptr message = DictionaryValue::create(); - message->setObject("error", std::move(error)); - if (m_hasCallId) - message->setInteger("id", m_callId); - return message->serialize(); + return serialize()->serializeToJSON(); + } + + std::vector serializeToBinary() override + { + return serialize()->serializeToBinary(); } ~ProtocolError() override {} @@ -176,6 +161,19 @@ private: { } + std::unique_ptr serialize() { + std::unique_ptr error = DictionaryValue::create(); + error->setInteger("code", m_code); + error->setString("message", m_errorMessage); + if (m_data.length()) + error->setString("data", m_data); + std::unique_ptr message = DictionaryValue::create(); + message->setObject("error", std::move(error)); + if (m_hasCallId) + message->setInteger("id", m_callId); + return message; + } + DispatchResponse::ErrorCode m_code; String m_errorMessage; String m_data; @@ -218,100 +216,87 @@ std::unique_ptr DispatcherBase::weakPtr() } UberDispatcher::UberDispatcher(FrontendChannel* frontendChannel) - : m_frontendChannel(frontendChannel) - , m_fallThroughForNotFound(false) { } - -void UberDispatcher::setFallThroughForNotFound(bool fallThroughForNotFound) -{ - m_fallThroughForNotFound = fallThroughForNotFound; -} + : m_frontendChannel(frontendChannel) { } void UberDispatcher::registerBackend(const String& name, std::unique_ptr dispatcher) { m_dispatchers[name] = std::move(dispatcher); } -void UberDispatcher::setupRedirects(const HashMap& redirects) +void UberDispatcher::setupRedirects(const std::unordered_map& redirects) { for (const auto& pair : redirects) m_redirects[pair.first] = pair.second; } -DispatchResponse::Status UberDispatcher::dispatch(std::unique_ptr parsedMessage, int* outCallId, String* outMethod) -{ +bool UberDispatcher::parseCommand(Value* parsedMessage, int* outCallId, String* outMethod) { if (!parsedMessage) { reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kParseError, "Message must be a valid JSON"); - return DispatchResponse::kError; + return false; } - std::unique_ptr messageObject = DictionaryValue::cast(std::move(parsedMessage)); + protocol::DictionaryValue* messageObject = DictionaryValue::cast(parsedMessage); if (!messageObject) { reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must be an object"); - return DispatchResponse::kError; + return false; } int callId = 0; protocol::Value* callIdValue = messageObject->get("id"); bool success = callIdValue && callIdValue->asInteger(&callId); - if (outCallId) - *outCallId = callId; if (!success) { reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must have integer 'id' property"); - return DispatchResponse::kError; + return false; } + if (outCallId) + *outCallId = callId; protocol::Value* methodValue = messageObject->get("method"); String method; success = methodValue && methodValue->asString(&method); - if (outMethod) - *outMethod = method; if (!success) { reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kInvalidRequest, "Message must have string 'method' property", nullptr); - return DispatchResponse::kError; + return false; } + if (outMethod) + *outMethod = method; + return true; +} - HashMap::iterator redirectIt = m_redirects.find(method); - if (redirectIt != m_redirects.end()) - method = redirectIt->second; - +protocol::DispatcherBase* UberDispatcher::findDispatcher(const String& method) { size_t dotIndex = StringUtil::find(method, "."); - if (dotIndex == StringUtil::kNotFound) { - if (m_fallThroughForNotFound) - return DispatchResponse::kFallThrough; - reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); - return DispatchResponse::kError; - } + if (dotIndex == StringUtil::kNotFound) + return nullptr; String domain = StringUtil::substring(method, 0, dotIndex); auto it = m_dispatchers.find(domain); - if (it == m_dispatchers.end()) { - if (m_fallThroughForNotFound) - return DispatchResponse::kFallThrough; - reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); - return DispatchResponse::kError; - } - return it->second->dispatch(callId, method, std::move(messageObject)); + if (it == m_dispatchers.end()) + return nullptr; + if (!it->second->canDispatch(method)) + return nullptr; + return it->second.get(); } -bool UberDispatcher::getCommandName(const String& message, String* method, std::unique_ptr* parsedMessage) +bool UberDispatcher::canDispatch(const String& in_method) { - std::unique_ptr value = StringUtil::parseJSON(message); - if (!value) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kParseError, "Message must be a valid JSON"); - return false; - } - - protocol::DictionaryValue* object = DictionaryValue::cast(value.get()); - if (!object) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must be an object"); - return false; - } + String method = in_method; + auto redirectIt = m_redirects.find(method); + if (redirectIt != m_redirects.end()) + method = redirectIt->second; + return !!findDispatcher(method); +} - if (!object->getString("method", method)) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must have string 'method' property"); - return false; +void UberDispatcher::dispatch(int callId, const String& in_method, std::unique_ptr parsedMessage, const ProtocolMessage& rawMessage) +{ + String method = in_method; + auto redirectIt = m_redirects.find(method); + if (redirectIt != m_redirects.end()) + method = redirectIt->second; + protocol::DispatcherBase* dispatcher = findDispatcher(method); + if (!dispatcher) { + reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); + return; } - - parsedMessage->reset(DictionaryValue::cast(value.release())); - return true; + std::unique_ptr messageObject = DictionaryValue::cast(std::move(parsedMessage)); + dispatcher->dispatch(callId, method, rawMessage, std::move(messageObject)); } UberDispatcher::~UberDispatcher() = default; @@ -328,18 +313,32 @@ std::unique_ptr InternalResponse::createNotification(const Str return std::unique_ptr(new InternalResponse(0, notification, std::move(params))); } -String InternalResponse::serialize() +String InternalResponse::serializeToJSON() +{ + std::unique_ptr result = DictionaryValue::create(); + std::unique_ptr params(m_params ? std::move(m_params) : DictionaryValue::create()); + if (m_notification.length()) { + result->setString("method", m_notification); + result->setValue("params", SerializedValue::fromJSON(params->serializeToJSON())); + } else { + result->setInteger("id", m_callId); + result->setValue("result", SerializedValue::fromJSON(params->serializeToJSON())); + } + return result->serializeToJSON(); +} + +std::vector InternalResponse::serializeToBinary() { std::unique_ptr result = DictionaryValue::create(); std::unique_ptr params(m_params ? std::move(m_params) : DictionaryValue::create()); if (m_notification.length()) { result->setString("method", m_notification); - result->setValue("params", SerializedValue::create(params->serialize())); + result->setValue("params", SerializedValue::fromBinary(params->serializeToBinary())); } else { result->setInteger("id", m_callId); - result->setValue("result", SerializedValue::create(params->serialize())); + result->setValue("result", SerializedValue::fromBinary(params->serializeToBinary())); } - return result->serialize(); + return result->serializeToBinary(); } InternalResponse::InternalResponse(int callId, const String& notification, std::unique_ptr params) diff --git a/tools/inspector_protocol/lib/DispatcherBase_h.template b/tools/inspector_protocol/lib/DispatcherBase_h.template index d70a4afe71de2c..7d859c4f2753bb 100644 --- a/tools/inspector_protocol/lib/DispatcherBase_h.template +++ b/tools/inspector_protocol/lib/DispatcherBase_h.template @@ -1,3 +1,5 @@ +// This file is generated by DispatcherBase_h.template. + // Copyright 2016 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. @@ -5,9 +7,8 @@ #ifndef {{"_".join(config.protocol.namespace)}}_DispatcherBase_h #define {{"_".join(config.protocol.namespace)}}_DispatcherBase_h -//#include "Collections.h" -//#include "ErrorSupport.h" //#include "Forward.h" +//#include "ErrorSupport.h" //#include "Values.h" {% for namespace in config.protocol.namespace %} @@ -22,7 +23,6 @@ public: kSuccess = 0, kError = 1, kFallThrough = 2, - kAsync = 3 }; enum ErrorCode { @@ -68,7 +68,7 @@ public: class {{config.lib.export_macro}} Callback { public: - Callback(std::unique_ptr backendImpl, int callId, int callbackId); + Callback(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message); virtual ~Callback(); void dispose(); @@ -79,13 +79,16 @@ public: private: std::unique_ptr m_backendImpl; int m_callId; - int m_callbackId; + String m_method; + ProtocolMessage m_message; }; explicit DispatcherBase(FrontendChannel*); virtual ~DispatcherBase(); - virtual DispatchResponse::Status dispatch(int callId, const String& method, std::unique_ptr messageObject) = 0; + virtual bool canDispatch(const String& method) = 0; + virtual void dispatch(int callId, const String& method, const ProtocolMessage& rawMessage, std::unique_ptr messageObject) = 0; + FrontendChannel* channel() { return m_frontendChannel; } void sendResponse(int callId, const DispatchResponse&, std::unique_ptr result); void sendResponse(int callId, const DispatchResponse&); @@ -95,15 +98,9 @@ public: std::unique_ptr weakPtr(); - int nextCallbackId(); - void markFallThrough(int callbackId); - bool lastCallbackFallThrough() { return m_lastCallbackFallThrough; } - private: FrontendChannel* m_frontendChannel; - protocol::HashSet m_weakPtrs; - int m_lastCallbackId; - bool m_lastCallbackFallThrough; + std::unordered_set m_weakPtrs; }; class {{config.lib.export_macro}} UberDispatcher { @@ -111,19 +108,18 @@ class {{config.lib.export_macro}} UberDispatcher { public: explicit UberDispatcher(FrontendChannel*); void registerBackend(const String& name, std::unique_ptr); - void setupRedirects(const HashMap&); - DispatchResponse::Status dispatch(std::unique_ptr message, int* callId = nullptr, String* method = nullptr); + void setupRedirects(const std::unordered_map&); + bool parseCommand(Value* message, int* callId, String* method); + bool canDispatch(const String& method); + void dispatch(int callId, const String& method, std::unique_ptr message, const ProtocolMessage& rawMessage); FrontendChannel* channel() { return m_frontendChannel; } - bool fallThroughForNotFound() { return m_fallThroughForNotFound; } - void setFallThroughForNotFound(bool); - bool getCommandName(const String& message, String* method, std::unique_ptr* parsedMessage); virtual ~UberDispatcher(); private: + protocol::DispatcherBase* findDispatcher(const String& method); FrontendChannel* m_frontendChannel; - bool m_fallThroughForNotFound; - HashMap m_redirects; - protocol::HashMap> m_dispatchers; + std::unordered_map m_redirects; + std::unordered_map> m_dispatchers; }; class InternalResponse : public Serializable { @@ -132,7 +128,8 @@ public: static std::unique_ptr createResponse(int callId, std::unique_ptr params); static std::unique_ptr createNotification(const String& notification, std::unique_ptr params = nullptr); - String serialize() override; + String serializeToJSON() override; + std::vector serializeToBinary() override; ~InternalResponse() override {} @@ -146,24 +143,36 @@ private: class InternalRawNotification : public Serializable { public: - static std::unique_ptr create(const String& notification) + static std::unique_ptr fromJSON(String notification) + { + return std::unique_ptr(new InternalRawNotification(std::move(notification))); + } + + static std::unique_ptr fromBinary(std::vector notification) { - return std::unique_ptr(new InternalRawNotification(notification)); + return std::unique_ptr(new InternalRawNotification(std::move(notification))); } + ~InternalRawNotification() override {} - String serialize() override + String serializeToJSON() override + { + return std::move(m_jsonNotification); + } + + std::vector serializeToBinary() override { - return m_notification; + return std::move(m_binaryNotification); } private: - explicit InternalRawNotification(const String& notification) - : m_notification(notification) - { - } + explicit InternalRawNotification(String notification) + : m_jsonNotification(std::move(notification)) { } + explicit InternalRawNotification(std::vector notification) + : m_binaryNotification(std::move(notification)) { } - String m_notification; + String m_jsonNotification; + std::vector m_binaryNotification; }; {% for namespace in config.protocol.namespace %} diff --git a/tools/inspector_protocol/lib/ErrorSupport_cpp.template b/tools/inspector_protocol/lib/ErrorSupport_cpp.template index 7b858b8dc48f37..a5c2a79bbd25c4 100644 --- a/tools/inspector_protocol/lib/ErrorSupport_cpp.template +++ b/tools/inspector_protocol/lib/ErrorSupport_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by ErrorSupport_cpp.template. + // Copyright 2016 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. diff --git a/tools/inspector_protocol/lib/ErrorSupport_h.template b/tools/inspector_protocol/lib/ErrorSupport_h.template index 083f2a5eb0d4d3..f317a3cfb411b5 100644 --- a/tools/inspector_protocol/lib/ErrorSupport_h.template +++ b/tools/inspector_protocol/lib/ErrorSupport_h.template @@ -1,3 +1,5 @@ +// This file is generated by ErrorSupport_h.template. + // Copyright 2016 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. @@ -5,7 +7,7 @@ #ifndef {{"_".join(config.protocol.namespace)}}_ErrorSupport_h #define {{"_".join(config.protocol.namespace)}}_ErrorSupport_h -//#include "Forward.h" +#include {{format_include(config.protocol.package, "Forward")}} {% for namespace in config.protocol.namespace %} namespace {{namespace}} { diff --git a/tools/inspector_protocol/lib/Forward_h.template b/tools/inspector_protocol/lib/Forward_h.template index 34d1c0d3e946cd..ff5e685863395b 100644 --- a/tools/inspector_protocol/lib/Forward_h.template +++ b/tools/inspector_protocol/lib/Forward_h.template @@ -1,3 +1,5 @@ +// This file is generated by Forward_h.template. + // Copyright 2016 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. @@ -10,7 +12,11 @@ {% endif %} #include {{format_include(config.lib.string_header)}} +#include +#include #include +#include +#include {% for namespace in config.protocol.namespace %} namespace {{namespace}} { diff --git a/tools/inspector_protocol/lib/FrontendChannel_h.template b/tools/inspector_protocol/lib/FrontendChannel_h.template index 0454978b0c8e88..df104debadbe85 100644 --- a/tools/inspector_protocol/lib/FrontendChannel_h.template +++ b/tools/inspector_protocol/lib/FrontendChannel_h.template @@ -1,3 +1,5 @@ +// This file is generated by FrontendChannel_h.template. + // Copyright 2016 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. @@ -11,7 +13,14 @@ namespace {{namespace}} { class {{config.lib.export_macro}} Serializable { public: - virtual String serialize() = 0; + ProtocolMessage serialize(bool binary) { + if (binary) + return StringUtil::binaryToMessage(serializeToBinary()); + else + return StringUtil::jsonToMessage(serializeToJSON()); + } + virtual String serializeToJSON() = 0; + virtual std::vector serializeToBinary() = 0; virtual ~Serializable() = default; }; @@ -20,6 +29,7 @@ public: virtual ~FrontendChannel() { } virtual void sendProtocolResponse(int callId, std::unique_ptr message) = 0; virtual void sendProtocolNotification(std::unique_ptr message) = 0; + virtual void fallThrough(int callId, const String& method, const ProtocolMessage& message) = 0; virtual void flushProtocolNotifications() = 0; }; diff --git a/tools/inspector_protocol/lib/Maybe_h.template b/tools/inspector_protocol/lib/Maybe_h.template index 71593acd0e553d..22cfac6b240bef 100644 --- a/tools/inspector_protocol/lib/Maybe_h.template +++ b/tools/inspector_protocol/lib/Maybe_h.template @@ -1,3 +1,5 @@ +// This file is generated by Maybe_h.template. + // Copyright 2016 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. @@ -5,6 +7,41 @@ #ifndef {{"_".join(config.protocol.namespace)}}_Maybe_h #define {{"_".join(config.protocol.namespace)}}_Maybe_h +// This macro allows to test for the version of the GNU C++ compiler. +// Note that this also applies to compilers that masquerade as GCC, +// for example clang and the Intel C++ compiler for Linux. +// Use like: +// #if IP_GNUC_PREREQ(4, 3, 1) +// ... +// #endif +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) +#define IP_GNUC_PREREQ(major, minor, patchlevel) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= \ + ((major)*10000 + (minor)*100 + (patchlevel))) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) +#define IP_GNUC_PREREQ(major, minor, patchlevel) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= \ + ((major)*10000 + (minor)*100 + (patchlevel))) +#else +#define IP_GNUC_PREREQ(major, minor, patchlevel) 0 +#endif + +#if defined(__mips64) +#define IP_TARGET_ARCH_MIPS64 1 +#elif defined(__MIPSEB__) || defined(__MIPSEL__) +#define IP_TARGET_ARCH_MIPS 1 +#endif + +// Allowing the use of noexcept by removing the keyword on older compilers that +// do not support adding noexcept to default members. +#if ((IP_GNUC_PREREQ(4, 9, 0) && !defined(IP_TARGET_ARCH_MIPS) && \ + !defined(IP_TARGET_ARCH_MIPS64)) || \ + (defined(__clang__) && __cplusplus > 201300L)) +#define IP_NOEXCEPT noexcept +#else +#define IP_NOEXCEPT +#endif + //#include "Forward.h" {% for namespace in config.protocol.namespace %} @@ -16,7 +53,7 @@ class Maybe { public: Maybe() : m_value() { } Maybe(std::unique_ptr value) : m_value(std::move(value)) { } - Maybe(Maybe&& other) : m_value(std::move(other.m_value)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : m_value(std::move(other.m_value)) {} void operator=(std::unique_ptr value) { m_value = std::move(value); } T* fromJust() const { DCHECK(m_value); return m_value.get(); } T* fromMaybe(T* defaultValue) const { return m_value ? m_value.get() : defaultValue; } @@ -31,7 +68,9 @@ class MaybeBase { public: MaybeBase() : m_isJust(false) { } MaybeBase(T value) : m_isJust(true), m_value(value) { } - MaybeBase(MaybeBase&& other) : m_isJust(other.m_isJust), m_value(std::move(other.m_value)) { } + MaybeBase(MaybeBase&& other) IP_NOEXCEPT + : m_isJust(other.m_isJust), + m_value(std::move(other.m_value)) {} void operator=(T value) { m_value = value; m_isJust = true; } T fromJust() const { DCHECK(m_isJust); return m_value; } T fromMaybe(const T& defaultValue) const { return m_isJust ? m_value : defaultValue; } @@ -46,27 +85,27 @@ protected: template<> class Maybe : public MaybeBase { public: - Maybe() { } + Maybe() { m_value = false; } Maybe(bool value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; template<> class Maybe : public MaybeBase { public: - Maybe() { } + Maybe() { m_value = 0; } Maybe(int value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; template<> class Maybe : public MaybeBase { public: - Maybe() { } + Maybe() { m_value = 0; } Maybe(double value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; @@ -75,7 +114,16 @@ class Maybe : public MaybeBase { public: Maybe() { } Maybe(const String& value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} + using MaybeBase::operator=; +}; + +template<> +class Maybe : public MaybeBase { +public: + Maybe() { } + Maybe(Binary value) : MaybeBase(value) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; @@ -83,4 +131,9 @@ public: } // namespace {{namespace}} {% endfor %} +#undef IP_GNUC_PREREQ +#undef IP_TARGET_ARCH_MIPS64 +#undef IP_TARGET_ARCH_MIPS +#undef IP_NOEXCEPT + #endif // !defined({{"_".join(config.protocol.namespace)}}_Maybe_h) diff --git a/tools/inspector_protocol/lib/Object_cpp.template b/tools/inspector_protocol/lib/Object_cpp.template index 91723a71e29ce4..1640a11127b442 100644 --- a/tools/inspector_protocol/lib/Object_cpp.template +++ b/tools/inspector_protocol/lib/Object_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by Object_cpp.template. + // Copyright 2016 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. diff --git a/tools/inspector_protocol/lib/Object_h.template b/tools/inspector_protocol/lib/Object_h.template index f6ffc57659e148..ec953d0d4836a4 100644 --- a/tools/inspector_protocol/lib/Object_h.template +++ b/tools/inspector_protocol/lib/Object_h.template @@ -1,3 +1,5 @@ +// This file is generated by Object_h.template. + // Copyright 2016 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. @@ -16,12 +18,12 @@ namespace {{namespace}} { class {{config.lib.export_macro}} Object { public: static std::unique_ptr fromValue(protocol::Value*, ErrorSupport*); + explicit Object(std::unique_ptr); ~Object(); std::unique_ptr toValue() const; std::unique_ptr clone() const; private: - explicit Object(std::unique_ptr); std::unique_ptr m_object; }; diff --git a/tools/inspector_protocol/lib/Parser_cpp.template b/tools/inspector_protocol/lib/Parser_cpp.template index f3dde5ac218e6f..ea7ecc5a1a4756 100644 --- a/tools/inspector_protocol/lib/Parser_cpp.template +++ b/tools/inspector_protocol/lib/Parser_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by Parser_cpp.template. + // Copyright 2016 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. @@ -425,9 +427,8 @@ std::unique_ptr buildValue(const Char* start, const Char* end, const Char double value = charactersToDouble(tokenStart, tokenEnd - tokenStart, &ok); if (!ok) return nullptr; - int number = static_cast(value); - if (number == value) - result = FundamentalValue::create(number); + if (value >= INT_MIN && value <= INT_MAX && static_cast(value) == value) + result = FundamentalValue::create(static_cast(value)); else result = FundamentalValue::create(value); break; diff --git a/tools/inspector_protocol/lib/Parser_h.template b/tools/inspector_protocol/lib/Parser_h.template index 8397d3f5d6911c..1832c2e9724b4a 100644 --- a/tools/inspector_protocol/lib/Parser_h.template +++ b/tools/inspector_protocol/lib/Parser_h.template @@ -1,3 +1,5 @@ +// This file is generated by Parser_h.template. + // Copyright 2016 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. diff --git a/tools/inspector_protocol/lib/Protocol_cpp.template b/tools/inspector_protocol/lib/Protocol_cpp.template index 901656373a4f52..88303a27ab9e7e 100644 --- a/tools/inspector_protocol/lib/Protocol_cpp.template +++ b/tools/inspector_protocol/lib/Protocol_cpp.template @@ -1,4 +1,4 @@ -// This file is generated. +// This file is generated by Protocol_cpp.template. // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -7,6 +7,6 @@ #include {{format_include(config.protocol.package, "Protocol")}} #include +#include #include - #include diff --git a/tools/inspector_protocol/lib/ValueConversions_h.template b/tools/inspector_protocol/lib/ValueConversions_h.template index 4d64ec9091a6c2..2ee5b724545a33 100644 --- a/tools/inspector_protocol/lib/ValueConversions_h.template +++ b/tools/inspector_protocol/lib/ValueConversions_h.template @@ -1,3 +1,5 @@ +// This file is generated by ValueConversions_h.template. + // Copyright 2016 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. @@ -99,6 +101,33 @@ struct ValueConversions { } }; +template<> +struct ValueConversions { + static Binary fromValue(protocol::Value* value, ErrorSupport* errors) + { + if (!value || + (value->type() != Value::TypeBinary && value->type() != Value::TypeString)) { + errors->addError("Either string base64 or binary value expected"); + return Binary(); + } + Binary binary; + if (value->asBinary(&binary)) + return binary; + String result; + value->asString(&result); + bool success; + Binary out = Binary::fromBase64(result, &success); + if (!success) + errors->addError("base64 decoding error"); + return out; + } + + static std::unique_ptr toValue(const Binary& value) + { + return BinaryValue::create(value); + } +}; + template<> struct ValueConversions { static std::unique_ptr fromValue(protocol::Value* value, ErrorSupport* errors) diff --git a/tools/inspector_protocol/lib/Values_cpp.template b/tools/inspector_protocol/lib/Values_cpp.template index b9f061346bf66f..4b4ba994151db7 100644 --- a/tools/inspector_protocol/lib/Values_cpp.template +++ b/tools/inspector_protocol/lib/Values_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by Values_cpp.template. + // Copyright 2016 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. @@ -58,8 +60,162 @@ void escapeStringForJSONInternal(const Char* str, unsigned len, } } +// When parsing CBOR, we limit recursion depth for objects and arrays +// to this constant. +static constexpr int kStackLimitValues = 1000; + +// Below are three parsing routines for CBOR, which cover enough +// to roundtrip JSON messages. +std::unique_ptr parseMap(int32_t stack_depth, CBORTokenizer* tokenizer); +std::unique_ptr parseArray(int32_t stack_depth, CBORTokenizer* tokenizer); +std::unique_ptr parseValue(int32_t stack_depth, CBORTokenizer* tokenizer); + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +std::unique_ptr parseArray(int32_t stack_depth, CBORTokenizer* tokenizer) { + DCHECK(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START); + tokenizer->Next(); + auto list = ListValue::create(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + // Error::CBOR_UNEXPECTED_EOF_IN_ARRAY + if (tokenizer->TokenTag() == CBORTokenTag::DONE) return nullptr; + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + // Parse value. + auto value = parseValue(stack_depth, tokenizer); + if (!value) return nullptr; + list->pushValue(std::move(value)); + } + tokenizer->Next(); + return list; +} + +std::unique_ptr parseValue( + int32_t stack_depth, CBORTokenizer* tokenizer) { + // Error::CBOR_STACK_LIMIT_EXCEEDED + if (stack_depth > kStackLimitValues) return nullptr; + // Skip past the envelope to get to what's inside. + if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + switch (tokenizer->TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + return nullptr; + case CBORTokenTag::DONE: + // Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE + return nullptr; + case CBORTokenTag::TRUE_VALUE: { + std::unique_ptr value = FundamentalValue::create(true); + tokenizer->Next(); + return value; + } + case CBORTokenTag::FALSE_VALUE: { + std::unique_ptr value = FundamentalValue::create(false); + tokenizer->Next(); + return value; + } + case CBORTokenTag::NULL_VALUE: { + std::unique_ptr value = FundamentalValue::null(); + tokenizer->Next(); + return value; + } + case CBORTokenTag::INT32: { + std::unique_ptr value = FundamentalValue::create(tokenizer->GetInt32()); + tokenizer->Next(); + return value; + } + case CBORTokenTag::DOUBLE: { + std::unique_ptr value = FundamentalValue::create(tokenizer->GetDouble()); + tokenizer->Next(); + return value; + } + case CBORTokenTag::STRING8: { + span str = tokenizer->GetString8(); + std::unique_ptr value = StringValue::create(StringUtil::fromUTF8(str.data(), str.size())); + tokenizer->Next(); + return value; + } + case CBORTokenTag::STRING16: + // NOT SUPPORTED YET. + return nullptr; + case CBORTokenTag::BINARY: { + span payload = tokenizer->GetBinary(); + tokenizer->Next(); + return BinaryValue::create(Binary::fromSpan(payload.data(), payload.size())); + } + case CBORTokenTag::MAP_START: + return parseMap(stack_depth + 1, tokenizer); + case CBORTokenTag::ARRAY_START: + return parseArray(stack_depth + 1, tokenizer); + default: + // Error::CBOR_UNSUPPORTED_VALUE + return nullptr; + } +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +std::unique_ptr parseMap( + int32_t stack_depth, CBORTokenizer* tokenizer) { + auto dict = DictionaryValue::create(); + tokenizer->Next(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + // Error::CBOR_UNEXPECTED_EOF_IN_MAP + return nullptr; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + // Parse key. + String key; + if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { + span key_span = tokenizer->GetString8(); + key = StringUtil::fromUTF8(key_span.data(), key_span.size()); + tokenizer->Next(); + } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { + return nullptr; // STRING16 not supported yet. + } else { + // Error::CBOR_INVALID_MAP_KEY + return nullptr; + } + // Parse value. + auto value = parseValue(stack_depth, tokenizer); + if (!value) return nullptr; + dict->setValue(key, std::move(value)); + } + tokenizer->Next(); + return dict; +} + } // anonymous namespace +// static +std::unique_ptr Value::parseBinary(const uint8_t* data, size_t size) { + span bytes(data, size); + + // Error::CBOR_NO_INPUT + if (bytes.empty()) return nullptr; + + // Error::CBOR_INVALID_START_BYTE + // TODO(johannes): EncodeInitialByteForEnvelope() method. + if (bytes[0] != 0xd8) return nullptr; + + CBORTokenizer tokenizer(bytes); + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + + // We checked for the envelope start byte above, so the tokenizer + // must agree here, since it's not an error. + DCHECK(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE); + tokenizer.EnterEnvelope(); + // Error::MAP_START_EXPECTED + if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) return nullptr; + std::unique_ptr result = parseMap(/*stack_depth=*/1, &tokenizer); + if (!result) return nullptr; + if (tokenizer.TokenTag() == CBORTokenTag::DONE) return result; + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + // Error::CBOR_TRAILING_JUNK + return nullptr; +} + bool Value::asBoolean(bool*) const { return false; @@ -80,7 +236,7 @@ bool Value::asString(String*) const return false; } -bool Value::asSerialized(String*) const +bool Value::asBinary(Binary*) const { return false; } @@ -91,12 +247,17 @@ void Value::writeJSON(StringBuilder* output) const StringUtil::builderAppend(*output, nullValueString, 4); } +void Value::writeBinary(std::vector* bytes) const { + DCHECK(m_type == TypeNull); + bytes->push_back(EncodeNull()); +} + std::unique_ptr Value::clone() const { return Value::null(); } -String Value::serialize() +String Value::toJSONString() const { StringBuilder result; StringUtil::builderReserve(result, 512); @@ -104,6 +265,16 @@ String Value::serialize() return StringUtil::builderToString(result); } +String Value::serializeToJSON() { + return toJSONString(); +} + +std::vector Value::serializeToBinary() { + std::vector bytes; + writeBinary(&bytes); + return bytes; +} + bool FundamentalValue::asBoolean(bool* output) const { if (type() != TypeBoolean) @@ -152,6 +323,22 @@ void FundamentalValue::writeJSON(StringBuilder* output) const } } +void FundamentalValue::writeBinary(std::vector* bytes) const { + switch (type()) { + case TypeDouble: + EncodeDouble(m_doubleValue, bytes); + return; + case TypeInteger: + EncodeInt32(m_integerValue, bytes); + return; + case TypeBoolean: + bytes->push_back(m_boolValue ? EncodeTrue() : EncodeFalse()); + return; + default: + DCHECK(false); + } +} + std::unique_ptr FundamentalValue::clone() const { switch (type()) { @@ -176,26 +363,53 @@ void StringValue::writeJSON(StringBuilder* output) const StringUtil::builderAppendQuotedString(*output, m_stringValue); } +void StringValue::writeBinary(std::vector* bytes) const { + StringUTF8Adapter utf8(m_stringValue); + EncodeString8(span(reinterpret_cast(utf8.Data()), + utf8.length()), bytes); +} + std::unique_ptr StringValue::clone() const { return StringValue::create(m_stringValue); } -bool SerializedValue::asSerialized(String* output) const +bool BinaryValue::asBinary(Binary* output) const { - *output = m_serializedValue; + *output = m_binaryValue; return true; } +void BinaryValue::writeJSON(StringBuilder* output) const +{ + DCHECK(type() == TypeBinary); + StringUtil::builderAppendQuotedString(*output, m_binaryValue.toBase64()); +} + +void BinaryValue::writeBinary(std::vector* bytes) const { + EncodeBinary(span(m_binaryValue.data(), m_binaryValue.size()), bytes); +} + +std::unique_ptr BinaryValue::clone() const +{ + return BinaryValue::create(m_binaryValue); +} + void SerializedValue::writeJSON(StringBuilder* output) const { DCHECK(type() == TypeSerialized); - StringUtil::builderAppend(*output, m_serializedValue); + StringUtil::builderAppend(*output, m_serializedJSON); +} + +void SerializedValue::writeBinary(std::vector* output) const +{ + DCHECK(type() == TypeSerialized); + output->insert(output->end(), m_serializedBinary.begin(), m_serializedBinary.end()); } std::unique_ptr SerializedValue::clone() const { - return SerializedValue::create(m_serializedValue); + return std::unique_ptr(new SerializedValue(m_serializedJSON, m_serializedBinary)); } DictionaryValue::~DictionaryValue() @@ -335,6 +549,23 @@ void DictionaryValue::writeJSON(StringBuilder* output) const StringUtil::builderAppend(*output, '}'); } +void DictionaryValue::writeBinary(std::vector* bytes) const { + EnvelopeEncoder encoder; + encoder.EncodeStart(bytes); + bytes->push_back(EncodeIndefiniteLengthMapStart()); + for (size_t i = 0; i < m_order.size(); ++i) { + const String& key = m_order[i]; + Dictionary::const_iterator value = m_data.find(key); + DCHECK(value != m_data.cend() && value->second); + StringUTF8Adapter utf8(key); + EncodeString8(span(reinterpret_cast(utf8.Data()), + utf8.length()), bytes); + value->second->writeBinary(bytes); + } + bytes->push_back(EncodeStop()); + encoder.EncodeStop(bytes); +} + std::unique_ptr DictionaryValue::clone() const { std::unique_ptr result = DictionaryValue::create(); @@ -369,6 +600,17 @@ void ListValue::writeJSON(StringBuilder* output) const StringUtil::builderAppend(*output, ']'); } +void ListValue::writeBinary(std::vector* bytes) const { + EnvelopeEncoder encoder; + encoder.EncodeStart(bytes); + bytes->push_back(EncodeIndefiniteLengthArrayStart()); + for (size_t i = 0; i < m_data.size(); ++i) { + m_data[i]->writeBinary(bytes); + } + bytes->push_back(EncodeStop()); + encoder.EncodeStop(bytes); +} + std::unique_ptr ListValue::clone() const { std::unique_ptr result = ListValue::create(); diff --git a/tools/inspector_protocol/lib/Values_h.template b/tools/inspector_protocol/lib/Values_h.template index 3638b34b4e7718..4a2e58f4cd6850 100644 --- a/tools/inspector_protocol/lib/Values_h.template +++ b/tools/inspector_protocol/lib/Values_h.template @@ -1,3 +1,5 @@ +// This file is generated by Values_h.template. + // Copyright 2016 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. @@ -6,7 +8,6 @@ #define {{"_".join(config.protocol.namespace)}}_Values_h //#include "Allocator.h" -//#include "Collections.h" //#include "Forward.h" {% for namespace in config.protocol.namespace %} @@ -27,15 +28,19 @@ public: return std::unique_ptr(new Value()); } + static std::unique_ptr parseBinary(const uint8_t* data, size_t size); + enum ValueType { TypeNull = 0, TypeBoolean, TypeInteger, TypeDouble, TypeString, + TypeBinary, TypeObject, TypeArray, - TypeSerialized + TypeSerialized, + TypeImported }; ValueType type() const { return m_type; } @@ -46,11 +51,14 @@ public: virtual bool asDouble(double* output) const; virtual bool asInteger(int* output) const; virtual bool asString(String* output) const; - virtual bool asSerialized(String* output) const; + virtual bool asBinary(Binary* output) const; virtual void writeJSON(StringBuilder* output) const; + virtual void writeBinary(std::vector* bytes) const; virtual std::unique_ptr clone() const; - String serialize() override; + String toJSONString() const; + String serializeToJSON() override; + std::vector serializeToBinary() override; protected: Value() : m_type(TypeNull) { } @@ -84,6 +92,7 @@ public: bool asDouble(double* output) const override; bool asInteger(int* output) const override; void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; private: @@ -112,6 +121,7 @@ public: bool asString(String* output) const override; void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; private: @@ -121,21 +131,47 @@ private: String m_stringValue; }; +class {{config.lib.export_macro}} BinaryValue : public Value { +public: + static std::unique_ptr create(const Binary& value) + { + return std::unique_ptr(new BinaryValue(value)); + } + + bool asBinary(Binary* output) const override; + void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; + std::unique_ptr clone() const override; + +private: + explicit BinaryValue(const Binary& value) : Value(TypeBinary), m_binaryValue(value) { } + + Binary m_binaryValue; +}; + class {{config.lib.export_macro}} SerializedValue : public Value { public: - static std::unique_ptr create(const String& value) + static std::unique_ptr fromJSON(const String& value) { return std::unique_ptr(new SerializedValue(value)); } - bool asSerialized(String* output) const override; + static std::unique_ptr fromBinary(std::vector value) + { + return std::unique_ptr(new SerializedValue(std::move(value))); + } + void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; private: - explicit SerializedValue(const String& value) : Value(TypeSerialized), m_serializedValue(value) { } - - String m_serializedValue; + explicit SerializedValue(const String& json) : Value(TypeSerialized), m_serializedJSON(json) { } + explicit SerializedValue(std::vector binary) : Value(TypeSerialized), m_serializedBinary(std::move(binary)) { } + SerializedValue(const String& json, const std::vector& binary) + : Value(TypeSerialized), m_serializedJSON(json), m_serializedBinary(binary) { } + String m_serializedJSON; + std::vector m_serializedBinary; }; class {{config.lib.export_macro}} DictionaryValue : public Value { @@ -159,6 +195,7 @@ public: } void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; size_t size() const { return m_data.size(); } @@ -200,7 +237,7 @@ private: m_order.push_back(key); } - using Dictionary = protocol::HashMap>; + using Dictionary = std::unordered_map>; Dictionary m_data; std::vector m_order; }; @@ -227,6 +264,7 @@ public: ~ListValue() override; void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; void pushValue(std::unique_ptr); diff --git a/tools/inspector_protocol/lib/base_string_adapter_cc.template b/tools/inspector_protocol/lib/base_string_adapter_cc.template new file mode 100644 index 00000000000000..ed3316446f4a51 --- /dev/null +++ b/tools/inspector_protocol/lib/base_string_adapter_cc.template @@ -0,0 +1,304 @@ +// This file is generated by DispatcherBase_cpp.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include {{format_include(config.protocol.package, "base_string_adapter")}} +#include {{format_include(config.protocol.package, "Protocol")}} + +#include +#include "base/base64.h" +#include "base/json/json_reader.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string16.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +std::unique_ptr toProtocolValue( + const base::Value* value, int depth) { + if (!value || !depth) + return nullptr; + if (value->is_none()) + return protocol::Value::null(); + if (value->is_bool()) { + bool inner; + value->GetAsBoolean(&inner); + return protocol::FundamentalValue::create(inner); + } + if (value->is_int()) { + int inner; + value->GetAsInteger(&inner); + return protocol::FundamentalValue::create(inner); + } + if (value->is_double()) { + double inner; + value->GetAsDouble(&inner); + return protocol::FundamentalValue::create(inner); + } + if (value->is_string()) { + std::string inner; + value->GetAsString(&inner); + return protocol::StringValue::create(inner); + } + if (value->is_list()) { + const base::ListValue* list = nullptr; + value->GetAsList(&list); + std::unique_ptr result = protocol::ListValue::create(); + for (size_t i = 0; i < list->GetSize(); i++) { + const base::Value* item = nullptr; + list->Get(i, &item); + std::unique_ptr converted = + toProtocolValue(item, depth - 1); + if (converted) + result->pushValue(std::move(converted)); + } + return std::move(result); + } + if (value->is_dict()) { + const base::DictionaryValue* dictionary = nullptr; + value->GetAsDictionary(&dictionary); + std::unique_ptr result = + protocol::DictionaryValue::create(); + for (base::DictionaryValue::Iterator it(*dictionary); + !it.IsAtEnd(); it.Advance()) { + std::unique_ptr converted = + toProtocolValue(&it.value(), depth - 1); + if (converted) + result->setValue(it.key(), std::move(converted)); + } + return std::move(result); + } + return nullptr; +} + +std::unique_ptr toBaseValue(Value* value, int depth) { + if (!value || !depth) + return nullptr; + if (value->type() == Value::TypeNull) + return std::make_unique(); + if (value->type() == Value::TypeBoolean) { + bool inner; + value->asBoolean(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeInteger) { + int inner; + value->asInteger(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeDouble) { + double inner; + value->asDouble(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeString) { + std::string inner; + value->asString(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeArray) { + ListValue* list = ListValue::cast(value); + std::unique_ptr result(new base::ListValue()); + for (size_t i = 0; i < list->size(); i++) { + std::unique_ptr converted = + toBaseValue(list->at(i), depth - 1); + if (converted) + result->Append(std::move(converted)); + } + return std::move(result); + } + if (value->type() == Value::TypeObject) { + DictionaryValue* dict = DictionaryValue::cast(value); + std::unique_ptr result(new base::DictionaryValue()); + for (size_t i = 0; i < dict->size(); i++) { + DictionaryValue::Entry entry = dict->at(i); + std::unique_ptr converted = + toBaseValue(entry.second, depth - 1); + if (converted) + result->SetWithoutPathExpansion(entry.first, std::move(converted)); + } + return std::move(result); + } + return nullptr; +} + +// static +std::unique_ptr StringUtil::parseMessage( + const std::string& message, bool binary) { + if (binary) { + return Value::parseBinary( + reinterpret_cast(message.data()), + message.length()); + } + std::unique_ptr value = base::JSONReader::Read(message); + return toProtocolValue(value.get(), 1000); +} + +// static +ProtocolMessage StringUtil::jsonToMessage(String message) { + return message; +} + +// static +ProtocolMessage StringUtil::binaryToMessage(std::vector message) { + // TODO(pfeldman): figure out what to do with this copy. + return std::string(reinterpret_cast(message.data()), message.size()); +} + +StringBuilder::StringBuilder() {} + +StringBuilder::~StringBuilder() {} + +void StringBuilder::append(const std::string& s) { + string_ += s; +} + +void StringBuilder::append(char c) { + string_ += c; +} + +void StringBuilder::append(const char* characters, size_t length) { + string_.append(characters, length); +} + +// static +void StringUtil::builderAppendQuotedString(StringBuilder& builder, + const String& str) { + builder.append('"'); + base::string16 str16 = base::UTF8ToUTF16(str); + escapeWideStringForJSON(reinterpret_cast(&str16[0]), + str16.length(), &builder); + builder.append('"'); +} + +std::string StringBuilder::toString() { + return string_; +} + +void StringBuilder::reserveCapacity(size_t capacity) { + string_.reserve(capacity); +} + +Binary::Binary() : bytes_(new base::RefCountedBytes) {} +Binary::Binary(const Binary& binary) : bytes_(binary.bytes_) {} +Binary::Binary(scoped_refptr bytes) : bytes_(bytes) {} +Binary::~Binary() {} + +String Binary::toBase64() const { + std::string encoded; + base::Base64Encode( + base::StringPiece(reinterpret_cast(bytes_->front()), + bytes_->size()), + &encoded); + return encoded; +} + +// static +Binary Binary::fromBase64(const String& base64, bool* success) { + std::string decoded; + *success = base::Base64Decode(base::StringPiece(base64), &decoded); + if (*success) { + return Binary::fromString(std::move(decoded)); + } + return Binary(); +} + +// static +Binary Binary::fromRefCounted(scoped_refptr memory) { + return Binary(memory); +} + +// static +Binary Binary::fromVector(std::vector data) { + return Binary(base::RefCountedBytes::TakeVector(&data)); +} + +// static +Binary Binary::fromString(std::string data) { + return Binary(base::RefCountedString::TakeString(&data)); +} + +// static +Binary Binary::fromSpan(const uint8_t* data, size_t size) { + return Binary(scoped_refptr( + new base::RefCountedBytes(data, size))); +} + +namespace { +int32_t ReadEnvelopeSize(const uint8_t* in) { + return (in[0] << 24) + (in[1] << 16) + (in[2] << 8) + in[3]; +} + +void WriteEnvelopeSize(uint32_t value, uint8_t* out) { + *(out++) = (value >> 24) & 0xFF; + *(out++) = (value >> 16) & 0xFF; + *(out++) = (value >> 8) & 0xFF; + *(out++) = (value) & 0xFF; +} + +} + +bool AppendStringValueToMapBinary(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out) { + if (in.size() < 1 + 1 + 4 + 1 + 1) + return false; + const uint8_t* envelope = reinterpret_cast(in.data()); + if (cbor::kInitialByteForEnvelope != envelope[0]) + return false; + if (cbor::kInitialByteFor32BitLengthByteString != envelope[1]) + return false; + if (cbor::kInitialByteIndefiniteLengthMap != envelope[6]) + return false; + + uint32_t envelope_size = ReadEnvelopeSize(envelope + 2); + if (envelope_size + 2 + 4 != in.size()) + return false; + if (cbor::kStopByte != static_cast(*in.rbegin())) + return false; + + std::vector encoded_entry; + encoded_entry.reserve(1 + 4 + key.size() + 1 + 4 + value.size()); + span key_span( + reinterpret_cast(key.data()), key.size()); + EncodeString8(key_span, &encoded_entry); + span value_span( + reinterpret_cast(value.data()), value.size()); + EncodeString8(value_span, &encoded_entry); + + out->clear(); + out->reserve(in.size() + encoded_entry.size()); + out->append(in.begin(), in.end() - 1); + out->append(reinterpret_cast(encoded_entry.data()), + encoded_entry.size()); + out->append(1, static_cast(cbor::kStopByte)); + std::size_t new_size = envelope_size + out->size() - in.size(); + if (new_size > static_cast( + std::numeric_limits::max())) { + return false; + } + WriteEnvelopeSize(new_size, reinterpret_cast(&*out->begin() + 2)); + return true; +} + +bool AppendStringValueToMapJSON(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out) { + if (!in.length() || *in.rbegin() != '}') + return false; + std::string suffix = + base::StringPrintf(", \"%s\": \"%s\"}", key.begin(), value.begin()); + out->clear(); + out->reserve(in.length() + suffix.length() - 1); + out->append(in.data(), in.length() - 1); + out->append(suffix); + return true; +} + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} diff --git a/tools/inspector_protocol/lib/base_string_adapter_h.template b/tools/inspector_protocol/lib/base_string_adapter_h.template new file mode 100644 index 00000000000000..b0215e0745017a --- /dev/null +++ b/tools/inspector_protocol/lib/base_string_adapter_h.template @@ -0,0 +1,150 @@ +// This file is generated by Parser_h.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef {{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H +#define {{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H + +#include +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted_memory.h" +#include "base/strings/string_number_conversions.h" +{% if config.lib.export_header %} +#include "{{config.lib.export_header}}" +{% endif %} + +namespace base { +class Value; +} + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +class Value; + +using String = std::string; +using ProtocolMessage = std::string; + +class {{config.lib.export_macro}} StringUTF8Adapter { + public: + StringUTF8Adapter(const std::string& string) : string_(string) { } + const char* Data() const { return string_.data(); } + size_t length() const { return string_.length(); } + + private: + const std::string& string_; +}; + +class {{config.lib.export_macro}} StringBuilder { + public: + StringBuilder(); + ~StringBuilder(); + void append(const String&); + void append(char); + void append(const char*, size_t); + String toString(); + void reserveCapacity(size_t); + + private: + std::string string_; +}; + +class {{config.lib.export_macro}} StringUtil { + public: + static String substring(const String& s, unsigned pos, unsigned len) { + return s.substr(pos, len); + } + static String fromInteger(int number) { return base::NumberToString(number); } + static String fromDouble(double number) { + String s = base::NumberToString(number); + if (!s.empty()) { // .123 -> 0.123; -.123 -> -0.123 for valid JSON. + if (s[0] == '.') + s.insert(/*index=*/ 0, /*count=*/ 1, /*ch=*/ '0'); + else if (s[0] == '-' && s.size() >= 2 && s[1] == '.') + s.insert(/*index=*/ 1, /*count=*/ 1, /*ch=*/ '0'); + } + return s; + } + static double toDouble(const char* s, size_t len, bool* ok) { + double v = 0.0; + *ok = base::StringToDouble(std::string(s, len), &v); + return *ok ? v : 0.0; + } + static size_t find(const String& s, const char* needle) { + return s.find(needle); + } + static size_t find(const String& s, const String& needle) { + return s.find(needle); + } + static const size_t kNotFound = static_cast(-1); + static void builderAppend(StringBuilder& builder, const String& s) { + builder.append(s); + } + static void builderAppend(StringBuilder& builder, char c) { + builder.append(c); + } + static void builderAppend(StringBuilder& builder, const char* s, size_t len) { + builder.append(s, len); + } + static void builderAppendQuotedString(StringBuilder& builder, + const String& str); + static void builderReserve(StringBuilder& builder, unsigned capacity) { + builder.reserveCapacity(capacity); + } + static String builderToString(StringBuilder& builder) { + return builder.toString(); + } + + static std::unique_ptr parseMessage(const std::string& message, bool binary); + static ProtocolMessage jsonToMessage(String message); + static ProtocolMessage binaryToMessage(std::vector message); + + static String fromUTF8(const uint8_t* data, size_t length) { + return std::string(reinterpret_cast(data), length); + } +}; + +// A read-only sequence of uninterpreted bytes with reference-counted storage. +class {{config.lib.export_macro}} Binary { + public: + Binary(const Binary&); + Binary(); + ~Binary(); + + const uint8_t* data() const { return bytes_->front(); } + size_t size() const { return bytes_->size(); } + scoped_refptr bytes() const { return bytes_; } + + String toBase64() const; + + static Binary fromBase64(const String& base64, bool* success); + static Binary fromRefCounted(scoped_refptr memory); + static Binary fromVector(std::vector data); + static Binary fromString(std::string data); + static Binary fromSpan(const uint8_t* data, size_t size); + + private: + explicit Binary(scoped_refptr bytes); + scoped_refptr bytes_; +}; + +std::unique_ptr toProtocolValue(const base::Value* value, int depth); +std::unique_ptr toBaseValue(Value* value, int depth); + +bool AppendStringValueToMapBinary(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out); +bool AppendStringValueToMapJSON(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out); + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} + +#endif // !defined({{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H) diff --git a/tools/inspector_protocol/ConvertProtocolToJSON.py b/tools/inspector_protocol/pdl.py similarity index 80% rename from tools/inspector_protocol/ConvertProtocolToJSON.py rename to tools/inspector_protocol/pdl.py index 56fc09d78cb18f..43111e944b4f5c 100644 --- a/tools/inspector_protocol/ConvertProtocolToJSON.py +++ b/tools/inspector_protocol/pdl.py @@ -1,28 +1,31 @@ -# Copyright 2017 The Chromium Authors. All rights reserved. +# 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. +from __future__ import print_function import collections import json import os.path import re import sys -file_name = None description = '' -primitiveTypes = ['integer', 'number', 'boolean', 'string', 'object', 'any', 'array'] +primitiveTypes = ['integer', 'number', 'boolean', 'string', 'object', 'any', 'array', 'binary'] -def assignType(item, type, isArray=False): - if isArray: + +def assignType(item, type, is_array=False, map_binary_to_string=False): + if is_array: item['type'] = 'array' item['items'] = collections.OrderedDict() - assignType(item['items'], type) + assignType(item['items'], type, False, map_binary_to_string) return if type == 'enum': type = 'string' + if map_binary_to_string and type == 'binary': + type = 'string' if type in primitiveTypes: item['type'] = type else: @@ -43,7 +46,7 @@ def createItem(d, experimental, deprecated, name=None): return result -def parse(data): +def parse(data, file_name, map_binary_to_string=False): protocol = collections.OrderedDict() protocol['version'] = collections.OrderedDict() protocol['domains'] = [] @@ -89,7 +92,7 @@ def parse(data): if 'types' not in domain: domain['types'] = [] item = createItem({'id': match.group(3)}, match.group(1), match.group(2)) - assignType(item, match.group(5), match.group(4)) + assignType(item, match.group(5), match.group(4), map_binary_to_string) domain['types'].append(item) continue @@ -116,7 +119,7 @@ def parse(data): param = createItem({}, match.group(1), match.group(2), match.group(6)) if match.group(3): param['optional'] = True - assignType(param, match.group(5), match.group(4)) + assignType(param, match.group(5), match.group(4), map_binary_to_string) if match.group(5) == 'enum': enumliterals = param['enum'] = [] subitems.append(param) @@ -157,27 +160,12 @@ def parse(data): enumliterals.append(trimLine) continue - print 'Error in %s:%s, illegal token: \t%s' % (file_name, i, line) + print('Error in %s:%s, illegal token: \t%s' % (file_name, i, line)) sys.exit(1) return protocol -def main(argv): - if len(argv) < 2: - sys.stderr.write("Usage: %s \n" % sys.argv[0]) - return 1 - global file_name - file_name = os.path.normpath(argv[0]) - input_file = open(file_name, "r") - pdl_string = input_file.read() - protocol = parse(pdl_string) - output_file = open(argv[0].replace('.pdl', '.json'), 'wb') - json.dump(protocol, output_file, indent=4, separators=(',', ': ')) - output_file.close() - - output_file = open(os.path.normpath(argv[1]), 'wb') - json.dump(protocol, output_file, indent=4, separators=(',', ': ')) - output_file.close() - - -if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + +def loads(data, file_name, map_binary_to_string=False): + if file_name.endswith(".pdl"): + return parse(data, file_name, map_binary_to_string) + return json.loads(data) diff --git a/tools/inspector_protocol/templates/Exported_h.template b/tools/inspector_protocol/templates/Exported_h.template index 3d36ecffae3ca3..765f6c2135b9c3 100644 --- a/tools/inspector_protocol/templates/Exported_h.template +++ b/tools/inspector_protocol/templates/Exported_h.template @@ -1,4 +1,4 @@ -// This file is generated +// This file is generated by Exported_h.template. // Copyright (c) 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -15,6 +15,17 @@ {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} + +#ifndef {{"_".join(config.protocol.namespace)}}_exported_api_h +#define {{"_".join(config.protocol.namespace)}}_exported_api_h +class {{config.exported.export_macro}} Exported { +public: + virtual {{config.exported.string_out}} toJSONString() const = 0; + virtual void writeBinary(std::vector* out) const = 0; + virtual ~Exported() { } +}; +#endif // !defined({{"_".join(config.protocol.namespace)}}_exported_api_h) + namespace {{domain.domain}} { namespace API { @@ -48,11 +59,10 @@ namespace {{param.name | to_title_case}}Enum { {% for type in domain.types %} {% if not (type.type == "object") or not ("properties" in type) or not protocol.is_exported(domain.domain, type.id) %}{% continue %}{% endif %} -class {{config.exported.export_macro}} {{type.id}} { +class {{config.exported.export_macro}} {{type.id}} : public Exported { public: - virtual {{config.exported.string_out}} toJSONString() const = 0; - virtual ~{{type.id}}() { } static std::unique_ptr fromJSONString(const {{config.exported.string_in}}& json); + static std::unique_ptr fromBinary(const uint8_t* data, size_t length); }; {% endfor %} diff --git a/tools/inspector_protocol/templates/Imported_h.template b/tools/inspector_protocol/templates/Imported_h.template index 4c9d24bd5fccf7..f2e576a9c470ae 100644 --- a/tools/inspector_protocol/templates/Imported_h.template +++ b/tools/inspector_protocol/templates/Imported_h.template @@ -1,4 +1,4 @@ -// This file is generated +// This file is generated by Imported_h.template. // Copyright (c) 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -17,6 +17,37 @@ {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} + +using Exported = {{"::".join(config.imported.namespace)}}::Exported; + +#ifndef {{"_".join(config.protocol.namespace)}}_imported_imported_h +#define {{"_".join(config.protocol.namespace)}}_imported_imported_h + +class {{config.lib.export_macro}} ImportedValue : public Value { +public: + static std::unique_ptr fromExported(const Exported* value) { + return std::unique_ptr(new ImportedValue(value)); + } + + void writeJSON(StringBuilder* output) const override { + auto json = m_exported->toJSONString(); + String local_json = ({{config.imported.from_imported_string % "std::move(json)"}}); + StringUtil::builderAppend(*output, local_json); + } + void writeBinary(std::vector* output) const override { + m_exported->writeBinary(output); + } + std::unique_ptr clone() const override { + return std::unique_ptr(new ImportedValue(m_exported)); + } + +private: + explicit ImportedValue(const Exported* exported) : Value(TypeImported), m_exported(exported) { } + const Exported* m_exported; +}; + +#endif // !defined({{"_".join(config.protocol.namespace)}}_imported_imported_h) + {% for type in domain.types %} {% if not (type.type == "object") or not ("properties" in type) or not protocol.is_imported(domain.domain, type.id) %}{% continue %}{% endif %} @@ -28,17 +59,18 @@ struct ValueConversions<{{"::".join(config.imported.namespace)}}::{{domain.domai errors->addError("value expected"); return nullptr; } - String json = value->serialize(); - auto result = {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}::fromJSONString({{config.imported.to_imported_string % "json"}}); + + std::vector binary; + value->writeBinary(&binary); + auto result = {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}::fromBinary(binary.data(), binary.size()); if (!result) errors->addError("cannot parse"); return result; } - static std::unique_ptr toValue(const {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}* value) + static std::unique_ptr toValue(const {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}* exported) { - auto json = value->toJSONString(); - return SerializedValue::create({{config.imported.from_imported_string % "std::move(json)"}}); + return ImportedValue::fromExported(exported); } static std::unique_ptr toValue(const std::unique_ptr<{{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}>& value) @@ -46,6 +78,7 @@ struct ValueConversions<{{"::".join(config.imported.namespace)}}::{{domain.domai return toValue(value.get()); } }; + {% endfor %} {% for namespace in config.protocol.namespace %} diff --git a/tools/inspector_protocol/templates/TypeBuilder_cpp.template b/tools/inspector_protocol/templates/TypeBuilder_cpp.template index 026c1cdb8da9e1..4ef60a6ea2cdef 100644 --- a/tools/inspector_protocol/templates/TypeBuilder_cpp.template +++ b/tools/inspector_protocol/templates/TypeBuilder_cpp.template @@ -1,10 +1,10 @@ -// This file is generated +// This file is generated by TypeBuilder_cpp.template. // Copyright (c) 2016 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 {{format_include(config.protocol.package, domain.domain)}} +#include {{format_domain_include(config.protocol.package, domain.domain)}} #include {{format_include(config.protocol.package, "Protocol")}} @@ -24,7 +24,7 @@ const char Metainfo::version[] = "{{domain.version}}"; namespace {{type.id}}Enum { {% for literal in type.enum %} -const char* {{ literal | dash_to_camelcase}} = "{{literal}}"; +const char {{ literal | dash_to_camelcase}}[] = "{{literal}}"; {% endfor %} } // namespace {{type.id}}Enum {% if protocol.is_exported(domain.domain, type.id) %} @@ -101,10 +101,15 @@ std::unique_ptr<{{type.id}}> {{type.id}}::clone() const {{config.exported.string_out}} {{type.id}}::toJSONString() const { - String json = toValue()->serialize(); + String json = toValue()->serializeToJSON(); return {{config.exported.to_string_out % "json"}}; } +void {{type.id}}::writeBinary(std::vector* out) const +{ + toValue()->writeBinary(out); +} + // static std::unique_ptr API::{{type.id}}::fromJSONString(const {{config.exported.string_in}}& json) { @@ -114,6 +119,17 @@ std::unique_ptr API::{{type.id}}::fromJSONString(const {{confi return nullptr; return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors); } + +// static +std::unique_ptr API::{{type.id}}::fromBinary(const uint8_t* data, size_t length) +{ + ErrorSupport errors; + std::unique_ptr value = Value::parseBinary(data, length); + if (!value) + return nullptr; + return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors); +} + {% endif %} {% endfor %} @@ -187,19 +203,23 @@ void Frontend::flush() m_frontendChannel->flushProtocolNotifications(); } -void Frontend::sendRawNotification(const String& notification) +void Frontend::sendRawNotification(String notification) +{ + m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromJSON(std::move(notification))); +} + +void Frontend::sendRawNotification(std::vector notification) { - m_frontendChannel->sendProtocolNotification(InternalRawNotification::create(notification)); + m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromBinary(std::move(notification))); } // --------------------- Dispatcher. class DispatcherImpl : public protocol::DispatcherBase { public: - DispatcherImpl(FrontendChannel* frontendChannel, Backend* backend, bool fallThroughForNotFound) + DispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) : DispatcherBase(frontendChannel) - , m_backend(backend) - , m_fallThroughForNotFound(fallThroughForNotFound) { + , m_backend(backend) { {% for command in domain.commands %} {% if "redirect" in command %} m_redirects["{{domain.domain}}.{{command.name}}"] = "{{command.redirect}}.{{command.name}}"; @@ -210,37 +230,35 @@ public: {% endfor %} } ~DispatcherImpl() override { } - DispatchResponse::Status dispatch(int callId, const String& method, std::unique_ptr messageObject) override; - HashMap& redirects() { return m_redirects; } + bool canDispatch(const String& method) override; + void dispatch(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr messageObject) override; + std::unordered_map& redirects() { return m_redirects; } protected: - using CallHandler = DispatchResponse::Status (DispatcherImpl::*)(int callId, std::unique_ptr messageObject, ErrorSupport* errors); - using DispatchMap = protocol::HashMap; + using CallHandler = void (DispatcherImpl::*)(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr messageObject, ErrorSupport* errors); + using DispatchMap = std::unordered_map; DispatchMap m_dispatchMap; - HashMap m_redirects; + std::unordered_map m_redirects; {% for command in domain.commands %} {% if "redirect" in command %}{% continue %}{% endif %} {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %} - DispatchResponse::Status {{command.name}}(int callId, std::unique_ptr requestMessageObject, ErrorSupport*); + void {{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr requestMessageObject, ErrorSupport*); {% endfor %} Backend* m_backend; - bool m_fallThroughForNotFound; }; -DispatchResponse::Status DispatcherImpl::dispatch(int callId, const String& method, std::unique_ptr messageObject) -{ - protocol::HashMap::iterator it = m_dispatchMap.find(method); - if (it == m_dispatchMap.end()) { - if (m_fallThroughForNotFound) - return DispatchResponse::kFallThrough; - reportProtocolError(callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); - return DispatchResponse::kError; - } +bool DispatcherImpl::canDispatch(const String& method) { + return m_dispatchMap.find(method) != m_dispatchMap.end(); +} +void DispatcherImpl::dispatch(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr messageObject) +{ + std::unordered_map::iterator it = m_dispatchMap.find(method); + DCHECK(it != m_dispatchMap.end()); protocol::ErrorSupport errors; - return (this->*(it->second))(callId, std::move(messageObject), &errors); + (this->*(it->second))(callId, method, message, std::move(messageObject), &errors); } {% for command in domain.commands %} @@ -251,8 +269,8 @@ DispatchResponse::Status DispatcherImpl::dispatch(int callId, const String& meth class {{command_name_title}}CallbackImpl : public Backend::{{command_name_title}}Callback, public DispatcherBase::Callback { public: - {{command_name_title}}CallbackImpl(std::unique_ptr backendImpl, int callId, int callbackId) - : DispatcherBase::Callback(std::move(backendImpl), callId, callbackId) { } + {{command_name_title}}CallbackImpl(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message) + : DispatcherBase::Callback(std::move(backendImpl), callId, method, message) { } void sendSuccess( {%- for parameter in command.returns -%} @@ -289,7 +307,7 @@ public: }; {% endif %} -DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::unique_ptr requestMessageObject, ErrorSupport* errors) +void DispatcherImpl::{{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr requestMessageObject, ErrorSupport* errors) { {% if "parameters" in command %} // Prepare input parameters. @@ -312,7 +330,7 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu errors->pop(); if (errors->hasErrors()) { reportProtocolError(callId, DispatchResponse::kInvalidParams, kInvalidParamsString, errors); - return DispatchResponse::kError; + return; } {% endif %} {% if "returns" in command and not protocol.is_async_command(domain.domain, command.name) %} @@ -343,8 +361,10 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu &out_{{parameter.name}} {%- endfor %} {% endif %}); - if (response.status() == DispatchResponse::kFallThrough) - return response.status(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, message); + return; + } {% if "returns" in command %} std::unique_ptr result = DictionaryValue::create(); if (response.status() == DispatchResponse::kSuccess) { @@ -363,10 +383,10 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu if (weak->get()) weak->get()->sendResponse(callId, response); {% endif %} - return response.status(); + return; {% else %} std::unique_ptr weak = weakPtr(); - std::unique_ptr<{{command_name_title}}CallbackImpl> callback(new {{command.name | to_title_case}}CallbackImpl(weakPtr(), callId, nextCallbackId())); + std::unique_ptr<{{command_name_title}}CallbackImpl> callback(new {{command.name | to_title_case}}CallbackImpl(weakPtr(), callId, method, message)); m_backend->{{command.name | to_method_case}}( {%- for property in command.parameters -%} {%- if not loop.first -%}, {% endif -%} @@ -378,7 +398,7 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu {%- endfor -%} {%- if command.parameters -%}, {% endif -%} std::move(callback)); - return (weak->get() && weak->get()->lastCallbackFallThrough()) ? DispatchResponse::kFallThrough : DispatchResponse::kAsync; + return; {% endif %} } {% endfor %} @@ -386,7 +406,7 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu // static void Dispatcher::wire(UberDispatcher* uber, Backend* backend) { - std::unique_ptr dispatcher(new DispatcherImpl(uber->channel(), backend, uber->fallThroughForNotFound())); + std::unique_ptr dispatcher(new DispatcherImpl(uber->channel(), backend)); uber->setupRedirects(dispatcher->redirects()); uber->registerBackend("{{domain.domain}}", std::move(dispatcher)); } diff --git a/tools/inspector_protocol/templates/TypeBuilder_h.template b/tools/inspector_protocol/templates/TypeBuilder_h.template index 744d496026a279..c670d65c46f20d 100644 --- a/tools/inspector_protocol/templates/TypeBuilder_h.template +++ b/tools/inspector_protocol/templates/TypeBuilder_h.template @@ -1,4 +1,4 @@ -// This file is generated +// This file is generated by TypeBuilder_h.template. // Copyright (c) 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -15,7 +15,7 @@ // and include Domain::API version from there. {% for name in domain.dependencies %} {% if protocol.is_imported_dependency(name) %} -#include {{format_include(config.protocol.package, name)}} +#include {{format_domain_include(config.protocol.package, name)}} {% endif %} {% endfor %} {% if protocol.is_exported_domain(domain.domain) %} @@ -46,7 +46,7 @@ using {{type.id}} = {{protocol.resolve_type(type).type}}; namespace {{type.id}}Enum { {% for literal in type.enum %} -{{config.protocol.export_macro}} extern const char* {{ literal | dash_to_camelcase}}; +{{config.protocol.export_macro}} extern const char {{ literal | dash_to_camelcase}}[]; {% endfor %} } // namespace {{type.id}}Enum {% endif %} @@ -100,10 +100,13 @@ public: {% endfor %} std::unique_ptr toValue() const; - String serialize() override { return toValue()->serialize(); } + String serializeToJSON() override { return toValue()->serializeToJSON(); } + std::vector serializeToBinary() override { return toValue()->serializeToBinary(); } + String toJSON() const { return toValue()->toJSONString(); } std::unique_ptr<{{type.id}}> clone() const; {% if protocol.is_exported(domain.domain, type.id) %} {{config.exported.string_out}} toJSONString() const override; + void writeBinary(std::vector* out) const override; {% endif %} template @@ -266,7 +269,8 @@ public: {% endfor %} void flush(); - void sendRawNotification(const String&); + void sendRawNotification(String); + void sendRawNotification(std::vector); private: FrontendChannel* m_frontendChannel; };