diff --git a/src/inspector/node_inspector.gypi b/src/inspector/node_inspector.gypi index 221972a0b5a168..b1e1094a3710bd 100644 --- a/src/inspector/node_inspector.gypi +++ b/src/inspector/node_inspector.gypi @@ -13,32 +13,20 @@ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.h', ], 'node_protocol_files': [ - '<(protocol_tool_path)/lib/Allocator_h.template', - '<(protocol_tool_path)/lib/Array_h.template', '<(protocol_tool_path)/lib/base_string_adapter_cc.template', '<(protocol_tool_path)/lib/base_string_adapter_h.template', - '<(protocol_tool_path)/lib/DispatcherBase_cpp.template', - '<(protocol_tool_path)/lib/DispatcherBase_h.template', - '<(protocol_tool_path)/lib/encoding_cpp.template', - '<(protocol_tool_path)/lib/encoding_h.template', - '<(protocol_tool_path)/lib/ErrorSupport_cpp.template', - '<(protocol_tool_path)/lib/ErrorSupport_h.template', '<(protocol_tool_path)/lib/Forward_h.template', - '<(protocol_tool_path)/lib/FrontendChannel_h.template', - '<(protocol_tool_path)/lib/Maybe_h.template', '<(protocol_tool_path)/lib/Object_cpp.template', '<(protocol_tool_path)/lib/Object_h.template', - '<(protocol_tool_path)/lib/Parser_cpp.template', - '<(protocol_tool_path)/lib/Parser_h.template', '<(protocol_tool_path)/lib/Protocol_cpp.template', + '<(protocol_tool_path)/lib/ValueConversions_cpp.template', '<(protocol_tool_path)/lib/ValueConversions_h.template', '<(protocol_tool_path)/lib/Values_cpp.template', '<(protocol_tool_path)/lib/Values_h.template', '<(protocol_tool_path)/templates/Exported_h.template', '<(protocol_tool_path)/templates/Imported_h.template', '<(protocol_tool_path)/templates/TypeBuilder_cpp.template', - '<(protocol_tool_path)/templates/TypeBuilder_h.template', - '<(protocol_tool_path)/code_generator.py', + '<(protocol_tool_path)/templates/TypeBuilder_h.template' ] }, 'defines': [ @@ -107,6 +95,7 @@ '--jinja_dir', '<@(protocol_tool_path)', '--output_base', '<(SHARED_INTERMEDIATE_DIR)/src/', '--config', 'src/inspector/node_protocol_config.json', + '--inspector_protocol_dir', '<(protocol_tool_path)/', ], 'message': 'Generating node protocol sources from protocol json', }, diff --git a/src/inspector/node_protocol_config.json b/src/inspector/node_protocol_config.json index 4ef37856068093..7061291d4745ee 100644 --- a/src/inspector/node_protocol_config.json +++ b/src/inspector/node_protocol_config.json @@ -1,10 +1,14 @@ { "protocol": { - "path": "node_protocol.json", + "path": "node_protocol.pdl", "package": "src/node/inspector/protocol", "output": "node/inspector/protocol", "namespace": ["node", "inspector", "protocol"] }, + "crdtp": { + "dir": "../tools/inspector_protocol/crdtp", + "namespace": "v8_inspector_protocol_crdtp" + }, "exported": { "package": "include/inspector", "output": "../../include/inspector", diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 2226c432839e18..d54a9875fdac23 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -209,7 +209,7 @@ std::string GetWorkerLabel(node::Environment* env) { } class ChannelImpl final : public v8_inspector::V8Inspector::Channel, - public protocol::FrontendChannel { + public v8_inspector_protocol_crdtp::FrontendChannel { public: explicit ChannelImpl(Environment* env, const std::unique_ptr& inspector, @@ -220,7 +220,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, : delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown), retaining_context_(false) { session_ = inspector->connect(CONTEXT_GROUP_ID, this, StringView()); - node_dispatcher_ = std::make_unique(this); + node_dispatcher_ = std::make_unique(this); tracing_agent_ = std::make_unique(env, main_thread_); tracing_agent_->Wire(node_dispatcher_.get()); @@ -300,7 +300,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, sendMessageToFrontend(Utf8ToStringView(message)->string()); } - using Serializable = protocol::Serializable; + using Serializable = v8_inspector_protocol_crdtp::Serializable; void sendProtocolResponse(int callId, std::unique_ptr message) override { @@ -322,7 +322,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, std::unique_ptr worker_agent_; std::unique_ptr delegate_; std::unique_ptr session_; - std::unique_ptr node_dispatcher_; + std::unique_ptr node_dispatcher_; bool prevent_shutdown_; bool retaining_context_; }; diff --git a/tools/inspector_protocol/BUILD.gn b/tools/inspector_protocol/BUILD.gn index 974471bf2718d5..880b651c095f0f 100644 --- a/tools/inspector_protocol/BUILD.gn +++ b/tools/inspector_protocol/BUILD.gn @@ -2,29 +2,83 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -static_library("encoding") { +import("../../gni/v8.gni") + +config("crdtp_config") { + visibility = [ "../../src/inspector:*", ":*" ] + configs = [ "../../:internal_config" ] + include_dirs = [ "../../include" ] +} + +v8_source_set("crdtp") { sources = [ - "encoding/encoding.cc", - "encoding/encoding.h", + "crdtp/cbor.cc", + "crdtp/cbor.h", + "crdtp/dispatch.cc", + "crdtp/dispatch.h", + "crdtp/error_support.cc", + "crdtp/error_support.h", + "crdtp/export.h", + "crdtp/find_by_first.h", + "crdtp/json.cc", + "crdtp/json.h", + "crdtp/maybe.h", + "crdtp/parser_handler.h", + "crdtp/protocol_core.cc", + "crdtp/protocol_core.h", + "crdtp/serializable.cc", + "crdtp/serializable.h", + "crdtp/serializer_traits.h", + "crdtp/span.cc", + "crdtp/span.h", + "crdtp/status.cc", + "crdtp/status.h", ] + configs = [ ":crdtp_config" ] + deps = [ ":crdtp_platform" ] } -# encoding_test is part of the unittests, defined in -# test/unittests/BUILD.gn. - -import("../../gni/v8.gni") +# A small adapter library which only :crdtp may depend on. +v8_source_set("crdtp_platform") { + visibility = [ ":crdtp" ] + sources = [ + "crdtp/json_platform.h", + "crdtp/json_platform_v8.cc", + ] + public_deps = [ "../..:v8_libbase" ] + configs = [ ":crdtp_config" ] +} -v8_source_set("encoding_test") { +# These tests are linked into test/unittests. +v8_source_set("crdtp_test") { sources = [ - "encoding/encoding_test.cc", - "encoding/encoding_test_helper.h", + "crdtp/cbor_test.cc", + "crdtp/dispatch_test.cc", + "crdtp/error_support_test.cc", + "crdtp/find_by_first_test.cc", + "crdtp/glue_test.cc", + "crdtp/json_test.cc", + "crdtp/serializable_test.cc", + "crdtp/serializer_traits_test.cc", + "crdtp/span_test.cc", + "crdtp/status_test.cc", + "crdtp/status_test_support.cc", + "crdtp/status_test_support.h", ] - configs = [ - "../..:external_config", - "../..:internal_config_base", + configs = [ ":crdtp_config" ] + deps = [ ":crdtp_test_platform" ] + testonly = true +} + +# A small adapter library which only :crdtp_test may depend on. +v8_source_set("crdtp_test_platform") { + sources = [ + "crdtp/test_platform.h", + "crdtp/test_platform_v8.cc", ] - deps = [ - ":encoding", + configs = [ ":crdtp_config" ] + public_deps = [ + ":crdtp", "../..:v8_libbase", "../../src/inspector:inspector_string_conversions", "//testing/gmock", diff --git a/tools/inspector_protocol/OWNERS b/tools/inspector_protocol/OWNERS index 8d0b6d90cb233b..6fa5bf123ddfde 100644 --- a/tools/inspector_protocol/OWNERS +++ b/tools/inspector_protocol/OWNERS @@ -6,3 +6,4 @@ dgozman@chromium.org kozyatinskiy@chromium.org pfeldman@chromium.org yangguo@chromium.org +johannes@chromium.org diff --git a/tools/inspector_protocol/README.md b/tools/inspector_protocol/README.md index da3f93f3f3b49e..d15f719fdda32c 100644 --- a/tools/inspector_protocol/README.md +++ b/tools/inspector_protocol/README.md @@ -14,20 +14,5 @@ https://cs.chromium.org/chromium/src/v8/third_party/inspector_protocol/ See also [Contributing to Chrome Devtools Protocol](https://docs.google.com/document/d/1c-COD2kaK__5iMM5SEx-PzNA7HFmgttcYfOHHX0HaOM/edit). -We're working on enabling standalone builds for parts of this package for -testing and development, please feel free to ignore this for now. -But, if you're familiar with -[Chromium's development process](https://www.chromium.org/developers/contributing-code) -and have the depot_tools installed, you may use these commands -to fetch the package (and dependencies) and build and run the tests: - - fetch inspector_protocol - cd src - gn gen out/Release - ninja -C out/Release json_parser_test - out/Release/json_parser_test - -You'll probably also need to install g++, since Clang uses this to find the -standard C++ headers. E.g., - - sudo apt-get install g++-8 +To build and run the tests of the crdtp library, see +[CRDTP - Chrome DevTools Protocol](crdtp/README.md). diff --git a/tools/inspector_protocol/README.node b/tools/inspector_protocol/README.node index a8380198576d46..97bc1c34147364 100644 --- a/tools/inspector_protocol/README.node +++ 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: 0aafd2876f7485db7b07c513c0457b7cbbbe3304 +Revision: 94298cef795ec994106bdaff002c41182911b767 License: BSD License File: LICENSE Security Critical: no diff --git a/tools/inspector_protocol/code_generator.py b/tools/inspector_protocol/code_generator.py index c1f78dc7492d78..92ca530e056118 100755 --- a/tools/inspector_protocol/code_generator.py +++ b/tools/inspector_protocol/code_generator.py @@ -11,18 +11,18 @@ import re import copy try: - import json + import json except ImportError: - import simplejson as json + import simplejson as json import pdl try: - unicode + unicode except NameError: - # Define unicode for Py3 - def unicode(s, *_): - return s + # Define unicode for Py3 + def unicode(s, *_): + return s # Path handling for libraries and templates # Paths have to be normalized because Jinja uses the exact template path to @@ -35,656 +35,668 @@ def unicode(s, *_): module_path, module_filename = os.path.split(os.path.realpath(__file__)) def read_config(): - # pylint: disable=W0703 - def json_to_object(data, output_base): - def json_object_hook(object_dict): - items = [(k, os.path.join(output_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 = list(zip(*items)) - # 'async' is a python 3.7 keyword. Don't use namedtuple(rename=True) - # because that only renames it in python 3 but not python 2. - keys = tuple('async_' if k == 'async' else k for k in keys) - 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 range(len(keys)): - if hasattr(values[i], "_fields"): - values[i] = init_defaults(values[i], path + "." + keys[i], defaults) - for optional in defaults: - if optional.find(path + ".") != 0: - continue - optional_key = optional[len(path) + 1:] - if optional_key.find(".") == -1 and optional_key not in keys: - keys.append(optional_key) - values.append(defaults[optional]) - return collections.namedtuple('X', keys)(*values) - - try: - cmdline_parser = argparse.ArgumentParser() - cmdline_parser.add_argument("--output_base", type=unicode, required=True) - cmdline_parser.add_argument("--jinja_dir", type=unicode, required=True) - cmdline_parser.add_argument("--config", type=unicode, required=True) - cmdline_parser.add_argument("--config_value", default=[], action="append") - arg_options = cmdline_parser.parse_args() - jinja_dir = arg_options.jinja_dir - output_base = arg_options.output_base - config_file = arg_options.config - config_values = arg_options.config_value - except Exception: - # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html - exc = sys.exc_info()[1] - sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc) - exit(1) - - try: - config_json_file = open(config_file, "r") - config_json_string = config_json_file.read() - config_partial = json_to_object(config_json_string, output_base) - config_json_file.close() - defaults = { - ".use_snake_file_names": False, - ".use_title_case_methods": False, - ".imported": False, - ".imported.export_macro": "", - ".imported.export_header": False, - ".imported.header": False, - ".imported.package": False, - ".imported.options": False, - ".protocol.export_macro": "", - ".protocol.export_header": False, - ".protocol.options": False, - ".protocol.file_name_prefix": "", - ".exported": False, - ".exported.export_macro": "", - ".exported.export_header": False, - ".lib": False, - ".lib.export_macro": "", - ".lib.export_header": False, - } - for key_value in config_values: - parts = key_value.split("=") - if len(parts) == 2: - defaults["." + parts[0]] = parts[1] - return (jinja_dir, config_file, init_defaults(config_partial, "", defaults)) - except Exception: - # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html - exc = sys.exc_info()[1] - sys.stderr.write("Failed to parse config file: %s\n\n" % exc) - exit(1) + # pylint: disable=W0703 + 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 = list(zip(*items)) + # 'async' is a keyword since Python 3.7. + # Avoid namedtuple(rename=True) for compatibility with Python 2.X. + keys = tuple('async_' if k == 'async' else k for k in keys) + 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 range(len(keys)): + if hasattr(values[i], "_fields"): + values[i] = init_defaults(values[i], path + "." + keys[i], defaults) + for optional in defaults: + if optional.find(path + ".") != 0: + continue + optional_key = optional[len(path) + 1:] + if optional_key.find(".") == -1 and optional_key not in keys: + keys.append(optional_key) + values.append(defaults[optional]) + return collections.namedtuple('X', keys)(*values) + + try: + cmdline_parser = argparse.ArgumentParser() + cmdline_parser.add_argument("--output_base", type=unicode, required=True) + cmdline_parser.add_argument("--jinja_dir", type=unicode, required=True) + cmdline_parser.add_argument("--config", type=unicode, required=True) + cmdline_parser.add_argument("--config_value", default=[], action="append") + cmdline_parser.add_argument( + "--inspector_protocol_dir", type=unicode, required=True, + help=("directory with code_generator.py and C++ encoding / binding " + "libraries, relative to the root of the source tree.")) + arg_options = cmdline_parser.parse_args() + jinja_dir = arg_options.jinja_dir + output_base = arg_options.output_base + config_file = arg_options.config + config_base = os.path.dirname(config_file) + config_values = arg_options.config_value + inspector_protocol_dir = arg_options.inspector_protocol_dir.lstrip('/') + except Exception: + # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html + exc = sys.exc_info()[1] + sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc) + exit(1) + + try: + config_json_file = open(config_file, "r") + config_json_string = config_json_file.read() + config_partial = json_to_object(config_json_string, output_base, + config_base) + config_json_file.close() + defaults = { + ".use_snake_file_names": False, + ".use_title_case_methods": False, + ".imported": False, + ".imported.export_macro": "", + ".imported.export_header": False, + ".imported.header": False, + ".imported.package": False, + ".imported.options": False, + ".protocol.export_macro": "", + ".protocol.export_header": False, + ".protocol.options": False, + ".protocol.file_name_prefix": "", + ".exported": False, + ".exported.export_macro": "", + ".exported.export_header": False, + ".lib": False, + ".lib.export_macro": "", + ".lib.export_header": False, + ".crdtp": False, + ".crdtp.dir": os.path.join(inspector_protocol_dir, "crdtp"), + ".crdtp.namespace": "crdtp", + } + for key_value in config_values: + parts = key_value.split("=") + if len(parts) == 2: + defaults["." + parts[0]] = parts[1] + return (jinja_dir, config_file, init_defaults(config_partial, "", defaults)) + except Exception: + # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html + exc = sys.exc_info()[1] + sys.stderr.write("Failed to parse config file: %s\n\n" % exc) + exit(1) # ---- Begin of utilities exposed to generator ---- def to_title_case(name): - return name[:1].upper() + name[1:] + return name[:1].upper() + name[1:] def dash_to_camelcase(word): - prefix = "" - if word[0] == "-": - prefix = "Negative" - word = word[1:] - return prefix + "".join(to_title_case(x) or "-" for x in word.split("-")) + prefix = "" + if word[0] == "-": + prefix = "Negative" + word = word[1:] + return prefix + "".join(to_title_case(x) or "-" for x in word.split("-")) def to_snake_case(name): - return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxsize).lower() + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxsize).lower() def to_method_case(config, name): - if config.use_title_case_methods: - return to_title_case(name) - return name + if config.use_title_case_methods: + return to_title_case(name) + return name def join_arrays(dict, keys): - result = [] - for key in keys: - if key in dict: - result += dict[key] - return result + result = [] + for key in keys: + if key in dict: + result += dict[key] + return result def format_include(config, header, file_name=None): - if file_name is not None: - header = header + "/" + file_name + ".h" - header = "\"" + header + "\"" if header[0] not in "<\"" else header - if config.use_snake_file_names: - header = to_snake_case(header) - return header + if file_name is not None: + header = header + "/" + file_name + ".h" + header = "\"" + header + "\"" if header[0] not in "<\"" else header + if config.use_snake_file_names: + header = to_snake_case(header) + return header def format_domain_include(config, header, file_name): - return format_include(config, header, config.protocol.file_name_prefix + 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") - return file_name + if config.use_snake_file_names: + return to_snake_case(file_name).replace(".cpp", ".cc") + return file_name # ---- End of utilities exposed to generator ---- def initialize_jinja_env(jinja_dir, cache_dir, config): - # pylint: disable=F0401 - sys.path.insert(1, os.path.abspath(jinja_dir)) - import jinja2 - - jinja_env = jinja2.Environment( - loader=jinja2.FileSystemLoader(module_path), - # Bytecode cache is not concurrency-safe unless pre-cached: - # if pre-cached this is read-only, but writing creates a race condition. - bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), - keep_trailing_newline=True, # newline-terminate generated files - lstrip_blocks=True, # so can indent control flow tags - trim_blocks=True) - jinja_env.filters.update({"to_title_case": to_title_case, "dash_to_camelcase": dash_to_camelcase, "to_method_case": functools.partial(to_method_case, config)}) - jinja_env.add_extension("jinja2.ext.loopcontrols") - return jinja_env + # pylint: disable=F0401 + sys.path.insert(1, os.path.abspath(jinja_dir)) + import jinja2 + + jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(module_path), + # Bytecode cache is not concurrency-safe unless pre-cached: + # if pre-cached this is read-only, but writing creates a race condition. + bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), + keep_trailing_newline=True, # newline-terminate generated files + lstrip_blocks=True, # so can indent control flow tags + trim_blocks=True) + jinja_env.filters.update({ + "to_title_case": to_title_case, + "dash_to_camelcase": dash_to_camelcase, + "to_method_case": functools.partial(to_method_case, config)}) + jinja_env.add_extension("jinja2.ext.loopcontrols") + return jinja_env def create_imported_type_definition(domain_name, type, imported_namespace): - # pylint: disable=W0622 - return { - "return_type": "std::unique_ptr<%s::%s::API::%s>" % (imported_namespace, domain_name, type["id"]), - "pass_type": "std::unique_ptr<%s::%s::API::%s>" % (imported_namespace, domain_name, type["id"]), - "to_raw_type": "%s.get()", - "to_pass_type": "std::move(%s)", - "to_rvalue": "std::move(%s)", - "type": "std::unique_ptr<%s::%s::API::%s>" % (imported_namespace, domain_name, type["id"]), - "raw_type": "%s::%s::API::%s" % (imported_namespace, domain_name, type["id"]), - "raw_pass_type": "%s::%s::API::%s*" % (imported_namespace, domain_name, type["id"]), - "raw_return_type": "%s::%s::API::%s*" % (imported_namespace, domain_name, type["id"]), - } + # pylint: disable=W0622 + return { + "return_type": "std::unique_ptr<%s::%s::API::%s>" % ( + imported_namespace, domain_name, type["id"]), + "pass_type": "std::unique_ptr<%s::%s::API::%s>" % ( + imported_namespace, domain_name, type["id"]), + "to_raw_type": "%s.get()", + "to_pass_type": "std::move(%s)", + "to_rvalue": "std::move(%s)", + "type": "std::unique_ptr<%s::%s::API::%s>" % ( + imported_namespace, domain_name, type["id"]), + "raw_type": "%s::%s::API::%s" % ( + imported_namespace, domain_name, type["id"]), + "raw_pass_type": "%s::%s::API::%s*" % ( + imported_namespace, domain_name, type["id"]), + "raw_return_type": "%s::%s::API::%s*" % ( + imported_namespace, domain_name, type["id"]), + } def create_user_type_definition(domain_name, type): - # pylint: disable=W0622 - return { - "return_type": "std::unique_ptr" % (domain_name, type["id"]), - "pass_type": "std::unique_ptr" % (domain_name, type["id"]), - "to_raw_type": "%s.get()", - "to_pass_type": "std::move(%s)", - "to_rvalue": "std::move(%s)", - "type": "std::unique_ptr" % (domain_name, type["id"]), - "raw_type": "protocol::%s::%s" % (domain_name, type["id"]), - "raw_pass_type": "protocol::%s::%s*" % (domain_name, type["id"]), - "raw_return_type": "protocol::%s::%s*" % (domain_name, type["id"]), - } + # pylint: disable=W0622 + return { + "return_type": "std::unique_ptr" % ( + domain_name, type["id"]), + "pass_type": "std::unique_ptr" % ( + domain_name, type["id"]), + "to_raw_type": "%s.get()", + "to_pass_type": "std::move(%s)", + "to_rvalue": "std::move(%s)", + "type": "std::unique_ptr" % (domain_name, type["id"]), + "raw_type": "protocol::%s::%s" % (domain_name, type["id"]), + "raw_pass_type": "protocol::%s::%s*" % (domain_name, type["id"]), + "raw_return_type": "protocol::%s::%s*" % (domain_name, type["id"]), + } def create_object_type_definition(): - # pylint: disable=W0622 - return { - "return_type": "std::unique_ptr", - "pass_type": "std::unique_ptr", - "to_raw_type": "%s.get()", - "to_pass_type": "std::move(%s)", - "to_rvalue": "std::move(%s)", - "type": "std::unique_ptr", - "raw_type": "protocol::DictionaryValue", - "raw_pass_type": "protocol::DictionaryValue*", - "raw_return_type": "protocol::DictionaryValue*", - } + # pylint: disable=W0622 + return { + "return_type": "std::unique_ptr", + "pass_type": "std::unique_ptr", + "to_raw_type": "%s.get()", + "to_pass_type": "std::move(%s)", + "to_rvalue": "std::move(%s)", + "type": "std::unique_ptr", + "raw_type": "protocol::DictionaryValue", + "raw_pass_type": "protocol::DictionaryValue*", + "raw_return_type": "protocol::DictionaryValue*", + } def create_any_type_definition(): - # pylint: disable=W0622 - return { - "return_type": "std::unique_ptr", - "pass_type": "std::unique_ptr", - "to_raw_type": "%s.get()", - "to_pass_type": "std::move(%s)", - "to_rvalue": "std::move(%s)", - "type": "std::unique_ptr", - "raw_type": "protocol::Value", - "raw_pass_type": "protocol::Value*", - "raw_return_type": "protocol::Value*", - } + # pylint: disable=W0622 + return { + "return_type": "std::unique_ptr", + "pass_type": "std::unique_ptr", + "to_raw_type": "%s.get()", + "to_pass_type": "std::move(%s)", + "to_rvalue": "std::move(%s)", + "type": "std::unique_ptr", + "raw_type": "protocol::Value", + "raw_pass_type": "protocol::Value*", + "raw_return_type": "protocol::Value*", + } def create_string_type_definition(): - # pylint: disable=W0622 - return { - "return_type": "String", - "pass_type": "const String&", - "to_pass_type": "%s", - "to_raw_type": "%s", - "to_rvalue": "%s", - "type": "String", - "raw_type": "String", - "raw_pass_type": "const String&", - "raw_return_type": "String", - } + # pylint: disable=W0622 + return { + "return_type": "String", + "pass_type": "const String&", + "to_pass_type": "%s", + "to_raw_type": "%s", + "to_rvalue": "%s", + "type": "String", + "raw_type": "String", + "raw_pass_type": "const String&", + "raw_return_type": "String", + } 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", - } + # 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 = { - "number": "double", - "integer": "int", - "boolean": "bool" - } - defaults = { - "number": "0", - "integer": "0", - "boolean": "false" - } - jsontypes = { - "number": "TypeDouble", - "integer": "TypeInteger", - "boolean": "TypeBoolean", - } - return { - "return_type": typedefs[type], - "pass_type": typedefs[type], - "to_pass_type": "%s", - "to_raw_type": "%s", - "to_rvalue": "%s", - "type": typedefs[type], - "raw_type": typedefs[type], - "raw_pass_type": typedefs[type], - "raw_return_type": typedefs[type], - "default_value": defaults[type] - } + # pylint: disable=W0622 + typedefs = { + "number": "double", + "integer": "int", + "boolean": "bool" + } + defaults = { + "number": "0", + "integer": "0", + "boolean": "false" + } + jsontypes = { + "number": "TypeDouble", + "integer": "TypeInteger", + "boolean": "TypeBoolean", + } + return { + "return_type": typedefs[type], + "pass_type": typedefs[type], + "to_pass_type": "%s", + "to_raw_type": "%s", + "to_rvalue": "%s", + "type": typedefs[type], + "raw_type": typedefs[type], + "raw_pass_type": typedefs[type], + "raw_return_type": typedefs[type], + "default_value": defaults[type] + } def wrap_array_definition(type): - # pylint: disable=W0622 - return { - "return_type": "std::unique_ptr>" % type["raw_type"], - "pass_type": "std::unique_ptr>" % type["raw_type"], - "to_raw_type": "%s.get()", - "to_pass_type": "std::move(%s)", - "to_rvalue": "std::move(%s)", - "type": "std::unique_ptr>" % type["raw_type"], - "raw_type": "protocol::Array<%s>" % type["raw_type"], - "raw_pass_type": "protocol::Array<%s>*" % type["raw_type"], - "raw_return_type": "protocol::Array<%s>*" % type["raw_type"], - "out_type": "protocol::Array<%s>&" % type["raw_type"], - } + # pylint: disable=W0622 + return { + "return_type": "std::unique_ptr>" % type["raw_type"], + "pass_type": "std::unique_ptr>" % type["raw_type"], + "to_raw_type": "%s.get()", + "to_pass_type": "std::move(%s)", + "to_rvalue": "std::move(%s)", + "type": "std::unique_ptr>" % type["raw_type"], + "raw_type": "protocol::Array<%s>" % type["raw_type"], + "raw_pass_type": "protocol::Array<%s>*" % type["raw_type"], + "raw_return_type": "protocol::Array<%s>*" % type["raw_type"], + "out_type": "protocol::Array<%s>&" % type["raw_type"], + } class Protocol(object): - def __init__(self, config): - self.config = config - self.json_api = {"domains": []} - self.imported_domains = [] - self.exported_domains = [] - self.generate_domains = self.read_protocol_file(config.protocol.path) - - if config.protocol.options: - self.generate_domains = [rule.domain for rule in config.protocol.options] - self.exported_domains = [rule.domain for rule in config.protocol.options if hasattr(rule, "exported")] - - if config.imported: - self.imported_domains = self.read_protocol_file(config.imported.path) - if config.imported.options: - self.imported_domains = [rule.domain for rule in config.imported.options] - - self.patch_full_qualified_refs() - self.create_notification_types() - self.create_type_definitions() - self.generate_used_types() - - - def read_protocol_file(self, file_name): - input_file = open(file_name, "r") - parsed_json = pdl.loads(input_file.read(), file_name) - input_file.close() - version = parsed_json["version"]["major"] + "." + parsed_json["version"]["minor"] - domains = [] - for domain in parsed_json["domains"]: - domains.append(domain["domain"]) - domain["version"] = version - self.json_api["domains"] += parsed_json["domains"] - return domains - - - def patch_full_qualified_refs(self): - def patch_full_qualified_refs_in_domain(json, domain_name): - if isinstance(json, list): - for item in json: - patch_full_qualified_refs_in_domain(item, domain_name) - if not isinstance(json, dict): - return - for key in json: - if key == "type" and json[key] == "string": - json[key] = domain_name + ".string" - if key != "$ref": - patch_full_qualified_refs_in_domain(json[key], domain_name) - continue - if json["$ref"].find(".") == -1: - json["$ref"] = domain_name + "." + json["$ref"] - return - - for domain in self.json_api["domains"]: - patch_full_qualified_refs_in_domain(domain, domain["domain"]) - - - def all_references(self, json): - refs = set() - if isinstance(json, list): - for item in json: - refs |= self.all_references(item) - if not isinstance(json, dict): - return refs - for key in json: - if key != "$ref": - refs |= self.all_references(json[key]) - else: - refs.add(json["$ref"]) - return refs - - def generate_used_types(self): - all_refs = set() - for domain in self.json_api["domains"]: - domain_name = domain["domain"] - if "commands" in domain: - for command in domain["commands"]: - if self.generate_command(domain_name, command["name"]): - all_refs |= self.all_references(command) - if "events" in domain: - for event in domain["events"]: - if self.generate_event(domain_name, event["name"]): - all_refs |= self.all_references(event) - all_refs.add(domain_name + "." + to_title_case(event["name"]) + "Notification") - - dependencies = self.generate_type_dependencies() - queue = set(all_refs) - while len(queue): - ref = queue.pop() - if ref in dependencies: - queue |= dependencies[ref] - all_refs - all_refs |= dependencies[ref] - self.used_types = all_refs - - - def generate_type_dependencies(self): - dependencies = dict() - domains_with_types = (x for x in self.json_api["domains"] if "types" in x) - for domain in domains_with_types: - domain_name = domain["domain"] - for type in domain["types"]: - related_types = self.all_references(type) - if len(related_types): - dependencies[domain_name + "." + type["id"]] = related_types - return dependencies - - - def create_notification_types(self): - for domain in self.json_api["domains"]: - if "events" in domain: - for event in domain["events"]: - event_type = dict() - event_type["description"] = "Wrapper for notification params" - event_type["type"] = "object" - event_type["id"] = to_title_case(event["name"]) + "Notification" - if "parameters" in event: - event_type["properties"] = copy.deepcopy(event["parameters"]) - if "types" not in domain: - domain["types"] = list() - domain["types"].append(event_type) - - - def create_type_definitions(self): - imported_namespace = "::".join(self.config.imported.namespace) if self.config.imported else "" - self.type_definitions = {} - self.type_definitions["number"] = create_primitive_type_definition("number") - self.type_definitions["integer"] = create_primitive_type_definition("integer") - 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"]: - type_name = domain["domain"] + "." + type["id"] - if type["type"] == "object" and domain["domain"] in self.imported_domains: - self.type_definitions[type_name] = create_imported_type_definition(domain["domain"], type, imported_namespace) - elif type["type"] == "object": - self.type_definitions[type_name] = create_user_type_definition(domain["domain"], type) - elif type["type"] == "array": - 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"]) - - - def check_options(self, options, domain, name, include_attr, exclude_attr, default): - for rule in options: - if rule.domain != domain: - continue - if include_attr and hasattr(rule, include_attr): - return name in getattr(rule, include_attr) - if exclude_attr and hasattr(rule, exclude_attr): - return name not in getattr(rule, exclude_attr) - return default - return False - - - # ---- Begin of methods exposed to generator - - - def type_definition(self, name): - return self.type_definitions[name] - - - def resolve_type(self, prop): - if "$ref" in prop: - return self.type_definitions[prop["$ref"]] - if prop["type"] == "array": - return wrap_array_definition(self.resolve_type(prop["items"])) - return self.type_definitions[prop["type"]] + def __init__(self, config): + self.config = config + self.json_api = {"domains": []} + self.imported_domains = [] + self.exported_domains = [] + self.generate_domains = self.read_protocol_file(config.protocol.path) - def generate_command(self, domain, command): - if not self.config.protocol.options: - return domain in self.generate_domains - return self.check_options(self.config.protocol.options, domain, command, "include", "exclude", True) + if config.protocol.options: + self.generate_domains = [rule.domain for rule in config.protocol.options] + self.exported_domains = [rule.domain for rule in config.protocol.options + if hasattr(rule, "exported")] - - def generate_event(self, domain, event): - if not self.config.protocol.options: - return domain in self.generate_domains - return self.check_options(self.config.protocol.options, domain, event, "include_events", "exclude_events", True) - - - def generate_type(self, domain, typename): - return domain + "." + typename in self.used_types - - - def is_async_command(self, domain, command): - if not self.config.protocol.options: - return False - return self.check_options(self.config.protocol.options, domain, command, "async_", None, False) - - - def is_exported(self, domain, name): - if not self.config.protocol.options: - return False - return self.check_options(self.config.protocol.options, domain, name, "exported", None, False) - - - def is_imported(self, domain, name): - if not self.config.imported: - return False - if not self.config.imported.options: - return domain in self.imported_domains - return self.check_options(self.config.imported.options, domain, name, "imported", None, False) - - - def is_exported_domain(self, domain): - return domain in self.exported_domains - - - def generate_disable(self, domain): - if "commands" not in domain: - return True + if config.imported: + self.imported_domains = self.read_protocol_file(config.imported.path) + if config.imported.options: + self.imported_domains = [rule.domain + for rule in config.imported.options] + + self.patch_full_qualified_refs() + self.create_type_definitions() + self.generate_used_types() + + def read_protocol_file(self, file_name): + input_file = open(file_name, "r") + parsed_json = pdl.loads(input_file.read(), file_name) + input_file.close() + version = '%s.%s' % (parsed_json["version"]["major"], + parsed_json["version"]["minor"]) + domains = [] + for domain in parsed_json["domains"]: + domains.append(domain["domain"]) + domain["version"] = version + self.json_api["domains"] += parsed_json["domains"] + return domains + + def patch_full_qualified_refs(self): + def patch_full_qualified_refs_in_domain(json, domain_name): + if isinstance(json, list): + for item in json: + patch_full_qualified_refs_in_domain(item, domain_name) + if not isinstance(json, dict): + return + for key in json: + if key == "type" and json[key] == "string": + json[key] = domain_name + ".string" + if key != "$ref": + patch_full_qualified_refs_in_domain(json[key], domain_name) + continue + if json["$ref"].find(".") == -1: + json["$ref"] = domain_name + "." + json["$ref"] + return + + for domain in self.json_api["domains"]: + patch_full_qualified_refs_in_domain(domain, domain["domain"]) + + def all_references(self, json): + refs = set() + if isinstance(json, list): + for item in json: + refs |= self.all_references(item) + if not isinstance(json, dict): + return refs + for key in json: + if key != "$ref": + refs |= self.all_references(json[key]) + else: + refs.add(json["$ref"]) + return refs + + def generate_used_types(self): + all_refs = set() + for domain in self.json_api["domains"]: + domain_name = domain["domain"] + if "commands" in domain: for command in domain["commands"]: - if command["name"] == "disable" and self.generate_command(domain["domain"], "disable"): - return False - return True - + if self.generate_command(domain_name, command["name"]): + all_refs |= self.all_references(command) + if "events" in domain: + for event in domain["events"]: + if self.generate_event(domain_name, event["name"]): + all_refs |= self.all_references(event) + + dependencies = self.generate_type_dependencies() + queue = set(all_refs) + while len(queue): + ref = queue.pop() + if ref in dependencies: + queue |= dependencies[ref] - all_refs + all_refs |= dependencies[ref] + self.used_types = all_refs + + def generate_type_dependencies(self): + dependencies = dict() + domains_with_types = (x for x in self.json_api["domains"] if "types" in x) + for domain in domains_with_types: + domain_name = domain["domain"] + for type in domain["types"]: + related_types = self.all_references(type) + if len(related_types): + dependencies[domain_name + "." + type["id"]] = related_types + return dependencies + + def create_type_definitions(self): + imported_namespace = "" + if self.config.imported: + imported_namespace = "::".join(self.config.imported.namespace) + self.type_definitions = {} + self.type_definitions["number"] = create_primitive_type_definition("number") + self.type_definitions["integer"] = create_primitive_type_definition("integer") + 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"]: + type_name = domain["domain"] + "." + type["id"] + if type["type"] == "object" and domain["domain"] in self.imported_domains: + self.type_definitions[type_name] = create_imported_type_definition( + domain["domain"], type, imported_namespace) + elif type["type"] == "object": + self.type_definitions[type_name] = create_user_type_definition( + domain["domain"], type) + elif type["type"] == "array": + 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"]) + + def check_options(self, options, domain, name, include_attr, exclude_attr, + default): + for rule in options: + if rule.domain != domain: + continue + if include_attr and hasattr(rule, include_attr): + return name in getattr(rule, include_attr) + if exclude_attr and hasattr(rule, exclude_attr): + return name not in getattr(rule, exclude_attr) + return default + return False + + + # ---- Begin of methods exposed to generator + + def type_definition(self, name): + return self.type_definitions[name] + + def resolve_type(self, prop): + if "$ref" in prop: + return self.type_definitions[prop["$ref"]] + if prop["type"] == "array": + return wrap_array_definition(self.resolve_type(prop["items"])) + return self.type_definitions[prop["type"]] + + def generate_command(self, domain, command): + if not self.config.protocol.options: + return domain in self.generate_domains + return self.check_options(self.config.protocol.options, domain, command, + "include", "exclude", True) + + def generate_event(self, domain, event): + if not self.config.protocol.options: + return domain in self.generate_domains + return self.check_options(self.config.protocol.options, domain, event, + "include_events", "exclude_events", True) + + def generate_type(self, domain, typename): + return domain + "." + typename in self.used_types + + def is_async_command(self, domain, command): + if not self.config.protocol.options: + return False + return self.check_options(self.config.protocol.options, domain, command, + "async_", None, False) + + def is_exported(self, domain, name): + if not self.config.protocol.options: + return False + return self.check_options(self.config.protocol.options, domain, name, + "exported", None, False) + + def is_imported(self, domain, name): + if not self.config.imported: + return False + if not self.config.imported.options: + return domain in self.imported_domains + return self.check_options(self.config.imported.options, domain, name, + "imported", None, False) + + def is_exported_domain(self, domain): + return domain in self.exported_domains + + def generate_disable(self, domain): + if "commands" not in domain: + return True + for command in domain["commands"]: + if command["name"] == "disable" and self.generate_command( + domain["domain"], "disable"): + return False + return True - def is_imported_dependency(self, domain): - return domain in self.generate_domains or domain in self.imported_domains + def is_imported_dependency(self, domain): + return domain in self.generate_domains or domain in self.imported_domains def main(): - jinja_dir, config_file, config = read_config() - - protocol = Protocol(config) - - if not config.exported and len(protocol.exported_domains): - sys.stderr.write("Domains [%s] are exported, but config is missing export entry\n\n" % ", ".join(protocol.exported_domains)) - exit(1) + jinja_dir, config_file, config = read_config() + + protocol = Protocol(config) + + if not config.exported and len(protocol.exported_domains): + sys.stderr.write(("Domains [%s] are exported, but config is missing export " + "entry\n\n") % ", ".join(protocol.exported_domains)) + exit(1) + + if not os.path.exists(config.protocol.output): + os.mkdir(config.protocol.output) + if len(protocol.exported_domains) and not os.path.exists( + config.exported.output): + os.mkdir(config.exported.output) + jinja_env = initialize_jinja_env(jinja_dir, config.protocol.output, config) + + inputs = [] + inputs.append(__file__) + inputs.append(config_file) + inputs.append(config.protocol.path) + if config.imported: + inputs.append(config.imported.path) + templates_dir = os.path.join(module_path, "templates") + inputs.append(os.path.join(templates_dir, "TypeBuilder_h.template")) + inputs.append(os.path.join(templates_dir, "TypeBuilder_cpp.template")) + inputs.append(os.path.join(templates_dir, "Exported_h.template")) + inputs.append(os.path.join(templates_dir, "Imported_h.template")) + + h_template = jinja_env.get_template("templates/TypeBuilder_h.template") + cpp_template = jinja_env.get_template("templates/TypeBuilder_cpp.template") + exported_template = jinja_env.get_template("templates/Exported_h.template") + imported_template = jinja_env.get_template("templates/Imported_h.template") + + outputs = dict() + + 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 not os.path.exists(config.protocol.output): - os.mkdir(config.protocol.output) - if len(protocol.exported_domains) and not os.path.exists(config.exported.output): - os.mkdir(config.exported.output) - jinja_env = initialize_jinja_env(jinja_dir, config.protocol.output, config) + if domain["domain"] in protocol.generate_domains: + 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, 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, file_name + ".h"))] = imported_template.render( + template_context) + + if config.lib: + template_context = { + "config": config, + "format_include": functools.partial(format_include, config), + } - inputs = [] - inputs.append(__file__) - inputs.append(config_file) - inputs.append(config.protocol.path) - if config.imported: - inputs.append(config.imported.path) - templates_dir = os.path.join(module_path, "templates") - inputs.append(os.path.join(templates_dir, "TypeBuilder_h.template")) - inputs.append(os.path.join(templates_dir, "TypeBuilder_cpp.template")) - inputs.append(os.path.join(templates_dir, "Exported_h.template")) - inputs.append(os.path.join(templates_dir, "Imported_h.template")) - - h_template = jinja_env.get_template("templates/TypeBuilder_h.template") - cpp_template = jinja_env.get_template("templates/TypeBuilder_cpp.template") - exported_template = jinja_env.get_template("templates/Exported_h.template") - imported_template = jinja_env.get_template("templates/Imported_h.template") - - outputs = dict() - - 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, 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, 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, file_name + ".h"))] = imported_template.render(template_context) - - if config.lib: - template_context = { - "config": config, - "format_include": functools.partial(format_include, config), - } - - 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. - protocol_h_templates = [ - "ErrorSupport_h.template", - "Values_h.template", - "Object_h.template", - "ValueConversions_h.template", - "Maybe_h.template", - "Array_h.template", - "DispatcherBase_h.template", - "Parser_h.template", - "encoding_h.template", - ] - - protocol_cpp_templates = [ - "Protocol_cpp.template", - "ErrorSupport_cpp.template", - "Values_cpp.template", - "Object_cpp.template", - "DispatcherBase_cpp.template", - "Parser_cpp.template", - "encoding_cpp.template", - ] - - forward_h_templates = [ - "Forward_h.template", - "Allocator_h.template", - "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: - inputs.append(os.path.join(lib_templates_dir, template_file)) - template = jinja_env.get_template("lib/" + template_file) - parts.append(template.render(template_context)) - 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")), 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.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.items(): - out_file = open(file_name, "w") - out_file.write(content) - out_file.close() - - -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. + protocol_h_templates = [ + "Values_h.template", + "Object_h.template", + "ValueConversions_h.template", + ] + + protocol_cpp_templates = [ + "Protocol_cpp.template", + "Values_cpp.template", + "Object_cpp.template", + "ValueConversions_cpp.template", + ] + + forward_h_templates = [ + "Forward_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: + inputs.append(os.path.join(lib_templates_dir, template_file)) + template = jinja_env.get_template("lib/" + template_file) + parts.append(template.render(template_context)) + 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")), 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.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.items(): + out_file = open(file_name, "w") + out_file.write(content) + out_file.close() + + +if __name__ == "__main__": + main() diff --git a/tools/inspector_protocol/concatenate_protocols.py b/tools/inspector_protocol/concatenate_protocols.py index e9f448efe72702..42ecc7f9cf11b2 100755 --- a/tools/inspector_protocol/concatenate_protocols.py +++ b/tools/inspector_protocol/concatenate_protocols.py @@ -7,33 +7,36 @@ import sys try: - import json + import json except ImportError: - import simplejson as json + import simplejson as json import pdl def main(argv): - if len(argv) < 1: - sys.stderr.write("Usage: %s [ [, ...]] \n" % sys.argv[0]) - return 1 - - domains = [] - version = None - for protocol in argv[:-1]: - file_name = os.path.normpath(protocol) - if not os.path.isfile(file_name): - sys.stderr.write("Cannot find %s\n" % file_name) - return 1 - input_file = open(file_name, "r") - parsed_json = pdl.loads(input_file.read(), file_name) - domains += parsed_json["domains"] - version = parsed_json["version"] - - output_file = open(argv[-1], "w") - json.dump({"version": version, "domains": domains}, output_file, indent=4, sort_keys=False, separators=(',', ': ')) - output_file.close() + if len(argv) < 1: + sys.stderr.write( + "Usage: %s [ [, ...]] " + "\n" % sys.argv[0]) + return 1 + + domains = [] + version = None + for protocol in argv[:-1]: + file_name = os.path.normpath(protocol) + if not os.path.isfile(file_name): + sys.stderr.write("Cannot find %s\n" % file_name) + return 1 + input_file = open(file_name, "r") + parsed_json = pdl.loads(input_file.read(), file_name) + domains += parsed_json["domains"] + version = parsed_json["version"] + + output_file = open(argv[-1], "w") + json.dump({"version": version, "domains": domains}, output_file, + indent=4, sort_keys=False, separators=(',', ': ')) + output_file.close() if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + sys.exit(main(sys.argv[1:])) diff --git a/tools/inspector_protocol/convert_protocol_to_json.py b/tools/inspector_protocol/convert_protocol_to_json.py index 835e6387120bcc..f98bebcd5e66c5 100755 --- a/tools/inspector_protocol/convert_protocol_to_json.py +++ b/tools/inspector_protocol/convert_protocol_to_json.py @@ -21,12 +21,14 @@ def main(argv): parser.add_argument("json_file", help="The .json output file write.") args = parser.parse_args(argv) file_name = os.path.normpath(args.pdl_file) - with open(file_name, "r") as input_file: - pdl_string = input_file.read() + 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() - with open(os.path.normpath(args.json_file), 'w') as output_file: - json.dump(protocol, output_file, indent=4, separators=(',', ': ')) + output_file = open(os.path.normpath(args.json_file), 'wb') + json.dump(protocol, output_file, indent=4, separators=(',', ': ')) + output_file.close() if __name__ == '__main__': diff --git a/tools/inspector_protocol/crdtp/README.md b/tools/inspector_protocol/crdtp/README.md new file mode 100644 index 00000000000000..81957e1692e044 --- /dev/null +++ b/tools/inspector_protocol/crdtp/README.md @@ -0,0 +1,114 @@ +# CRDTP - Chrome DevTools Protocol Library. + +[Canonical location for this library.](https://chromium.googlesource.com/deps/inspector_protocol/+/refs/heads/master) + +This is a support library for the Chrome DevTools protocol implementation. + +It's used from within the Jinja templates which we use for code generation +(see ../lib and ../templates) as well as from Chromium (headless, +chrome, content, blink), V8, and other code bases that use the DevTools +protocol. + +The library is designed to be portable. The only allowed dependencies are: + +- The C/C++ standard libraries, up to C++14. + The litmus test is that it compiles and passes tests for all platforms + supported by V8. + +- For testing, we depend on mini_chromium and gtest. This is isolated + into the `crdtp/test_platform.{h,cc}` library. + +We support 32 bit and 64 bit architectures. + +# Common types used in this library. + +- `uint8_t`: a byte, e.g. for raw bytes or UTF8 characters + +- `uint16_t`: two bytes, e.g. for UTF16 characters + +For input parameters: + +- `span`: pointer to bytes and length + +- `span`: pointer to UTF16 chars and length + +For output parameters: + +- `std::vector` - Owned segment of bytes / utf8 characters and length. + +- `std::string` - Same, for compatibility, even though char is signed. + +# Building and running the tests. + +If you're familiar with +[Chromium's development process](https://www.chromium.org/developers/contributing-code) +and have the depot_tools installed, you may use these commands +to fetch the package (and dependencies) and build and run the tests: + + fetch inspector_protocol + cd src + gn gen out/Release + ninja -C out/Release crdtp_test + out/Release/crdtp_test + +You'll probably also need to install g++, since Clang uses this to find the +standard C++ headers. E.g., + + sudo apt-get install g++-8 + +# Purpose of the tests + +crdtp comes with unittest coverage. + +Upstream, in this standalone package, the unittests make development +more pleasant because they are very fast and light (try the previous +section to see). + +Downstream (in Chromium, V8, etc.), they ensure that the library behaves +correctly within each specific code base. We have seen bugs from different +architectures / compilers / etc. in the past. We have also seen +that a tweaked downstream crdtp_platform library did not behave correctly, +becaues V8's strtod routine interprets out of range literals as 'inf'. +Thus, the unittests function as a conformance test suite for such code-base +specific tweaks downstream. + +# Customization by downstream users (Chrome, V8, google3, etc.). + +Downstream users may need to customize the library. We isolate these typical +customizations into two platform libraries (crdtp_plaform and +crdtp_test_platform), to reduce the chance of merge conflicts and grief when +rolling as much as possible. While customized platform libraries may +depend on the downstream code base (e.g. abseil, Chromium's base, V8's utility +functions, Boost, etc.), they are not exposed to the headers that +downstream code depends on. + +## crdtp_platform + +This platform library is only used by the crdtp library; it is not part of the +crdtp API. Thus far it consists only of json_platform.h and json_platform.cc, +because conversion between a `std::string` and a double is tricky, and different +code bases have different preferences in this regard. In this repository +(upstream), json_platform.cc provides a reference implementation which uses the +C++ standard library. + +Downstream, in Chromium, json_platform_chromium.cc has a different +implementation that uses the routines in Chromium's //base, that is, it's a .cc +file that's specific to Chromium. Similarly, in V8, json_platform_v8.cc uses +V8's number conversion utilities, so it's a .cc file that's specific to V8. And +in google3, we use the absl library. crdtp/json_platform.cc is designed to be +easy to modify or replace, and the interface defined by its header is designed +to be stable. + +## crdtp_test_platform + +This platform library is only used by the tests. Upstream, it's setup to +use mini_chromium and gtest. Downstream, Chromium uses its //base libraries, +and V8 uses theirs; and a small amount of tweaking is needed in each code +base - e.g., Chromium, V8, and google3 each place `#include` declarations into +test_platform.h that are specific to their code base, and they have their +own routines in test_platform.cc which uses their own libraries. + +The purpose of crdtp_test_platform is to isolate the tweaking to this small, +stable library (modifying test_platform.h and test_platform.cc). This avoids +having to modify the actual tests (json_test.cc, cbor_test.cc, ...) +when rolling changes downstream. We try to not use patch files. diff --git a/tools/inspector_protocol/crdtp/cbor.cc b/tools/inspector_protocol/crdtp/cbor.cc new file mode 100644 index 00000000000000..9729ad68e55174 --- /dev/null +++ b/tools/inspector_protocol/crdtp/cbor.cc @@ -0,0 +1,1044 @@ +// 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 "cbor.h" + +#include +#include +#include +#include +#include +#include + +namespace v8_inspector_protocol_crdtp { +namespace cbor { +namespace { +// Indicates the number of bits the "initial byte" needs to be shifted to the +// right after applying |kMajorTypeMask| to produce the major type in the +// lowermost bits. +static constexpr uint8_t kMajorTypeBitShift = 5u; +// Mask selecting the low-order 5 bits of the "initial byte", which is where +// the additional information is encoded. +static constexpr uint8_t kAdditionalInformationMask = 0x1f; +// Mask selecting the high-order 3 bits of the "initial byte", which indicates +// the major type of the encoded value. +static constexpr uint8_t kMajorTypeMask = 0xe0; +// Indicates the integer is in the following byte. +static constexpr uint8_t kAdditionalInformation1Byte = 24u; +// Indicates the integer is in the next 2 bytes. +static constexpr uint8_t kAdditionalInformation2Bytes = 25u; +// Indicates the integer is in the next 4 bytes. +static constexpr uint8_t kAdditionalInformation4Bytes = 26u; +// Indicates the integer is in the next 8 bytes. +static constexpr uint8_t kAdditionalInformation8Bytes = 27u; + +// Encodes the initial byte, consisting of the |type| in the first 3 bits +// followed by 5 bits of |additional_info|. +constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) { + return (static_cast(type) << kMajorTypeBitShift) | + (additional_info & kAdditionalInformationMask); +} + +// TAG 24 indicates that what follows is a byte string which is +// encoded in CBOR format. We use this as a wrapper for +// maps and arrays, allowing us to skip them, because the +// byte string carries its size (byte length). +// https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +static constexpr uint8_t kInitialByteForEnvelope = + EncodeInitialByte(MajorType::TAG, 24); +// The initial byte for a byte string with at most 2^32 bytes +// of payload. This is used for envelope encoding, even if +// the byte string is shorter. +static constexpr uint8_t kInitialByteFor32BitLengthByteString = + EncodeInitialByte(MajorType::BYTE_STRING, 26); + +// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional +// info = 31. +static constexpr uint8_t kInitialByteIndefiniteLengthArray = + EncodeInitialByte(MajorType::ARRAY, 31); +static constexpr uint8_t kInitialByteIndefiniteLengthMap = + EncodeInitialByte(MajorType::MAP, 31); +// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite +// length maps / arrays. +static constexpr uint8_t kStopByte = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 31); + +// See RFC 7049 Section 2.3, Table 2. +static constexpr uint8_t kEncodedTrue = + 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); + +// 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); + +// 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))); +} + +// 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(in.size() >= sizeof(T)); + T result = 0; + for (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 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 0. +size_t ReadTokenStart(span bytes, MajorType* type, uint64_t* value) { + if (bytes.empty()) + return 0; + 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 0; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 2; + } + if (additional_information == kAdditionalInformation2Bytes) { + // Values 256-65535: 1 initial byte + 2 bytes payload. + if (bytes.size() < 1 + sizeof(uint16_t)) + return 0; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 3; + } + if (additional_information == kAdditionalInformation4Bytes) { + // 32 bit uint: 1 initial byte + 4 bytes payload. + if (bytes.size() < 1 + sizeof(uint32_t)) + return 0; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 5; + } + if (additional_information == kAdditionalInformation8Bytes) { + // 64 bit uint: 1 initial byte + 8 bytes payload. + if (bytes.size() < 1 + sizeof(uint64_t)) + return 0; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 9; + } + return 0; +} + +// 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 internals + +// ============================================================================= +// Detecting CBOR content +// ============================================================================= + +uint8_t InitialByteForEnvelope() { + return kInitialByteForEnvelope; +} + +uint8_t InitialByteFor32BitLengthByteString() { + return kInitialByteFor32BitLengthByteString; +} + +bool IsCBORMessage(span msg) { + return msg.size() >= 6 && msg[0] == InitialByteForEnvelope() && + msg[1] == InitialByteFor32BitLengthByteString(); +} + +Status CheckCBORMessage(span msg) { + if (msg.empty()) + return Status(Error::CBOR_NO_INPUT, 0); + if (msg[0] != InitialByteForEnvelope()) + return Status(Error::CBOR_INVALID_START_BYTE, 0); + if (msg.size() < 6 || msg[1] != InitialByteFor32BitLengthByteString()) + return Status(Error::CBOR_INVALID_ENVELOPE, 1); + if (msg[2] == 0 && msg[3] == 0 && msg[4] == 0 && msg[5] == 0) + return Status(Error::CBOR_INVALID_ENVELOPE, 1); + if (msg.size() < 7 || msg[6] != EncodeIndefiniteLengthMapStart()) + return Status(Error::CBOR_MAP_START_EXPECTED, 6); + return Status(); +} + +// ============================================================================= +// Encoding invidiual CBOR items +// ============================================================================= + +uint8_t EncodeTrue() { + return kEncodedTrue; +} + +uint8_t EncodeFalse() { + return kEncodedFalse; +} + +uint8_t EncodeNull() { + return kEncodedNull; +} + +uint8_t EncodeIndefiniteLengthArrayStart() { + return kInitialByteIndefiniteLengthArray; +} + +uint8_t EncodeIndefiniteLengthMapStart() { + return kInitialByteIndefiniteLengthMap; +} + +uint8_t EncodeStop() { + return kStopByte; +} + +void EncodeInt32(int32_t value, std::vector* out) { + if (value >= 0) { + internals::WriteTokenStart(MajorType::UNSIGNED, value, out); + } else { + uint64_t representation = static_cast(-(value + 1)); + internals::WriteTokenStart(MajorType::NEGATIVE, representation, out); + } +} + +void EncodeString16(span in, std::vector* out) { + uint64_t byte_length = static_cast(in.size_bytes()); + internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); + // When emitting UTF16 characters, we always write the least significant byte + // first; this is because it's the native representation for X86. + // TODO(johannes): Implement a more efficient thing here later, e.g. + // 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) { + internals::WriteTokenStart(MajorType::STRING, + static_cast(in.size_bytes()), out); + out->insert(out->end(), in.begin(), in.end()); +} + +void EncodeFromLatin1(span latin1, std::vector* out) { + for (size_t ii = 0; ii < latin1.size(); ++ii) { + if (latin1[ii] <= 127) + continue; + // If there's at least one non-ASCII char, convert to UTF8. + std::vector utf8(latin1.begin(), latin1.begin() + ii); + for (; ii < latin1.size(); ++ii) { + if (latin1[ii] <= 127) { + utf8.push_back(latin1[ii]); + } else { + // 0xC0 means it's a UTF8 sequence with 2 bytes. + utf8.push_back((latin1[ii] >> 6) | 0xc0); + utf8.push_back((latin1[ii] | 0x80) & 0xbf); + } + } + EncodeString8(SpanFrom(utf8), out); + return; + } + EncodeString8(latin1, out); +} + +void EncodeFromUTF16(span utf16, std::vector* out) { + // If there's at least one non-ASCII char, encode as STRING16 (UTF16). + for (uint16_t ch : utf16) { + if (ch <= 127) + continue; + EncodeString16(utf16, out); + return; + } + // It's all US-ASCII, strip out every second byte and encode as UTF8. + internals::WriteTokenStart(MajorType::STRING, + static_cast(utf16.size()), out); + out->insert(out->end(), utf16.begin(), utf16.end()); +} + +void EncodeBinary(span in, std::vector* out) { + out->push_back(kExpectedConversionToBase64Tag); + uint64_t byte_length = static_cast(in.size_bytes()); + internals::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 size_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 size_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); +} + +// ============================================================================= +// cbor::EnvelopeEncoder - for wrapping submessages +// ============================================================================= + +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; +} + +// ============================================================================= +// cbor::NewCBOREncoder - for encoding from a streaming parser +// ============================================================================= + +namespace { +class CBOREncoder : public ParserHandler { + public: + CBOREncoder(std::vector* out, Status* status) + : out_(out), status_(status) { + *status_ = Status(); + } + + void HandleMapBegin() override { + if (!status_->ok()) + return; + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthMap); + } + + void HandleMapEnd() override { + if (!status_->ok()) + return; + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + if (!envelopes_.back().EncodeStop(out_)) { + HandleError( + Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size())); + return; + } + envelopes_.pop_back(); + } + + void HandleArrayBegin() override { + if (!status_->ok()) + return; + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthArray); + } + + void HandleArrayEnd() override { + if (!status_->ok()) + return; + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + if (!envelopes_.back().EncodeStop(out_)) { + HandleError( + Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size())); + return; + } + envelopes_.pop_back(); + } + + void HandleString8(span chars) override { + if (!status_->ok()) + return; + EncodeString8(chars, out_); + } + + void HandleString16(span chars) override { + if (!status_->ok()) + return; + EncodeFromUTF16(chars, out_); + } + + void HandleBinary(span bytes) override { + if (!status_->ok()) + return; + EncodeBinary(bytes, out_); + } + + void HandleDouble(double value) override { + if (!status_->ok()) + return; + EncodeDouble(value, out_); + } + + void HandleInt32(int32_t value) override { + if (!status_->ok()) + return; + EncodeInt32(value, out_); + } + + void HandleBool(bool value) override { + if (!status_->ok()) + return; + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(value ? kEncodedTrue : kEncodedFalse); + } + + void HandleNull() override { + if (!status_->ok()) + return; + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(kEncodedNull); + } + + void HandleError(Status error) override { + if (!status_->ok()) + return; + *status_ = error; + out_->clear(); + } + + private: + std::vector* out_; + std::vector envelopes_; + Status* status_; +}; +} // namespace + +std::unique_ptr NewCBOREncoder(std::vector* out, + Status* status) { + return std::unique_ptr(new CBOREncoder(out, status)); +} + +// ============================================================================= +// cbor::CBORTokenizer - for parsing individual CBOR items +// ============================================================================= + +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_; +} + +// The following accessor functions ::GetInt32, ::GetDouble, +// ::GetString8, ::GetString16WireRep, ::GetBinary, ::GetEnvelopeContents +// assume that a particular token was recognized in ::ReadNextToken. +// That's where all the error checking is done. By design, +// the accessors (assuming the token was recognized) never produce +// an error. + +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); +} + +span CBORTokenizer::GetEnvelope() const { + assert(token_tag_ == CBORTokenTag::ENVELOPE); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos, length + kEncodedEnvelopeHeaderSize); +} + +span CBORTokenizer::GetEnvelopeContents() const { + assert(token_tag_ == CBORTokenTag::ENVELOPE); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length); +} + +// All error checking happens in ::ReadNextToken, so that the accessors +// can avoid having to carry an error return value. +// +// With respect to checking the encoded lengths of strings, arrays, etc: +// On the wire, CBOR uses 1,2,4, and 8 byte unsigned integers, so +// we initially read them as uint64_t, usually into token_start_internal_value_. +// +// However, since these containers have a representation on the machine, +// we need to do corresponding size computations on the input byte array, +// output span (e.g. the payload for a string), etc., and size_t is +// machine specific (in practice either 32 bit or 64 bit). +// +// Further, we must avoid overflowing size_t. Therefore, we use this +// kMaxValidLength constant to: +// - Reject values that are larger than the architecture specific +// max size_t (differs between 32 bit and 64 bit arch). +// - Reserve at least one bit so that we can check against overflows +// when adding lengths (array / string length / etc.); we do this by +// ensuring that the inputs to an addition are <= kMaxValidLength, +// and then checking whether the sum went past it. +// +// See also +// https://chromium.googlesource.com/chromium/src/+/master/docs/security/integer-semantics.md +static const uint64_t kMaxValidLength = + std::min(std::numeric_limits::max() >> 2, + std::numeric_limits::max()); + +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; + } + const size_t remaining_bytes = bytes_.size() - status_.pos; + 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 + const size_t bytes_read = internals::ReadTokenStart( + bytes_.subspan(status_.pos + 1), &token_start_type_, + &token_start_internal_value_); + if (!bytes_read || token_start_type_ != MajorType::BYTE_STRING || + token_start_internal_value_ > kMaxValidLength) { + SetError(Error::CBOR_INVALID_BINARY); + return; + } + const uint64_t token_byte_length = token_start_internal_value_ + + /* tag before token start: */ 1 + + /* token start: */ bytes_read; + if (token_byte_length > remaining_bytes) { + SetError(Error::CBOR_INVALID_BINARY); + return; + } + SetToken(CBORTokenTag::BINARY, static_cast(token_byte_length)); + return; + } + case kInitialByteForDouble: { // DOUBLE + if (kEncodedDoubleSize > remaining_bytes) { + SetError(Error::CBOR_INVALID_DOUBLE); + return; + } + SetToken(CBORTokenTag::DOUBLE, kEncodedDoubleSize); + return; + } + case kInitialByteForEnvelope: { // ENVELOPE + if (kEncodedEnvelopeHeaderSize > remaining_bytes) { + 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)); + if (token_start_internal_value_ > kMaxValidLength) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + uint64_t token_byte_length = + token_start_internal_value_ + kEncodedEnvelopeHeaderSize; + if (token_byte_length > remaining_bytes) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + SetToken(CBORTokenTag::ENVELOPE, static_cast(token_byte_length)); + return; + } + default: { + const size_t bytes_read = internals::ReadTokenStart( + bytes_.subspan(status_.pos), &token_start_type_, + &token_start_internal_value_); + switch (token_start_type_) { + case MajorType::UNSIGNED: // INT32. + // INT32 is a signed int32 (int32 makes sense for the + // inspector protocol, it's not a CBOR limitation), so we check + // against the signed max, so that the allowable values are + // 0, 1, 2, ... 2^31 - 1. + if (!bytes_read || std::numeric_limits::max() < + token_start_internal_value_) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, bytes_read); + return; + case MajorType::NEGATIVE: { // INT32. + // INT32 is a signed int32 (int32 makes sense for the + // inspector protocol, it's not a CBOR limitation); in CBOR, the + // negative values for INT32 are represented as NEGATIVE, that is, -1 + // INT32 is represented as 1 << 5 | 0 (major type 1, additional info + // value 0). + // The represented allowed values range is -1 to -2^31. + // They are mapped into the encoded range of 0 to 2^31-1. + // We check the payload in token_start_internal_value_ against + // that range (2^31-1 is also known as + // std::numeric_limits::max()). + if (!bytes_read || token_start_internal_value_ > + std::numeric_limits::max()) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, bytes_read); + return; + } + case MajorType::STRING: { // STRING8. + if (!bytes_read || token_start_internal_value_ > kMaxValidLength) { + SetError(Error::CBOR_INVALID_STRING8); + return; + } + uint64_t token_byte_length = token_start_internal_value_ + bytes_read; + if (token_byte_length > remaining_bytes) { + SetError(Error::CBOR_INVALID_STRING8); + return; + } + SetToken(CBORTokenTag::STRING8, + static_cast(token_byte_length)); + return; + } + case MajorType::BYTE_STRING: { // STRING16. + // Length must be divisible by 2 since UTF16 is 2 bytes per + // character, hence the &1 check. + if (!bytes_read || token_start_internal_value_ > kMaxValidLength || + token_start_internal_value_ & 1) { + SetError(Error::CBOR_INVALID_STRING16); + return; + } + uint64_t token_byte_length = token_start_internal_value_ + bytes_read; + if (token_byte_length > remaining_bytes) { + SetError(Error::CBOR_INVALID_STRING16); + return; + } + SetToken(CBORTokenTag::STRING16, + static_cast(token_byte_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, size_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; +} + +// ============================================================================= +// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages +// ============================================================================= + +namespace { +// When parsing CBOR, we limit recursion depth for objects and arrays +// to this constant. +static constexpr int kStackLimit = 300; + +// Below are three parsing routines for CBOR, which cover enough +// to roundtrip JSON messages. +bool ParseMap(int32_t stack_depth, + CBORTokenizer* tokenizer, + ParserHandler* out); +bool ParseArray(int32_t stack_depth, + CBORTokenizer* tokenizer, + ParserHandler* out); +bool ParseValue(int32_t stack_depth, + CBORTokenizer* tokenizer, + ParserHandler* out); +bool ParseEnvelope(int32_t stack_depth, + CBORTokenizer* tokenizer, + ParserHandler* out); + +void ParseUTF16String(CBORTokenizer* tokenizer, ParserHandler* out) { + std::vector value; + span rep = tokenizer->GetString16WireRep(); + for (size_t ii = 0; ii < rep.size(); ii += 2) + value.push_back((rep[ii + 1] << 8) | rep[ii]); + out->HandleString16(span(value.data(), value.size())); + tokenizer->Next(); +} + +bool ParseUTF8String(CBORTokenizer* tokenizer, ParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::STRING8); + out->HandleString8(tokenizer->GetString8()); + tokenizer->Next(); + return true; +} + +bool ParseEnvelope(int32_t stack_depth, + CBORTokenizer* tokenizer, + ParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::ENVELOPE); + // Before we enter the envelope, we save the position that we + // expect to see after we're done parsing the envelope contents. + // This way we can compare and produce an error if the contents + // didn't fit exactly into the envelope length. + size_t pos_past_envelope = tokenizer->Status().pos + + kEncodedEnvelopeHeaderSize + + tokenizer->GetEnvelopeContents().size(); + tokenizer->EnterEnvelope(); + switch (tokenizer->TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + out->HandleError(tokenizer->Status()); + return false; + case CBORTokenTag::MAP_START: + if (!ParseMap(stack_depth + 1, tokenizer, out)) + return false; + break; // Continue to check pos_past_envelope below. + case CBORTokenTag::ARRAY_START: + if (!ParseArray(stack_depth + 1, tokenizer, out)) + return false; + break; // Continue to check pos_past_envelope below. + default: + out->HandleError(Status{Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE, + tokenizer->Status().pos}); + return false; + } + // The contents of the envelope parsed OK, now check that we're at + // the expected position. + if (pos_past_envelope != tokenizer->Status().pos) { + out->HandleError(Status{Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH, + tokenizer->Status().pos}); + return false; + } + return true; +} + +bool ParseValue(int32_t stack_depth, + CBORTokenizer* tokenizer, + ParserHandler* out) { + if (stack_depth > kStackLimit) { + out->HandleError( + Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos}); + return false; + } + 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::ENVELOPE: + return ParseEnvelope(stack_depth, tokenizer, out); + case CBORTokenTag::TRUE_VALUE: + out->HandleBool(true); + tokenizer->Next(); + return true; + case CBORTokenTag::FALSE_VALUE: + out->HandleBool(false); + tokenizer->Next(); + return true; + case CBORTokenTag::NULL_VALUE: + out->HandleNull(); + tokenizer->Next(); + return true; + case CBORTokenTag::INT32: + out->HandleInt32(tokenizer->GetInt32()); + tokenizer->Next(); + return true; + case CBORTokenTag::DOUBLE: + out->HandleDouble(tokenizer->GetDouble()); + tokenizer->Next(); + return true; + case CBORTokenTag::STRING8: + return ParseUTF8String(tokenizer, out); + case CBORTokenTag::STRING16: + ParseUTF16String(tokenizer, out); + return true; + case CBORTokenTag::BINARY: { + out->HandleBinary(tokenizer->GetBinary()); + tokenizer->Next(); + return true; + } + case CBORTokenTag::MAP_START: + return ParseMap(stack_depth + 1, tokenizer, out); + case CBORTokenTag::ARRAY_START: + return ParseArray(stack_depth + 1, tokenizer, out); + default: + out->HandleError( + Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos}); + return false; + } +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +bool ParseArray(int32_t stack_depth, + CBORTokenizer* tokenizer, + ParserHandler* 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, + ParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START); + out->HandleMapBegin(); + tokenizer->Next(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + out->HandleError( + Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos}); + return false; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer->Status()); + return false; + } + // Parse key. + if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { + if (!ParseUTF8String(tokenizer, out)) + return false; + } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { + ParseUTF16String(tokenizer, out); + } else { + out->HandleError( + Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos}); + return false; + } + // Parse value. + if (!ParseValue(stack_depth, tokenizer, out)) + return false; + } + out->HandleMapEnd(); + tokenizer->Next(); + return true; +} +} // namespace + +void ParseCBOR(span bytes, ParserHandler* out) { + if (bytes.empty()) { + out->HandleError(Status{Error::CBOR_NO_INPUT, 0}); + return; + } + CBORTokenizer tokenizer(bytes); + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer.Status()); + return; + } + if (!ParseValue(/*stack_depth=*/0, &tokenizer, out)) + return; + if (tokenizer.TokenTag() == CBORTokenTag::DONE) + return; + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer.Status()); + return; + } + out->HandleError(Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos}); +} + +// ============================================================================= +// cbor::AppendString8EntryToMap - for limited in-place editing of messages +// ============================================================================= + +Status AppendString8EntryToCBORMap(span string8_key, + span string8_value, + std::vector* cbor) { + // Careful below: Don't compare (*cbor)[idx] with a uint8_t, since + // it could be a char (signed!). Instead, use bytes. + span bytes(reinterpret_cast(cbor->data()), + cbor->size()); + CBORTokenizer tokenizer(bytes); + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) + return tokenizer.Status(); + if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE) + return Status(Error::CBOR_INVALID_ENVELOPE, 0); + size_t envelope_size = tokenizer.GetEnvelopeContents().size(); + size_t old_size = cbor->size(); + if (old_size != envelope_size + kEncodedEnvelopeHeaderSize) + return Status(Error::CBOR_INVALID_ENVELOPE, 0); + if (envelope_size == 0 || + (tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart())) + return Status(Error::CBOR_MAP_START_EXPECTED, kEncodedEnvelopeHeaderSize); + if (bytes[bytes.size() - 1] != EncodeStop()) + return Status(Error::CBOR_MAP_STOP_EXPECTED, cbor->size() - 1); + cbor->pop_back(); + EncodeString8(string8_key, cbor); + EncodeString8(string8_value, cbor); + cbor->push_back(EncodeStop()); + size_t new_envelope_size = envelope_size + (cbor->size() - old_size); + if (new_envelope_size > std::numeric_limits::max()) + return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0); + size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t); + uint8_t* out = reinterpret_cast(&cbor->at(size_pos)); + *(out++) = (new_envelope_size >> 24) & 0xff; + *(out++) = (new_envelope_size >> 16) & 0xff; + *(out++) = (new_envelope_size >> 8) & 0xff; + *(out) = new_envelope_size & 0xff; + return Status(); +} +} // namespace cbor +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/encoding/encoding.h b/tools/inspector_protocol/crdtp/cbor.h similarity index 54% rename from tools/inspector_protocol/encoding/encoding.h rename to tools/inspector_protocol/crdtp/cbor.h index 90916d42b36dae..b9e0359e2c332e 100644 --- a/tools/inspector_protocol/encoding/encoding.h +++ b/tools/inspector_protocol/crdtp/cbor.h @@ -2,161 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_ -#define V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_ +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_CBOR_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_CBOR_H_ #include #include -#include -#include #include #include #include -namespace v8_inspector_protocol_encoding { - -// ============================================================================= -// span - sequence of bytes -// ============================================================================= - -// This template is similar to std::span, which will be included in C++20. -template -class span { - public: - using index_type = size_t; - - span() : data_(nullptr), size_(0) {} - span(const T* data, index_type size) : data_(data), size_(size) {} - - const T* data() const { return data_; } - - const T* begin() const { return data_; } - const T* end() const { return data_ + size_; } - - const T& operator[](index_type idx) const { return data_[idx]; } - - span 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_; -}; - -template -span SpanFrom(const std::vector& v) { - return span(v.data(), v.size()); -} - -template -span SpanFrom(const char (&str)[N]) { - return span(reinterpret_cast(str), N - 1); -} - -inline span SpanFrom(const char* str) { - return str ? span(reinterpret_cast(str), strlen(str)) - : span(); -} - -inline span SpanFrom(const std::string& v) { - return span(reinterpret_cast(v.data()), v.size()); -} - -// ============================================================================= -// Status and Error codes -// ============================================================================= -enum class Error { - OK = 0, - // JSON parsing errors - json_parser.{h,cc}. - JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01, - JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02, - JSON_PARSER_NO_INPUT = 0x03, - JSON_PARSER_INVALID_TOKEN = 0x04, - JSON_PARSER_INVALID_NUMBER = 0x05, - JSON_PARSER_INVALID_STRING = 0x06, - JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07, - JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08, - JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09, - JSON_PARSER_COLON_EXPECTED = 0x0a, - JSON_PARSER_UNEXPECTED_MAP_END = 0x0b, - JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c, - JSON_PARSER_VALUE_EXPECTED = 0x0d, - - CBOR_INVALID_INT32 = 0x0e, - CBOR_INVALID_DOUBLE = 0x0f, - CBOR_INVALID_ENVELOPE = 0x10, - CBOR_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_TRAILING_JUNK = 0x1c, - CBOR_MAP_START_EXPECTED = 0x1d, - CBOR_MAP_STOP_EXPECTED = 0x1e, - CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x1f, -}; - -// A status value with position that can be copied. The default status -// is OK. Usually, error status values should come with a valid position. -struct Status { - static constexpr size_t npos() { return std::numeric_limits::max(); } - - bool ok() const { return error == Error::OK; } - - Error error = Error::OK; - size_t pos = npos(); - Status(Error error, size_t pos) : error(error), pos(pos) {} - Status() = default; - - // Returns a 7 bit US-ASCII string, either "OK" or an error message - // that includes the position. - std::string ToASCIIString() const; - - private: - std::string ToASCIIString(const char* msg) const; -}; - -// Handler interface for parser events emitted by a streaming parser. -// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder, -// json::ParseJSON. -class StreamingParserHandler { - public: - virtual ~StreamingParserHandler() = default; - virtual void HandleMapBegin() = 0; - virtual void HandleMapEnd() = 0; - virtual void HandleArrayBegin() = 0; - virtual void HandleArrayEnd() = 0; - virtual void HandleString8(span chars) = 0; - virtual void HandleString16(span chars) = 0; - virtual void HandleBinary(span bytes) = 0; - virtual void HandleDouble(double value) = 0; - virtual void HandleInt32(int32_t value) = 0; - virtual void HandleBool(bool value) = 0; - virtual void HandleNull() = 0; - - // The parser may send one error even after other events have already - // been received. Client code is reponsible to then discard the - // already processed events. - // |error| must be an eror, as in, |error.is_ok()| can't be true. - virtual void HandleError(Status error) = 0; -}; +#include "export.h" +#include "parser_handler.h" +#include "span.h" +namespace v8_inspector_protocol_crdtp { namespace cbor { // The binary encoding for the inspector protocol follows the CBOR specification // (RFC 7049). Additional constraints: @@ -194,6 +53,15 @@ uint8_t InitialByteFor32BitLengthByteString(); // Checks whether |msg| is a cbor message. bool IsCBORMessage(span msg); +// Performs a leightweight check of |msg|. +// Disallows: +// - Empty message +// - Not starting with the two bytes 0xd8, 0x5a +// - Empty envelope (all length bytes are 0) +// - Not starting with a map after the envelope stanza +// DevTools messages should pass this check. +Status CheckCBORMessage(span msg); + // ============================================================================= // Encoding individual CBOR items // ============================================================================= @@ -209,39 +77,32 @@ uint8_t EncodeStop(); // Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE| // (major type 1) iff < 0. void EncodeInt32(int32_t value, std::vector* out); -void EncodeInt32(int32_t value, std::string* 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); -void EncodeString16(span in, std::string* out); // Encodes a UTF8 string |in| as STRING (major type 3). void EncodeString8(span in, std::vector* out); -void EncodeString8(span in, std::string* out); // Encodes the given |latin1| string as STRING8. // If any non-ASCII character is present, it will be represented // as a 2 byte UTF8 sequence. void EncodeFromLatin1(span latin1, std::vector* out); -void EncodeFromLatin1(span latin1, std::string* out); // Encodes the given |utf16| string as STRING8 if it's entirely US-ASCII. // Otherwise, encodes as STRING16. void EncodeFromUTF16(span utf16, std::vector* out); -void EncodeFromUTF16(span utf16, std::string* 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); -void EncodeBinary(span in, std::string* 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); -void EncodeDouble(double value, std::string* out); // ============================================================================= // cbor::EnvelopeEncoder - for wrapping submessages @@ -260,11 +121,9 @@ class EnvelopeEncoder { // byte size in |byte_size_pos_|. Also emits empty bytes for the // byte sisze so that encoding can continue. void EncodeStart(std::vector* out); - void EncodeStart(std::string* out); // This records the current size in |out| at position byte_size_pos_. // Returns true iff successful. bool EncodeStop(std::vector* out); - bool EncodeStop(std::string* out); private: size_t byte_size_pos_ = 0; @@ -278,11 +137,8 @@ class EnvelopeEncoder { // that drives it. The handler will encode into |out|, and iff an error occurs // it will set |status| to an error and clear |out|. Otherwise, |status.ok()| // will be |true|. -std::unique_ptr NewCBOREncoder( - std::vector* out, - Status* status); -std::unique_ptr NewCBOREncoder(std::string* out, - Status* status); +std::unique_ptr NewCBOREncoder(std::vector* out, + Status* status); // ============================================================================= // cbor::CBORTokenizer - for parsing individual CBOR items @@ -387,6 +243,17 @@ class CBORTokenizer { span GetBinary() const; // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE. + // Returns the envelope including its payload; message which + // can be passed to the CBORTokenizer constructor, which will + // then see the envelope token first (looking at it a second time, + // basically). + span GetEnvelope() const; + + // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE. + // Returns only the payload inside the envelope, e.g., a map + // or an array. This is not a complete message by our + // IsCBORMessage definition, since it doesn't include the + // enclosing envelope (the header, basically). span GetEnvelopeContents() const; private: @@ -410,7 +277,7 @@ class CBORTokenizer { // |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, StreamingParserHandler* out); +void ParseCBOR(span bytes, ParserHandler* out); // ============================================================================= // cbor::AppendString8EntryToMap - for limited in-place editing of messages @@ -422,89 +289,17 @@ void ParseCBOR(span bytes, StreamingParserHandler* out); Status AppendString8EntryToCBORMap(span string8_key, span string8_value, std::vector* cbor); -Status AppendString8EntryToCBORMap(span string8_key, - span string8_value, - std::string* cbor); namespace internals { // Exposed only for writing tests. -int8_t ReadTokenStart(span bytes, +size_t ReadTokenStart(span bytes, cbor::MajorType* type, uint64_t* value); void WriteTokenStart(cbor::MajorType type, uint64_t value, std::vector* encoded); -void WriteTokenStart(cbor::MajorType type, - uint64_t value, - std::string* encoded); } // namespace internals } // namespace cbor +} // namespace v8_crdtp -namespace json { -// Client code must provide an instance. Implementation should delegate -// to whatever is appropriate. -class Platform { - public: - virtual ~Platform() = default; - // Parses |str| into |result|. Returns false iff there are - // leftover characters or parsing errors. - virtual bool StrToD(const char* str, double* result) const = 0; - - // Prints |value| in a format suitable for JSON. - virtual std::unique_ptr DToStr(double value) const = 0; -}; - -// ============================================================================= -// json::NewJSONEncoder - for encoding streaming parser events as JSON -// ============================================================================= - -// Returns a handler object which will write ascii characters to |out|. -// |status->ok()| will be false iff the handler routine HandleError() is called. -// In that case, we'll stop emitting output. -// Except for calling the HandleError routine at any time, the client -// code must call the Handle* methods in an order in which they'd occur -// in valid JSON; otherwise we may crash (the code uses assert). -std::unique_ptr NewJSONEncoder( - const Platform* platform, - std::vector* out, - Status* status); -std::unique_ptr NewJSONEncoder(const Platform* platform, - std::string* out, - Status* status); - -// ============================================================================= -// json::ParseJSON - for receiving streaming parser events for JSON -// ============================================================================= - -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler); -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler); - -// ============================================================================= -// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding -// ============================================================================= -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::string* json); -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::vector* json); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor); -} // namespace json -} // namespace v8_inspector_protocol_encoding - -#endif // V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_ +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_CBOR_H_ diff --git a/tools/inspector_protocol/encoding/encoding_test.cc b/tools/inspector_protocol/crdtp/cbor_test.cc similarity index 56% rename from tools/inspector_protocol/encoding/encoding_test.cc rename to tools/inspector_protocol/crdtp/cbor_test.cc index b8d75e09baaf31..351345e05ebcd2 100644 --- a/tools/inspector_protocol/encoding/encoding_test.cc +++ b/tools/inspector_protocol/crdtp/cbor_test.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "encoding.h" +#include "cbor.h" #include #include @@ -13,129 +13,17 @@ #include #include #include - -#include "encoding_test_helper.h" +#include "json.h" +#include "parser_handler.h" +#include "span.h" +#include "status.h" +#include "status_test_support.h" +#include "test_platform.h" using testing::ElementsAreArray; -namespace v8_inspector_protocol_encoding { - -class TestPlatform : public json::Platform { - bool StrToD(const char* str, double* result) const override { - // This is not thread-safe - // (see https://en.cppreference.com/w/cpp/locale/setlocale) - // but good enough for a unittest. - const char* saved_locale = std::setlocale(LC_NUMERIC, nullptr); - char* end; - *result = std::strtod(str, &end); - std::setlocale(LC_NUMERIC, saved_locale); - if (errno == ERANGE) { - // errno must be reset, e.g. see the example here: - // https://en.cppreference.com/w/cpp/string/byte/strtof - errno = 0; - return false; - } - return end == str + strlen(str); - } - - std::unique_ptr DToStr(double value) const override { - std::stringstream ss; - ss.imbue(std::locale("C")); - ss << value; - std::string str = ss.str(); - std::unique_ptr result(new char[str.size() + 1]); - memcpy(result.get(), str.c_str(), str.size() + 1); - return result; - } -}; - -const json::Platform& GetTestPlatform() { - static TestPlatform* platform = new TestPlatform; - return *platform; -} - -// ============================================================================= -// span - sequence of bytes -// ============================================================================= - -template -class SpanTest : public ::testing::Test {}; - -using TestTypes = ::testing::Types; -TYPED_TEST_SUITE(SpanTest, TestTypes); - -TYPED_TEST(SpanTest, Empty) { - span empty; - EXPECT_TRUE(empty.empty()); - EXPECT_EQ(0u, empty.size()); - EXPECT_EQ(0u, empty.size_bytes()); - EXPECT_EQ(empty.begin(), empty.end()); -} - -TYPED_TEST(SpanTest, SingleItem) { - TypeParam single_item = 42; - span singular(&single_item, 1); - EXPECT_FALSE(singular.empty()); - EXPECT_EQ(1u, singular.size()); - EXPECT_EQ(sizeof(TypeParam), singular.size_bytes()); - EXPECT_EQ(singular.begin() + 1, singular.end()); - EXPECT_EQ(42, singular[0]); -} - -TYPED_TEST(SpanTest, FiveItems) { - std::vector test_input = {31, 32, 33, 34, 35}; - span five_items(test_input.data(), 5); - EXPECT_FALSE(five_items.empty()); - EXPECT_EQ(5u, five_items.size()); - EXPECT_EQ(sizeof(TypeParam) * 5, five_items.size_bytes()); - EXPECT_EQ(five_items.begin() + 5, five_items.end()); - EXPECT_EQ(31, five_items[0]); - EXPECT_EQ(32, five_items[1]); - EXPECT_EQ(33, five_items[2]); - EXPECT_EQ(34, five_items[3]); - EXPECT_EQ(35, five_items[4]); - span three_items = five_items.subspan(2); - EXPECT_EQ(3u, three_items.size()); - EXPECT_EQ(33, three_items[0]); - EXPECT_EQ(34, three_items[1]); - EXPECT_EQ(35, three_items[2]); - span two_items = five_items.subspan(2, 2); - EXPECT_EQ(2u, two_items.size()); - EXPECT_EQ(33, two_items[0]); - EXPECT_EQ(34, two_items[1]); -} - -TEST(SpanFromTest, FromConstCharAndLiteral) { - // Testing this is useful because strlen(nullptr) is undefined. - EXPECT_EQ(nullptr, SpanFrom(nullptr).data()); - EXPECT_EQ(0u, SpanFrom(nullptr).size()); - - const char* kEmpty = ""; - EXPECT_EQ(kEmpty, reinterpret_cast(SpanFrom(kEmpty).data())); - EXPECT_EQ(0u, SpanFrom(kEmpty).size()); - - const char* kFoo = "foo"; - EXPECT_EQ(kFoo, reinterpret_cast(SpanFrom(kFoo).data())); - EXPECT_EQ(3u, SpanFrom(kFoo).size()); - - EXPECT_EQ(3u, SpanFrom("foo").size()); -} - -// ============================================================================= -// Status and Error codes -// ============================================================================= - -TEST(StatusTest, StatusToASCIIString) { - Status ok_status; - EXPECT_EQ("OK", ok_status.ToASCIIString()); - Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42); - EXPECT_EQ("JSON: colon expected at position 42", json_error.ToASCIIString()); - Status cbor_error(Error::CBOR_TRAILING_JUNK, 21); - EXPECT_EQ("CBOR: trailing junk at position 21", cbor_error.ToASCIIString()); -} - +namespace v8_inspector_protocol_crdtp { namespace cbor { - // ============================================================================= // Detecting CBOR content // ============================================================================= @@ -152,6 +40,73 @@ TEST(IsCBORMessage, SomeSmokeTests) { EXPECT_TRUE(IsCBORMessage(SpanFrom(one))); } +TEST(CheckCBORMessage, SmallestValidExample) { + // The smallest example that we consider valid for this lightweight check is + // an empty dictionary inside of an envelope. + std::vector empty_dict = { + 0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()}; + Status status = CheckCBORMessage(SpanFrom(empty_dict)); + EXPECT_THAT(status, StatusIsOk()); +} + +TEST(CheckCBORMessage, ValidCBORButNotValidMessage) { + // The CBOR parser supports parsing values that aren't messages. E.g., this is + // the encoded unsigned int 7 (CBOR really encodes it as a single byte with + // value 7). + std::vector not_a_message = {7}; + + // Show that the parser (happily) decodes it into JSON + std::string json; + Status status; + std::unique_ptr json_writer = + json::NewJSONEncoder(&json, &status); + ParseCBOR(SpanFrom(not_a_message), json_writer.get()); + EXPECT_THAT(status, StatusIsOk()); + EXPECT_EQ("7", json); + + // ... but it's not a message. + EXPECT_THAT(CheckCBORMessage(SpanFrom(not_a_message)), + StatusIs(Error::CBOR_INVALID_START_BYTE, 0)); +} + +TEST(CheckCBORMessage, EmptyMessage) { + std::vector empty; + Status status = CheckCBORMessage(SpanFrom(empty)); + EXPECT_THAT(status, StatusIs(Error::CBOR_NO_INPUT, 0)); +} + +TEST(CheckCBORMessage, InvalidStartByte) { + // Here we test that some actual json, which usually starts with {, is not + // considered CBOR. CBOR messages must start with 0xd8, 0x5a, the envelope + // start bytes. + Status status = CheckCBORMessage(SpanFrom("{\"msg\": \"Hello, world.\"}")); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_START_BYTE, 0)); +} + +TEST(CheckCBORMessage, InvalidEnvelopes) { + std::vector bytes = {0xd8, 0x5a}; + EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), + StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); + bytes = {0xd8, 0x5a, 0}; + EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), + StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); + bytes = {0xd8, 0x5a, 0, 0}; + EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), + StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); + bytes = {0xd8, 0x5a, 0, 0, 0}; + EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), + StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); + bytes = {0xd8, 0x5a, 0, 0, 0, 0}; + EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), + StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); +} + +TEST(CheckCBORMessage, MapStartExpected) { + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, 1}; + EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), + StatusIs(Error::CBOR_MAP_START_EXPECTED, 6)); +} + // ============================================================================= // Encoding individual CBOR items // cbor::CBORTokenizer - for parsing individual CBOR items @@ -234,6 +189,30 @@ TEST(EncodeDecodeInt32Test, RoundtripsInt32Max) { EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); } +TEST(EncodeDecodeInt32Test, RoundtripsInt32Min) { + // std::numeric_limits is encoded as a uint32 (4 unsigned bytes) + // after the initial byte, which effectively carries the sign by + // designating the token as NEGATIVE. + std::vector encoded; + EncodeInt32(std::numeric_limits::min(), &encoded); + // 1 for initial byte, 4 for the uint32. + // first three bits: major type = 1; + // remaining five bits: additional info = 26, indicating payload is uint32. + EXPECT_THAT(encoded, ElementsAreArray(std::array{ + {1 << 5 | 26, 0x7f, 0xff, 0xff, 0xff}})); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(SpanFrom(encoded)); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(std::numeric_limits::min(), tokenizer.GetInt32()); + // It's nice to see how the min int32 value reads in hex: + // That is, -1 minus the unsigned payload (0x7fffffff, see above). + int32_t expected = -1 - 0x7fffffff; + EXPECT_EQ(expected, tokenizer.GetInt32()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + TEST(EncodeDecodeInt32Test, CantRoundtripUint32) { // 0xdeadbeef is a value which does not fit below // std::numerical_limits::max(), so we can't encode @@ -253,7 +232,7 @@ TEST(EncodeDecodeInt32Test, CantRoundtripUint32) { CBORTokenizer tokenizer(SpanFrom(encoded)); // 0xdeadbeef is > std::numerical_limits::max(). EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); - EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error); + EXPECT_THAT(tokenizer.Status(), StatusIs(Error::CBOR_INVALID_INT32, 0u)); } TEST(EncodeDecodeInt32Test, DecodeErrorCases) { @@ -261,20 +240,26 @@ TEST(EncodeDecodeInt32Test, DecodeErrorCases) { std::vector data; std::string msg; }; - std::vector tests{ - {TestCase{ - {24}, - "additional info = 24 would require 1 byte of payload (but it's 0)"}, - TestCase{{27, 0xaa, 0xbb, 0xcc}, - "additional info = 27 would require 8 bytes of payload (but " - "it's 3)"}, - TestCase{{29}, "additional info = 29 isn't recognized"}}}; - + std::vector tests{{ + TestCase{ + {24}, + "additional info = 24 would require 1 byte of payload (but it's 0)"}, + TestCase{{27, 0xaa, 0xbb, 0xcc}, + "additional info = 27 would require 8 bytes of payload (but " + "it's 3)"}, + TestCase{{29}, "additional info = 29 isn't recognized"}, + TestCase{{1 << 5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + "Max UINT64 payload is outside the allowed range"}, + TestCase{{1 << 5 | 26, 0xff, 0xff, 0xff, 0xff}, + "Max UINT32 payload is outside the allowed range"}, + TestCase{{1 << 5 | 26, 0x80, 0x00, 0x00, 0x00}, + "UINT32 payload w/ high bit set is outside the allowed range"}, + }}; for (const TestCase& test : tests) { SCOPED_TRACE(test.msg); CBORTokenizer tokenizer(SpanFrom(test.data)); EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); - EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error); + EXPECT_THAT(tokenizer.Status(), StatusIs(Error::CBOR_INVALID_INT32, 0u)); } } @@ -436,7 +421,7 @@ TEST(EncodeDecodeString16Test, ErrorCases) { SCOPED_TRACE(test.msg); CBORTokenizer tokenizer(SpanFrom(test.data)); EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); - EXPECT_EQ(Error::CBOR_INVALID_STRING16, tokenizer.Status().error); + EXPECT_THAT(tokenizer.Status(), StatusIs(Error::CBOR_INVALID_STRING16, 0u)); } } @@ -485,7 +470,7 @@ TEST(EncodeDecodeString8Test, ErrorCases) { SCOPED_TRACE(test.msg); CBORTokenizer tokenizer(SpanFrom(test.data)); EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); - EXPECT_EQ(Error::CBOR_INVALID_STRING8, tokenizer.Status().error); + EXPECT_THAT(tokenizer.Status(), StatusIs(Error::CBOR_INVALID_STRING8, 0u)); } } @@ -560,7 +545,7 @@ TEST(EncodeDecodeBinaryTest, RoundtripsHelloWorld) { std::vector decoded; CBORTokenizer tokenizer(SpanFrom(encoded)); EXPECT_EQ(CBORTokenTag::BINARY, tokenizer.TokenTag()); - EXPECT_EQ(0, static_cast(tokenizer.Status().error)); + EXPECT_THAT(tokenizer.Status(), StatusIsOk()); decoded = std::vector(tokenizer.GetBinary().begin(), tokenizer.GetBinary().end()); EXPECT_THAT(decoded, ElementsAreArray(binary)); @@ -582,7 +567,7 @@ TEST(EncodeDecodeBinaryTest, ErrorCases) { SCOPED_TRACE(test.msg); CBORTokenizer tokenizer(SpanFrom(test.data)); EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); - EXPECT_EQ(Error::CBOR_INVALID_BINARY, tokenizer.Status().error); + EXPECT_THAT(tokenizer.Status(), StatusIs(Error::CBOR_INVALID_BINARY, 0u)); } } @@ -636,24 +621,85 @@ TEST(EncodeDecodeDoubleTest, RoundtripsAdditionalExamples) { } } +TEST(EncodeDecodeEnvelopesTest, MessageWithNestingAndEnvelopeContentsAccess) { + // This encodes and decodes the following message, which has some nesting + // and therefore envelopes. + // { "inner": { "foo" : "bar" } } + // The decoding is done with the Tokenizer, + // and we test both ::GetEnvelopeContents and GetEnvelope here. + std::vector message; + EnvelopeEncoder envelope; + envelope.EncodeStart(&message); + size_t pos_after_header = message.size(); + message.push_back(EncodeIndefiniteLengthMapStart()); + EncodeString8(SpanFrom("inner"), &message); + size_t pos_inside_inner = message.size(); + EnvelopeEncoder inner_envelope; + inner_envelope.EncodeStart(&message); + size_t pos_inside_inner_contents = message.size(); + message.push_back(EncodeIndefiniteLengthMapStart()); + EncodeString8(SpanFrom("foo"), &message); + EncodeString8(SpanFrom("bar"), &message); + message.push_back(EncodeStop()); + size_t pos_after_inner = message.size(); + inner_envelope.EncodeStop(&message); + message.push_back(EncodeStop()); + envelope.EncodeStop(&message); + + CBORTokenizer tokenizer(SpanFrom(message)); + ASSERT_EQ(CBORTokenTag::ENVELOPE, tokenizer.TokenTag()); + EXPECT_EQ(message.size(), tokenizer.GetEnvelope().size()); + EXPECT_EQ(message.data(), tokenizer.GetEnvelope().data()); + EXPECT_EQ(message.data() + pos_after_header, + tokenizer.GetEnvelopeContents().data()); + EXPECT_EQ(message.size() - pos_after_header, + tokenizer.GetEnvelopeContents().size()); + tokenizer.EnterEnvelope(); + ASSERT_EQ(CBORTokenTag::MAP_START, tokenizer.TokenTag()); + tokenizer.Next(); + ASSERT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag()); + EXPECT_EQ("inner", std::string(tokenizer.GetString8().begin(), + tokenizer.GetString8().end())); + tokenizer.Next(); + ASSERT_EQ(CBORTokenTag::ENVELOPE, tokenizer.TokenTag()); + EXPECT_EQ(message.data() + pos_inside_inner, tokenizer.GetEnvelope().data()); + EXPECT_EQ(pos_after_inner - pos_inside_inner, tokenizer.GetEnvelope().size()); + EXPECT_EQ(message.data() + pos_inside_inner_contents, + tokenizer.GetEnvelopeContents().data()); + EXPECT_EQ(pos_after_inner - pos_inside_inner_contents, + tokenizer.GetEnvelopeContents().size()); + tokenizer.EnterEnvelope(); + ASSERT_EQ(CBORTokenTag::MAP_START, tokenizer.TokenTag()); + tokenizer.Next(); + ASSERT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag()); + EXPECT_EQ("foo", std::string(tokenizer.GetString8().begin(), + tokenizer.GetString8().end())); + tokenizer.Next(); + ASSERT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag()); + EXPECT_EQ("bar", std::string(tokenizer.GetString8().begin(), + tokenizer.GetString8().end())); + tokenizer.Next(); + ASSERT_EQ(CBORTokenTag::STOP, tokenizer.TokenTag()); + tokenizer.Next(); + ASSERT_EQ(CBORTokenTag::STOP, tokenizer.TokenTag()); + tokenizer.Next(); + ASSERT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + // ============================================================================= // cbor::NewCBOREncoder - for encoding from a streaming parser // ============================================================================= -void EncodeUTF8ForTest(const std::string& key, std::vector* out) { - EncodeString8(SpanFrom(key), out); -} TEST(JSONToCBOREncoderTest, SevenBitStrings) { // When a string can be represented as 7 bit ASCII, the encoder will use the // STRING (major Type 3) type, so the actual characters end up as bytes on the // wire. std::vector encoded; Status status; - std::unique_ptr encoder = - NewCBOREncoder(&encoded, &status); + std::unique_ptr encoder = NewCBOREncoder(&encoded, &status); std::vector utf16 = {'f', 'o', 'o'}; encoder->HandleString16(span(utf16.data(), utf16.size())); - EXPECT_EQ(Error::OK, status.error); + EXPECT_THAT(status, StatusIsOk()); // Here we assert that indeed, seven bit strings are represented as // bytes on the wire, "foo" is just "foo". EXPECT_THAT(encoded, @@ -662,7 +708,7 @@ TEST(JSONToCBOREncoderTest, SevenBitStrings) { } TEST(JsonCborRoundtrip, EncodingDecoding) { - // Hits all the cases except binary and error in StreamingParserHandler, first + // Hits all the cases except binary and error in ParserHandler, first // parsing a JSON message into CBOR, then parsing it back from CBOR into JSON. std::string json = "{" @@ -676,10 +722,9 @@ TEST(JsonCborRoundtrip, EncodingDecoding) { "}"; std::vector encoded; Status status; - std::unique_ptr encoder = - NewCBOREncoder(&encoded, &status); + std::unique_ptr encoder = NewCBOREncoder(&encoded, &status); span ascii_in = SpanFrom(json); - json::ParseJSON(GetTestPlatform(), ascii_in, encoder.get()); + json::ParseJSON(ascii_in, encoder.get()); std::vector expected = { 0xd8, // envelope 0x5a, // byte string with 32 bit length @@ -722,10 +767,10 @@ TEST(JsonCborRoundtrip, EncodingDecoding) { // And now we roundtrip, decoding the message we just encoded. std::string decoded; - std::unique_ptr json_encoder = - NewJSONEncoder(&GetTestPlatform(), &decoded, &status); + std::unique_ptr json_encoder = + json::NewJSONEncoder(&decoded, &status); ParseCBOR(span(encoded.data(), encoded.size()), json_encoder.get()); - EXPECT_EQ(Error::OK, status.error); + EXPECT_THAT(status, StatusIsOk()); EXPECT_EQ(json, decoded); } @@ -738,21 +783,20 @@ TEST(JsonCborRoundtrip, MoreRoundtripExamples) { SCOPED_TRACE(std::string("example: ") + json); std::vector encoded; Status status; - std::unique_ptr encoder = - NewCBOREncoder(&encoded, &status); + std::unique_ptr encoder = NewCBOREncoder(&encoded, &status); span ascii_in = SpanFrom(json); - ParseJSON(GetTestPlatform(), ascii_in, encoder.get()); + json::ParseJSON(ascii_in, encoder.get()); std::string decoded; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &decoded, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&decoded, &status); ParseCBOR(span(encoded.data(), encoded.size()), json_writer.get()); - EXPECT_EQ(Error::OK, status.error); + EXPECT_THAT(status, StatusIsOk()); EXPECT_EQ(json, decoded); } } TEST(JSONToCBOREncoderTest, HelloWorldBinary_WithTripToJson) { - // The StreamingParserHandler::HandleBinary is a special case: The JSON parser + // The ParserHandler::HandleBinary is a special case: The JSON parser // will never call this method, because JSON does not natively support the // binary type. So, we can't fully roundtrip. However, the other direction // works: binary will be rendered in JSON, as a base64 string. So, we make @@ -761,8 +805,7 @@ TEST(JSONToCBOREncoderTest, HelloWorldBinary_WithTripToJson) { // containing "Hello, world.". std::vector encoded; Status status; - std::unique_ptr encoder = - NewCBOREncoder(&encoded, &status); + std::unique_ptr encoder = NewCBOREncoder(&encoded, &status); encoder->HandleMapBegin(); // Emit a key. std::vector key = {'f', 'o', 'o'}; @@ -772,15 +815,14 @@ TEST(JSONToCBOREncoderTest, HelloWorldBinary_WithTripToJson) { encoder->HandleBinary(SpanFrom(std::vector{ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})); encoder->HandleMapEnd(); - EXPECT_EQ(Error::OK, status.error); + EXPECT_THAT(status, StatusIsOk()); // Now drive the json writer via the CBOR decoder. std::string decoded; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &decoded, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&decoded, &status); ParseCBOR(SpanFrom(encoded), json_writer.get()); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); + EXPECT_THAT(status, StatusIsOk()); // "Hello, world." in base64 is "SGVsbG8sIHdvcmxkLg==". EXPECT_EQ("{\"foo\":\"SGVsbG8sIHdvcmxkLg==\"}", decoded); } @@ -796,18 +838,18 @@ TEST(ParseCBORTest, ParseEmptyCBORMessage) { std::vector in = {0xd8, 0x5a, 0, 0, 0, 2, 0xbf, 0xff}; std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(in.data(), in.size()), json_writer.get()); - EXPECT_EQ(Error::OK, status.error); + EXPECT_THAT(status, StatusIsOk()); EXPECT_EQ("{}", out); } TEST(ParseCBORTest, ParseCBORHelloWorld) { const uint8_t kPayloadLen = 27; std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen}; - bytes.push_back(0xbf); // start indef length map. - EncodeString8(SpanFrom("msg"), &bytes); // key: msg + bytes.push_back(0xbf); // start indef length map. + EncodeString8(SpanFrom("msg"), &bytes); // key: msg // Now write the value, the familiar "Hello, 🌎." where the globe is expressed // as two utf16 chars. bytes.push_back(/*major type=*/2 << 5 | /*additional info=*/20); @@ -820,10 +862,10 @@ TEST(ParseCBORTest, ParseCBORHelloWorld) { std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::OK, status.error); + EXPECT_THAT(status, StatusIsOk()); EXPECT_EQ("{\"msg\":\"Hello, \\ud83c\\udf0e.\"}", out); } @@ -845,10 +887,10 @@ TEST(ParseCBORTest, UTF8IsSupportedInKeys) { std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::OK, status.error); + EXPECT_THAT(status, StatusIsOk()); EXPECT_EQ("{\"\\ud83c\\udf0e\":\"\\u263e\"}", out); } @@ -856,24 +898,10 @@ TEST(ParseCBORTest, NoInputError) { std::vector in = {}; std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(in.data(), in.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_NO_INPUT, status.error); - EXPECT_EQ("", out); -} - -TEST(ParseCBORTest, InvalidStartByteError) { - // Here we test that some actual json, which usually starts with {, - // is not considered CBOR. CBOR messages must start with 0x5a, the - // envelope start byte. - std::string json = "{\"msg\": \"Hello, world.\"}"; - std::string out; - Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - ParseCBOR(SpanFrom(json), json_writer.get()); - EXPECT_EQ(Error::CBOR_INVALID_START_BYTE, status.error); + EXPECT_THAT(status, StatusIs(Error::CBOR_NO_INPUT, 0u)); EXPECT_EQ("", out); } @@ -886,11 +914,11 @@ TEST(ParseCBORTest, UnexpectedEofExpectedValueError) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, status.error); - EXPECT_EQ(bytes.size(), status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, + bytes.size())); EXPECT_EQ("", out); } @@ -904,11 +932,11 @@ TEST(ParseCBORTest, UnexpectedEofInArrayError) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, status.error); - EXPECT_EQ(bytes.size(), status.pos); + EXPECT_THAT(status, + StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, bytes.size())); EXPECT_EQ("", out); } @@ -919,11 +947,52 @@ TEST(ParseCBORTest, UnexpectedEofInMapError) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_THAT(status, StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_MAP, 7u)); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, NoEmptyEnvelopesAllowed) { + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, 0}; // envelope + std::string out; + Status status; + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_THAT(status, StatusIs(Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE, + bytes.size())); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, OnlyMapsAndArraysSupportedInsideEnvelopes) { + // The top level is a map with key "foo", and the value + // is an envelope that contains just a number (1). We don't + // allow numbers to be contained in an envelope though, only + // maps and arrays. + constexpr uint8_t kPayloadLen = 1; + std::vector bytes = {0xd8, + 0x5a, + 0, + 0, + 0, + kPayloadLen, // envelope + EncodeIndefiniteLengthMapStart()}; + EncodeString8(SpanFrom("foo"), &bytes); + for (uint8_t byte : {0xd8, 0x5a, 0, 0, 0, /*payload_len*/ 1}) + bytes.emplace_back(byte); + size_t error_pos = bytes.size(); + bytes.push_back(1); // Envelope contents / payload = number 1. + bytes.emplace_back(EncodeStop()); + + std::string out; + Status status; + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_MAP, status.error); - EXPECT_EQ(7u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE, + error_pos)); EXPECT_EQ("", out); } @@ -936,11 +1005,10 @@ TEST(ParseCBORTest, InvalidMapKeyError) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_INVALID_MAP_KEY, status.error); - EXPECT_EQ(7u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_MAP_KEY, 7u)); EXPECT_EQ("", out); } @@ -967,22 +1035,20 @@ TEST(ParseCBORTest, StackLimitExceededError) { std::vector bytes = MakeNestedCBOR(3); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); + EXPECT_THAT(status, StatusIsOk()); EXPECT_EQ("{\"key\":{\"key\":{\"key\":\"innermost_value\"}}}", out); } { // Depth 300: no stack limit exceeded. std::vector bytes = MakeNestedCBOR(300); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); + EXPECT_THAT(status, StatusIsOk()); } // We just want to know the length of one opening map so we can compute @@ -998,21 +1064,21 @@ TEST(ParseCBORTest, StackLimitExceededError) { std::vector bytes = MakeNestedCBOR(301); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error); - EXPECT_EQ(opening_segment_size * 301, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_STACK_LIMIT_EXCEEDED, + opening_segment_size * 301)); } { // Depth 320: still limit exceeded, and at the same pos as for 1001 std::vector bytes = MakeNestedCBOR(320); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error); - EXPECT_EQ(opening_segment_size * 301, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_STACK_LIMIT_EXCEEDED, + opening_segment_size * 301)); } } @@ -1027,11 +1093,10 @@ TEST(ParseCBORTest, UnsupportedValueError) { std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_UNSUPPORTED_VALUE, status.error); - EXPECT_EQ(error_pos, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_UNSUPPORTED_VALUE, error_pos)); EXPECT_EQ("", out); } @@ -1050,11 +1115,10 @@ TEST(ParseCBORTest, InvalidString16Error) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_INVALID_STRING16, status.error); - EXPECT_EQ(error_pos, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_STRING16, error_pos)); EXPECT_EQ("", out); } @@ -1070,11 +1134,10 @@ TEST(ParseCBORTest, InvalidString8Error) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_INVALID_STRING8, status.error); - EXPECT_EQ(error_pos, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_STRING8, error_pos)); EXPECT_EQ("", out); } @@ -1092,11 +1155,10 @@ TEST(ParseCBORTest, InvalidBinaryError) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_INVALID_BINARY, status.error); - EXPECT_EQ(error_pos, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_BINARY, error_pos)); EXPECT_EQ("", out); } @@ -1113,11 +1175,10 @@ TEST(ParseCBORTest, InvalidDoubleError) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_INVALID_DOUBLE, status.error); - EXPECT_EQ(error_pos, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_DOUBLE, error_pos)); EXPECT_EQ("", out); } @@ -1134,34 +1195,55 @@ TEST(ParseCBORTest, InvalidSignedError) { EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_INVALID_INT32, status.error); - EXPECT_EQ(error_pos, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_INT32, error_pos)); EXPECT_EQ("", out); } TEST(ParseCBORTest, TrailingJunk) { - constexpr uint8_t kPayloadLen = 35; + constexpr uint8_t kPayloadLen = 12; std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope 0xbf}; // map start EncodeString8(SpanFrom("key"), &bytes); EncodeString8(SpanFrom("value"), &bytes); bytes.push_back(0xff); // Up to here, it's a perfectly fine msg. + ASSERT_EQ(kPayloadLen, bytes.size() - 6); size_t error_pos = bytes.size(); + // Now write some trailing junk after the message. EncodeString8(SpanFrom("trailing junk"), &bytes); - internals::WriteTokenStart(MajorType::UNSIGNED, std::numeric_limits::max(), &bytes); - EXPECT_EQ(kPayloadLen, bytes.size() - 6); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); - EXPECT_EQ(Error::CBOR_TRAILING_JUNK, status.error); - EXPECT_EQ(error_pos, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_TRAILING_JUNK, error_pos)); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, EnvelopeContentsLengthMismatch) { + constexpr uint8_t kPartialPayloadLen = 5; + std::vector bytes = {0xd8, 0x5a, 0, + 0, 0, kPartialPayloadLen, // envelope + 0xbf}; // map start + EncodeString8(SpanFrom("key"), &bytes); + // kPartialPayloadLen would need to indicate the length of the entire map, + // all the way past the 0xff map stop character. Instead, it only covers + // a portion of the map. + EXPECT_EQ(bytes.size() - 6, kPartialPayloadLen); + EncodeString8(SpanFrom("value"), &bytes); + bytes.push_back(0xff); // map stop + + std::string out; + Status status; + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_THAT(status, StatusIs(Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH, + bytes.size())); EXPECT_EQ("", out); } @@ -1175,7 +1257,7 @@ class AppendString8EntryToMapTest : public ::testing::Test {}; using ContainerTestTypes = ::testing::Types, std::string>; TYPED_TEST_SUITE(AppendString8EntryToMapTest, ContainerTestTypes); -TYPED_TEST(AppendString8EntryToMapTest, AppendsEntrySuccessfully) { +TEST(AppendString8EntryToMapTest, AppendsEntrySuccessfully) { constexpr uint8_t kPayloadLen = 12; std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope 0xbf}; // map start @@ -1185,654 +1267,88 @@ TYPED_TEST(AppendString8EntryToMapTest, AppendsEntrySuccessfully) { bytes.push_back(0xff); // A perfectly fine cbor message. EXPECT_EQ(kPayloadLen, bytes.size() - pos_before_payload); - TypeParam msg(bytes.begin(), bytes.end()); + std::vector msg(bytes.begin(), bytes.end()); Status status = AppendString8EntryToCBORMap(SpanFrom("foo"), SpanFrom("bar"), &msg); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); + EXPECT_THAT(status, StatusIsOk()); std::string out; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(SpanFrom(msg), json_writer.get()); EXPECT_EQ("{\"key\":\"value\",\"foo\":\"bar\"}", out); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); + EXPECT_THAT(status, StatusIsOk()); } TYPED_TEST(AppendString8EntryToMapTest, AppendThreeEntries) { std::vector encoded = { 0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()}; - EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key"), - SpanFrom("value"), &encoded) - .error); - EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key1"), - SpanFrom("value1"), &encoded) - .error); - EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key2"), - SpanFrom("value2"), &encoded) - .error); + EXPECT_THAT( + AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &encoded), + StatusIsOk()); + EXPECT_THAT(AppendString8EntryToCBORMap(SpanFrom("key1"), SpanFrom("value1"), + &encoded), + StatusIsOk()); + EXPECT_THAT(AppendString8EntryToCBORMap(SpanFrom("key2"), SpanFrom("value2"), + &encoded), + StatusIsOk()); TypeParam msg(encoded.begin(), encoded.end()); std::string out; Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); + std::unique_ptr json_writer = + json::NewJSONEncoder(&out, &status); ParseCBOR(SpanFrom(msg), json_writer.get()); EXPECT_EQ("{\"key\":\"value\",\"key1\":\"value1\",\"key2\":\"value2\"}", out); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); + EXPECT_THAT(status, StatusIsOk()); } -TYPED_TEST(AppendString8EntryToMapTest, MapStartExpected_Error) { - std::vector bytes = { +TEST(AppendString8EntryToMapTest, MapStartExpected_Error) { + std::vector msg = { 0xd8, 0x5a, 0, 0, 0, 1, EncodeIndefiniteLengthArrayStart()}; - TypeParam msg(bytes.begin(), bytes.end()); Status status = AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); - EXPECT_EQ(Error::CBOR_MAP_START_EXPECTED, status.error); - EXPECT_EQ(6u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_MAP_START_EXPECTED, 6u)); } -TYPED_TEST(AppendString8EntryToMapTest, MapStopExpected_Error) { - std::vector bytes = { +TEST(AppendString8EntryToMapTest, MapStopExpected_Error) { + std::vector msg = { 0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), 42}; - TypeParam msg(bytes.begin(), bytes.end()); Status status = AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); - EXPECT_EQ(Error::CBOR_MAP_STOP_EXPECTED, status.error); - EXPECT_EQ(7u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_MAP_STOP_EXPECTED, 7u)); } -TYPED_TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) { +TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) { { // Second byte is wrong. - std::vector bytes = { + std::vector msg = { 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop(), 0}; - TypeParam msg(bytes.begin(), bytes.end()); Status status = AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); - EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error); - EXPECT_EQ(0u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0u)); } { // Second byte is wrong. - std::vector bytes = { + std::vector msg = { 0xd8, 0x7a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()}; - TypeParam msg(bytes.begin(), bytes.end()); Status status = AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); - EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error); - EXPECT_EQ(0u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0u)); } { // Invalid envelope size example. - std::vector bytes = { + std::vector msg = { 0xd8, 0x5a, 0, 0, 0, 3, EncodeIndefiniteLengthMapStart(), EncodeStop(), }; - TypeParam msg(bytes.begin(), bytes.end()); Status status = AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); - EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error); - EXPECT_EQ(0u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0u)); } { // Invalid envelope size example. - std::vector bytes = { + std::vector msg = { 0xd8, 0x5a, 0, 0, 0, 1, EncodeIndefiniteLengthMapStart(), EncodeStop(), }; - TypeParam msg(bytes.begin(), bytes.end()); Status status = AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); - EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error); - EXPECT_EQ(0u, status.pos); + EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0u)); } } } // namespace cbor - -namespace json { - -// ============================================================================= -// json::NewJSONEncoder - for encoding streaming parser events as JSON -// ============================================================================= - -void WriteUTF8AsUTF16(StreamingParserHandler* writer, const std::string& utf8) { - writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8)))); -} - -TEST(JsonStdStringWriterTest, HelloWorld) { - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - writer->HandleMapBegin(); - WriteUTF8AsUTF16(writer.get(), "msg1"); - WriteUTF8AsUTF16(writer.get(), "Hello, 🌎."); - std::string key = "msg1-as-utf8"; - std::string value = "Hello, 🌎."; - writer->HandleString8(SpanFrom(key)); - writer->HandleString8(SpanFrom(value)); - WriteUTF8AsUTF16(writer.get(), "msg2"); - WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\""); - WriteUTF8AsUTF16(writer.get(), "nested"); - writer->HandleMapBegin(); - WriteUTF8AsUTF16(writer.get(), "double"); - writer->HandleDouble(3.1415); - WriteUTF8AsUTF16(writer.get(), "int"); - writer->HandleInt32(-42); - WriteUTF8AsUTF16(writer.get(), "bool"); - writer->HandleBool(false); - WriteUTF8AsUTF16(writer.get(), "null"); - writer->HandleNull(); - writer->HandleMapEnd(); - WriteUTF8AsUTF16(writer.get(), "array"); - writer->HandleArrayBegin(); - writer->HandleInt32(1); - writer->HandleInt32(2); - writer->HandleInt32(3); - writer->HandleArrayEnd(); - writer->HandleMapEnd(); - EXPECT_TRUE(status.ok()); - EXPECT_EQ( - "{\"msg1\":\"Hello, \\ud83c\\udf0e.\"," - "\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\"," - "\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\"," - "\"nested\":{\"double\":3.1415,\"int\":-42," - "\"bool\":false,\"null\":null},\"array\":[1,2,3]}", - out); -} - -TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) { - // JSON can't represent +Infinity, -Infinity, or NaN. - // So in practice it's mapped to null. - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - writer->HandleMapBegin(); - writer->HandleString8(SpanFrom("Infinity")); - writer->HandleDouble(std::numeric_limits::infinity()); - writer->HandleString8(SpanFrom("-Infinity")); - writer->HandleDouble(-std::numeric_limits::infinity()); - writer->HandleString8(SpanFrom("NaN")); - writer->HandleDouble(std::numeric_limits::quiet_NaN()); - writer->HandleMapEnd(); - EXPECT_TRUE(status.ok()); - EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out); -} - -TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) { - // The encoder emits binary submitted to StreamingParserHandler::HandleBinary - // as base64. The following three examples are taken from - // https://en.wikipedia.org/wiki/Base64. - { - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - writer->HandleBinary(SpanFrom(std::vector({'M', 'a', 'n'}))); - EXPECT_TRUE(status.ok()); - EXPECT_EQ("\"TWFu\"", out); - } - { - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - writer->HandleBinary(SpanFrom(std::vector({'M', 'a'}))); - EXPECT_TRUE(status.ok()); - EXPECT_EQ("\"TWE=\"", out); - } - { - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - writer->HandleBinary(SpanFrom(std::vector({'M'}))); - EXPECT_TRUE(status.ok()); - EXPECT_EQ("\"TQ==\"", out); - } - { // "Hello, world.", verified with base64decode.org. - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - writer->HandleBinary(SpanFrom(std::vector( - {'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'}))); - EXPECT_TRUE(status.ok()); - EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out); - } -} - -TEST(JsonStdStringWriterTest, HandlesErrors) { - // When an error is sent via HandleError, it saves it in the provided - // status and clears the output. - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&GetTestPlatform(), &out, &status); - writer->HandleMapBegin(); - WriteUTF8AsUTF16(writer.get(), "msg1"); - writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42}); - EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, status.error); - EXPECT_EQ(42u, status.pos); - EXPECT_EQ("", out); -} - -// We'd use Gmock but unfortunately it only handles copyable return types. -class MockPlatform : public Platform { - public: - // Not implemented. - bool StrToD(const char* str, double* result) const override { return false; } - - // A map with pre-registered responses for DToSTr. - std::map dtostr_responses_; - - std::unique_ptr DToStr(double value) const override { - auto it = dtostr_responses_.find(value); - CHECK(it != dtostr_responses_.end()); - const std::string& str = it->second; - std::unique_ptr response(new char[str.size() + 1]); - memcpy(response.get(), str.c_str(), str.size() + 1); - return response; - } -}; - -TEST(JsonStdStringWriterTest, DoubleToString) { - // This "broken" platform responds without the leading 0 before the - // decimal dot, so it'd be invalid JSON. - MockPlatform platform; - platform.dtostr_responses_[.1] = ".1"; - platform.dtostr_responses_[-.7] = "-.7"; - - std::string out; - Status status; - std::unique_ptr writer = - NewJSONEncoder(&platform, &out, &status); - writer->HandleArrayBegin(); - writer->HandleDouble(.1); - writer->HandleDouble(-.7); - writer->HandleArrayEnd(); - EXPECT_EQ("[0.1,-0.7]", out); -} - -// ============================================================================= -// json::ParseJSON - for receiving streaming parser events for JSON -// ============================================================================= - -class Log : public StreamingParserHandler { - public: - void HandleMapBegin() override { log_ << "map begin\n"; } - - void HandleMapEnd() override { log_ << "map end\n"; } - - void HandleArrayBegin() override { log_ << "array begin\n"; } - - void HandleArrayEnd() override { log_ << "array end\n"; } - - void HandleString8(span chars) override { - log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n"; - } - - void HandleString16(span chars) override { - log_ << "string16: " << UTF16ToUTF8(chars) << "\n"; - } - - void HandleBinary(span bytes) override { - // JSON doesn't have native support for arbitrary bytes, so our parser will - // never call this. - CHECK(false); - } - - void HandleDouble(double value) override { - log_ << "double: " << value << "\n"; - } - - void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; } - - void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; } - - void HandleNull() override { log_ << "null\n"; } - - void HandleError(Status status) override { status_ = status; } - - std::string str() const { return status_.ok() ? log_.str() : ""; } - - Status status() const { return status_; } - - private: - std::ostringstream log_; - Status status_; -}; - -class JsonParserTest : public ::testing::Test { - protected: - Log log_; -}; - -TEST_F(JsonParserTest, SimpleDictionary) { - std::string json = "{\"foo\": 42}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: foo\n" - "int: 42\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, Whitespace) { - std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: msg\n" - "string16: Hello, world.\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, NestedDictionary) { - std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: foo\n" - "map begin\n" - "string16: bar\n" - "map begin\n" - "string16: baz\n" - "int: 1\n" - "map end\n" - "string16: bar2\n" - "int: 2\n" - "map end\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, Doubles) { - std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: foo\n" - "double: 3.1415\n" - "string16: bar\n" - "double: 3.1415\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, Unicode) { - // Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16. - std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: msg\n" - "string16: Hello, 🌎.\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, Unicode_ParseUtf16) { - // Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E. - // Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19. - - // We provide the moon with json escape, but the earth as utf16 input. - // Either way they arrive as utf8 (after decoding in log_.str()). - std::vector json = - UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}")); - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: space\n" - "string16: 🌎 🌙.\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, Unicode_ParseUtf8) { - // Used below: - // гласность - example for 2 byte utf8, Russian word "glasnost" - // 屋 - example for 3 byte utf8, Chinese word for "house" - // 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E. - // 🌙 - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19. - - // We provide the moon with json escape, but the earth as utf8 input. - // Either way they arrive as utf8 (after decoding in log_.str()). - std::string json = - "{" - "\"escapes\": \"\\uD83C\\uDF19\"," - "\"2 byte\":\"гласность\"," - "\"3 byte\":\"屋\"," - "\"4 byte\":\"🌎\"" - "}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: escapes\n" - "string16: 🌙\n" - "string16: 2 byte\n" - "string16: гласность\n" - "string16: 3 byte\n" - "string16: 屋\n" - "string16: 4 byte\n" - "string16: 🌎\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, UnprocessedInputRemainsError) { - // Trailing junk after the valid JSON. - std::string json = "{\"foo\": 3.1415} junk"; - size_t junk_idx = json.find("junk"); - EXPECT_NE(junk_idx, std::string::npos); - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, log_.status().error); - EXPECT_EQ(junk_idx, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -std::string MakeNestedJson(int depth) { - std::string json; - for (int ii = 0; ii < depth; ++ii) - json += "{\"foo\":"; - json += "42"; - for (int ii = 0; ii < depth; ++ii) - json += "}"; - return json; -} - -TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) { - // kStackLimit is 300 (see json_parser.cc). First let's - // try with a small nested example. - std::string json_3 = MakeNestedJson(3); - ParseJSON(GetTestPlatform(), SpanFrom(json_3), &log_); - EXPECT_TRUE(log_.status().ok()); - EXPECT_EQ( - "map begin\n" - "string16: foo\n" - "map begin\n" - "string16: foo\n" - "map begin\n" - "string16: foo\n" - "int: 42\n" - "map end\n" - "map end\n" - "map end\n", - log_.str()); -} - -TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) { - // Now with kStackLimit (300). - std::string json_limit = MakeNestedJson(300); - ParseJSON(GetTestPlatform(), - span(reinterpret_cast(json_limit.data()), - json_limit.size()), - &log_); - EXPECT_TRUE(log_.status().ok()); -} - -TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) { - // Now with kStackLimit + 1 (301) - it exceeds in the innermost instance. - std::string exceeded = MakeNestedJson(301); - ParseJSON(GetTestPlatform(), SpanFrom(exceeded), &log_); - EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error); - EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos); -} - -TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) { - // Now way past the limit. Still, the point of exceeding is 301. - std::string far_out = MakeNestedJson(320); - ParseJSON(GetTestPlatform(), SpanFrom(far_out), &log_); - EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error); - EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos); -} - -TEST_F(JsonParserTest, NoInputError) { - std::string json = ""; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_NO_INPUT, log_.status().error); - EXPECT_EQ(0u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, InvalidTokenError) { - std::string json = "|"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_INVALID_TOKEN, log_.status().error); - EXPECT_EQ(0u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, InvalidNumberError) { - // Mantissa exceeds max (the constant used here is int64_t max). - std::string json = "1E9223372036854775807"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_INVALID_NUMBER, log_.status().error); - EXPECT_EQ(0u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, InvalidStringError) { - // \x22 is an unsupported escape sequence - std::string json = "\"foo\\x22\""; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_INVALID_STRING, log_.status().error); - EXPECT_EQ(0u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, UnexpectedArrayEndError) { - std::string json = "[1,2,]"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, log_.status().error); - EXPECT_EQ(5u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) { - std::string json = "[1,2 2"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED, - log_.status().error); - EXPECT_EQ(5u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, StringLiteralExpectedError) { - // There's an error because the key bar, a string, is not terminated. - std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, log_.status().error); - EXPECT_EQ(16u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, ColonExpectedError) { - std::string json = "{\"foo\", 42}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_COLON_EXPECTED, log_.status().error); - EXPECT_EQ(6u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, UnexpectedMapEndError) { - std::string json = "{\"foo\": 42, }"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_MAP_END, log_.status().error); - EXPECT_EQ(12u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, CommaOrMapEndExpectedError) { - // The second separator should be a comma. - std::string json = "{\"foo\": 3.1415: \"bar\": 0}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, log_.status().error); - EXPECT_EQ(14u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -TEST_F(JsonParserTest, ValueExpectedError) { - std::string json = "}"; - ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); - EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, log_.status().error); - EXPECT_EQ(0u, log_.status().pos); - EXPECT_EQ("", log_.str()); -} - -template -class ConvertJSONToCBORTest : public ::testing::Test {}; - -using ContainerTestTypes = ::testing::Types, std::string>; -TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes); - -TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) { - std::string json_in = "{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}"; - TypeParam json(json_in.begin(), json_in.end()); - TypeParam cbor; - { - Status status = ConvertJSONToCBOR(GetTestPlatform(), SpanFrom(json), &cbor); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); - } - TypeParam roundtrip_json; - { - Status status = - ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); - } - EXPECT_EQ(json, roundtrip_json); -} - -TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) { - std::vector json16 = { - '{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l', - 'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't', - '"', ':', '[', '1', ',', '2', ',', '3', ']', '}'}; - TypeParam cbor; - { - Status status = ConvertJSONToCBOR( - GetTestPlatform(), span(json16.data(), json16.size()), &cbor); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); - } - TypeParam roundtrip_json; - { - Status status = - ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json); - EXPECT_EQ(Error::OK, status.error); - EXPECT_EQ(Status::npos(), status.pos); - } - std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}"; - TypeParam expected_json(json.begin(), json.end()); - EXPECT_EQ(expected_json, roundtrip_json); -} -} // namespace json -} // namespace v8_inspector_protocol_encoding +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/dispatch.cc b/tools/inspector_protocol/crdtp/dispatch.cc new file mode 100644 index 00000000000000..4721f352d84dca --- /dev/null +++ b/tools/inspector_protocol/crdtp/dispatch.cc @@ -0,0 +1,604 @@ +// Copyright 2020 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 "dispatch.h" + +#include +#include "cbor.h" +#include "error_support.h" +#include "find_by_first.h" +#include "frontend_channel.h" +#include "protocol_core.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// DispatchResponse - Error status and chaining / fall through +// ============================================================================= + +// static +DispatchResponse DispatchResponse::Success() { + DispatchResponse result; + result.code_ = DispatchCode::SUCCESS; + return result; +} + +// static +DispatchResponse DispatchResponse::FallThrough() { + DispatchResponse result; + result.code_ = DispatchCode::FALL_THROUGH; + return result; +} + +// static +DispatchResponse DispatchResponse::ParseError(std::string message) { + DispatchResponse result; + result.code_ = DispatchCode::PARSE_ERROR; + result.message_ = std::move(message); + return result; +} + +// static +DispatchResponse DispatchResponse::InvalidRequest(std::string message) { + DispatchResponse result; + result.code_ = DispatchCode::INVALID_REQUEST; + result.message_ = std::move(message); + return result; +} + +// static +DispatchResponse DispatchResponse::MethodNotFound(std::string message) { + DispatchResponse result; + result.code_ = DispatchCode::METHOD_NOT_FOUND; + result.message_ = std::move(message); + return result; +} + +// static +DispatchResponse DispatchResponse::InvalidParams(std::string message) { + DispatchResponse result; + result.code_ = DispatchCode::INVALID_PARAMS; + result.message_ = std::move(message); + return result; +} + +// static +DispatchResponse DispatchResponse::InternalError() { + DispatchResponse result; + result.code_ = DispatchCode::INTERNAL_ERROR; + result.message_ = "Internal error"; + return result; +} + +// static +DispatchResponse DispatchResponse::ServerError(std::string message) { + DispatchResponse result; + result.code_ = DispatchCode::SERVER_ERROR; + result.message_ = std::move(message); + return result; +} + +// ============================================================================= +// Dispatchable - a shallow parser for CBOR encoded DevTools messages +// ============================================================================= +namespace { +constexpr size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t); +} // namespace + +Dispatchable::Dispatchable(span serialized) : serialized_(serialized) { + Status s = cbor::CheckCBORMessage(serialized); + if (!s.ok()) { + status_ = {Error::MESSAGE_MUST_BE_AN_OBJECT, s.pos}; + return; + } + cbor::CBORTokenizer tokenizer(serialized); + if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) { + status_ = 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() == cbor::CBORTokenTag::ENVELOPE); + + // Before we enter the envelope, we save the position that we + // expect to see after we're done parsing the envelope contents. + // This way we can compare and produce an error if the contents + // didn't fit exactly into the envelope length. + const size_t pos_past_envelope = tokenizer.Status().pos + + kEncodedEnvelopeHeaderSize + + tokenizer.GetEnvelopeContents().size(); + tokenizer.EnterEnvelope(); + if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) { + status_ = tokenizer.Status(); + return; + } + if (tokenizer.TokenTag() != cbor::CBORTokenTag::MAP_START) { + status_ = {Error::MESSAGE_MUST_BE_AN_OBJECT, tokenizer.Status().pos}; + return; + } + assert(tokenizer.TokenTag() == cbor::CBORTokenTag::MAP_START); + tokenizer.Next(); // Now we should be pointed at the map key. + while (tokenizer.TokenTag() != cbor::CBORTokenTag::STOP) { + switch (tokenizer.TokenTag()) { + case cbor::CBORTokenTag::DONE: + status_ = + Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer.Status().pos}; + return; + case cbor::CBORTokenTag::ERROR_VALUE: + status_ = tokenizer.Status(); + return; + case cbor::CBORTokenTag::STRING8: + if (!MaybeParseProperty(&tokenizer)) + return; + break; + default: + // We require the top-level keys to be UTF8 (US-ASCII in practice). + status_ = Status{Error::CBOR_INVALID_MAP_KEY, tokenizer.Status().pos}; + return; + } + } + tokenizer.Next(); + if (!has_call_id_) { + status_ = Status{Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY, + tokenizer.Status().pos}; + return; + } + if (method_.empty()) { + status_ = Status{Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY, + tokenizer.Status().pos}; + return; + } + // The contents of the envelope parsed OK, now check that we're at + // the expected position. + if (pos_past_envelope != tokenizer.Status().pos) { + status_ = Status{Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH, + tokenizer.Status().pos}; + return; + } + if (tokenizer.TokenTag() != cbor::CBORTokenTag::DONE) { + status_ = Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos}; + return; + } +} + +bool Dispatchable::ok() const { + return status_.ok(); +} + +DispatchResponse Dispatchable::DispatchError() const { + // TODO(johannes): Replace with DCHECK / similar? + if (status_.ok()) + return DispatchResponse::Success(); + + if (status_.IsMessageError()) + return DispatchResponse::InvalidRequest(status_.Message()); + return DispatchResponse::ParseError(status_.ToASCIIString()); +} + +bool Dispatchable::MaybeParseProperty(cbor::CBORTokenizer* tokenizer) { + span property_name = tokenizer->GetString8(); + if (SpanEquals(SpanFrom("id"), property_name)) + return MaybeParseCallId(tokenizer); + if (SpanEquals(SpanFrom("method"), property_name)) + return MaybeParseMethod(tokenizer); + if (SpanEquals(SpanFrom("params"), property_name)) + return MaybeParseParams(tokenizer); + if (SpanEquals(SpanFrom("sessionId"), property_name)) + return MaybeParseSessionId(tokenizer); + status_ = + Status{Error::MESSAGE_HAS_UNKNOWN_PROPERTY, tokenizer->Status().pos}; + return false; +} + +bool Dispatchable::MaybeParseCallId(cbor::CBORTokenizer* tokenizer) { + if (has_call_id_) { + status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos}; + return false; + } + tokenizer->Next(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::INT32) { + status_ = Status{Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY, + tokenizer->Status().pos}; + return false; + } + call_id_ = tokenizer->GetInt32(); + has_call_id_ = true; + tokenizer->Next(); + return true; +} + +bool Dispatchable::MaybeParseMethod(cbor::CBORTokenizer* tokenizer) { + if (!method_.empty()) { + status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos}; + return false; + } + tokenizer->Next(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) { + status_ = Status{Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY, + tokenizer->Status().pos}; + return false; + } + method_ = tokenizer->GetString8(); + tokenizer->Next(); + return true; +} + +bool Dispatchable::MaybeParseParams(cbor::CBORTokenizer* tokenizer) { + if (params_seen_) { + status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos}; + return false; + } + params_seen_ = true; + tokenizer->Next(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::NULL_VALUE) { + tokenizer->Next(); + return true; + } + if (tokenizer->TokenTag() != cbor::CBORTokenTag::ENVELOPE) { + status_ = Status{Error::MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY, + tokenizer->Status().pos}; + return false; + } + params_ = tokenizer->GetEnvelope(); + tokenizer->Next(); + return true; +} + +bool Dispatchable::MaybeParseSessionId(cbor::CBORTokenizer* tokenizer) { + if (!session_id_.empty()) { + status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos}; + return false; + } + tokenizer->Next(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) { + status_ = Status{Error::MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY, + tokenizer->Status().pos}; + return false; + } + session_id_ = tokenizer->GetString8(); + tokenizer->Next(); + return true; +} + +namespace { +class ProtocolError : public Serializable { + public: + explicit ProtocolError(DispatchResponse dispatch_response) + : dispatch_response_(std::move(dispatch_response)) {} + + void AppendSerialized(std::vector* out) const override { + Status status; + std::unique_ptr encoder = cbor::NewCBOREncoder(out, &status); + encoder->HandleMapBegin(); + if (has_call_id_) { + encoder->HandleString8(SpanFrom("id")); + encoder->HandleInt32(call_id_); + } + encoder->HandleString8(SpanFrom("error")); + encoder->HandleMapBegin(); + encoder->HandleString8(SpanFrom("code")); + encoder->HandleInt32(static_cast(dispatch_response_.Code())); + encoder->HandleString8(SpanFrom("message")); + encoder->HandleString8(SpanFrom(dispatch_response_.Message())); + if (!data_.empty()) { + encoder->HandleString8(SpanFrom("data")); + encoder->HandleString8(SpanFrom(data_)); + } + encoder->HandleMapEnd(); + encoder->HandleMapEnd(); + assert(status.ok()); + } + + void SetCallId(int call_id) { + has_call_id_ = true; + call_id_ = call_id; + } + void SetData(std::string data) { data_ = std::move(data); } + + private: + const DispatchResponse dispatch_response_; + std::string data_; + int call_id_ = 0; + bool has_call_id_ = false; +}; +} // namespace + +// ============================================================================= +// Helpers for creating protocol cresponses and notifications. +// ============================================================================= + +std::unique_ptr CreateErrorResponse( + int call_id, + DispatchResponse dispatch_response, + const ErrorSupport* errors) { + auto protocol_error = + std::make_unique(std::move(dispatch_response)); + protocol_error->SetCallId(call_id); + if (errors && !errors->Errors().empty()) { + protocol_error->SetData( + std::string(errors->Errors().begin(), errors->Errors().end())); + } + return protocol_error; +} + +std::unique_ptr CreateErrorResponse( + int call_id, + DispatchResponse dispatch_response, + const DeserializerState& state) { + auto protocol_error = + std::make_unique(std::move(dispatch_response)); + protocol_error->SetCallId(call_id); + // TODO(caseq): should we plumb the call name here? + protocol_error->SetData(state.ErrorMessage(MakeSpan("params"))); + return protocol_error; +} + +std::unique_ptr CreateErrorNotification( + DispatchResponse dispatch_response) { + return std::make_unique(std::move(dispatch_response)); +} + +namespace { +class Response : public Serializable { + public: + Response(int call_id, std::unique_ptr params) + : call_id_(call_id), params_(std::move(params)) {} + + void AppendSerialized(std::vector* out) const override { + Status status; + std::unique_ptr encoder = cbor::NewCBOREncoder(out, &status); + encoder->HandleMapBegin(); + encoder->HandleString8(SpanFrom("id")); + encoder->HandleInt32(call_id_); + encoder->HandleString8(SpanFrom("result")); + if (params_) { + params_->AppendSerialized(out); + } else { + encoder->HandleMapBegin(); + encoder->HandleMapEnd(); + } + encoder->HandleMapEnd(); + assert(status.ok()); + } + + private: + const int call_id_; + std::unique_ptr params_; +}; + +class Notification : public Serializable { + public: + Notification(const char* method, std::unique_ptr params) + : method_(method), params_(std::move(params)) {} + + void AppendSerialized(std::vector* out) const override { + Status status; + std::unique_ptr encoder = cbor::NewCBOREncoder(out, &status); + encoder->HandleMapBegin(); + encoder->HandleString8(SpanFrom("method")); + encoder->HandleString8(SpanFrom(method_)); + encoder->HandleString8(SpanFrom("params")); + if (params_) { + params_->AppendSerialized(out); + } else { + encoder->HandleMapBegin(); + encoder->HandleMapEnd(); + } + encoder->HandleMapEnd(); + assert(status.ok()); + } + + private: + const char* method_; + std::unique_ptr params_; +}; +} // namespace + +std::unique_ptr CreateResponse( + int call_id, + std::unique_ptr params) { + return std::make_unique(call_id, std::move(params)); +} + +std::unique_ptr CreateNotification( + const char* method, + std::unique_ptr params) { + return std::make_unique(method, std::move(params)); +} + +// ============================================================================= +// DomainDispatcher - Dispatching betwen protocol methods within a domain. +// ============================================================================= +DomainDispatcher::WeakPtr::WeakPtr(DomainDispatcher* dispatcher) + : dispatcher_(dispatcher) {} + +DomainDispatcher::WeakPtr::~WeakPtr() { + if (dispatcher_) + dispatcher_->weak_ptrs_.erase(this); +} + +DomainDispatcher::Callback::~Callback() = default; + +void DomainDispatcher::Callback::dispose() { + backend_impl_ = nullptr; +} + +DomainDispatcher::Callback::Callback( + std::unique_ptr backend_impl, + int call_id, + span method, + span message) + : backend_impl_(std::move(backend_impl)), + call_id_(call_id), + method_(method), + message_(message.begin(), message.end()) {} + +void DomainDispatcher::Callback::sendIfActive( + std::unique_ptr partialMessage, + const DispatchResponse& response) { + if (!backend_impl_ || !backend_impl_->get()) + return; + backend_impl_->get()->sendResponse(call_id_, response, + std::move(partialMessage)); + backend_impl_ = nullptr; +} + +void DomainDispatcher::Callback::fallThroughIfActive() { + if (!backend_impl_ || !backend_impl_->get()) + return; + backend_impl_->get()->channel()->FallThrough(call_id_, method_, + SpanFrom(message_)); + backend_impl_ = nullptr; +} + +DomainDispatcher::DomainDispatcher(FrontendChannel* frontendChannel) + : frontend_channel_(frontendChannel) {} + +DomainDispatcher::~DomainDispatcher() { + clearFrontend(); +} + +void DomainDispatcher::sendResponse(int call_id, + const DispatchResponse& response, + std::unique_ptr result) { + if (!frontend_channel_) + return; + std::unique_ptr serializable; + if (response.IsError()) { + serializable = CreateErrorResponse(call_id, response); + } else { + serializable = CreateResponse(call_id, std::move(result)); + } + frontend_channel_->SendProtocolResponse(call_id, std::move(serializable)); +} + +bool DomainDispatcher::MaybeReportInvalidParams( + const Dispatchable& dispatchable, + const ErrorSupport& errors) { + if (errors.Errors().empty()) + return false; + if (frontend_channel_) { + frontend_channel_->SendProtocolResponse( + dispatchable.CallId(), + CreateErrorResponse( + dispatchable.CallId(), + DispatchResponse::InvalidParams("Invalid parameters"), &errors)); + } + return true; +} + +bool DomainDispatcher::MaybeReportInvalidParams( + const Dispatchable& dispatchable, + const DeserializerState& state) { + if (state.status().ok()) + return false; + if (frontend_channel_) { + frontend_channel_->SendProtocolResponse( + dispatchable.CallId(), + CreateErrorResponse( + dispatchable.CallId(), + DispatchResponse::InvalidParams("Invalid parameters"), state)); + } + return true; +} + +void DomainDispatcher::clearFrontend() { + frontend_channel_ = nullptr; + for (auto& weak : weak_ptrs_) + weak->dispose(); + weak_ptrs_.clear(); +} + +std::unique_ptr DomainDispatcher::weakPtr() { + auto weak = std::make_unique(this); + weak_ptrs_.insert(weak.get()); + return weak; +} + +// ============================================================================= +// UberDispatcher - dispatches between domains (backends). +// ============================================================================= +UberDispatcher::DispatchResult::DispatchResult(bool method_found, + std::function runnable) + : method_found_(method_found), runnable_(runnable) {} + +void UberDispatcher::DispatchResult::Run() { + if (!runnable_) + return; + runnable_(); + runnable_ = nullptr; +} + +UberDispatcher::UberDispatcher(FrontendChannel* frontend_channel) + : frontend_channel_(frontend_channel) { + assert(frontend_channel); +} + +UberDispatcher::~UberDispatcher() = default; + +constexpr size_t kNotFound = std::numeric_limits::max(); + +namespace { +size_t DotIdx(span method) { + const void* p = memchr(method.data(), '.', method.size()); + return p ? reinterpret_cast(p) - method.data() : kNotFound; +} +} // namespace + +UberDispatcher::DispatchResult UberDispatcher::Dispatch( + const Dispatchable& dispatchable) const { + span method = FindByFirst(redirects_, dispatchable.Method(), + /*default_value=*/dispatchable.Method()); + size_t dot_idx = DotIdx(method); + if (dot_idx != kNotFound) { + span domain = method.subspan(0, dot_idx); + span command = method.subspan(dot_idx + 1); + DomainDispatcher* dispatcher = FindByFirst(dispatchers_, domain); + if (dispatcher) { + std::function dispatched = + dispatcher->Dispatch(command); + if (dispatched) { + return DispatchResult( + true, [dispatchable, dispatched = std::move(dispatched)]() { + dispatched(dispatchable); + }); + } + } + } + return DispatchResult(false, [this, dispatchable]() { + frontend_channel_->SendProtocolResponse( + dispatchable.CallId(), + CreateErrorResponse(dispatchable.CallId(), + DispatchResponse::MethodNotFound( + "'" + + std::string(dispatchable.Method().begin(), + dispatchable.Method().end()) + + "' wasn't found"))); + }); +} + +template +struct FirstLessThan { + bool operator()(const std::pair, T>& left, + const std::pair, T>& right) { + return SpanLessThan(left.first, right.first); + } +}; + +void UberDispatcher::WireBackend( + span domain, + const std::vector, span>>& + sorted_redirects, + std::unique_ptr dispatcher) { + auto it = redirects_.insert(redirects_.end(), sorted_redirects.begin(), + sorted_redirects.end()); + std::inplace_merge(redirects_.begin(), it, redirects_.end(), + FirstLessThan>()); + auto jt = dispatchers_.insert(dispatchers_.end(), + std::make_pair(domain, std::move(dispatcher))); + std::inplace_merge(dispatchers_.begin(), jt, dispatchers_.end(), + FirstLessThan>()); +} + +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/dispatch.h b/tools/inspector_protocol/crdtp/dispatch.h new file mode 100644 index 00000000000000..84d40d4617bd55 --- /dev/null +++ b/tools/inspector_protocol/crdtp/dispatch.h @@ -0,0 +1,314 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_DISPATCH_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_DISPATCH_H_ + +#include +#include +#include +#include +#include +#include "export.h" +#include "serializable.h" +#include "span.h" +#include "status.h" + +namespace v8_inspector_protocol_crdtp { +class DeserializerState; +class ErrorSupport; +class FrontendChannel; +namespace cbor { +class CBORTokenizer; +} // namespace cbor + +// ============================================================================= +// DispatchResponse - Error status and chaining / fall through +// ============================================================================= +enum class DispatchCode { + SUCCESS = 1, + FALL_THROUGH = 2, + // For historical reasons, these error codes correspond to commonly used + // XMLRPC codes (e.g. see METHOD_NOT_FOUND in + // https://github.com/python/cpython/blob/master/Lib/xmlrpc/client.py). + PARSE_ERROR = -32700, + INVALID_REQUEST = -32600, + METHOD_NOT_FOUND = -32601, + INVALID_PARAMS = -32602, + INTERNAL_ERROR = -32603, + SERVER_ERROR = -32000, +}; + +// Information returned by command handlers. Usually returned after command +// execution attempts. +class DispatchResponse { + public: + const std::string& Message() const { return message_; } + + DispatchCode Code() const { return code_; } + + bool IsSuccess() const { return code_ == DispatchCode::SUCCESS; } + bool IsFallThrough() const { return code_ == DispatchCode::FALL_THROUGH; } + bool IsError() const { return code_ < DispatchCode::SUCCESS; } + + static DispatchResponse Success(); + static DispatchResponse FallThrough(); + + // Indicates that a message could not be parsed. E.g., malformed JSON. + static DispatchResponse ParseError(std::string message); + + // Indicates that a request is lacking required top-level properties + // ('id', 'method'), has top-level properties of the wrong type, or has + // unknown top-level properties. + static DispatchResponse InvalidRequest(std::string message); + + // Indicates that a protocol method such as "Page.bringToFront" could not be + // dispatched because it's not known to the (domain) dispatcher. + static DispatchResponse MethodNotFound(std::string message); + + // Indicates that the params sent to a domain handler are invalid. + static DispatchResponse InvalidParams(std::string message); + + // Used for application level errors, e.g. within protocol agents. + static DispatchResponse InternalError(); + + // Used for application level errors, e.g. within protocol agents. + static DispatchResponse ServerError(std::string message); + + private: + DispatchResponse() = default; + DispatchCode code_; + std::string message_; +}; + +// ============================================================================= +// Dispatchable - a shallow parser for CBOR encoded DevTools messages +// ============================================================================= + +// This parser extracts only the known top-level fields from a CBOR encoded map; +// method, id, sessionId, and params. +class Dispatchable { + public: + // This constructor parses the |serialized| message. If successful, + // |ok()| will yield |true|, and |Method()|, |SessionId()|, |CallId()|, + // |Params()| can be used to access, the extracted contents. Otherwise, + // |ok()| will yield |false|, and |DispatchError()| can be + // used to send a response or notification to the client. + explicit Dispatchable(span serialized); + + // The serialized message that we just parsed. + span Serialized() const { return serialized_; } + + // Yields true if parsing was successful. This is cheaper than calling + // ::DispatchError(). + bool ok() const; + + // If !ok(), returns a DispatchResponse with appropriate code and error + // which can be sent to the client as a response or notification. + DispatchResponse DispatchError() const; + + // Top level field: the command to be executed, fully qualified by + // domain. E.g. "Page.createIsolatedWorld". + span Method() const { return method_; } + // Used to identify protocol connections attached to a specific + // target. See Target.attachToTarget, Target.setAutoAttach. + span SessionId() const { return session_id_; } + // The call id, a sequence number that's used in responses to indicate + // the request to which the response belongs. + int32_t CallId() const { return call_id_; } + bool HasCallId() const { return has_call_id_; } + // The payload of the request in CBOR format. The |Dispatchable| parser does + // not parse into this; it only provides access to its raw contents here. + span Params() const { return params_; } + + private: + bool MaybeParseProperty(cbor::CBORTokenizer* tokenizer); + bool MaybeParseCallId(cbor::CBORTokenizer* tokenizer); + bool MaybeParseMethod(cbor::CBORTokenizer* tokenizer); + bool MaybeParseParams(cbor::CBORTokenizer* tokenizer); + bool MaybeParseSessionId(cbor::CBORTokenizer* tokenizer); + + span serialized_; + + Status status_; + + bool has_call_id_ = false; + int32_t call_id_; + span method_; + bool params_seen_ = false; + span params_; + span session_id_; +}; + +// ============================================================================= +// Helpers for creating protocol cresponses and notifications. +// ============================================================================= + +// The resulting notifications can be sent to a protocol client, +// usually via a FrontendChannel (see frontend_channel.h). + +std::unique_ptr CreateErrorResponse( + int callId, + DispatchResponse dispatch_response, + const ErrorSupport* errors = nullptr); + +std::unique_ptr CreateErrorNotification( + DispatchResponse dispatch_response); + +std::unique_ptr CreateResponse( + int callId, + std::unique_ptr params); + +std::unique_ptr CreateNotification( + const char* method, + std::unique_ptr params = nullptr); + +// ============================================================================= +// DomainDispatcher - Dispatching betwen protocol methods within a domain. +// ============================================================================= + +// This class is subclassed by |DomainDispatcherImpl|, which we generate per +// DevTools domain. It contains routines called from the generated code, +// e.g. ::MaybeReportInvalidParams, which are optimized for small code size. +// The most important method is ::Dispatch, which implements method dispatch +// by command name lookup. +class DomainDispatcher { + public: + class WeakPtr { + public: + explicit WeakPtr(DomainDispatcher*); + ~WeakPtr(); + DomainDispatcher* get() { return dispatcher_; } + void dispose() { dispatcher_ = nullptr; } + + private: + DomainDispatcher* dispatcher_; + }; + + class Callback { + public: + virtual ~Callback(); + void dispose(); + + protected: + // |method| must point at static storage (a C++ string literal in practice). + Callback(std::unique_ptr backend_impl, + int call_id, + span method, + span message); + + void sendIfActive(std::unique_ptr partialMessage, + const DispatchResponse& response); + void fallThroughIfActive(); + + private: + std::unique_ptr backend_impl_; + int call_id_; + // Subclasses of this class are instantiated from generated code which + // passes a string literal for the method name to the constructor. So the + // storage for |method| is the binary of the running process. + span method_; + std::vector message_; + }; + + explicit DomainDispatcher(FrontendChannel*); + virtual ~DomainDispatcher(); + + // Given a |command_name| without domain qualification, looks up the + // corresponding method. If the method is not found, returns nullptr. + // Otherwise, Returns a closure that will parse the provided + // Dispatchable.params() to a protocol object and execute the + // apprpropriate method. If the parsing fails it will issue an + // error response on the frontend channel, otherwise it will execute the + // command. + virtual std::function Dispatch( + span command_name) = 0; + + // Sends a response to the client via the channel. + void sendResponse(int call_id, + const DispatchResponse&, + std::unique_ptr result = nullptr); + + // Returns true if |errors| contains errors *and* reports these errors + // as a response on the frontend channel. Called from generated code, + // optimized for code size of the callee. + bool MaybeReportInvalidParams(const Dispatchable& dispatchable, + const ErrorSupport& errors); + bool MaybeReportInvalidParams(const Dispatchable& dispatchable, + const DeserializerState& state); + + FrontendChannel* channel() { return frontend_channel_; } + + void clearFrontend(); + + std::unique_ptr weakPtr(); + + private: + FrontendChannel* frontend_channel_; + std::unordered_set weak_ptrs_; +}; + +// ============================================================================= +// UberDispatcher - dispatches between domains (backends). +// ============================================================================= +class UberDispatcher { + public: + // Return type for ::Dispatch. + class DispatchResult { + public: + DispatchResult(bool method_found, std::function runnable); + + // Indicates whether the method was found, that is, it could be dispatched + // to a backend registered with this dispatcher. + bool MethodFound() const { return method_found_; } + + // Runs the dispatched result. This will send the appropriate error + // responses if the method wasn't found or if something went wrong during + // parameter parsing. + void Run(); + + private: + bool method_found_; + std::function runnable_; + }; + + // |frontend_hannel| can't be nullptr. + explicit UberDispatcher(FrontendChannel* frontend_channel); + virtual ~UberDispatcher(); + + // Dispatches the provided |dispatchable| considering all redirects and domain + // handlers registered with this uber dispatcher. Also see |DispatchResult|. + // |dispatchable.ok()| must hold - callers must check this separately and + // deal with errors. + DispatchResult Dispatch(const Dispatchable& dispatchable) const; + + // Invoked from generated code for wiring domain backends; that is, + // connecting domain handlers to an uber dispatcher. + // See ::Dispatcher::Wire(UberDispatcher*,Backend*). + FrontendChannel* channel() const { + assert(frontend_channel_); + return frontend_channel_; + } + + // Invoked from generated code for wiring domain backends; that is, + // connecting domain handlers to an uber dispatcher. + // See ::Dispatcher::Wire(UberDispatcher*,Backend*). + void WireBackend(span domain, + const std::vector, span>>&, + std::unique_ptr dispatcher); + + private: + DomainDispatcher* findDispatcher(span method); + FrontendChannel* const frontend_channel_; + // Pairs of ascii strings of the form ("Domain1.method1","Domain2.method2") + // indicating that the first element of each pair redirects to the second. + // Sorted by first element. + std::vector, span>> redirects_; + // Domain dispatcher instances, sorted by their domain name. + std::vector, std::unique_ptr>> + dispatchers_; +}; +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_DISPATCH_H_ diff --git a/tools/inspector_protocol/crdtp/dispatch_test.cc b/tools/inspector_protocol/crdtp/dispatch_test.cc new file mode 100644 index 00000000000000..8a1727992f67c6 --- /dev/null +++ b/tools/inspector_protocol/crdtp/dispatch_test.cc @@ -0,0 +1,445 @@ +// Copyright 2020 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 "cbor.h" +#include "dispatch.h" +#include "error_support.h" +#include "frontend_channel.h" +#include "json.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// DispatchResponse - Error status and chaining / fall through +// ============================================================================= +TEST(DispatchResponseTest, OK) { + EXPECT_EQ(DispatchCode::SUCCESS, DispatchResponse::Success().Code()); + EXPECT_TRUE(DispatchResponse::Success().IsSuccess()); +} + +TEST(DispatchResponseTest, ServerError) { + DispatchResponse error = DispatchResponse::ServerError("Oops!"); + EXPECT_FALSE(error.IsSuccess()); + EXPECT_EQ(DispatchCode::SERVER_ERROR, error.Code()); + EXPECT_EQ("Oops!", error.Message()); +} + +TEST(DispatchResponseTest, InternalError) { + DispatchResponse error = DispatchResponse::InternalError(); + EXPECT_FALSE(error.IsSuccess()); + EXPECT_EQ(DispatchCode::INTERNAL_ERROR, error.Code()); + EXPECT_EQ("Internal error", error.Message()); +} + +TEST(DispatchResponseTest, InvalidParams) { + DispatchResponse error = DispatchResponse::InvalidParams("too cool"); + EXPECT_FALSE(error.IsSuccess()); + EXPECT_EQ(DispatchCode::INVALID_PARAMS, error.Code()); + EXPECT_EQ("too cool", error.Message()); +} + +TEST(DispatchResponseTest, FallThrough) { + DispatchResponse error = DispatchResponse::FallThrough(); + EXPECT_FALSE(error.IsSuccess()); + EXPECT_TRUE(error.IsFallThrough()); + EXPECT_EQ(DispatchCode::FALL_THROUGH, error.Code()); +} + +// ============================================================================= +// Dispatchable - a shallow parser for CBOR encoded DevTools messages +// ============================================================================= +TEST(DispatchableTest, MessageMustBeAnObject) { + // Provide no input whatsoever. + span empty_span; + Dispatchable empty(empty_span); + EXPECT_FALSE(empty.ok()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, empty.DispatchError().Code()); + EXPECT_EQ("Message must be an object", empty.DispatchError().Message()); +} + +TEST(DispatchableTest, MessageMustHaveIntegerIdProperty) { + // Construct an empty map inside of an envelope. + std::vector cbor; + ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{}"), &cbor).ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_FALSE(dispatchable.HasCallId()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code()); + EXPECT_EQ("Message must have integer 'id' property", + dispatchable.DispatchError().Message()); +} + +TEST(DispatchableTest, MessageMustHaveIntegerIdProperty_IncorrectType) { + // This time we set the id property, but fail to make it an int32. + std::vector cbor; + ASSERT_TRUE( + json::ConvertJSONToCBOR(SpanFrom("{\"id\":\"foo\"}"), &cbor).ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_FALSE(dispatchable.HasCallId()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code()); + EXPECT_EQ("Message must have integer 'id' property", + dispatchable.DispatchError().Message()); +} + +TEST(DispatchableTest, MessageMustHaveStringMethodProperty) { + // This time we set the id property, but not the method property. + std::vector cbor; + ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{\"id\":42}"), &cbor).ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_TRUE(dispatchable.HasCallId()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code()); + EXPECT_EQ("Message must have string 'method' property", + dispatchable.DispatchError().Message()); +} + +TEST(DispatchableTest, MessageMustHaveStringMethodProperty_IncorrectType) { + // This time we set the method property, but fail to make it a string. + std::vector cbor; + ASSERT_TRUE( + json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":42}"), &cbor) + .ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_TRUE(dispatchable.HasCallId()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code()); + EXPECT_EQ("Message must have string 'method' property", + dispatchable.DispatchError().Message()); +} + +TEST(DispatchableTest, MessageMayHaveStringSessionIdProperty) { + // This time, the session id is an int but it should be a string. Method and + // call id are present. + std::vector cbor; + ASSERT_TRUE(json::ConvertJSONToCBOR( + SpanFrom("{\"id\":42,\"method\":\"Foo.executeBar\"," + "\"sessionId\":42" // int32 is wrong type + "}"), + &cbor) + .ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_TRUE(dispatchable.HasCallId()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code()); + EXPECT_EQ("Message may have string 'sessionId' property", + dispatchable.DispatchError().Message()); +} + +TEST(DispatchableTest, MessageMayHaveObjectParamsProperty) { + // This time, we fail to use the correct type for the params property. + std::vector cbor; + ASSERT_TRUE(json::ConvertJSONToCBOR( + SpanFrom("{\"id\":42,\"method\":\"Foo.executeBar\"," + "\"params\":42" // int32 is wrong type + "}"), + &cbor) + .ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_TRUE(dispatchable.HasCallId()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code()); + EXPECT_EQ("Message may have object 'params' property", + dispatchable.DispatchError().Message()); +} + +TEST(DispatchableTest, MessageWithUnknownProperty) { + // This time we set the 'unknown' property, so we are told what's allowed. + std::vector cbor; + ASSERT_TRUE( + json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"unknown\":42}"), &cbor) + .ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_TRUE(dispatchable.HasCallId()); + EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code()); + EXPECT_EQ( + "Message has property other than 'id', 'method', 'sessionId', 'params'", + dispatchable.DispatchError().Message()); +} + +TEST(DispatchableTest, DuplicateMapKey) { + for (const std::string& json : + {"{\"id\":42,\"id\":42}", "{\"params\":null,\"params\":null}", + "{\"method\":\"foo\",\"method\":\"foo\"}", + "{\"sessionId\":\"42\",\"sessionId\":\"42\"}"}) { + SCOPED_TRACE("json = " + json); + std::vector cbor; + ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code()); + EXPECT_THAT(dispatchable.DispatchError().Message(), + testing::StartsWith("CBOR: duplicate map key at position ")); + } +} + +TEST(DispatchableTest, ValidMessageParsesOK_NoParams) { + for (const std::string& json : + {"{\"id\":42,\"method\":\"Foo.executeBar\",\"sessionId\":" + "\"f421ssvaz4\"}", + "{\"id\":42,\"method\":\"Foo.executeBar\",\"sessionId\":\"f421ssvaz4\"," + "\"params\":null}"}) { + SCOPED_TRACE("json = " + json); + std::vector cbor; + ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok()); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_TRUE(dispatchable.ok()); + EXPECT_TRUE(dispatchable.HasCallId()); + EXPECT_EQ(42, dispatchable.CallId()); + EXPECT_EQ("Foo.executeBar", std::string(dispatchable.Method().begin(), + dispatchable.Method().end())); + EXPECT_EQ("f421ssvaz4", std::string(dispatchable.SessionId().begin(), + dispatchable.SessionId().end())); + EXPECT_TRUE(dispatchable.Params().empty()); + } +} + +TEST(DispatchableTest, ValidMessageParsesOK_WithParams) { + std::vector cbor; + cbor::EnvelopeEncoder envelope; + envelope.EncodeStart(&cbor); + cbor.push_back(cbor::EncodeIndefiniteLengthMapStart()); + cbor::EncodeString8(SpanFrom("id"), &cbor); + cbor::EncodeInt32(42, &cbor); + cbor::EncodeString8(SpanFrom("method"), &cbor); + cbor::EncodeString8(SpanFrom("Foo.executeBar"), &cbor); + cbor::EncodeString8(SpanFrom("params"), &cbor); + cbor::EnvelopeEncoder params_envelope; + params_envelope.EncodeStart(&cbor); + // The |Dispatchable| class does not parse into the "params" envelope, + // so we can stick anything into there for the purpose of this test. + // For convenience, we use a String8. + cbor::EncodeString8(SpanFrom("params payload"), &cbor); + params_envelope.EncodeStop(&cbor); + cbor::EncodeString8(SpanFrom("sessionId"), &cbor); + cbor::EncodeString8(SpanFrom("f421ssvaz4"), &cbor); + cbor.push_back(cbor::EncodeStop()); + envelope.EncodeStop(&cbor); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_TRUE(dispatchable.ok()); + EXPECT_TRUE(dispatchable.HasCallId()); + EXPECT_EQ(42, dispatchable.CallId()); + EXPECT_EQ("Foo.executeBar", std::string(dispatchable.Method().begin(), + dispatchable.Method().end())); + EXPECT_EQ("f421ssvaz4", std::string(dispatchable.SessionId().begin(), + dispatchable.SessionId().end())); + cbor::CBORTokenizer params_tokenizer(dispatchable.Params()); + ASSERT_EQ(cbor::CBORTokenTag::ENVELOPE, params_tokenizer.TokenTag()); + params_tokenizer.EnterEnvelope(); + ASSERT_EQ(cbor::CBORTokenTag::STRING8, params_tokenizer.TokenTag()); + EXPECT_EQ("params payload", std::string(params_tokenizer.GetString8().begin(), + params_tokenizer.GetString8().end())); +} + +TEST(DispatchableTest, FaultyCBORTrailingJunk) { + // In addition to the higher level parsing errors, we also catch CBOR + // structural corruption. E.g., in this case, the message would be + // OK but has some extra trailing bytes. + std::vector cbor; + cbor::EnvelopeEncoder envelope; + envelope.EncodeStart(&cbor); + cbor.push_back(cbor::EncodeIndefiniteLengthMapStart()); + cbor::EncodeString8(SpanFrom("id"), &cbor); + cbor::EncodeInt32(42, &cbor); + cbor::EncodeString8(SpanFrom("method"), &cbor); + cbor::EncodeString8(SpanFrom("Foo.executeBar"), &cbor); + cbor::EncodeString8(SpanFrom("sessionId"), &cbor); + cbor::EncodeString8(SpanFrom("f421ssvaz4"), &cbor); + cbor.push_back(cbor::EncodeStop()); + envelope.EncodeStop(&cbor); + size_t trailing_junk_pos = cbor.size(); + cbor.push_back('t'); + cbor.push_back('r'); + cbor.push_back('a'); + cbor.push_back('i'); + cbor.push_back('l'); + Dispatchable dispatchable(SpanFrom(cbor)); + EXPECT_FALSE(dispatchable.ok()); + EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code()); + EXPECT_EQ(56u, trailing_junk_pos); + EXPECT_EQ("CBOR: trailing junk at position 56", + dispatchable.DispatchError().Message()); +} + +// ============================================================================= +// Helpers for creating protocol cresponses and notifications. +// ============================================================================= +TEST(CreateErrorResponseTest, SmokeTest) { + ErrorSupport errors; + errors.Push(); + errors.SetName("foo"); + errors.Push(); + errors.SetName("bar"); + errors.AddError("expected a string"); + errors.SetName("baz"); + errors.AddError("expected a surprise"); + auto serializable = CreateErrorResponse( + 42, DispatchResponse::InvalidParams("invalid params message"), &errors); + std::string json; + auto status = + json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json); + ASSERT_TRUE(status.ok()); + EXPECT_EQ( + "{\"id\":42,\"error\":" + "{\"code\":-32602," + "\"message\":\"invalid params message\"," + "\"data\":\"foo.bar: expected a string; " + "foo.baz: expected a surprise\"}}", + json); +} + +TEST(CreateErrorNotificationTest, SmokeTest) { + auto serializable = + CreateErrorNotification(DispatchResponse::InvalidRequest("oops!")); + std::string json; + auto status = + json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json); + ASSERT_TRUE(status.ok()); + EXPECT_EQ("{\"error\":{\"code\":-32600,\"message\":\"oops!\"}}", json); +} + +TEST(CreateResponseTest, SmokeTest) { + auto serializable = CreateResponse(42, nullptr); + std::string json; + auto status = + json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json); + ASSERT_TRUE(status.ok()); + EXPECT_EQ("{\"id\":42,\"result\":{}}", json); +} + +TEST(CreateNotificationTest, SmokeTest) { + auto serializable = CreateNotification("Foo.bar"); + std::string json; + auto status = + json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json); + ASSERT_TRUE(status.ok()); + EXPECT_EQ("{\"method\":\"Foo.bar\",\"params\":{}}", json); +} + +// ============================================================================= +// UberDispatcher - dispatches between domains (backends). +// ============================================================================= +class TestChannel : public FrontendChannel { + public: + std::string JSON() const { + std::string json; + json::ConvertCBORToJSON(SpanFrom(cbor_), &json); + return json; + } + + private: + void SendProtocolResponse(int call_id, + std::unique_ptr message) override { + cbor_ = message->Serialize(); + } + + void SendProtocolNotification( + std::unique_ptr message) override { + cbor_ = message->Serialize(); + } + + void FallThrough(int call_id, + span method, + span message) override {} + + void FlushProtocolNotifications() override {} + + std::vector cbor_; +}; + +TEST(UberDispatcherTest, MethodNotFound) { + // No domain dispatchers are registered, so unsuprisingly, we'll get a method + // not found error and can see that DispatchResult::MethodFound() yields + // false. + TestChannel channel; + UberDispatcher dispatcher(&channel); + std::vector message; + json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.bar\"}"), + &message); + Dispatchable dispatchable(SpanFrom(message)); + ASSERT_TRUE(dispatchable.ok()); + UberDispatcher::DispatchResult dispatched = dispatcher.Dispatch(dispatchable); + EXPECT_FALSE(dispatched.MethodFound()); + dispatched.Run(); + EXPECT_EQ( + "{\"id\":42,\"error\":" + "{\"code\":-32601,\"message\":\"'Foo.bar' wasn't found\"}}", + channel.JSON()); +} + +// A domain dispatcher which captured dispatched and executed commands in fields +// for testing. +class TestDomain : public DomainDispatcher { + public: + explicit TestDomain(FrontendChannel* channel) : DomainDispatcher(channel) {} + + std::function Dispatch( + span command_name) override { + dispatched_commands_.push_back( + std::string(command_name.begin(), command_name.end())); + return [this](const Dispatchable& dispatchable) { + executed_commands_.push_back(dispatchable.CallId()); + }; + } + + // Command names of the dispatched commands. + std::vector DispatchedCommands() const { + return dispatched_commands_; + } + + // Call ids of the executed commands. + std::vector ExecutedCommands() const { return executed_commands_; } + + private: + std::vector dispatched_commands_; + std::vector executed_commands_; +}; + +TEST(UberDispatcherTest, DispatchingToDomainWithRedirects) { + // This time, we register two domain dispatchers (Foo and Bar) and issue one + // command 'Foo.execute' which executes on Foo and one command 'Foo.redirect' + // which executes as 'Bar.redirected'. + TestChannel channel; + UberDispatcher dispatcher(&channel); + auto foo_dispatcher = std::make_unique(&channel); + TestDomain* foo = foo_dispatcher.get(); + auto bar_dispatcher = std::make_unique(&channel); + TestDomain* bar = bar_dispatcher.get(); + + dispatcher.WireBackend( + SpanFrom("Foo"), {{SpanFrom("Foo.redirect"), SpanFrom("Bar.redirected")}}, + std::move(foo_dispatcher)); + dispatcher.WireBackend(SpanFrom("Bar"), {}, std::move(bar_dispatcher)); + + { + std::vector message; + json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.execute\"}"), + &message); + Dispatchable dispatchable(SpanFrom(message)); + ASSERT_TRUE(dispatchable.ok()); + UberDispatcher::DispatchResult dispatched = + dispatcher.Dispatch(dispatchable); + EXPECT_TRUE(dispatched.MethodFound()); + dispatched.Run(); + } + { + std::vector message; + json::ConvertJSONToCBOR(SpanFrom("{\"id\":43,\"method\":\"Foo.redirect\"}"), + &message); + Dispatchable dispatchable(SpanFrom(message)); + ASSERT_TRUE(dispatchable.ok()); + UberDispatcher::DispatchResult dispatched = + dispatcher.Dispatch(dispatchable); + EXPECT_TRUE(dispatched.MethodFound()); + dispatched.Run(); + } + EXPECT_THAT(foo->DispatchedCommands(), testing::ElementsAre("execute")); + EXPECT_THAT(foo->ExecutedCommands(), testing::ElementsAre(42)); + EXPECT_THAT(bar->DispatchedCommands(), testing::ElementsAre("redirected")); + EXPECT_THAT(bar->ExecutedCommands(), testing::ElementsAre(43)); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/error_support.cc b/tools/inspector_protocol/crdtp/error_support.cc new file mode 100644 index 00000000000000..ff255beb662eaf --- /dev/null +++ b/tools/inspector_protocol/crdtp/error_support.cc @@ -0,0 +1,59 @@ +// Copyright 2020 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 "error_support.h" + +#include + +namespace v8_inspector_protocol_crdtp { + +void ErrorSupport::Push() { + stack_.emplace_back(); +} + +void ErrorSupport::Pop() { + stack_.pop_back(); +} + +void ErrorSupport::SetName(const char* name) { + assert(!stack_.empty()); + stack_.back().type = NAME; + stack_.back().name = name; +} + +void ErrorSupport::SetIndex(size_t index) { + assert(!stack_.empty()); + stack_.back().type = INDEX; + stack_.back().index = index; +} + +void ErrorSupport::AddError(const char* error) { + assert(!stack_.empty()); + if (!errors_.empty()) + errors_ += "; "; + for (size_t ii = 0; ii < stack_.size(); ++ii) { + if (ii) + errors_ += "."; + const Segment& s = stack_[ii]; + switch (s.type) { + case NAME: + errors_ += s.name; + continue; + case INDEX: + errors_ += std::to_string(s.index); + continue; + default: + assert(s.type != EMPTY); + continue; + } + } + errors_ += ": "; + errors_ += error; +} + +span ErrorSupport::Errors() const { + return SpanFrom(errors_); +} + +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/error_support.h b/tools/inspector_protocol/crdtp/error_support.h new file mode 100644 index 00000000000000..f8f59291d2cb9a --- /dev/null +++ b/tools/inspector_protocol/crdtp/error_support.h @@ -0,0 +1,62 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_ERROR_SUPPORT_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_ERROR_SUPPORT_H_ + +#include +#include +#include +#include "export.h" +#include "span.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// ErrorSupport - For tracking errors in tree structures. +// ============================================================================= + +// This abstraction is used when converting between Values and inspector +// objects, e.g. in lib/ValueConversions_{h,cc}.template. As the processing +// enters and exits a branch, we call Push / Pop. Within the branch, +// we either set the name or an index (in case we're processing the element of a +// list/vector). Only once an error is seen, the path which is now on the +// stack is materialized and prefixes the error message. E.g., +// "foo.bar.2: some error". After error collection, ::Errors() is used to +// access the message. +class ErrorSupport { + public: + // Push / Pop operations for the path segments; after Push, either SetName or + // SetIndex must be called exactly once. + void Push(); + void Pop(); + + // Sets the name of the current segment on the stack; e.g. a field name. + // |name| must be a C++ string literal in 7 bit US-ASCII. + void SetName(const char* name); + // Sets the index of the current segment on the stack; e.g. an array index. + void SetIndex(size_t index); + + // Materializes the error internally. |error| must be a C++ string literal + // in 7 bit US-ASCII. + void AddError(const char* error); + + // Returns the semicolon-separated list of errors as in 7 bit ASCII. + span Errors() const; + + private: + enum SegmentType { EMPTY, NAME, INDEX }; + struct Segment { + SegmentType type = EMPTY; + union { + const char* name; + size_t index; + }; + }; + std::vector stack_; + std::string errors_; +}; + +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_ERROR_SUPPORT_H_ diff --git a/tools/inspector_protocol/crdtp/error_support_test.cc b/tools/inspector_protocol/crdtp/error_support_test.cc new file mode 100644 index 00000000000000..5ac024fff7ac98 --- /dev/null +++ b/tools/inspector_protocol/crdtp/error_support_test.cc @@ -0,0 +1,45 @@ +// Copyright 2020 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 "error_support.h" + +#include +#include + +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +TEST(ErrorSupportTest, Empty) { + ErrorSupport errors; + EXPECT_TRUE(errors.Errors().empty()); +} + +TEST(ErrorSupportTest, Nesting) { + ErrorSupport errors; + // Enter field foo, inter element at index 42, enter field bar, and encounter + // an error there ("something wrong"). + errors.Push(); + errors.SetName("foo"); + errors.Push(); + errors.SetIndex(42); + errors.Push(); + errors.SetName("bar_sibling"); + errors.SetName("bar"); + errors.AddError("something wrong"); + errors.Pop(); // bar + errors.Pop(); // 42 + // The common case is actually that we'll enter some field, set the name + // or index, and leave without ever producing an error. + errors.Push(); + errors.SetName("no_error_here"); + errors.Pop(); // no_error_here + errors.Push(); + errors.SetName("bang"); + errors.AddError("one last error"); + errors.Pop(); // bang + errors.Pop(); // foo + std::string out(errors.Errors().begin(), errors.Errors().end()); + EXPECT_EQ("foo.42.bar: something wrong; foo.bang: one last error", out); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/export.h b/tools/inspector_protocol/crdtp/export.h new file mode 100644 index 00000000000000..a97722e746d02c --- /dev/null +++ b/tools/inspector_protocol/crdtp/export.h @@ -0,0 +1,6 @@ +// 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. + +// This file is V8 specific. It's not rolled from the upstream project. +// CRDTP doesn't export symbols from V8, so it's empty. diff --git a/tools/inspector_protocol/crdtp/find_by_first.h b/tools/inspector_protocol/crdtp/find_by_first.h new file mode 100644 index 00000000000000..55827cf4237d90 --- /dev/null +++ b/tools/inspector_protocol/crdtp/find_by_first.h @@ -0,0 +1,58 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_FIND_BY_FIRST_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_FIND_BY_FIRST_H_ + +#include +#include +#include +#include + +#include "export.h" +#include "span.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// FindByFirst - Retrieval from a sorted vector that's keyed by span. +// ============================================================================= + +// Given a vector of pairs sorted by the first element of each pair, find +// the corresponding value given a key to be compared to the first element. +// Together with std::inplace_merge and pre-sorting or std::sort, this can +// be used to implement a minimalistic equivalent of Chromium's flat_map. + +// In this variant, the template parameter |T| is a value type and a +// |default_value| is provided. +template +T FindByFirst(const std::vector, T>>& sorted_by_first, + span key, + T default_value) { + auto it = std::lower_bound( + sorted_by_first.begin(), sorted_by_first.end(), key, + [](const std::pair, T>& left, span right) { + return SpanLessThan(left.first, right); + }); + return (it != sorted_by_first.end() && SpanEquals(it->first, key)) + ? it->second + : default_value; +} + +// In this variant, the template parameter |T| is a class or struct that's +// instantiated in std::unique_ptr, and we return either a T* or a nullptr. +template +T* FindByFirst(const std::vector, std::unique_ptr>>& + sorted_by_first, + span key) { + auto it = std::lower_bound( + sorted_by_first.begin(), sorted_by_first.end(), key, + [](const std::pair, std::unique_ptr>& left, + span right) { return SpanLessThan(left.first, right); }); + return (it != sorted_by_first.end() && SpanEquals(it->first, key)) + ? it->second.get() + : nullptr; +} +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_FIND_BY_FIRST_H_ diff --git a/tools/inspector_protocol/crdtp/find_by_first_test.cc b/tools/inspector_protocol/crdtp/find_by_first_test.cc new file mode 100644 index 00000000000000..91040c66900822 --- /dev/null +++ b/tools/inspector_protocol/crdtp/find_by_first_test.cc @@ -0,0 +1,76 @@ +// Copyright 2020 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 "find_by_first.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// FindByFirst - Efficient retrieval from a sorted vector. +// ============================================================================= +TEST(FindByFirst, SpanBySpan) { + std::vector, span>> sorted_span_by_span = { + {SpanFrom("foo1"), SpanFrom("bar1")}, + {SpanFrom("foo2"), SpanFrom("bar2")}, + {SpanFrom("foo3"), SpanFrom("bar3")}, + }; + { + auto result = FindByFirst(sorted_span_by_span, SpanFrom("foo1"), + SpanFrom("not_found")); + EXPECT_EQ("bar1", std::string(result.begin(), result.end())); + } + { + auto result = FindByFirst(sorted_span_by_span, SpanFrom("foo3"), + SpanFrom("not_found")); + EXPECT_EQ("bar3", std::string(result.begin(), result.end())); + } + { + auto result = FindByFirst(sorted_span_by_span, SpanFrom("baz"), + SpanFrom("not_found")); + EXPECT_EQ("not_found", std::string(result.begin(), result.end())); + } +} + +namespace { +class TestObject { + public: + explicit TestObject(const std::string& message) : message_(message) {} + + const std::string& message() const { return message_; } + + private: + std::string message_; +}; +} // namespace + +TEST(FindByFirst, ObjectBySpan) { + std::vector, std::unique_ptr>> + sorted_object_by_span; + sorted_object_by_span.push_back( + std::make_pair(SpanFrom("foo1"), std::make_unique("bar1"))); + sorted_object_by_span.push_back( + std::make_pair(SpanFrom("foo2"), std::make_unique("bar2"))); + sorted_object_by_span.push_back( + std::make_pair(SpanFrom("foo3"), std::make_unique("bar3"))); + { + TestObject* result = + FindByFirst(sorted_object_by_span, SpanFrom("foo1")); + ASSERT_TRUE(result); + ASSERT_EQ("bar1", result->message()); + } + { + TestObject* result = + FindByFirst(sorted_object_by_span, SpanFrom("foo3")); + ASSERT_TRUE(result); + ASSERT_EQ("bar3", result->message()); + } + { + TestObject* result = + FindByFirst(sorted_object_by_span, SpanFrom("baz")); + ASSERT_FALSE(result); + } +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/frontend_channel.h b/tools/inspector_protocol/crdtp/frontend_channel.h new file mode 100644 index 00000000000000..1a17adf30ce463 --- /dev/null +++ b/tools/inspector_protocol/crdtp/frontend_channel.h @@ -0,0 +1,47 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_FRONTEND_CHANNEL_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_FRONTEND_CHANNEL_H_ + +#include +#include +#include "export.h" +#include "serializable.h" +#include "span.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// FrontendChannel - For sending notifications and responses to protocol clients +// ============================================================================= +class FrontendChannel { + public: + virtual ~FrontendChannel() = default; + + // Sends protocol responses and notifications. The |call_id| parameter is + // seemingly redundant because it's also included in the message, but + // responses may be sent from an untrusted source to a trusted process (e.g. + // from Chromium's renderer (blink) to the browser process), which needs + // to be able to match the response to an earlier request without parsing the + // messsage. + virtual void SendProtocolResponse(int call_id, + std::unique_ptr message) = 0; + virtual void SendProtocolNotification( + std::unique_ptr message) = 0; + + // FallThrough indicates that |message| should be handled in another layer. + // Usually this means the layer responding to the message didn't handle it, + // but in some cases messages are handled by multiple layers (e.g. both + // the embedder and the content layer in Chromium). + virtual void FallThrough(int call_id, + span method, + span message) = 0; + + // Session implementations may queue notifications for performance or + // other considerations; this is a hook for domain handlers to manually flush. + virtual void FlushProtocolNotifications() = 0; +}; +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_FRONTEND_CHANNEL_H_ diff --git a/tools/inspector_protocol/crdtp/glue.h b/tools/inspector_protocol/crdtp/glue.h new file mode 100644 index 00000000000000..48883a39303cbe --- /dev/null +++ b/tools/inspector_protocol/crdtp/glue.h @@ -0,0 +1,80 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_GLUE_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_GLUE_H_ + +#include +#include + +namespace v8_inspector_protocol_crdtp { +namespace glue { +// ============================================================================= +// glue::detail::PtrMaybe, glue::detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= +namespace detail { +template +class PtrMaybe { + public: + PtrMaybe() = default; + PtrMaybe(std::unique_ptr value) : value_(std::move(value)) {} + PtrMaybe(PtrMaybe&& other) noexcept : value_(std::move(other.value_)) {} + void operator=(std::unique_ptr value) { value_ = std::move(value); } + T* fromJust() const { + assert(value_); + return value_.get(); + } + T* fromMaybe(T* default_value) const { + return value_ ? value_.get() : default_value; + } + bool isJust() const { return value_ != nullptr; } + std::unique_ptr takeJust() { + assert(value_); + return std::move(value_); + } + + private: + std::unique_ptr value_; +}; + +template +class ValueMaybe { + public: + ValueMaybe() : is_just_(false), value_() {} + ValueMaybe(T value) : is_just_(true), value_(std::move(value)) {} + ValueMaybe(ValueMaybe&& other) noexcept + : is_just_(other.is_just_), value_(std::move(other.value_)) {} + void operator=(T value) { + value_ = value; + is_just_ = true; + } + const T& fromJust() const { + assert(is_just_); + return value_; + } + const T& fromMaybe(const T& default_value) const { + return is_just_ ? value_ : default_value; + } + bool isJust() const { return is_just_; } + T takeJust() { + assert(is_just_); + is_just_ = false; + return std::move(value_); + } + + private: + bool is_just_; + T value_; +}; +} // namespace detail +} // namespace glue +} // namespace v8_crdtp + +#define PROTOCOL_DISALLOW_COPY(ClassName) \ + private: \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_GLUE_H_ diff --git a/tools/inspector_protocol/crdtp/glue_test.cc b/tools/inspector_protocol/crdtp/glue_test.cc new file mode 100644 index 00000000000000..523d0c541b1e7a --- /dev/null +++ b/tools/inspector_protocol/crdtp/glue_test.cc @@ -0,0 +1,44 @@ +// 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 "glue.h" + +#include +#include + +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +namespace glue { +// ============================================================================= +// glue::detail::PtrMaybe, glue::detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= +TEST(PtrMaybeTest, SmokeTest) { + detail::PtrMaybe> example; + EXPECT_FALSE(example.isJust()); + EXPECT_TRUE(nullptr == example.fromMaybe(nullptr)); + std::unique_ptr> v(new std::vector); + v->push_back(42); + v->push_back(21); + example = std::move(v); + EXPECT_TRUE(example.isJust()); + EXPECT_THAT(*example.fromJust(), testing::ElementsAre(42, 21)); + std::unique_ptr> out = example.takeJust(); + EXPECT_FALSE(example.isJust()); + EXPECT_THAT(*out, testing::ElementsAre(42, 21)); +} + +TEST(PtrValueTest, SmokeTest) { + detail::ValueMaybe example; + EXPECT_FALSE(example.isJust()); + EXPECT_EQ(-1, example.fromMaybe(-1)); + example = 42; + EXPECT_TRUE(example.isJust()); + EXPECT_EQ(42, example.fromJust()); + int32_t out = example.takeJust(); + EXPECT_EQ(out, 42); +} +} // namespace glue +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/json.cc b/tools/inspector_protocol/crdtp/json.cc new file mode 100644 index 00000000000000..a4d78e9dcd4258 --- /dev/null +++ b/tools/inspector_protocol/crdtp/json.cc @@ -0,0 +1,1031 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "json.h" + +#include +#include +#include +#include +#include +#include + +#include "cbor.h" +#include "json_platform.h" + +namespace v8_inspector_protocol_crdtp { +namespace json { +// ============================================================================= +// json::NewJSONEncoder - for encoding streaming parser events as JSON +// ============================================================================= + +namespace { +// Prints |value| to |out| with 4 hex digits, most significant chunk first. +template +void PrintHex(uint16_t value, C* out) { + for (int ii = 3; ii >= 0; --ii) { + int four_bits = 0xf & (value >> (4 * ii)); + out->push_back(four_bits + ((four_bits <= 9) ? '0' : ('a' - 10))); + } +} + +// In the writer below, we maintain a stack of State instances. +// It is just enough to emit the appropriate delimiters and brackets +// in JSON. +enum class Container { + // Used for the top-level, initial state. + NONE, + // Inside a JSON object. + MAP, + // Inside a JSON array. + ARRAY +}; + +class State { + public: + explicit State(Container container) : container_(container) {} + void StartElement(std::vector* out) { StartElementTmpl(out); } + void StartElement(std::string* out) { StartElementTmpl(out); } + Container container() const { return container_; } + + private: + template + void StartElementTmpl(C* out) { + assert(container_ != Container::NONE || size_ == 0); + if (size_ != 0) { + char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':'; + out->push_back(delim); + } + ++size_; + } + + Container container_ = Container::NONE; + int size_ = 0; +}; + +constexpr char kBase64Table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/"; + +template +void Base64Encode(const span& in, C* out) { + // The following three cases are based on the tables in the example + // section in https://en.wikipedia.org/wiki/Base64. We process three + // input bytes at a time, emitting 4 output bytes at a time. + size_t ii = 0; + + // While possible, process three input bytes. + for (; ii + 3 <= in.size(); ii += 3) { + uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8) | in[ii + 2]; + out->push_back(kBase64Table[(twentyfour_bits >> 18)]); + out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); + out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]); + out->push_back(kBase64Table[twentyfour_bits & 0x3f]); + } + if (ii + 2 <= in.size()) { // Process two input bytes. + uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8); + out->push_back(kBase64Table[(twentyfour_bits >> 18)]); + out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); + out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]); + out->push_back('='); // Emit padding. + return; + } + if (ii + 1 <= in.size()) { // Process a single input byte. + uint32_t twentyfour_bits = (in[ii] << 16); + out->push_back(kBase64Table[(twentyfour_bits >> 18)]); + out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); + out->push_back('='); // Emit padding. + out->push_back('='); // Emit padding. + } +} + +// Implements a handler for JSON parser events to emit a JSON string. +template +class JSONEncoder : public ParserHandler { + public: + JSONEncoder(C* out, Status* status) : out_(out), status_(status) { + *status_ = Status(); + state_.emplace(Container::NONE); + } + + void HandleMapBegin() override { + if (!status_->ok()) + return; + assert(!state_.empty()); + state_.top().StartElement(out_); + state_.emplace(Container::MAP); + Emit('{'); + } + + void HandleMapEnd() override { + if (!status_->ok()) + return; + assert(state_.size() >= 2 && state_.top().container() == Container::MAP); + state_.pop(); + Emit('}'); + } + + void HandleArrayBegin() override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + state_.emplace(Container::ARRAY); + Emit('['); + } + + void HandleArrayEnd() override { + if (!status_->ok()) + return; + assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY); + state_.pop(); + Emit(']'); + } + + void HandleString16(span chars) override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + Emit('"'); + for (const uint16_t ch : chars) { + if (ch == '"') { + Emit("\\\""); + } else if (ch == '\\') { + Emit("\\\\"); + } else if (ch == '\b') { + Emit("\\b"); + } else if (ch == '\f') { + Emit("\\f"); + } else if (ch == '\n') { + Emit("\\n"); + } else if (ch == '\r') { + Emit("\\r"); + } else if (ch == '\t') { + Emit("\\t"); + } else if (ch >= 32 && ch <= 127) { + Emit(ch); + } else { + Emit("\\u"); + PrintHex(ch, out_); + } + } + Emit('"'); + } + + void HandleString8(span chars) override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + Emit('"'); + for (size_t ii = 0; ii < chars.size(); ++ii) { + uint8_t c = chars[ii]; + if (c == '"') { + Emit("\\\""); + } else if (c == '\\') { + Emit("\\\\"); + } else if (c == '\b') { + Emit("\\b"); + } else if (c == '\f') { + Emit("\\f"); + } else if (c == '\n') { + Emit("\\n"); + } else if (c == '\r') { + Emit("\\r"); + } else if (c == '\t') { + Emit("\\t"); + } else if (c >= 32 && c <= 127) { + Emit(c); + } else if (c < 32) { + Emit("\\u"); + PrintHex(static_cast(c), out_); + } else { + // Inspect the leading byte to figure out how long the utf8 + // byte sequence is; while doing this initialize |codepoint| + // with the first few bits. + // See table in: https://en.wikipedia.org/wiki/UTF-8 + // byte one is 110x xxxx -> 2 byte utf8 sequence + // byte one is 1110 xxxx -> 3 byte utf8 sequence + // byte one is 1111 0xxx -> 4 byte utf8 sequence + uint32_t codepoint; + int num_bytes_left; + if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence + num_bytes_left = 1; + codepoint = c & 0x1f; + } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence + num_bytes_left = 2; + codepoint = c & 0x0f; + } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence + codepoint = c & 0x07; + num_bytes_left = 3; + } else { + continue; // invalid leading byte + } + + // If we have enough bytes in our input, decode the remaining ones + // belonging to this Unicode character into |codepoint|. + if (ii + num_bytes_left >= chars.size()) + continue; + bool invalid_byte_seen = false; + while (num_bytes_left > 0) { + c = chars[++ii]; + --num_bytes_left; + // Check the next byte is a continuation byte, that is 10xx xxxx. + if ((c & 0xc0) != 0x80) + invalid_byte_seen = true; + codepoint = (codepoint << 6) | (c & 0x3f); + } + if (invalid_byte_seen) + continue; + + // Disallow overlong encodings for ascii characters, as these + // would include " and other characters significant to JSON + // string termination / control. + if (codepoint <= 0x7f) + continue; + // Invalid in UTF8, and can't be represented in UTF16 anyway. + if (codepoint > 0x10ffff) + continue; + + // So, now we transcode to UTF16, + // using the math described at https://en.wikipedia.org/wiki/UTF-16, + // for either one or two 16 bit characters. + if (codepoint <= 0xffff) { + Emit("\\u"); + PrintHex(static_cast(codepoint), out_); + continue; + } + codepoint -= 0x10000; + // high surrogate + Emit("\\u"); + PrintHex(static_cast((codepoint >> 10) + 0xd800), out_); + // low surrogate + Emit("\\u"); + PrintHex(static_cast((codepoint & 0x3ff) + 0xdc00), out_); + } + } + Emit('"'); + } + + void HandleBinary(span bytes) override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + Emit('"'); + Base64Encode(bytes, out_); + Emit('"'); + } + + void HandleDouble(double value) override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + // JSON cannot represent NaN or Infinity. So, for compatibility, + // we behave like the JSON object in web browsers: emit 'null'. + if (!std::isfinite(value)) { + Emit("null"); + return; + } + // If |value| is a scalar, emit it as an int. Taken from json_writer.cc in + // Chromium. + if (value < static_cast(std::numeric_limits::max()) && + value >= std::numeric_limits::min() && + std::floor(value) == value) { + Emit(std::to_string(static_cast(value))); + return; + } + std::string str_value = json::platform::DToStr(value); + // The following is somewhat paranoid, but also taken from json_writer.cc + // in Chromium: + // Ensure that the number has a .0 if there's no decimal or 'e'. This + // makes sure that when we read the JSON back, it's interpreted as a + // real rather than an int. + if (str_value.find_first_of(".eE") == std::string::npos) + str_value.append(".0"); + + // DToStr may fail to emit a 0 before the decimal dot. E.g. this is + // the case in base::NumberToString in Chromium (which is based on + // dmg_fp). So, much like + // https://cs.chromium.org/chromium/src/base/json/json_writer.cc + // we probe for this and emit the leading 0 anyway if necessary. + const char* chars = str_value.c_str(); + if (chars[0] == '.') { + Emit('0'); + } else if (chars[0] == '-' && chars[1] == '.') { + Emit("-0"); + ++chars; + } + Emit(chars); + } + + void HandleInt32(int32_t value) override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + Emit(std::to_string(value)); + } + + void HandleBool(bool value) override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + Emit(value ? "true" : "false"); + } + + void HandleNull() override { + if (!status_->ok()) + return; + state_.top().StartElement(out_); + Emit("null"); + } + + void HandleError(Status error) override { + assert(!error.ok()); + *status_ = error; + out_->clear(); + } + + private: + void Emit(char c) { out_->push_back(c); } + void Emit(const char* str) { + out_->insert(out_->end(), str, str + strlen(str)); + } + void Emit(const std::string& str) { + out_->insert(out_->end(), str.begin(), str.end()); + } + + C* out_; + Status* status_; + std::stack state_; +}; +} // namespace + +std::unique_ptr NewJSONEncoder(std::vector* out, + Status* status) { + return std::unique_ptr( + new JSONEncoder>(out, status)); +} + +std::unique_ptr NewJSONEncoder(std::string* out, + Status* status) { + return std::unique_ptr( + new JSONEncoder(out, status)); +} + +// ============================================================================= +// json::ParseJSON - for receiving streaming parser events for JSON. +// ============================================================================= + +namespace { +const int kStackLimit = 300; + +enum Token { + ObjectBegin, + ObjectEnd, + ArrayBegin, + ArrayEnd, + StringLiteral, + Number, + BoolTrue, + BoolFalse, + NullToken, + ListSeparator, + ObjectPairSeparator, + InvalidToken, + NoInput +}; + +const char* const kNullString = "null"; +const char* const kTrueString = "true"; +const char* const kFalseString = "false"; + +template +class JsonParser { + public: + explicit JsonParser(ParserHandler* handler) : handler_(handler) {} + + void Parse(const Char* start, size_t length) { + start_pos_ = start; + const Char* end = start + length; + const Char* tokenEnd = nullptr; + ParseValue(start, end, &tokenEnd, 0); + if (error_) + return; + if (tokenEnd != end) { + HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd); + } + } + + private: + bool CharsToDouble(const uint16_t* chars, size_t length, double* result) { + std::string buffer; + buffer.reserve(length + 1); + for (size_t ii = 0; ii < length; ++ii) { + bool is_ascii = !(chars[ii] & ~0x7F); + if (!is_ascii) + return false; + buffer.push_back(static_cast(chars[ii])); + } + return platform::StrToD(buffer.c_str(), result); + } + + bool CharsToDouble(const uint8_t* chars, size_t length, double* result) { + std::string buffer(reinterpret_cast(chars), length); + return platform::StrToD(buffer.c_str(), result); + } + + static bool ParseConstToken(const Char* start, + const Char* end, + const Char** token_end, + const char* token) { + // |token| is \0 terminated, it's one of the constants at top of the file. + while (start < end && *token != '\0' && *start++ == *token++) { + } + if (*token != '\0') + return false; + *token_end = start; + return true; + } + + static bool ReadInt(const Char* start, + const Char* end, + const Char** token_end, + bool allow_leading_zeros) { + if (start == end) + return false; + bool has_leading_zero = '0' == *start; + int length = 0; + while (start < end && '0' <= *start && *start <= '9') { + ++start; + ++length; + } + if (!length) + return false; + if (!allow_leading_zeros && length > 1 && has_leading_zero) + return false; + *token_end = start; + return true; + } + + static bool ParseNumberToken(const Char* start, + const Char* end, + const Char** token_end) { + // We just grab the number here. We validate the size in DecodeNumber. + // According to RFC4627, a valid number is: [minus] int [frac] [exp] + if (start == end) + return false; + Char c = *start; + if ('-' == c) + ++start; + + if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/false)) + return false; + if (start == end) { + *token_end = start; + return true; + } + + // Optional fraction part + c = *start; + if ('.' == c) { + ++start; + if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true)) + return false; + if (start == end) { + *token_end = start; + return true; + } + c = *start; + } + + // Optional exponent part + if ('e' == c || 'E' == c) { + ++start; + if (start == end) + return false; + c = *start; + if ('-' == c || '+' == c) { + ++start; + if (start == end) + return false; + } + if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true)) + return false; + } + + *token_end = start; + return true; + } + + static bool ReadHexDigits(const Char* start, + const Char* end, + const Char** token_end, + int digits) { + if (end - start < digits) + return false; + for (int i = 0; i < digits; ++i) { + Char c = *start++; + if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || + ('A' <= c && c <= 'F'))) + return false; + } + *token_end = start; + return true; + } + + static bool ParseStringToken(const Char* start, + const Char* end, + const Char** token_end) { + while (start < end) { + Char c = *start++; + if ('\\' == c) { + if (start == end) + return false; + c = *start++; + // Make sure the escaped char is valid. + switch (c) { + case 'x': + if (!ReadHexDigits(start, end, &start, 2)) + return false; + break; + case 'u': + if (!ReadHexDigits(start, end, &start, 4)) + return false; + break; + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case 'v': + case '"': + break; + default: + return false; + } + } else if ('"' == c) { + *token_end = start; + return true; + } + } + return false; + } + + static bool SkipComment(const Char* start, + const Char* end, + const Char** comment_end) { + if (start == end) + return false; + + if (*start != '/' || start + 1 >= end) + return false; + ++start; + + if (*start == '/') { + // Single line comment, read to newline. + for (++start; start < end; ++start) { + if (*start == '\n' || *start == '\r') { + *comment_end = start + 1; + return true; + } + } + *comment_end = end; + // Comment reaches end-of-input, which is fine. + return true; + } + + if (*start == '*') { + Char previous = '\0'; + // Block comment, read until end marker. + for (++start; start < end; previous = *start++) { + if (previous == '*' && *start == '/') { + *comment_end = start + 1; + return true; + } + } + // Block comment must close before end-of-input. + return false; + } + + return false; + } + + static bool IsSpaceOrNewLine(Char c) { + // \v = vertial tab; \f = form feed page break. + return c == ' ' || c == '\n' || c == '\v' || c == '\f' || c == '\r' || + c == '\t'; + } + + static void SkipWhitespaceAndComments(const Char* start, + const Char* end, + const Char** whitespace_end) { + while (start < end) { + if (IsSpaceOrNewLine(*start)) { + ++start; + } else if (*start == '/') { + const Char* comment_end = nullptr; + if (!SkipComment(start, end, &comment_end)) + break; + start = comment_end; + } else { + break; + } + } + *whitespace_end = start; + } + + static Token ParseToken(const Char* start, + const Char* end, + const Char** tokenStart, + const Char** token_end) { + SkipWhitespaceAndComments(start, end, tokenStart); + start = *tokenStart; + + if (start == end) + return NoInput; + + switch (*start) { + case 'n': + if (ParseConstToken(start, end, token_end, kNullString)) + return NullToken; + break; + case 't': + if (ParseConstToken(start, end, token_end, kTrueString)) + return BoolTrue; + break; + case 'f': + if (ParseConstToken(start, end, token_end, kFalseString)) + return BoolFalse; + break; + case '[': + *token_end = start + 1; + return ArrayBegin; + case ']': + *token_end = start + 1; + return ArrayEnd; + case ',': + *token_end = start + 1; + return ListSeparator; + case '{': + *token_end = start + 1; + return ObjectBegin; + case '}': + *token_end = start + 1; + return ObjectEnd; + case ':': + *token_end = start + 1; + return ObjectPairSeparator; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + if (ParseNumberToken(start, end, token_end)) + return Number; + break; + case '"': + if (ParseStringToken(start + 1, end, token_end)) + return StringLiteral; + break; + } + return InvalidToken; + } + + static int HexToInt(Char c) { + if ('0' <= c && c <= '9') + return c - '0'; + if ('A' <= c && c <= 'F') + return c - 'A' + 10; + if ('a' <= c && c <= 'f') + return c - 'a' + 10; + assert(false); // Unreachable. + return 0; + } + + static bool DecodeString(const Char* start, + const Char* end, + std::vector* output) { + if (start == end) + return true; + if (start > end) + return false; + output->reserve(end - start); + while (start < end) { + uint16_t c = *start++; + // If the |Char| we're dealing with is really a byte, then + // we have utf8 here, and we need to check for multibyte characters + // and transcode them to utf16 (either one or two utf16 chars). + if (sizeof(Char) == sizeof(uint8_t) && c > 0x7f) { + // Inspect the leading byte to figure out how long the utf8 + // byte sequence is; while doing this initialize |codepoint| + // with the first few bits. + // See table in: https://en.wikipedia.org/wiki/UTF-8 + // byte one is 110x xxxx -> 2 byte utf8 sequence + // byte one is 1110 xxxx -> 3 byte utf8 sequence + // byte one is 1111 0xxx -> 4 byte utf8 sequence + uint32_t codepoint; + int num_bytes_left; + if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence + num_bytes_left = 1; + codepoint = c & 0x1f; + } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence + num_bytes_left = 2; + codepoint = c & 0x0f; + } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence + codepoint = c & 0x07; + num_bytes_left = 3; + } else { + return false; // invalid leading byte + } + + // If we have enough bytes in our inpput, decode the remaining ones + // belonging to this Unicode character into |codepoint|. + if (start + num_bytes_left > end) + return false; + while (num_bytes_left > 0) { + c = *start++; + --num_bytes_left; + // Check the next byte is a continuation byte, that is 10xx xxxx. + if ((c & 0xc0) != 0x80) + return false; + codepoint = (codepoint << 6) | (c & 0x3f); + } + + // Disallow overlong encodings for ascii characters, as these + // would include " and other characters significant to JSON + // string termination / control. + if (codepoint <= 0x7f) + return false; + // Invalid in UTF8, and can't be represented in UTF16 anyway. + if (codepoint > 0x10ffff) + return false; + + // So, now we transcode to UTF16, + // using the math described at https://en.wikipedia.org/wiki/UTF-16, + // for either one or two 16 bit characters. + if (codepoint <= 0xffff) { + output->push_back(codepoint); + continue; + } + codepoint -= 0x10000; + output->push_back((codepoint >> 10) + 0xd800); // high surrogate + output->push_back((codepoint & 0x3ff) + 0xdc00); // low surrogate + continue; + } + if ('\\' != c) { + output->push_back(c); + continue; + } + if (start == end) + return false; + c = *start++; + + if (c == 'x') { + // \x is not supported. + return false; + } + + switch (c) { + case '"': + case '/': + case '\\': + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'u': + c = (HexToInt(*start) << 12) + (HexToInt(*(start + 1)) << 8) + + (HexToInt(*(start + 2)) << 4) + HexToInt(*(start + 3)); + start += 4; + break; + default: + return false; + } + output->push_back(c); + } + return true; + } + + void ParseValue(const Char* start, + const Char* end, + const Char** value_token_end, + int depth) { + if (depth > kStackLimit) { + HandleError(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, start); + return; + } + const Char* token_start = nullptr; + const Char* token_end = nullptr; + Token token = ParseToken(start, end, &token_start, &token_end); + switch (token) { + case NoInput: + HandleError(Error::JSON_PARSER_NO_INPUT, token_start); + return; + case InvalidToken: + HandleError(Error::JSON_PARSER_INVALID_TOKEN, token_start); + return; + case NullToken: + handler_->HandleNull(); + break; + case BoolTrue: + handler_->HandleBool(true); + break; + case BoolFalse: + handler_->HandleBool(false); + break; + case Number: { + double value; + if (!CharsToDouble(token_start, token_end - token_start, &value)) { + HandleError(Error::JSON_PARSER_INVALID_NUMBER, token_start); + return; + } + if (value >= std::numeric_limits::min() && + value <= std::numeric_limits::max() && + static_cast(value) == value) + handler_->HandleInt32(static_cast(value)); + else + handler_->HandleDouble(value); + break; + } + case StringLiteral: { + std::vector value; + bool ok = DecodeString(token_start + 1, token_end - 1, &value); + if (!ok) { + HandleError(Error::JSON_PARSER_INVALID_STRING, token_start); + return; + } + handler_->HandleString16(span(value.data(), value.size())); + break; + } + case ArrayBegin: { + handler_->HandleArrayBegin(); + start = token_end; + token = ParseToken(start, end, &token_start, &token_end); + while (token != ArrayEnd) { + ParseValue(start, end, &token_end, depth + 1); + if (error_) + return; + + // After a list value, we expect a comma or the end of the list. + start = token_end; + token = ParseToken(start, end, &token_start, &token_end); + if (token == ListSeparator) { + start = token_end; + token = ParseToken(start, end, &token_start, &token_end); + if (token == ArrayEnd) { + HandleError(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, token_start); + return; + } + } else if (token != ArrayEnd) { + // Unexpected value after list value. Bail out. + HandleError(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED, + token_start); + return; + } + } + handler_->HandleArrayEnd(); + break; + } + case ObjectBegin: { + handler_->HandleMapBegin(); + start = token_end; + token = ParseToken(start, end, &token_start, &token_end); + while (token != ObjectEnd) { + if (token != StringLiteral) { + HandleError(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, + token_start); + return; + } + std::vector key; + if (!DecodeString(token_start + 1, token_end - 1, &key)) { + HandleError(Error::JSON_PARSER_INVALID_STRING, token_start); + return; + } + handler_->HandleString16(span(key.data(), key.size())); + start = token_end; + + token = ParseToken(start, end, &token_start, &token_end); + if (token != ObjectPairSeparator) { + HandleError(Error::JSON_PARSER_COLON_EXPECTED, token_start); + return; + } + start = token_end; + + ParseValue(start, end, &token_end, depth + 1); + if (error_) + return; + start = token_end; + + // After a key/value pair, we expect a comma or the end of the + // object. + token = ParseToken(start, end, &token_start, &token_end); + if (token == ListSeparator) { + start = token_end; + token = ParseToken(start, end, &token_start, &token_end); + if (token == ObjectEnd) { + HandleError(Error::JSON_PARSER_UNEXPECTED_MAP_END, token_start); + return; + } + } else if (token != ObjectEnd) { + // Unexpected value after last object value. Bail out. + HandleError(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, + token_start); + return; + } + } + handler_->HandleMapEnd(); + break; + } + + default: + // We got a token that's not a value. + HandleError(Error::JSON_PARSER_VALUE_EXPECTED, token_start); + return; + } + + SkipWhitespaceAndComments(token_end, end, value_token_end); + } + + void HandleError(Error error, const Char* pos) { + assert(error != Error::OK); + if (!error_) { + handler_->HandleError( + Status{error, static_cast(pos - start_pos_)}); + error_ = true; + } + } + + const Char* start_pos_ = nullptr; + bool error_ = false; + ParserHandler* handler_; +}; +} // namespace + +void ParseJSON(span chars, ParserHandler* handler) { + JsonParser parser(handler); + parser.Parse(chars.data(), chars.size()); +} + +void ParseJSON(span chars, ParserHandler* handler) { + JsonParser parser(handler); + parser.Parse(chars.data(), chars.size()); +} + +// ============================================================================= +// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding +// ============================================================================= +template +Status ConvertCBORToJSONTmpl(span cbor, C* json) { + Status status; + std::unique_ptr json_writer = NewJSONEncoder(json, &status); + cbor::ParseCBOR(cbor, json_writer.get()); + return status; +} + +Status ConvertCBORToJSON(span cbor, std::vector* json) { + return ConvertCBORToJSONTmpl(cbor, json); +} + +Status ConvertCBORToJSON(span cbor, std::string* json) { + return ConvertCBORToJSONTmpl(cbor, json); +} + +template +Status ConvertJSONToCBORTmpl(span json, std::vector* cbor) { + Status status; + std::unique_ptr encoder = cbor::NewCBOREncoder(cbor, &status); + ParseJSON(json, encoder.get()); + return status; +} + +Status ConvertJSONToCBOR(span json, std::vector* cbor) { + return ConvertJSONToCBORTmpl(json, cbor); +} + +Status ConvertJSONToCBOR(span json, std::vector* cbor) { + return ConvertJSONToCBORTmpl(json, cbor); +} +} // namespace json +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/json.h b/tools/inspector_protocol/crdtp/json.h new file mode 100644 index 00000000000000..067ddc61659ec5 --- /dev/null +++ b/tools/inspector_protocol/crdtp/json.h @@ -0,0 +1,52 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_JSON_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_JSON_H_ + +#include +#include +#include "export.h" +#include "parser_handler.h" + +namespace v8_inspector_protocol_crdtp { +namespace json { +// ============================================================================= +// json::NewJSONEncoder - for encoding streaming parser events as JSON +// ============================================================================= + +// Returns a handler object which will write ascii characters to |out|. +// |status->ok()| will be false iff the handler routine HandleError() is called. +// In that case, we'll stop emitting output. +// Except for calling the HandleError routine at any time, the client +// code must call the Handle* methods in an order in which they'd occur +// in valid JSON; otherwise we may crash (the code uses assert). +std::unique_ptr NewJSONEncoder(std::vector* out, + Status* status); + +std::unique_ptr NewJSONEncoder(std::string* out, Status* status); + +// ============================================================================= +// json::ParseJSON - for receiving streaming parser events for JSON +// ============================================================================= + +void ParseJSON(span chars, ParserHandler* handler); + +void ParseJSON(span chars, ParserHandler* handler); + +// ============================================================================= +// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding +// ============================================================================= + +Status ConvertCBORToJSON(span cbor, std::string* json); + +Status ConvertCBORToJSON(span cbor, std::vector* json); + +Status ConvertJSONToCBOR(span json, std::vector* cbor); + +Status ConvertJSONToCBOR(span json, std::vector* cbor); +} // namespace json +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_JSON_H_ diff --git a/tools/inspector_protocol/crdtp/json_platform.h b/tools/inspector_protocol/crdtp/json_platform.h new file mode 100644 index 00000000000000..398a7ff503d22a --- /dev/null +++ b/tools/inspector_protocol/crdtp/json_platform.h @@ -0,0 +1,26 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_JSON_PLATFORM_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_JSON_PLATFORM_H_ + +#include + +namespace v8_inspector_protocol_crdtp { +namespace json { +// These routines are implemented in json_platform.cc, or in a +// platform-dependent (code-base dependent) custom replacement. +// E.g., json_platform_chromium.cc, json_platform_v8.cc. +namespace platform { +// Parses |str| into |result|. Returns false iff there are +// leftover characters or parsing errors. +bool StrToD(const char* str, double* result); + +// Prints |value| in a format suitable for JSON. +std::string DToStr(double value); +} // namespace platform +} // namespace json +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_JSON_PLATFORM_H_ diff --git a/tools/inspector_protocol/crdtp/json_platform_v8.cc b/tools/inspector_protocol/crdtp/json_platform_v8.cc new file mode 100644 index 00000000000000..1ba04e0d00da61 --- /dev/null +++ b/tools/inspector_protocol/crdtp/json_platform_v8.cc @@ -0,0 +1,32 @@ +// Copyright 2019 The V8 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is V8 specific. It's not rolled from the upstream project. + +#include "json_platform.h" + +#include +#include "../../../src/numbers/conversions.h" +#include "../../../src/utils/vector.h" + +namespace v8_inspector_protocol_crdtp { +namespace json { +namespace platform { +// Parses |str| into |result|. Returns false iff there are +// leftover characters or parsing errors. +bool StrToD(const char* str, double* result) { + *result = v8::internal::StringToDouble(str, v8::internal::NO_FLAGS); + return std::isfinite(*result); +} + +// Prints |value| in a format suitable for JSON. +std::string DToStr(double value) { + v8::internal::ScopedVector buffer( + v8::internal::kDoubleToCStringMinBufferSize); + const char* str = v8::internal::DoubleToCString(value, buffer); + return (str == nullptr) ? "" : std::string(str); +} +} // namespace platform +} // namespace json +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/json_test.cc b/tools/inspector_protocol/crdtp/json_test.cc new file mode 100644 index 00000000000000..1409fdcd7a89a2 --- /dev/null +++ b/tools/inspector_protocol/crdtp/json_test.cc @@ -0,0 +1,753 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "json.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cbor.h" +#include "parser_handler.h" +#include "span.h" +#include "status.h" +#include "status_test_support.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +namespace json { +// ============================================================================= +// json::NewJSONEncoder - for encoding streaming parser events as JSON +// ============================================================================= + +void WriteUTF8AsUTF16(ParserHandler* writer, const std::string& utf8) { + writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8)))); +} + +TEST(JsonEncoder, OverlongEncodings) { + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + + // We encode 0x7f, which is the DEL ascii character, as a 4 byte UTF8 + // sequence. This is called an overlong encoding, because only 1 byte + // is needed to represent 0x7f as UTF8. + std::vector chars = { + 0xf0, // Starts 4 byte utf8 sequence + 0x80, // continuation byte + 0x81, // continuation byte w/ payload bit 7 set to 1. + 0xbf, // continuation byte w/ payload bits 0-6 set to 11111. + }; + writer->HandleString8(SpanFrom(chars)); + EXPECT_EQ("\"\"", out); // Empty string means that 0x7f was rejected (good). +} + +TEST(JsonEncoder, NotAContinuationByte) { + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + + // |world| encodes the globe as a 4 byte UTF8 sequence. So, naturally, it'll + // have a start byte, followed by three continuation bytes. + std::string world = "🌎"; + ASSERT_EQ(4u, world.size()); + ASSERT_EQ(world[1] & 0xc0, 0x80); // checks for continuation byte + ASSERT_EQ(world[2] & 0xc0, 0x80); + ASSERT_EQ(world[3] & 0xc0, 0x80); + + // Now create a corrupted UTF8 string, starting with the first two bytes from + // |world|, followed by an ASCII message. Upon encountering '!', our decoder + // will realize that it's not a continuation byte; it'll skip to the end of + // this UTF8 sequence and continue with the next character. In this case, the + // 'H', of "Hello". + std::vector chars; + chars.push_back(world[0]); + chars.push_back(world[1]); + chars.push_back('!'); + chars.push_back('?'); + chars.push_back('H'); + chars.push_back('e'); + chars.push_back('l'); + chars.push_back('l'); + chars.push_back('o'); + writer->HandleString8(SpanFrom(chars)); + EXPECT_EQ("\"Hello\"", out); // "Hello" shows we restarted at 'H'. +} + +TEST(JsonEncoder, EscapesLoneHighSurrogates) { + // This tests that the JSON encoder escapes lone high surrogates, i.e. + // invalid code points in the range from 0xD800 to 0xDBFF. In + // unescaped form, these cannot be represented in well-formed UTF-8 or + // UTF-16. + std::vector chars = {'a', 0xd800, 'b', 0xdada, 'c', 0xdbff, 'd'}; + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleString16(span(chars.data(), chars.size())); + EXPECT_EQ("\"a\\ud800b\\udadac\\udbffd\"", out); +} + +TEST(JsonEncoder, EscapesLoneLowSurrogates) { + // This tests that the JSON encoder escapes lone low surrogates, i.e. + // invalid code points in the range from 0xDC00 to 0xDFFF. In + // unescaped form, these cannot be represented in well-formed UTF-8 or + // UTF-16. + std::vector chars = {'a', 0xdc00, 'b', 0xdede, 'c', 0xdfff, 'd'}; + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleString16(span(chars.data(), chars.size())); + EXPECT_EQ("\"a\\udc00b\\udedec\\udfffd\"", out); +} + +TEST(JsonEncoder, EscapesFFFF) { + // This tests that the JSON encoder will escape the UTF16 input 0xffff as + // \uffff; useful to check this since it's an edge case. + std::vector chars = {'a', 'b', 'c', 0xffff, 'd'}; + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleString16(span(chars.data(), chars.size())); + EXPECT_EQ("\"abc\\uffffd\"", out); +} + +TEST(JsonEncoder, Passes0x7FString8) { + std::vector chars = {'a', 0x7f, 'b'}; + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleString8(span(chars.data(), chars.size())); + EXPECT_EQ( + "\"a\x7f" + "b\"", + out); +} + +TEST(JsonEncoder, Passes0x7FString16) { + std::vector chars16 = {'a', 0x7f, 'b'}; + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleString16(span(chars16.data(), chars16.size())); + EXPECT_EQ( + "\"a\x7f" + "b\"", + out); +} + +TEST(JsonEncoder, IncompleteUtf8Sequence) { + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + + writer->HandleArrayBegin(); // This emits [, which starts an array. + + { // 🌎 takes four bytes to encode in UTF-8. We test with the first three; + // This means we're trying to emit a string that consists solely of an + // incomplete UTF-8 sequence. So the string in the JSON output is empty. + std::string world_utf8 = "🌎"; + ASSERT_EQ(4u, world_utf8.size()); + std::vector chars(world_utf8.begin(), world_utf8.begin() + 3); + writer->HandleString8(SpanFrom(chars)); + EXPECT_EQ("[\"\"", out); // Incomplete sequence rejected: empty string. + } + + { // This time, the incomplete sequence is at the end of the string. + std::string msg = "Hello, \xF0\x9F\x8C"; + std::vector chars(msg.begin(), msg.end()); + writer->HandleString8(SpanFrom(chars)); + EXPECT_EQ("[\"\",\"Hello, \"", out); // Incomplete sequence dropped at end. + } +} + +TEST(JsonStdStringWriterTest, HelloWorld) { + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleMapBegin(); + WriteUTF8AsUTF16(writer.get(), "msg1"); + WriteUTF8AsUTF16(writer.get(), "Hello, 🌎."); + std::string key = "msg1-as-utf8"; + std::string value = "Hello, 🌎."; + writer->HandleString8(SpanFrom(key)); + writer->HandleString8(SpanFrom(value)); + WriteUTF8AsUTF16(writer.get(), "msg2"); + WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\""); + WriteUTF8AsUTF16(writer.get(), "nested"); + writer->HandleMapBegin(); + WriteUTF8AsUTF16(writer.get(), "double"); + writer->HandleDouble(3.1415); + WriteUTF8AsUTF16(writer.get(), "int"); + writer->HandleInt32(-42); + WriteUTF8AsUTF16(writer.get(), "bool"); + writer->HandleBool(false); + WriteUTF8AsUTF16(writer.get(), "null"); + writer->HandleNull(); + writer->HandleMapEnd(); + WriteUTF8AsUTF16(writer.get(), "array"); + writer->HandleArrayBegin(); + writer->HandleInt32(1); + writer->HandleInt32(2); + writer->HandleInt32(3); + writer->HandleArrayEnd(); + writer->HandleMapEnd(); + EXPECT_TRUE(status.ok()); + EXPECT_EQ( + "{\"msg1\":\"Hello, \\ud83c\\udf0e.\"," + "\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\"," + "\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\"," + "\"nested\":{\"double\":3.1415,\"int\":-42," + "\"bool\":false,\"null\":null},\"array\":[1,2,3]}", + out); +} + +TEST(JsonStdStringWriterTest, ScalarsAreRenderedAsInt) { + // Test that Number.MIN_SAFE_INTEGER / Number.MAX_SAFE_INTEGER from Javascript + // are rendered as integers (no decimal point / rounding), even when we + // encode them from double. Javascript's Number is an IEE754 double, so + // it has 53 bits to represent integers. + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleMapBegin(); + + writer->HandleString8(SpanFrom("Number.MIN_SAFE_INTEGER")); + EXPECT_EQ(-0x1fffffffffffff, -9007199254740991); // 53 bits for integers. + writer->HandleDouble(-9007199254740991); // Note HandleDouble here. + + writer->HandleString8(SpanFrom("Number.MAX_SAFE_INTEGER")); + EXPECT_EQ(0x1fffffffffffff, 9007199254740991); // 53 bits for integers. + writer->HandleDouble(9007199254740991); // Note HandleDouble here. + + writer->HandleMapEnd(); + EXPECT_TRUE(status.ok()); + EXPECT_EQ( + "{\"Number.MIN_SAFE_INTEGER\":-9007199254740991," + "\"Number.MAX_SAFE_INTEGER\":9007199254740991}", + out); +} + +TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) { + // JSON can't represent +Infinity, -Infinity, or NaN. + // So in practice it's mapped to null. + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleMapBegin(); + writer->HandleString8(SpanFrom("Infinity")); + writer->HandleDouble(std::numeric_limits::infinity()); + writer->HandleString8(SpanFrom("-Infinity")); + writer->HandleDouble(-std::numeric_limits::infinity()); + writer->HandleString8(SpanFrom("NaN")); + writer->HandleDouble(std::numeric_limits::quiet_NaN()); + writer->HandleMapEnd(); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out); +} + +TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) { + // The encoder emits binary submitted to ParserHandler::HandleBinary + // as base64. The following three examples are taken from + // https://en.wikipedia.org/wiki/Base64. + { + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleBinary(SpanFrom(std::vector({'M', 'a', 'n'}))); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("\"TWFu\"", out); + } + { + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleBinary(SpanFrom(std::vector({'M', 'a'}))); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("\"TWE=\"", out); + } + { + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleBinary(SpanFrom(std::vector({'M'}))); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("\"TQ==\"", out); + } + { // "Hello, world.", verified with base64decode.org. + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleBinary(SpanFrom(std::vector( + {'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'}))); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out); + } +} + +TEST(JsonStdStringWriterTest, HandlesErrors) { + // When an error is sent via HandleError, it saves it in the provided + // status and clears the output. + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleMapBegin(); + WriteUTF8AsUTF16(writer.get(), "msg1"); + writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42}); + EXPECT_THAT(status, StatusIs(Error::JSON_PARSER_VALUE_EXPECTED, 42u)); + EXPECT_EQ("", out); +} + +TEST(JsonStdStringWriterTest, DoubleToString_LeadingZero) { + // In JSON, .1 must be rendered as 0.1, and -.7 must be rendered as -0.7. + std::string out; + Status status; + std::unique_ptr writer = NewJSONEncoder(&out, &status); + writer->HandleArrayBegin(); + writer->HandleDouble(.1); + writer->HandleDouble(-.7); + writer->HandleArrayEnd(); + EXPECT_EQ("[0.1,-0.7]", out); +} + +// ============================================================================= +// json::ParseJSON - for receiving streaming parser events for JSON +// ============================================================================= + +class Log : public ParserHandler { + public: + void HandleMapBegin() override { log_ << "map begin\n"; } + + void HandleMapEnd() override { log_ << "map end\n"; } + + void HandleArrayBegin() override { log_ << "array begin\n"; } + + void HandleArrayEnd() override { log_ << "array end\n"; } + + void HandleString8(span chars) override { + log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n"; + } + + void HandleString16(span chars) override { + raw_log_string16_.emplace_back(chars.begin(), chars.end()); + log_ << "string16: " << UTF16ToUTF8(chars) << "\n"; + } + + void HandleBinary(span bytes) override { + // JSON doesn't have native support for arbitrary bytes, so our parser will + // never call this. + CHECK(false); + } + + void HandleDouble(double value) override { + log_ << "double: " << value << "\n"; + } + + void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; } + + void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; } + + void HandleNull() override { log_ << "null\n"; } + + void HandleError(Status status) override { status_ = status; } + + std::string str() const { return status_.ok() ? log_.str() : ""; } + + std::vector> raw_log_string16() const { + return raw_log_string16_; + } + + Status status() const { return status_; } + + private: + std::ostringstream log_; + std::vector> raw_log_string16_; + Status status_; +}; + +class JsonParserTest : public ::testing::Test { + protected: + Log log_; +}; + +TEST_F(JsonParserTest, SimpleDictionary) { + std::string json = "{\"foo\": 42}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: foo\n" + "int: 42\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, UsAsciiDelCornerCase) { + // DEL (0x7f) is a 7 bit US-ASCII character, and while it is a control + // character according to Unicode, it's not considered a control + // character in https://tools.ietf.org/html/rfc7159#section-7, so + // it can be placed directly into the JSON string, without JSON escaping. + std::string json = "{\"foo\": \"a\x7f\"}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: foo\n" + "string16: a\x7f\n" + "map end\n", + log_.str()); + + // We've seen an implementation of UTF16ToUTF8 which would replace the DEL + // character with ' ', so this simple roundtrip tests the routines in + // encoding_test_helper.h, to make test failures of the above easier to + // diagnose. + std::vector utf16 = UTF8ToUTF16(SpanFrom(json)); + EXPECT_EQ(json, UTF16ToUTF8(SpanFrom(utf16))); +} + +TEST_F(JsonParserTest, Whitespace) { + std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: msg\n" + "string16: Hello, world.\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, NestedDictionary) { + std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: foo\n" + "map begin\n" + "string16: bar\n" + "map begin\n" + "string16: baz\n" + "int: 1\n" + "map end\n" + "string16: bar2\n" + "int: 2\n" + "map end\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, Doubles) { + std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: foo\n" + "double: 3.1415\n" + "string16: bar\n" + "double: 3.1415\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, Unicode) { + // Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16. + std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: msg\n" + "string16: Hello, 🌎.\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, Unicode_ParseUtf16) { + // Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E. + // Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19. + + // We provide the moon with json escape, but the earth as utf16 input. + // Either way they arrive as utf8 (after decoding in log_.str()). + std::vector json = + UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}")); + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: space\n" + "string16: 🌎 🌙.\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, Unicode_ParseUtf16_SingleEscapeUpToFFFF) { + // 0xFFFF is the max codepoint that can be represented as a single \u escape. + // One way to write this is \uffff, another way is to encode it as a 3 byte + // UTF-8 sequence (0xef 0xbf 0xbf). Both are equivalent. + + // Example with both ways of encoding code point 0xFFFF in a JSON string. + std::string json = "{\"escape\": \"\xef\xbf\xbf or \\uffff\"}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + + // Shows both inputs result in equivalent output once converted to UTF-8. + EXPECT_EQ( + "map begin\n" + "string16: escape\n" + "string16: \xEF\xBF\xBF or \xEF\xBF\xBF\n" + "map end\n", + log_.str()); + + // Make an even stronger assertion: The parser represents \xffff as a single + // UTF-16 char. + ASSERT_EQ(2u, log_.raw_log_string16().size()); + std::vector expected = {0xffff, ' ', 'o', 'r', ' ', 0xffff}; + EXPECT_EQ(expected, log_.raw_log_string16()[1]); +} + +TEST_F(JsonParserTest, Unicode_ParseUtf8) { + // Used below: + // гласность - example for 2 byte utf8, Russian word "glasnost" + // 屋 - example for 3 byte utf8, Chinese word for "house" + // 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E. + // 🌙 - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19. + + // We provide the moon with json escape, but the earth as utf8 input. + // Either way they arrive as utf8 (after decoding in log_.str()). + std::string json = + "{" + "\"escapes\": \"\\uD83C\\uDF19\"," + "\"2 byte\":\"гласность\"," + "\"3 byte\":\"屋\"," + "\"4 byte\":\"🌎\"" + "}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: escapes\n" + "string16: 🌙\n" + "string16: 2 byte\n" + "string16: гласность\n" + "string16: 3 byte\n" + "string16: 屋\n" + "string16: 4 byte\n" + "string16: 🌎\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, UnprocessedInputRemainsError) { + // Trailing junk after the valid JSON. + std::string json = "{\"foo\": 3.1415} junk"; + size_t junk_idx = json.find("junk"); + EXPECT_NE(junk_idx, std::string::npos); + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), + StatusIs(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, junk_idx)); + EXPECT_EQ("", log_.str()); +} + +std::string MakeNestedJson(int depth) { + std::string json; + for (int ii = 0; ii < depth; ++ii) + json += "{\"foo\":"; + json += "42"; + for (int ii = 0; ii < depth; ++ii) + json += "}"; + return json; +} + +TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) { + // kStackLimit is 300 (see json_parser.cc). First let's + // try with a small nested example. + std::string json_3 = MakeNestedJson(3); + ParseJSON(SpanFrom(json_3), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: foo\n" + "map begin\n" + "string16: foo\n" + "map begin\n" + "string16: foo\n" + "int: 42\n" + "map end\n" + "map end\n" + "map end\n", + log_.str()); +} + +TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) { + // Now with kStackLimit (300). + std::string json_limit = MakeNestedJson(300); + ParseJSON(span(reinterpret_cast(json_limit.data()), + json_limit.size()), + &log_); + EXPECT_THAT(log_.status(), StatusIsOk()); +} + +TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) { + // Now with kStackLimit + 1 (301) - it exceeds in the innermost instance. + std::string exceeded = MakeNestedJson(301); + ParseJSON(SpanFrom(exceeded), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, + strlen("{\"foo\":") * 301)); +} + +TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) { + // Now way past the limit. Still, the point of exceeding is 301. + std::string far_out = MakeNestedJson(320); + ParseJSON(SpanFrom(far_out), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, + strlen("{\"foo\":") * 301)); +} + +TEST_F(JsonParserTest, NoInputError) { + std::string json = ""; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_NO_INPUT, 0u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, InvalidTokenError) { + std::string json = "|"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_INVALID_TOKEN, 0u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, InvalidNumberError) { + // Mantissa exceeds max (the constant used here is int64_t max). + std::string json = "1E9223372036854775807"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_INVALID_NUMBER, 0u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, InvalidStringError) { + // \x22 is an unsupported escape sequence + std::string json = "\"foo\\x22\""; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_INVALID_STRING, 0u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, UnexpectedArrayEndError) { + std::string json = "[1,2,]"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), + StatusIs(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, 5u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) { + std::string json = "[1,2 2"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), + StatusIs(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED, 5u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, StringLiteralExpectedError) { + // There's an error because the key bar, a string, is not terminated. + std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), + StatusIs(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, 16u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, ColonExpectedError) { + std::string json = "{\"foo\", 42}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_COLON_EXPECTED, 6u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, UnexpectedMapEndError) { + std::string json = "{\"foo\": 42, }"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), + StatusIs(Error::JSON_PARSER_UNEXPECTED_MAP_END, 12u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, CommaOrMapEndExpectedError) { + // The second separator should be a comma. + std::string json = "{\"foo\": 3.1415: \"bar\": 0}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), + StatusIs(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, 14u)); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, ValueExpectedError) { + std::string json = "}"; + ParseJSON(SpanFrom(json), &log_); + EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_VALUE_EXPECTED, 0u)); + EXPECT_EQ("", log_.str()); +} + +template +class ConvertJSONToCBORTest : public ::testing::Test {}; + +using ContainerTestTypes = ::testing::Types, std::string>; +TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes); + +TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) { + for (const std::string& json_in : { + "{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}", + "3.1415", + "false", + "true", + "\"Hello, world.\"", + "[1,2,3]", + "[]", + }) { + SCOPED_TRACE(json_in); + TypeParam json(json_in.begin(), json_in.end()); + std::vector cbor; + { + Status status = ConvertJSONToCBOR(SpanFrom(json), &cbor); + EXPECT_THAT(status, StatusIsOk()); + } + TypeParam roundtrip_json; + { + Status status = ConvertCBORToJSON(SpanFrom(cbor), &roundtrip_json); + EXPECT_THAT(status, StatusIsOk()); + } + EXPECT_EQ(json, roundtrip_json); + } +} + +TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) { + std::vector json16 = { + '{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l', + 'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't', + '"', ':', '[', '1', ',', '2', ',', '3', ']', '}'}; + std::vector cbor; + { + Status status = + ConvertJSONToCBOR(span(json16.data(), json16.size()), &cbor); + EXPECT_THAT(status, StatusIsOk()); + } + TypeParam roundtrip_json; + { + Status status = ConvertCBORToJSON(SpanFrom(cbor), &roundtrip_json); + EXPECT_THAT(status, StatusIsOk()); + } + std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}"; + TypeParam expected_json(json.begin(), json.end()); + EXPECT_EQ(expected_json, roundtrip_json); +} +} // namespace json +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/maybe.h b/tools/inspector_protocol/crdtp/maybe.h new file mode 100644 index 00000000000000..17caa044130cb0 --- /dev/null +++ b/tools/inspector_protocol/crdtp/maybe.h @@ -0,0 +1,104 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_MAYBE_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_MAYBE_H_ + +#include +#include + +namespace v8_inspector_protocol_crdtp { + +// ============================================================================= +// detail::PtrMaybe, detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= + +namespace detail { +template +class PtrMaybe { + public: + PtrMaybe() = default; + PtrMaybe(std::unique_ptr value) : value_(std::move(value)) {} + PtrMaybe(PtrMaybe&& other) noexcept : value_(std::move(other.value_)) {} + void operator=(std::unique_ptr value) { value_ = std::move(value); } + T* fromJust() const { + assert(value_); + return value_.get(); + } + T* fromMaybe(T* default_value) const { + return value_ ? value_.get() : default_value; + } + bool isJust() const { return value_ != nullptr; } + std::unique_ptr takeJust() { + assert(value_); + return std::move(value_); + } + + private: + std::unique_ptr value_; +}; + +template +class ValueMaybe { + public: + ValueMaybe() : is_just_(false), value_() {} + ValueMaybe(T value) : is_just_(true), value_(std::move(value)) {} + ValueMaybe(ValueMaybe&& other) noexcept + : is_just_(other.is_just_), value_(std::move(other.value_)) {} + void operator=(T value) { + value_ = value; + is_just_ = true; + } + const T& fromJust() const { + assert(is_just_); + return value_; + } + const T& fromMaybe(const T& default_value) const { + return is_just_ ? value_ : default_value; + } + bool isJust() const { return is_just_; } + T takeJust() { + assert(is_just_); + is_just_ = false; + return std::move(value_); + } + + private: + bool is_just_; + T value_; +}; + +template +struct MaybeTypedef { + typedef PtrMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +} // namespace detail + +template +using Maybe = typename detail::MaybeTypedef::type; + +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_MAYBE_H_ diff --git a/tools/inspector_protocol/crdtp/maybe_test.cc b/tools/inspector_protocol/crdtp/maybe_test.cc new file mode 100644 index 00000000000000..e72fdc34e228c8 --- /dev/null +++ b/tools/inspector_protocol/crdtp/maybe_test.cc @@ -0,0 +1,44 @@ +// 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 "maybe.h" + +#include +#include + +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { + +// ============================================================================= +// detail::PtrMaybe, detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= +TEST(PtrMaybeTest, SmokeTest) { + detail::PtrMaybe> example; + EXPECT_FALSE(example.isJust()); + EXPECT_TRUE(nullptr == example.fromMaybe(nullptr)); + std::unique_ptr> v(new std::vector); + v->push_back(42); + v->push_back(21); + example = std::move(v); + EXPECT_TRUE(example.isJust()); + EXPECT_THAT(*example.fromJust(), testing::ElementsAre(42, 21)); + std::unique_ptr> out = example.takeJust(); + EXPECT_FALSE(example.isJust()); + EXPECT_THAT(*out, testing::ElementsAre(42, 21)); +} + +TEST(PtrValueTest, SmokeTest) { + detail::ValueMaybe example; + EXPECT_FALSE(example.isJust()); + EXPECT_EQ(-1, example.fromMaybe(-1)); + example = 42; + EXPECT_TRUE(example.isJust()); + EXPECT_EQ(42, example.fromJust()); + int32_t out = example.takeJust(); + EXPECT_EQ(out, 42); +} + +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/parser_handler.h b/tools/inspector_protocol/crdtp/parser_handler.h new file mode 100644 index 00000000000000..d5478d9695ca2d --- /dev/null +++ b/tools/inspector_protocol/crdtp/parser_handler.h @@ -0,0 +1,39 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_PARSER_HANDLER_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_PARSER_HANDLER_H_ + +#include +#include "span.h" +#include "status.h" + +namespace v8_inspector_protocol_crdtp { +// Handler interface for parser events emitted by a streaming parser. +// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder, +// json::ParseJSON. +class ParserHandler { + public: + virtual ~ParserHandler() = default; + virtual void HandleMapBegin() = 0; + virtual void HandleMapEnd() = 0; + virtual void HandleArrayBegin() = 0; + virtual void HandleArrayEnd() = 0; + virtual void HandleString8(span chars) = 0; + virtual void HandleString16(span chars) = 0; + virtual void HandleBinary(span bytes) = 0; + virtual void HandleDouble(double value) = 0; + virtual void HandleInt32(int32_t value) = 0; + virtual void HandleBool(bool value) = 0; + virtual void HandleNull() = 0; + + // The parser may send one error even after other events have already + // been received. Client code is reponsible to then discard the + // already processed events. + // |error| must be an eror, as in, |error.is_ok()| can't be true. + virtual void HandleError(Status error) = 0; +}; +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_PARSER_HANDLER_H_ diff --git a/tools/inspector_protocol/crdtp/protocol_core.cc b/tools/inspector_protocol/crdtp/protocol_core.cc new file mode 100644 index 00000000000000..27aed329340e01 --- /dev/null +++ b/tools/inspector_protocol/crdtp/protocol_core.cc @@ -0,0 +1,289 @@ +// Copyright 2020 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 "protocol_core.h" + +#include +#include +#include + +namespace v8_inspector_protocol_crdtp { + +DeserializerState::DeserializerState(std::vector bytes) + : storage_(new std::vector(std::move(bytes))), + tokenizer_(span(storage_->data(), storage_->size())) {} + +DeserializerState::DeserializerState(Storage storage, span span) + : storage_(std::move(storage)), tokenizer_(span) {} + +void DeserializerState::RegisterError(Error error) { + assert(Error::OK != error); + if (tokenizer_.Status().ok()) + status_ = Status{error, tokenizer_.Status().pos}; +} + +void DeserializerState::RegisterFieldPath(span name) { + field_path_.push_back(name); +} + +std::string DeserializerState::ErrorMessage(span message_name) const { + std::string msg = "Failed to deserialize "; + msg.append(message_name.begin(), message_name.end()); + for (int field = static_cast(field_path_.size()) - 1; field >= 0; + --field) { + msg.append("."); + msg.append(field_path_[field].begin(), field_path_[field].end()); + } + Status s = status(); + if (!s.ok()) + msg += " - " + s.ToASCIIString(); + return msg; +} + +Status DeserializerState::status() const { + if (!tokenizer_.Status().ok()) + return tokenizer_.Status(); + return status_; +} + +namespace { +constexpr int32_t GetMandatoryFieldMask( + const DeserializerDescriptor::Field* fields, + size_t count) { + int32_t mask = 0; + for (size_t i = 0; i < count; ++i) { + if (!fields[i].is_optional) + mask |= (1 << i); + } + return mask; +} +} // namespace + +DeserializerDescriptor::DeserializerDescriptor(const Field* fields, + size_t field_count) + : fields_(fields), + field_count_(field_count), + mandatory_field_mask_(GetMandatoryFieldMask(fields, field_count)) {} + +bool DeserializerDescriptor::Deserialize(DeserializerState* state, + void* obj) const { + auto* tokenizer = state->tokenizer(); + + // As a special compatibility quirk, allow empty objects if + // no mandatory fields are required. + if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE && + !mandatory_field_mask_) { + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::MAP_START) { + state->RegisterError(Error::CBOR_MAP_START_EXPECTED); + return false; + } + tokenizer->Next(); + int32_t seen_mandatory_fields = 0; + for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP; tokenizer->Next()) { + if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) { + state->RegisterError(Error::CBOR_INVALID_MAP_KEY); + return false; + } + span u_key = tokenizer->GetString8(); + span key(reinterpret_cast(u_key.data()), u_key.size()); + tokenizer->Next(); + if (!DeserializeField(state, key, &seen_mandatory_fields, obj)) + return false; + } + // Only compute mandatory fields once per type. + int32_t missing_fields = seen_mandatory_fields ^ mandatory_field_mask_; + if (missing_fields) { + int32_t idx = 0; + while ((missing_fields & 1) == 0) { + missing_fields >>= 1; + ++idx; + } + state->RegisterError(Error::BINDINGS_MANDATORY_FIELD_MISSING); + state->RegisterFieldPath(fields_[idx].name); + return false; + } + return true; +} + +bool DeserializerDescriptor::DeserializeField(DeserializerState* state, + span name, + int* seen_mandatory_fields, + void* obj) const { + // TODO(caseq): consider checking if the sought field is the one + // after the last deserialized. + const auto* begin = fields_; + const auto* end = fields_ + field_count_; + auto entry = std::lower_bound( + begin, end, name, [](const Field& field_desc, span field_name) { + return SpanLessThan(field_desc.name, field_name); + }); + // Unknown field is not an error -- we may be working against an + // implementation of a later version of the protocol. + // TODO(caseq): support unknown arrays and maps not enclosed by an envelope. + if (entry == end || !SpanEquals(entry->name, name)) + return true; + if (!entry->deserializer(state, obj)) { + state->RegisterFieldPath(name); + return false; + } + if (!entry->is_optional) + *seen_mandatory_fields |= 1 << (entry - begin); + return true; +} + +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + bool* value) { + const auto tag = state->tokenizer()->TokenTag(); + if (tag == cbor::CBORTokenTag::TRUE_VALUE) { + *value = true; + return true; + } + if (tag == cbor::CBORTokenTag::FALSE_VALUE) { + *value = false; + return true; + } + state->RegisterError(Error::BINDINGS_BOOL_VALUE_EXPECTED); + return false; +} + +void ProtocolTypeTraits::Serialize(bool value, + std::vector* bytes) { + bytes->push_back(value ? cbor::EncodeTrue() : cbor::EncodeFalse()); +} + +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + int32_t* value) { + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::INT32) { + state->RegisterError(Error::BINDINGS_INT32_VALUE_EXPECTED); + return false; + } + *value = state->tokenizer()->GetInt32(); + return true; +} + +void ProtocolTypeTraits::Serialize(int32_t value, + std::vector* bytes) { + cbor::EncodeInt32(value, bytes); +} + +ContainerSerializer::ContainerSerializer(std::vector* bytes, + uint8_t tag) + : bytes_(bytes) { + envelope_.EncodeStart(bytes_); + bytes_->push_back(tag); +} + +void ContainerSerializer::EncodeStop() { + bytes_->push_back(cbor::EncodeStop()); + envelope_.EncodeStop(bytes_); +} + +ObjectSerializer::ObjectSerializer() + : serializer_(&owned_bytes_, cbor::EncodeIndefiniteLengthMapStart()) {} + +ObjectSerializer::~ObjectSerializer() = default; + +std::unique_ptr ObjectSerializer::Finish() { + serializer_.EncodeStop(); + return Serializable::From(std::move(owned_bytes_)); +} + +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, + double* value) { + // Double values that round-trip through JSON may end up getting represented + // as an int32 (SIGNED, UNSIGNED) on the wire in CBOR. Therefore, we also + // accept an INT32 here. + if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::INT32) { + *value = state->tokenizer()->GetInt32(); + return true; + } + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::DOUBLE) { + state->RegisterError(Error::BINDINGS_DOUBLE_VALUE_EXPECTED); + return false; + } + *value = state->tokenizer()->GetDouble(); + return true; +} + +void ProtocolTypeTraits::Serialize(double value, + std::vector* bytes) { + cbor::EncodeDouble(value, bytes); +} + +class IncomingDeferredMessage : public DeferredMessage { + public: + // Creates the state from the part of another message. + // Note storage is opaque and is mostly to retain ownership. + // It may be null in case caller owns the memory and will dispose + // of the message synchronously. + IncomingDeferredMessage(DeserializerState::Storage storage, + span span) + : storage_(storage), span_(span) {} + + private: + DeserializerState MakeDeserializer() const override { + return DeserializerState(storage_, span_); + } + void AppendSerialized(std::vector* out) const override { + out->insert(out->end(), span_.begin(), span_.end()); + } + + DeserializerState::Storage storage_; + span span_; +}; + +class OutgoingDeferredMessage : public DeferredMessage { + public: + OutgoingDeferredMessage() = default; + explicit OutgoingDeferredMessage(std::unique_ptr serializable) + : serializable_(std::move(serializable)) { + assert(!!serializable_); + } + + private: + DeserializerState MakeDeserializer() const override { + return DeserializerState(serializable_->Serialize()); + } + void AppendSerialized(std::vector* out) const override { + serializable_->AppendSerialized(out); + } + + std::unique_ptr serializable_; +}; + +// static +std::unique_ptr DeferredMessage::FromSerializable( + std::unique_ptr serializeable) { + return std::make_unique(std::move(serializeable)); +} + +// static +std::unique_ptr DeferredMessage::FromSpan( + span bytes) { + return std::make_unique(nullptr, bytes); +} + +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, + std::unique_ptr* value) { + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::ENVELOPE) { + state->RegisterError(Error::CBOR_INVALID_ENVELOPE); + return false; + } + *value = std::make_unique( + state->storage(), state->tokenizer()->GetEnvelope()); + return true; +} + +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, + std::vector* bytes) { + value->AppendSerialized(bytes); +} + +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/protocol_core.h b/tools/inspector_protocol/crdtp/protocol_core.h new file mode 100644 index 00000000000000..522e657b796ff2 --- /dev/null +++ b/tools/inspector_protocol/crdtp/protocol_core.h @@ -0,0 +1,406 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_PROTOCOL_CORE_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_PROTOCOL_CORE_H_ + +#include + +#include +#include +#include + +#include "cbor.h" +#include "maybe.h" +#include "serializable.h" +#include "span.h" +#include "status.h" + +namespace v8_inspector_protocol_crdtp { + +class DeserializerState { + public: + using Storage = std::shared_ptr>; + + // Creates a state from the raw bytes received from the peer. + explicit DeserializerState(std::vector bytes); + // Creates the state from the part of another message. + DeserializerState(Storage storage, span span); + DeserializerState(const DeserializerState& r) = delete; + DeserializerState(DeserializerState&& r) = default; + + // Registers |error|, unless the tokenizer's status is already an error. + void RegisterError(Error error); + // Registers |name| as a segment of the field path. + void RegisterFieldPath(span name); + + // Produces an error message considering |tokenizer.Status()|, + // status_, and field_path_. + std::string ErrorMessage(span message_name) const; + Status status() const; + const Storage& storage() const { return storage_; } + cbor::CBORTokenizer* tokenizer() { return &tokenizer_; } + + private: + const Storage storage_; + cbor::CBORTokenizer tokenizer_; + Status status_; + std::vector> field_path_; +}; + +template +struct ProtocolTypeTraits {}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, bool* value); + static void Serialize(bool value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, int* value); + static void Serialize(int value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, double* value); + static void Serialize(double value, std::vector* bytes); +}; + +class ContainerSerializer { + public: + ContainerSerializer(std::vector* bytes, uint8_t tag); + + template + void AddField(span field_name, const T& value) { + cbor::EncodeString8( + span(reinterpret_cast(field_name.data()), + field_name.size()), + bytes_); + ProtocolTypeTraits::Serialize(value, bytes_); + } + template + void AddField(span field_name, const detail::ValueMaybe& value) { + if (!value.isJust()) + return; + AddField(field_name, value.fromJust()); + } + template + void AddField(span field_name, const detail::PtrMaybe& value) { + if (!value.isJust()) + return; + AddField(field_name, *value.fromJust()); + } + + void EncodeStop(); + + private: + std::vector* const bytes_; + cbor::EnvelopeEncoder envelope_; +}; + +class ObjectSerializer { + public: + ObjectSerializer(); + ~ObjectSerializer(); + + template + void AddField(span name, const T& field) { + serializer_.AddField(name, field); + } + std::unique_ptr Finish(); + + private: + std::vector owned_bytes_; + ContainerSerializer serializer_; +}; + +class DeserializerDescriptor { + public: + struct Field { + span name; + bool is_optional; + bool (*deserializer)(DeserializerState* state, void* obj); + }; + + DeserializerDescriptor(const Field* fields, size_t field_count); + + bool Deserialize(DeserializerState* state, void* obj) const; + + private: + bool DeserializeField(DeserializerState* state, + span name, + int* seen_mandatory_fields, + void* obj) const; + + const Field* const fields_; + const size_t field_count_; + const int mandatory_field_mask_; +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::vector* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::ARRAY_START) { + state->RegisterError(Error::CBOR_ARRAY_START_EXPECTED); + return false; + } + assert(value->empty()); + tokenizer->Next(); + for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP; + tokenizer->Next()) { + value->emplace_back(); + if (!ProtocolTypeTraits::Deserialize(state, &value->back())) + return false; + } + return true; + } + + static void Serialize(const std::vector& value, + std::vector* bytes) { + ContainerSerializer container_serializer( + bytes, cbor::EncodeIndefiniteLengthArrayStart()); + for (const auto& item : value) + ProtocolTypeTraits::Serialize(item, bytes); + container_serializer.EncodeStop(); + } +}; + +template +struct ProtocolTypeTraits>> { + static bool Deserialize(DeserializerState* state, + std::unique_ptr>* value) { + auto res = std::make_unique>(); + if (!ProtocolTypeTraits>::Deserialize(state, res.get())) + return false; + *value = std::move(res); + return true; + } + static void Serialize(const std::unique_ptr>& value, + std::vector* bytes) { + ProtocolTypeTraits>::Serialize(*value, bytes); + } +}; + +class DeferredMessage : public Serializable { + public: + static std::unique_ptr FromSerializable( + std::unique_ptr serializeable); + static std::unique_ptr FromSpan(span bytes); + + ~DeferredMessage() override = default; + virtual DeserializerState MakeDeserializer() const = 0; + + protected: + DeferredMessage() = default; +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + std::unique_ptr* value); + static void Serialize(const std::unique_ptr& value, + std::vector* bytes); +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + detail::ValueMaybe* value) { + T res; + if (!ProtocolTypeTraits::Deserialize(state, &res)) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const detail::ValueMaybe& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(value.fromJust(), bytes); + } +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + detail::PtrMaybe* value) { + std::unique_ptr res; + if (!ProtocolTypeTraits>::Deserialize(state, &res)) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const detail::PtrMaybe& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(*value.fromJust(), bytes); + } +}; + +template +class DeserializableProtocolObject { + public: + static StatusOr> ReadFrom( + const DeferredMessage& deferred_message) { + auto state = deferred_message.MakeDeserializer(); + if (auto res = Deserialize(&state)) + return StatusOr>(std::move(res)); + return StatusOr>(state.status()); + } + + static StatusOr> ReadFrom(std::vector bytes) { + auto state = DeserializerState(std::move(bytes)); + if (auto res = Deserialize(&state)) + return StatusOr>(std::move(res)); + return StatusOr>(state.status()); + } + + // Short-hand for legacy clients. This would swallow any errors, consider + // using ReadFrom. + static std::unique_ptr FromBinary(const uint8_t* bytes, size_t size) { + std::unique_ptr value(new T()); + auto deserializer = DeferredMessage::FromSpan(span(bytes, size)) + ->MakeDeserializer(); + Deserialize(&deserializer, value.get()); + return value; + } + + static bool Deserialize(DeserializerState* state, T* value) { + return T::deserializer_descriptor().Deserialize(state, value); + } + + protected: + // This is for the sake of the macros used by derived classes thay may be in + // a different namespace; + using ProtocolType = T; + using DeserializerDescriptorType = DeserializerDescriptor; + template + using DeserializableBase = DeserializableProtocolObject; + + DeserializableProtocolObject() = default; + ~DeserializableProtocolObject() = default; + + private: + friend struct ProtocolTypeTraits>; + + static std::unique_ptr Deserialize(DeserializerState* state) { + std::unique_ptr value(new T()); + if (Deserialize(state, value.get())) + return value; + return nullptr; + } +}; + +template +class ProtocolObject : public Serializable, + public DeserializableProtocolObject { + public: + std::unique_ptr Clone() const { + std::vector serialized; + AppendSerialized(&serialized); + return T::ReadFrom(std::move(serialized)).value(); + } + // TODO(caseq): compatibility only, remove. + std::unique_ptr clone() const { return Clone(); } + + protected: + using ProtocolType = T; + + ProtocolObject() = default; +}; + +template +struct ProtocolTypeTraits< + T, + typename std::enable_if< + std::is_base_of, T>::value>::type> { + static bool Deserialize(DeserializerState* state, T* value) { + return T::Deserialize(state, value); + } + + static void Serialize(const T& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +template +struct ProtocolTypeTraits< + std::unique_ptr, + typename std::enable_if< + std::is_base_of, T>::value>::type> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value) { + std::unique_ptr res = T::Deserialize(state); + if (!res) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const std::unique_ptr& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(*value, bytes); + } +}; + +#define DECLARE_DESERIALIZATION_SUPPORT() \ + friend DeserializableBase; \ + static const DeserializerDescriptorType& deserializer_descriptor() + +#define DECLARE_SERIALIZATION_SUPPORT() \ + public: \ + void AppendSerialized(std::vector* bytes) const override; \ + \ + private: \ + friend DeserializableBase; \ + static const DeserializerDescriptorType& deserializer_descriptor() + +#define V8_INSPECTOR_PROTOCOL_CRDTP_DESERIALIZE_FILED_IMPL(name, field, is_optional) \ + { \ + MakeSpan(name), is_optional, \ + [](DeserializerState* __state, void* __obj) -> bool { \ + return ProtocolTypeTraits::Deserialize( \ + __state, &static_cast(__obj)->field); \ + } \ + } + +// clang-format off +#define V8_INSPECTOR_PROTOCOL_CRDTP_BEGIN_DESERIALIZER(type) \ + const type::DeserializerDescriptorType& type::deserializer_descriptor() { \ + using namespace v8_crdtp; \ + static const DeserializerDescriptorType::Field fields[] = { + +#define V8_INSPECTOR_PROTOCOL_CRDTP_END_DESERIALIZER() \ + }; \ + static const DeserializerDescriptorType s_desc( \ + fields, sizeof fields / sizeof fields[0]); \ + return s_desc; \ + } + +#define V8_INSPECTOR_PROTOCOL_CRDTP_DESERIALIZE_FIELD(name, field) \ + V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, false) +#define V8_INSPECTOR_PROTOCOL_CRDTP_DESERIALIZE_FIELD_OPT(name, field) \ + V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, true) + +#define V8_INSPECTOR_PROTOCOL_CRDTP_BEGIN_SERIALIZER(type) \ + void type::AppendSerialized(std::vector* bytes) const { \ + using namespace v8_crdtp; \ + ContainerSerializer __serializer(bytes, \ + cbor::EncodeIndefiniteLengthMapStart()); + +#define V8_INSPECTOR_PROTOCOL_CRDTP_SERIALIZE_FIELD(name, field) \ + __serializer.AddField(MakeSpan(name), field) + +#define V8_INSPECTOR_PROTOCOL_CRDTP_END_SERIALIZER() \ + __serializer.EncodeStop(); \ + } class __cddtp_dummy_name +// clang-format on + +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_PROTOCOL_CORE_H_ diff --git a/tools/inspector_protocol/crdtp/protocol_core_test.cc b/tools/inspector_protocol/crdtp/protocol_core_test.cc new file mode 100644 index 00000000000000..90f640b3aa8329 --- /dev/null +++ b/tools/inspector_protocol/crdtp/protocol_core_test.cc @@ -0,0 +1,497 @@ +// Copyright 2020 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 "protocol_core.h" + +#include + +#include "cbor.h" +#include "maybe.h" +#include "status_test_support.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { + +// Test-only. Real-life bindings use UTF8/16 conversions as needed. +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, std::string* value) { + if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::STRING8) { + auto cbor_span = state->tokenizer()->GetString8(); + value->assign(reinterpret_cast(cbor_span.data()), + cbor_span.size()); + return true; + } + state->RegisterError(Error::BINDINGS_STRING8_VALUE_EXPECTED); + return false; + } + + static void Serialize(const std::string& value, std::vector* bytes) { + cbor::EncodeString8( + span(reinterpret_cast(value.data()), + value.size()), + bytes); + } +}; + +namespace { +using ::testing::Eq; + +template +std::unique_ptr RoundtripToType(const TArg& obj) { + std::vector bytes; + obj.AppendSerialized(&bytes); + + StatusOr> result = + TResult::ReadFrom(std::move(bytes)); + return std::move(result).value(); +} + +template +std::unique_ptr Roundtrip(const T& obj) { + return RoundtripToType(obj); +} + +// These TestTypeFOO classes below would normally be generated +// by the protocol generator. + +class TestTypeBasic : public ProtocolObject { + public: + TestTypeBasic() = default; + + const std::string& GetValue() const { return value_; } + void SetValue(std::string value) { value_ = std::move(value); } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + std::string value_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeBasic) + V8_CRDTP_DESERIALIZE_FIELD("value", value_) +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeBasic) + V8_CRDTP_SERIALIZE_FIELD("value", value_); +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, Basic) { + TestTypeBasic obj1; + obj1.SetValue("foo"); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + EXPECT_THAT(obj2->GetValue(), Eq("foo")); +} + +TEST(ProtocolCoreTest, FailedToDeserializeTestTypeBasic) { + std::vector garbage = {'g', 'a', 'r', 'b', 'a', 'g', 'e'}; + StatusOr> result = + TestTypeBasic::ReadFrom(std::move(garbage)); + EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, 0)); +} + +class TestTypeBasicDouble : public ProtocolObject { + public: + TestTypeBasicDouble() = default; + + double GetValue() const { return value_; } + void SetValue(double value) { value_ = value; } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + double value_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeBasicDouble) + V8_CRDTP_DESERIALIZE_FIELD("value", value_) +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeBasicDouble) + V8_CRDTP_SERIALIZE_FIELD("value", value_); +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(TestBasicDouble, ParserAllowsAllowsDoubleEncodedAsInt) { + // We allow double's encoded as INT32, because this is what a roundtrip via + // JSON would produce. + std::vector encoded; + crdtp::cbor::EnvelopeEncoder envelope; + envelope.EncodeStart(&encoded); + encoded.push_back(crdtp::cbor::EncodeIndefiniteLengthMapStart()); + crdtp::cbor::EncodeString8(crdtp::SpanFrom("value"), &encoded); + crdtp::cbor::EncodeInt32( + 42, &encoded); // It's a double field, but we encode an int. + encoded.push_back(crdtp::cbor::EncodeStop()); + envelope.EncodeStop(&encoded); + auto obj = TestTypeBasicDouble::ReadFrom(encoded).value(); + ASSERT_THAT(obj, Not(testing::IsNull())); + EXPECT_THAT(obj->GetValue(), Eq(42)); +} + +class TestTypeComposite : public ProtocolObject { + public: + bool GetBoolField() const { return bool_field_; } + void SetBoolField(bool value) { bool_field_ = value; } + + int GetIntField() const { return int_field_; } + void SetIntField(int value) { int_field_ = value; } + + double GetDoubleField() const { return double_field_; } + void SetDoubleField(double value) { double_field_ = value; } + + const std::string& GetStrField() const { return str_field_; } + void SetStrField(std::string value) { str_field_ = std::move(value); } + + const TestTypeBasic* GetTestTypeBasicField() { + return test_type1_field_.get(); + } + void SetTestTypeBasicField(std::unique_ptr value) { + test_type1_field_ = std::move(value); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + bool bool_field_ = false; + int int_field_ = 0; + double double_field_ = 0.0; + std::string str_field_; + std::unique_ptr test_type1_field_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeComposite) + V8_CRDTP_DESERIALIZE_FIELD("bool_field", bool_field_), + V8_CRDTP_DESERIALIZE_FIELD("double_field", double_field_), + V8_CRDTP_DESERIALIZE_FIELD("int_field", int_field_), + V8_CRDTP_DESERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_DESERIALIZE_FIELD("test_type1_field", test_type1_field_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeComposite) + V8_CRDTP_SERIALIZE_FIELD("bool_field", bool_field_), + V8_CRDTP_SERIALIZE_FIELD("double_field", double_field_), + V8_CRDTP_SERIALIZE_FIELD("int_field", int_field_), + V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, Composite) { + TestTypeComposite obj1; + obj1.SetBoolField(true); + obj1.SetIntField(42); + obj1.SetDoubleField(2.718281828); + obj1.SetStrField("bar"); + auto val1 = std::make_unique(); + val1->SetValue("bazzzz"); + obj1.SetTestTypeBasicField(std::move(val1)); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + EXPECT_THAT(obj2->GetBoolField(), Eq(true)); + EXPECT_THAT(obj2->GetIntField(), Eq(42)); + EXPECT_THAT(obj2->GetDoubleField(), Eq(2.718281828)); + EXPECT_THAT(obj2->GetStrField(), Eq("bar")); + EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bazzzz")); +} + +class CompositeParsingTest : public testing::Test { + public: + CompositeParsingTest() { + TestTypeComposite top; + top.SetIntField(42); + top.SetBoolField(true); + top.SetIntField(42); + top.SetDoubleField(2.718281828); + top.SetStrField("junk"); + auto child = std::make_unique(); + child->SetValue("child_value"); + top.SetTestTypeBasicField(std::move(child)); + + // Let's establish that |serialized_| is a properly serialized + // representation of |top|, by checking that it deserializes ok. + top.AppendSerialized(&serialized_); + TestTypeComposite::ReadFrom(serialized_).value(); + } + + protected: + std::vector serialized_; +}; + +TEST_F(CompositeParsingTest, DecodingFailure_CBORTokenizer) { + // Mutates |serialized_| so that it won't parse correctly. In this case, + // we're changing a string value so that it's invalid, making CBORTokenizer + // unhappy. + size_t position = + std::string(reinterpret_cast(serialized_.data()), + serialized_.size()) + .find("child_value"); + EXPECT_GT(position, 0ul); + // We override the byte just before so that it's still a string + // (3 << 5), but the length is encoded in the bytes that follows. + // So, we override that with 0xff (255), which exceeds the length + // of the message and thereby makes the string8 invalid. + --position; + serialized_[position] = 3 << 5 | // major type: STRING + 25; // length in encoded in byte that follows. + serialized_[position + 1] = 0xff; // length + auto result = TestTypeComposite::ReadFrom(serialized_); + + EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, position)); +} + +TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingShallow) { + // We're changing the string key "int_field" to something else ("lnt_field"), + // so that the mandatory field value won't be found. Unknown fields are + // ignored for compatibility, so that's why this simple technique works here. + size_t position = + std::string(reinterpret_cast(serialized_.data()), + serialized_.size()) + .find("int_field"); + serialized_[position] = 'l'; // Change 'i' to 'l'. + // serialized_.size() - 1 is the STOP character for the entire message, + size_t expected_error_pos = serialized_.size() - 1; + auto result = TestTypeComposite::ReadFrom(serialized_); + EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING, + expected_error_pos)); +} + +TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingNested) { + // We're changing the string key "value" to something else ("falue"), so that + // the mandatory field value in TestTypeBasic in the child won't be found. + size_t position = + std::string(reinterpret_cast(serialized_.data()), + serialized_.size()) + .find("value"); + serialized_[position] = 'f'; // Change 'v' to 'f'. + // serialized_.size() - 1 is the STOP character for the enclosing message, + // and serialized_.size() - 2 is the STOP character for TestTypeBasic. + size_t expected_error_pos = serialized_.size() - 2; + auto result = TestTypeComposite::ReadFrom(serialized_); + EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING, + expected_error_pos)); +} + +TEST_F(CompositeParsingTest, DecodingFailure_BoolValueExpected) { + // We're changing the bool value (true) to null; we do this by looking + // for bool_field, and searching from there for TRUE; both TRUE and null + // are just one byte in the serialized buffer, so this swap is convenient. + std::string serialized_view(reinterpret_cast(serialized_.data()), + serialized_.size()); + size_t position = serialized_view.find("bool_field"); + for (; position < serialized_.size(); ++position) { + if (serialized_[position] == crdtp::cbor::EncodeTrue()) { + serialized_[position] = crdtp::cbor::EncodeNull(); + break; + } + } + auto result = TestTypeComposite::ReadFrom(serialized_); + EXPECT_THAT(result.status(), + StatusIs(Error::BINDINGS_BOOL_VALUE_EXPECTED, position)); +} + +class TestTypeArrays : public ProtocolObject { + public: + const std::vector* GetIntArray() const { return &int_array_; } + void SetIntArray(std::vector value) { int_array_ = std::move(value); } + + const std::vector* GetDoubleArray() const { return &double_array_; } + void SetDoubleArray(std::vector value) { + double_array_ = std::move(value); + } + + const std::vector* GetStrArray() const { return &str_array_; } + void SetStrArray(std::vector value) { + str_array_ = std::move(value); + } + + const std::vector>* GetTestTypeBasicArray() + const { + return &test_type_basic_array_; + } + + void SetTestTypeBasicArray( + std::vector> value) { + test_type_basic_array_ = std::move(value); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + std::vector int_array_; + std::vector double_array_; + std::vector str_array_; + std::vector> test_type_basic_array_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeArrays) + V8_CRDTP_DESERIALIZE_FIELD("int_array", int_array_), + V8_CRDTP_DESERIALIZE_FIELD("str_array", str_array_), + V8_CRDTP_DESERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeArrays) + V8_CRDTP_SERIALIZE_FIELD("int_array", int_array_), + V8_CRDTP_SERIALIZE_FIELD("str_array", str_array_), + V8_CRDTP_SERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST_F(CompositeParsingTest, Arrays) { + TestTypeArrays obj1; + obj1.SetIntArray(std::vector{1, 3, 5, 7}); + std::vector strs; + strs.emplace_back("foo"); + strs.emplace_back(std::string("bar")); + obj1.SetStrArray(std::move(strs)); + auto val1 = std::make_unique(); + val1->SetValue("bazzzz"); + std::vector> vec1; + vec1.emplace_back(std::move(val1)); + obj1.SetTestTypeBasicArray(std::move(vec1)); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + EXPECT_THAT(*obj2->GetIntArray(), testing::ElementsAre(1, 3, 5, 7)); + EXPECT_THAT(*obj2->GetStrArray(), testing::ElementsAre("foo", "bar")); + EXPECT_THAT(obj2->GetDoubleArray()->size(), Eq(0ul)); + EXPECT_THAT(obj2->GetTestTypeBasicArray()->size(), Eq(1ul)); + EXPECT_THAT(obj2->GetTestTypeBasicArray()->front()->GetValue(), Eq("bazzzz")); +} + +class TestTypeOptional : public ProtocolObject { + public: + TestTypeOptional() = default; + + bool HasIntField() const { return int_field_.isJust(); } + int GetIntField() const { return int_field_.fromJust(); } + void SetIntField(int value) { int_field_ = value; } + + bool HasStrField() { return str_field_.isJust(); } + const std::string& GetStrField() const { return str_field_.fromJust(); } + void SetStrField(std::string value) { str_field_ = std::move(value); } + + bool HasTestTypeBasicField() { return test_type_basic_field_.isJust(); } + const TestTypeBasic* GetTestTypeBasicField() const { + return test_type_basic_field_.isJust() ? test_type_basic_field_.fromJust() + : nullptr; + } + void SetTestTypeBasicField(std::unique_ptr value) { + test_type_basic_field_ = std::move(value); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + Maybe int_field_; + Maybe str_field_; + Maybe test_type_basic_field_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeOptional) + V8_CRDTP_DESERIALIZE_FIELD_OPT("int_field", int_field_), + V8_CRDTP_DESERIALIZE_FIELD_OPT("str_field", str_field_), + V8_CRDTP_DESERIALIZE_FIELD_OPT("test_type_basic_field", test_type_basic_field_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeOptional) + V8_CRDTP_SERIALIZE_FIELD("int_field", int_field_), + V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_SERIALIZE_FIELD("test_type_basic_field", test_type_basic_field_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, OptionalAbsent) { + TestTypeOptional obj1; + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + + EXPECT_THAT(obj2->HasIntField(), Eq(false)); + EXPECT_THAT(obj2->HasStrField(), Eq(false)); + EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(false)); +} + +TEST(ProtocolCoreTest, OptionalPresent) { + TestTypeOptional obj1; + obj1.SetIntField(42); + obj1.SetStrField("foo"); + + auto val1 = std::make_unique(); + val1->SetValue("bar"); + obj1.SetTestTypeBasicField(std::move(val1)); + + auto obj2 = Roundtrip(obj1); + ASSERT_THAT(obj2, Not(testing::IsNull())); + + EXPECT_THAT(obj2->HasIntField(), Eq(true)); + EXPECT_THAT(obj2->GetIntField(), Eq(42)); + EXPECT_THAT(obj2->HasStrField(), Eq(true)); + EXPECT_THAT(obj2->GetStrField(), Eq("foo")); + EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(true)); + EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bar")); +} + +class TestTypeLazy : public ProtocolObject { + public: + TestTypeLazy() = default; + + const std::string& GetStrField() const { return str_field_; } + void SetStrField(std::string value) { str_field_ = std::move(value); } + + const DeferredMessage* deferred_test_type1_field() const { + return test_type1_field_.get(); + } + + private: + DECLARE_SERIALIZATION_SUPPORT(); + + std::string str_field_; + std::unique_ptr test_type1_field_; +}; + +// clang-format off +V8_CRDTP_BEGIN_DESERIALIZER(TestTypeLazy) + V8_CRDTP_DESERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_DESERIALIZE_FIELD_OPT("test_type1_field", test_type1_field_), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(TestTypeLazy) + V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_), + V8_CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_), +V8_CRDTP_END_SERIALIZER(); +// clang-format on + +TEST(ProtocolCoreTest, TestDeferredMessage) { + TestTypeComposite obj1; + obj1.SetStrField("bar"); + auto val1 = std::make_unique(); + val1->SetValue("bazzzz"); + obj1.SetTestTypeBasicField(std::move(val1)); + + auto obj2 = RoundtripToType(obj1); + EXPECT_THAT(obj2->GetStrField(), Eq("bar")); + + TestTypeBasic basic_val; + auto deserializer = obj2->deferred_test_type1_field()->MakeDeserializer(); + EXPECT_THAT(TestTypeBasic::Deserialize(&deserializer, &basic_val), Eq(true)); + EXPECT_THAT(basic_val.GetValue(), Eq("bazzzz")); + + StatusOr> maybe_parsed = + TestTypeBasic::ReadFrom(*obj2->deferred_test_type1_field()); + ASSERT_THAT(maybe_parsed.status(), StatusIsOk()); + ASSERT_THAT((*maybe_parsed), Not(testing::IsNull())); + ASSERT_EQ((*maybe_parsed)->GetValue(), "bazzzz"); +} + +} // namespace +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/serializable.cc b/tools/inspector_protocol/crdtp/serializable.cc new file mode 100644 index 00000000000000..3bf05522454fc9 --- /dev/null +++ b/tools/inspector_protocol/crdtp/serializable.cc @@ -0,0 +1,36 @@ +// 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 "serializable.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// Serializable - An object to be emitted as a sequence of bytes. +// ============================================================================= + +std::vector Serializable::Serialize() const { + std::vector out; + AppendSerialized(&out); + return out; +} + +namespace { +class PreSerialized : public Serializable { + public: + explicit PreSerialized(std::vector bytes) : bytes_(bytes) {} + + void AppendSerialized(std::vector* out) const override { + out->insert(out->end(), bytes_.begin(), bytes_.end()); + } + + private: + std::vector bytes_; +}; +} // namespace + +// static +std::unique_ptr Serializable::From(std::vector bytes) { + return std::make_unique(std::move(bytes)); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/serializable.h b/tools/inspector_protocol/crdtp/serializable.h new file mode 100644 index 00000000000000..d08e6f5f782eba --- /dev/null +++ b/tools/inspector_protocol/crdtp/serializable.h @@ -0,0 +1,32 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_SERIALIZABLE_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_SERIALIZABLE_H_ + +#include +#include +#include +#include "export.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// Serializable - An object to be emitted as a sequence of bytes. +// ============================================================================= +class Serializable { + public: + // Convenience: Invokes |AppendSerialized| on an empty vector. + std::vector Serialize() const; + + virtual void AppendSerialized(std::vector* out) const = 0; + + virtual ~Serializable() = default; + + // Wraps a vector of |bytes| into a Serializable for situations in which we + // eagerly serialize a structure. + static std::unique_ptr From(std::vector bytes); +}; +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_SERIALIZABLE_H_ diff --git a/tools/inspector_protocol/crdtp/serializable_test.cc b/tools/inspector_protocol/crdtp/serializable_test.cc new file mode 100644 index 00000000000000..e8057fcf590c51 --- /dev/null +++ b/tools/inspector_protocol/crdtp/serializable_test.cc @@ -0,0 +1,40 @@ +// 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 + +#include "serializable.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// Serializable - An object to be emitted as a sequence of bytes. +// ============================================================================= + +namespace { +// Tests ::Serialize (which invokes ::AppendSerialized). +class SimpleExample : public Serializable { + public: + explicit SimpleExample(const std::vector& contents) + : contents_(contents) {} + + void AppendSerialized(std::vector* out) const override { + out->insert(out->end(), contents_.begin(), contents_.end()); + } + + private: + std::vector contents_; +}; +} // namespace + +TEST(SerializableTest, YieldsContents) { + std::vector contents = {1, 2, 3}; + SimpleExample foo(contents); + foo.AppendSerialized(&contents); // Yields contents by appending. + EXPECT_THAT(contents, testing::ElementsAre(1, 2, 3, 1, 2, 3)); + // Yields contents by returning. + EXPECT_THAT(foo.Serialize(), testing::ElementsAre(1, 2, 3)); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/serializer_traits.h b/tools/inspector_protocol/crdtp/serializer_traits.h new file mode 100644 index 00000000000000..8b079ee7a79db2 --- /dev/null +++ b/tools/inspector_protocol/crdtp/serializer_traits.h @@ -0,0 +1,158 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_SERIALIZER_TRAITS_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_SERIALIZER_TRAITS_H_ + +#include +#include +#include +#include "cbor.h" +#include "maybe.h" +#include "span.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// SerializerTraits - Encodes field values of protocol objects in CBOR. +// ============================================================================= +// +// A family of serialization functions which are used by FieldSerializerTraits +// (below) to encode field values in CBOR. Conceptually, it's this: +// +// Serialize(bool value, std::vector* out); +// Serialize(int32_t value, std::vector* out); +// Serialize(double value, std::vector* out); +// ... +// +// However, if this was to use straight-forward overloading, implicit +// type conversions would lead to ambiguity - e.g., a bool could be +// represented as an int32_t, but it should really be encoded as a bool. +// The template parameterized / specialized structs accomplish this. +// +// SerializerTraits::Serialize(bool value, std::vector* out); +// SerializerTraits::Serialize(int32_t value, std::vector* out); +// SerializerTraits::Serialize(double value, std::vector* out); +template +struct SerializerTraits { + // |Serializable| (defined in serializable.h) already knows how to serialize + // to CBOR, so we can just delegate. This covers domain specific types, + // protocol::Binary, etc. + // However, we use duck-typing here, because Exported, which is part of the V8 + // headers also comes with AppendSerialized, and logically it's the same type, + // but it lives in a different namespace (v8_inspector::protocol::Exported). + template < + typename LikeSerializable, + typename std::enable_if_t{}, + int> = 0> + static void Serialize(const LikeSerializable& value, + std::vector* out) { + value.AppendSerialized(out); + } +}; + +// This covers std::string, which is assumed to be UTF-8. +// The two other string implementations that are used in the protocol bindings: +// - WTF::String, for which the SerializerTraits specialization is located +// in third_party/blink/renderer/core/inspector/v8-inspector-string.h. +// - v8_inspector::String16, implemented in v8/src/inspector/string-16.h +// along with its SerializerTraits specialization. +template <> +struct SerializerTraits { + static void Serialize(const std::string& str, std::vector* out) { + cbor::EncodeString8(SpanFrom(str), out); + } +}; + +template <> +struct SerializerTraits { + static void Serialize(bool value, std::vector* out) { + out->push_back(value ? cbor::EncodeTrue() : cbor::EncodeFalse()); + } +}; + +template <> +struct SerializerTraits { + static void Serialize(int32_t value, std::vector* out) { + cbor::EncodeInt32(value, out); + } +}; + +template <> +struct SerializerTraits { + static void Serialize(double value, std::vector* out) { + cbor::EncodeDouble(value, out); + } +}; + +template +struct SerializerTraits> { + static void Serialize(const std::vector& value, + std::vector* out) { + out->push_back(cbor::EncodeIndefiniteLengthArrayStart()); + for (const T& element : value) + SerializerTraits::Serialize(element, out); + out->push_back(cbor::EncodeStop()); + } +}; + +template +struct SerializerTraits> { + static void Serialize(const std::unique_ptr& value, + std::vector* out) { + SerializerTraits::Serialize(*value, out); + } +}; + +// ============================================================================= +// FieldSerializerTraits - Encodes fields of protocol objects in CBOR +// ============================================================================= +// +// The generated code (see TypeBuilder_cpp.template) invokes SerializeField, +// which then instantiates the FieldSerializerTraits to emit the appropriate +// existence checks / dereference for the field value. This avoids emitting +// the field name if the value for an optional field isn't set. +template +struct FieldSerializerTraits { + static void Serialize(span field_name, + const T& field_value, + std::vector* out) { + cbor::EncodeString8(field_name, out); + SerializerTraits::Serialize(field_value, out); + } +}; + +template +struct FieldSerializerTraits> { + static void Serialize(span field_name, + const detail::PtrMaybe& field_value, + std::vector* out) { + if (!field_value.isJust()) + return; + cbor::EncodeString8(field_name, out); + SerializerTraits::Serialize(*field_value.fromJust(), out); + } +}; + +template +struct FieldSerializerTraits> { + static void Serialize(span field_name, + const detail::ValueMaybe& field_value, + std::vector* out) { + if (!field_value.isJust()) + return; + cbor::EncodeString8(field_name, out); + SerializerTraits::Serialize(field_value.fromJust(), out); + } +}; + +template +void SerializeField(span field_name, + const T& field_value, + std::vector* out) { + FieldSerializerTraits::Serialize(field_name, field_value, out); +} +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_SERIALIZER_TRAITS_H_ diff --git a/tools/inspector_protocol/crdtp/serializer_traits_test.cc b/tools/inspector_protocol/crdtp/serializer_traits_test.cc new file mode 100644 index 00000000000000..1399eda24094a6 --- /dev/null +++ b/tools/inspector_protocol/crdtp/serializer_traits_test.cc @@ -0,0 +1,226 @@ +// 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 "serializer_traits.h" + +#include +#include "serializable.h" +#include "test_platform.h" + +// The purpose of this test is to ensure that the +// {Field}SerializerTraits::Serialize methods invoke the appropriate +// functions from cbor.h; so, it's usually sufficient to compare with what +// cbor.h function invocations would produce, rather than making assertions on +// the specific bytes emitted by the SerializerTraits code. + +namespace v8_inspector_protocol_crdtp { +namespace { +// ============================================================================= +// SerializerTraits - Encodes field values of protocol objects in CBOR. +// ============================================================================= + +TEST(SerializerTraits, Bool) { + std::vector out; + SerializerTraits::Serialize(true, &out); + SerializerTraits::Serialize(false, &out); + EXPECT_THAT(out, + testing::ElementsAre(cbor::EncodeTrue(), cbor::EncodeFalse())); +} + +TEST(SerializerTraits, Double) { + std::vector out; + SerializerTraits::Serialize(1.00001, &out); + + std::vector expected; + cbor::EncodeDouble(1.00001, &expected); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +TEST(SerializerTraits, Int32) { + std::vector out; + SerializerTraits::Serialize(42, &out); + + std::vector expected; + cbor::EncodeInt32(42, &expected); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +TEST(SerializerTraits, VectorOfInt32) { + std::vector ints = {1, 2, 3}; + + std::vector out; + SerializerTraits>::Serialize(ints, &out); + + std::vector expected; + expected.push_back(cbor::EncodeIndefiniteLengthArrayStart()); + for (int32_t v : ints) + cbor::EncodeInt32(v, &expected); + expected.push_back(cbor::EncodeStop()); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +// Foo is an example for a domain specific type. +class Foo : public Serializable { + public: + Foo(int32_t value) : value(value) {} + + int32_t value; + + void AppendSerialized(std::vector* out) const override { + // In production, this would be generated code which emits a + // CBOR map that has STRING8 keys corresponding to the field names + // and field values encoded using SerializerTraits::Serialize. + // + // For the test we simplify this drastically and just emit the field + // value, for conveniently testing std::vector>, + // as well as the convenience methods for raw pointer and unique_ptr. + SerializerTraits::Serialize(value, out); + } +}; + +TEST(SerializerTraits, VectorOfDomainSpecificType) { + std::vector> foos; + foos.push_back(std::make_unique(1)); + foos.push_back(std::make_unique(2)); + foos.push_back(std::make_unique(3)); + + std::vector out; + SerializerTraits>>::Serialize(foos, &out); + + std::vector expected; + expected.push_back(cbor::EncodeIndefiniteLengthArrayStart()); + for (int32_t v : {1, 2, 3}) + cbor::EncodeInt32(v, &expected); + expected.push_back(cbor::EncodeStop()); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +TEST(SerializerTraits, ConstRefAndUniquePtr) { + // Shows that SerializerTraits allows unique_ptr. + Foo foo(42); + auto bar = std::make_unique(21); + + std::vector out; + // In this case, |foo| is taken as a const Foo&. + SerializerTraits::Serialize(foo, &out); + // In this case, |bar| is taken as a const std::unique_ptr&. + SerializerTraits>::Serialize(bar, &out); + + std::vector expected; + cbor::EncodeInt32(42, &expected); + cbor::EncodeInt32(21, &expected); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +TEST(SerializerTraits, UTF8String) { + std::string msg = "Hello, 🌎."; + + std::vector out; + SerializerTraits::Serialize(msg, &out); + + std::vector expected; + cbor::EncodeString8(SpanFrom(msg), &expected); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +// A trivial model of Exported. +// (see +// https://cs.chromium.org/chromium/src/out/Debug/gen/v8/include/inspector/Debugger.h). +struct Exported { + std::string msg; + void AppendSerialized(std::vector* out) const { + cbor::EncodeString8(SpanFrom(msg), out); + } +}; + +TEST(SerializerTraits, Exported) { + Exported exported; + exported.msg = "Hello, world."; + + std::vector out; + SerializerTraits::Serialize(exported, &out); + + std::vector expected; + cbor::EncodeString8(SpanFrom(exported.msg), &expected); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +// ============================================================================= +// FieldSerializerTraits - Encodes fields of protocol objects in CBOR +// ============================================================================= +TEST(FieldSerializerTraits, RequiredField) { + std::string value = "Hello, world."; + + std::vector out; + SerializeField(SpanFrom("msg"), value, &out); + + std::vector expected; + cbor::EncodeString8(SpanFrom("msg"), &expected); + cbor::EncodeString8(SpanFrom(value), &expected); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +template +class FieldSerializerTraits_MaybeTest : public ::testing::Test {}; +using MaybeTypes = + ::testing::Types, + detail::ValueMaybe, + detail::ValueMaybe, + detail::ValueMaybe, + detail::PtrMaybe, + detail::PtrMaybe>>>; +TYPED_TEST_SUITE(FieldSerializerTraits_MaybeTest, MaybeTypes); + +TYPED_TEST(FieldSerializerTraits_MaybeTest, NoOutputForFieldsIfNotJust) { + std::vector out; + SerializeField(SpanFrom("maybe"), TypeParam(), &out); + EXPECT_THAT(out, testing::ElementsAreArray(std::vector())); +} + +TEST(FieldSerializerTraits, MaybeBool) { + std::vector out; + SerializeField(SpanFrom("true"), detail::ValueMaybe(true), &out); + SerializeField(SpanFrom("false"), detail::ValueMaybe(false), &out); + + std::vector expected; + cbor::EncodeString8(SpanFrom("true"), &expected); + expected.push_back(cbor::EncodeTrue()); + cbor::EncodeString8(SpanFrom("false"), &expected); + expected.push_back(cbor::EncodeFalse()); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +TEST(FieldSerializerTraits, MaybeDouble) { + std::vector out; + SerializeField(SpanFrom("dbl"), detail::ValueMaybe(3.14), &out); + + std::vector expected; + cbor::EncodeString8(SpanFrom("dbl"), &expected); + cbor::EncodeDouble(3.14, &expected); + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} + +TEST(FieldSerializerTraits, MaybePtrFoo) { + std::vector out; + SerializeField(SpanFrom("foo"), + detail::PtrMaybe(std::make_unique(42)), &out); + + std::vector expected; + cbor::EncodeString8(SpanFrom("foo"), &expected); + cbor::EncodeInt32(42, &expected); // Simplified relative to production. + + EXPECT_THAT(out, testing::ElementsAreArray(expected)); +} +} // namespace +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/span.cc b/tools/inspector_protocol/crdtp/span.cc new file mode 100644 index 00000000000000..d3bedb84e41784 --- /dev/null +++ b/tools/inspector_protocol/crdtp/span.cc @@ -0,0 +1,39 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "span.h" + +#include + +namespace v8_inspector_protocol_crdtp { + +bool SpanLessThan(span x, span y) noexcept { + auto min_size = std::min(x.size(), y.size()); + const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size); + return (r < 0) || (r == 0 && x.size() < y.size()); +} + +bool SpanEquals(span x, span y) noexcept { + auto len = x.size(); + if (len != y.size()) + return false; + return x.data() == y.data() || len == 0 || + std::memcmp(x.data(), y.data(), len) == 0; +} + +bool SpanLessThan(span x, span y) noexcept { + auto min_size = std::min(x.size(), y.size()); + const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size); + return (r < 0) || (r == 0 && x.size() < y.size()); +} + +bool SpanEquals(span x, span y) noexcept { + auto len = x.size(); + if (len != y.size()) + return false; + return x.data() == y.data() || len == 0 || + std::memcmp(x.data(), y.data(), len) == 0; +} + +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/span.h b/tools/inspector_protocol/crdtp/span.h new file mode 100644 index 00000000000000..8be8ac9d9ea6a6 --- /dev/null +++ b/tools/inspector_protocol/crdtp/span.h @@ -0,0 +1,99 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_SPAN_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_SPAN_H_ + +#include +#include +#include + +#include "export.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// span - sequence of bytes +// ============================================================================= + +// This template is similar to std::span, which will be included in C++20. +template +class span { + public: + using index_type = size_t; + + constexpr span() : data_(nullptr), size_(0) {} + constexpr span(const T* data, index_type size) : data_(data), size_(size) {} + + constexpr const T* data() const { return data_; } + + constexpr const T* begin() const { return data_; } + constexpr const T* end() const { return data_ + size_; } + + constexpr const T& operator[](index_type idx) const { return data_[idx]; } + + constexpr span subspan(index_type offset, index_type count) const { + return span(data_ + offset, count); + } + + constexpr span subspan(index_type offset) const { + return span(data_ + offset, size_ - offset); + } + + constexpr bool empty() const { return size_ == 0; } + + constexpr index_type size() const { return size_; } + constexpr index_type size_bytes() const { return size_ * sizeof(T); } + + private: + const T* data_; + index_type size_; +}; + +template +constexpr span MakeSpan(const char (&str)[N]) { + return span(str, N - 1); +} + +template +constexpr span SpanFrom(const char (&str)[N]) { + return span(reinterpret_cast(str), N - 1); +} + +constexpr inline span SpanFrom(const char* str) { + return str ? span(reinterpret_cast(str), strlen(str)) + : span(); +} + +inline span SpanFrom(const std::string& v) { + return span(reinterpret_cast(v.data()), v.size()); +} + +// This SpanFrom routine works for std::vector and +// std::vector, but also for base::span in Chromium. +template {} && + std::is_member_function_pointer{}>> +inline span SpanFrom(const C& v) { + return span(v.data(), v.size()); +} + +// Less than / equality comparison functions for sorting / searching for byte +// spans. +bool SpanLessThan(span x, span y) noexcept; +bool SpanEquals(span x, span y) noexcept; + +// Less than / equality comparison functions for sorting / searching for byte +// spans. +bool SpanLessThan(span x, span y) noexcept; +bool SpanEquals(span x, span y) noexcept; + +struct SpanLt { + bool operator()(span l, span r) const { + return SpanLessThan(l, r); + } +}; +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_SPAN_H_ diff --git a/tools/inspector_protocol/crdtp/span_test.cc b/tools/inspector_protocol/crdtp/span_test.cc new file mode 100644 index 00000000000000..5bd561aa0bf79a --- /dev/null +++ b/tools/inspector_protocol/crdtp/span_test.cc @@ -0,0 +1,109 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "span.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// span - sequence of bytes +// ============================================================================= +template +class SpanTest : public ::testing::Test {}; + +using TestTypes = ::testing::Types; +TYPED_TEST_SUITE(SpanTest, TestTypes); + +TYPED_TEST(SpanTest, Empty) { + span empty; + EXPECT_TRUE(empty.empty()); + EXPECT_EQ(0u, empty.size()); + EXPECT_EQ(0u, empty.size_bytes()); + EXPECT_EQ(empty.begin(), empty.end()); +} + +TYPED_TEST(SpanTest, SingleItem) { + TypeParam single_item = 42; + span singular(&single_item, 1); + EXPECT_FALSE(singular.empty()); + EXPECT_EQ(1u, singular.size()); + EXPECT_EQ(sizeof(TypeParam), singular.size_bytes()); + EXPECT_EQ(singular.begin() + 1, singular.end()); + EXPECT_EQ(42, singular[0]); +} + +TYPED_TEST(SpanTest, FiveItems) { + std::vector test_input = {31, 32, 33, 34, 35}; + span five_items(test_input.data(), 5); + EXPECT_FALSE(five_items.empty()); + EXPECT_EQ(5u, five_items.size()); + EXPECT_EQ(sizeof(TypeParam) * 5, five_items.size_bytes()); + EXPECT_EQ(five_items.begin() + 5, five_items.end()); + EXPECT_EQ(31, five_items[0]); + EXPECT_EQ(32, five_items[1]); + EXPECT_EQ(33, five_items[2]); + EXPECT_EQ(34, five_items[3]); + EXPECT_EQ(35, five_items[4]); + span three_items = five_items.subspan(2); + EXPECT_EQ(3u, three_items.size()); + EXPECT_EQ(33, three_items[0]); + EXPECT_EQ(34, three_items[1]); + EXPECT_EQ(35, three_items[2]); + span two_items = five_items.subspan(2, 2); + EXPECT_EQ(2u, two_items.size()); + EXPECT_EQ(33, two_items[0]); + EXPECT_EQ(34, two_items[1]); +} + +TEST(SpanFromTest, FromConstCharAndLiteral) { + // Testing this is useful because strlen(nullptr) is undefined. + EXPECT_EQ(nullptr, SpanFrom(nullptr).data()); + EXPECT_EQ(0u, SpanFrom(nullptr).size()); + + const char* kEmpty = ""; + EXPECT_EQ(kEmpty, reinterpret_cast(SpanFrom(kEmpty).data())); + EXPECT_EQ(0u, SpanFrom(kEmpty).size()); + + const char* kFoo = "foo"; + EXPECT_EQ(kFoo, reinterpret_cast(SpanFrom(kFoo).data())); + EXPECT_EQ(3u, SpanFrom(kFoo).size()); + + EXPECT_EQ(3u, SpanFrom("foo").size()); +} + +TEST(SpanFromTest, FromVectorUint8AndUint16) { + std::vector foo = {'f', 'o', 'o'}; + span foo_span = SpanFrom(foo); + EXPECT_EQ(foo.size(), foo_span.size()); + + std::vector bar = {0xff, 0xef, 0xeb}; + span bar_span = SpanFrom(bar); + EXPECT_EQ(bar.size(), bar_span.size()); +} + +TEST(SpanComparisons, ByteWiseLexicographicalOrder) { + // Compare the empty span. + EXPECT_FALSE(SpanLessThan(span(), span())); + EXPECT_TRUE(SpanEquals(span(), span())); + + // Compare message with itself. + std::string msg = "Hello, world"; + EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(msg))); + EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(msg))); + + // Compare message and copy. + EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(std::string(msg)))); + EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(std::string(msg)))); + + // Compare two messages. |lesser_msg| < |msg| because of the first + // byte ('A' < 'H'). + std::string lesser_msg = "A lesser message."; + EXPECT_TRUE(SpanLessThan(SpanFrom(lesser_msg), SpanFrom(msg))); + EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(lesser_msg))); + EXPECT_FALSE(SpanEquals(SpanFrom(msg), SpanFrom(lesser_msg))); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/status.cc b/tools/inspector_protocol/crdtp/status.cc new file mode 100644 index 00000000000000..38fa3eff509fbb --- /dev/null +++ b/tools/inspector_protocol/crdtp/status.cc @@ -0,0 +1,126 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "status.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// Status and Error codes +// ============================================================================= + +std::string Status::Message() const { + switch (error) { + case Error::OK: + return "OK"; + case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS: + return "JSON: unprocessed input remains"; + case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED: + return "JSON: stack limit exceeded"; + case Error::JSON_PARSER_NO_INPUT: + return "JSON: no input"; + case Error::JSON_PARSER_INVALID_TOKEN: + return "JSON: invalid token"; + case Error::JSON_PARSER_INVALID_NUMBER: + return "JSON: invalid number"; + case Error::JSON_PARSER_INVALID_STRING: + return "JSON: invalid string"; + case Error::JSON_PARSER_UNEXPECTED_ARRAY_END: + return "JSON: unexpected array end"; + case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED: + return "JSON: comma or array end expected"; + case Error::JSON_PARSER_STRING_LITERAL_EXPECTED: + return "JSON: string literal expected"; + case Error::JSON_PARSER_COLON_EXPECTED: + return "JSON: colon expected"; + case Error::JSON_PARSER_UNEXPECTED_MAP_END: + return "JSON: unexpected map end"; + case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED: + return "JSON: comma or map end expected"; + case Error::JSON_PARSER_VALUE_EXPECTED: + return "JSON: value expected"; + + case Error::CBOR_INVALID_INT32: + return "CBOR: invalid int32"; + case Error::CBOR_INVALID_DOUBLE: + return "CBOR: invalid double"; + case Error::CBOR_INVALID_ENVELOPE: + return "CBOR: invalid envelope"; + case Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH: + return "CBOR: envelope contents length mismatch"; + case Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE: + return "CBOR: map or array expected in envelope"; + case Error::CBOR_INVALID_STRING8: + return "CBOR: invalid string8"; + case Error::CBOR_INVALID_STRING16: + return "CBOR: invalid string16"; + case Error::CBOR_INVALID_BINARY: + return "CBOR: invalid binary"; + case Error::CBOR_UNSUPPORTED_VALUE: + return "CBOR: unsupported value"; + case Error::CBOR_NO_INPUT: + return "CBOR: no input"; + case Error::CBOR_INVALID_START_BYTE: + return "CBOR: invalid start byte"; + case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE: + return "CBOR: unexpected eof expected value"; + case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY: + return "CBOR: unexpected eof in array"; + case Error::CBOR_UNEXPECTED_EOF_IN_MAP: + return "CBOR: unexpected eof in map"; + case Error::CBOR_INVALID_MAP_KEY: + return "CBOR: invalid map key"; + case Error::CBOR_DUPLICATE_MAP_KEY: + return "CBOR: duplicate map key"; + case Error::CBOR_STACK_LIMIT_EXCEEDED: + return "CBOR: stack limit exceeded"; + case Error::CBOR_TRAILING_JUNK: + return "CBOR: trailing junk"; + case Error::CBOR_MAP_START_EXPECTED: + return "CBOR: map start expected"; + case Error::CBOR_MAP_STOP_EXPECTED: + return "CBOR: map stop expected"; + case Error::CBOR_ARRAY_START_EXPECTED: + return "CBOR: array start expected"; + case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED: + return "CBOR: envelope size limit exceeded"; + + case Error::MESSAGE_MUST_BE_AN_OBJECT: + return "Message must be an object"; + case Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY: + return "Message must have integer 'id' property"; + case Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY: + return "Message must have string 'method' property"; + case Error::MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY: + return "Message may have string 'sessionId' property"; + case Error::MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY: + return "Message may have object 'params' property"; + case Error::MESSAGE_HAS_UNKNOWN_PROPERTY: + return "Message has property other than " + "'id', 'method', 'sessionId', 'params'"; + + case Error::BINDINGS_MANDATORY_FIELD_MISSING: + return "BINDINGS: mandatory field missing"; + case Error::BINDINGS_BOOL_VALUE_EXPECTED: + return "BINDINGS: bool value expected"; + case Error::BINDINGS_INT32_VALUE_EXPECTED: + return "BINDINGS: int32 value expected"; + case Error::BINDINGS_DOUBLE_VALUE_EXPECTED: + return "BINDINGS: double value expected"; + case Error::BINDINGS_STRING_VALUE_EXPECTED: + return "BINDINGS: string value expected"; + case Error::BINDINGS_STRING8_VALUE_EXPECTED: + return "BINDINGS: string8 value expected"; + case Error::BINDINGS_BINARY_VALUE_EXPECTED: + return "BINDINGS: binary value expected"; + } + // Some compilers can't figure out that we can't get here. + return "INVALID ERROR CODE"; +} + +std::string Status::ToASCIIString() const { + if (ok()) + return "OK"; + return Message() + " at position " + std::to_string(pos); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/status.h b/tools/inspector_protocol/crdtp/status.h new file mode 100644 index 00000000000000..7147597ae28be3 --- /dev/null +++ b/tools/inspector_protocol/crdtp/status.h @@ -0,0 +1,138 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_STATUS_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_STATUS_H_ + +#include +#include +#include +#include + +#include "export.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// Status and Error codes +// ============================================================================= + +enum class Error { + OK = 0, + + // JSON parsing errors; checked when parsing / converting from JSON. + // See json.{h,cc}. + JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01, + JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02, + JSON_PARSER_NO_INPUT = 0x03, + JSON_PARSER_INVALID_TOKEN = 0x04, + JSON_PARSER_INVALID_NUMBER = 0x05, + JSON_PARSER_INVALID_STRING = 0x06, + JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07, + JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08, + JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09, + JSON_PARSER_COLON_EXPECTED = 0x0a, + JSON_PARSER_UNEXPECTED_MAP_END = 0x0b, + JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c, + JSON_PARSER_VALUE_EXPECTED = 0x0d, + + // CBOR parsing errors; checked when parsing / converting from CBOR. + CBOR_INVALID_INT32 = 0x0e, + CBOR_INVALID_DOUBLE = 0x0f, + CBOR_INVALID_ENVELOPE = 0x10, + CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH = 0x11, + CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE = 0x12, + CBOR_INVALID_STRING8 = 0x13, + CBOR_INVALID_STRING16 = 0x14, + CBOR_INVALID_BINARY = 0x15, + CBOR_UNSUPPORTED_VALUE = 0x16, + CBOR_NO_INPUT = 0x17, + CBOR_INVALID_START_BYTE = 0x18, + CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19, + CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a, + CBOR_UNEXPECTED_EOF_IN_MAP = 0x1b, + CBOR_INVALID_MAP_KEY = 0x1c, + CBOR_DUPLICATE_MAP_KEY = 0x1d, + CBOR_STACK_LIMIT_EXCEEDED = 0x1e, + CBOR_TRAILING_JUNK = 0x1f, + CBOR_MAP_START_EXPECTED = 0x20, + CBOR_MAP_STOP_EXPECTED = 0x21, + CBOR_ARRAY_START_EXPECTED = 0x22, + CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x23, + + // Message errors are constraints we place on protocol messages coming + // from a protocol client; these are checked in crdtp::Dispatchable + // (see dispatch.h) as it performs a shallow parse. + MESSAGE_MUST_BE_AN_OBJECT = 0x24, + MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY = 0x25, + MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY = 0x26, + MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY = 0x27, + MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY = 0x28, + MESSAGE_HAS_UNKNOWN_PROPERTY = 0x29, + + BINDINGS_MANDATORY_FIELD_MISSING = 0x30, + BINDINGS_BOOL_VALUE_EXPECTED = 0x31, + BINDINGS_INT32_VALUE_EXPECTED = 0x32, + BINDINGS_DOUBLE_VALUE_EXPECTED = 0x33, + BINDINGS_STRING_VALUE_EXPECTED = 0x34, + BINDINGS_STRING8_VALUE_EXPECTED = 0x35, + BINDINGS_BINARY_VALUE_EXPECTED = 0x36, +}; + +// A status value with position that can be copied. The default status +// is OK. Usually, error status values should come with a valid position. +struct Status { + static constexpr size_t npos() { return std::numeric_limits::max(); } + + bool ok() const { return error == Error::OK; } + + Error error = Error::OK; + size_t pos = npos(); + Status(Error error, size_t pos) : error(error), pos(pos) {} + Status() = default; + + bool IsMessageError() const { + return error >= Error::MESSAGE_MUST_BE_AN_OBJECT && + error <= Error::MESSAGE_HAS_UNKNOWN_PROPERTY; + } + + // Returns 7 bit US-ASCII string, either "OK" or an error message without + // position. + std::string Message() const; + + // Returns a 7 bit US-ASCII string, either "OK" or an error message that + // includes the position. + std::string ToASCIIString() const; +}; + +template +class StatusOr { + public: + explicit StatusOr(const T& value) : value_(value) {} + explicit StatusOr(T&& value) : value_(std::move(value)) {} + explicit StatusOr(const Status& status) : status_(status) {} + + bool ok() const { return status_.ok(); } + T& operator*() & { + assert(ok()); + return value_; + } + const T& operator*() const& { return value(); } + T&& operator*() && { return value(); } + const Status& status() const { return status_; } + + T& value() & { return *this; } + T&& value() && { + assert(ok()); + return std::move(value_); + } + const T& value() const& { return *this; } + + private: + Status status_; + T value_; +}; + +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_STATUS_H_ diff --git a/tools/inspector_protocol/crdtp/status_test.cc b/tools/inspector_protocol/crdtp/status_test.cc new file mode 100644 index 00000000000000..d5b4282b7b2558 --- /dev/null +++ b/tools/inspector_protocol/crdtp/status_test.cc @@ -0,0 +1,29 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "status.h" +#include "status_test_support.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +// ============================================================================= +// Status and Error codes +// ============================================================================= + +TEST(StatusTest, StatusToASCIIString) { + Status ok_status; + EXPECT_EQ("OK", ok_status.ToASCIIString()); + Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42); + EXPECT_EQ("JSON: colon expected at position 42", json_error.ToASCIIString()); + Status cbor_error(Error::CBOR_TRAILING_JUNK, 21); + EXPECT_EQ("CBOR: trailing junk at position 21", cbor_error.ToASCIIString()); +} + +TEST(StatusTest, StatusTestSupport) { + Status ok_status; + EXPECT_THAT(ok_status, StatusIsOk()); + Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42); + EXPECT_THAT(json_error, StatusIs(Error::JSON_PARSER_COLON_EXPECTED, 42)); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/status_test_support.cc b/tools/inspector_protocol/crdtp/status_test_support.cc new file mode 100644 index 00000000000000..cff1e3ea73243f --- /dev/null +++ b/tools/inspector_protocol/crdtp/status_test_support.cc @@ -0,0 +1,50 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "status_test_support.h" + +namespace v8_inspector_protocol_crdtp { +void PrintTo(const Status& status, std::ostream* os) { + *os << status.ToASCIIString() << " (error: 0x" << std::hex + << static_cast(status.error) << ", " + << "pos: " << std::dec << status.pos << ")"; +} + +namespace { +class StatusIsMatcher : public testing::MatcherInterface { + public: + explicit StatusIsMatcher(Status status) : expected_(status) {} + + bool MatchAndExplain(Status status, + testing::MatchResultListener* listener) const override { + return status.error == expected_.error && status.pos == expected_.pos; + } + + void DescribeTo(std::ostream* os) const override { + *os << "equals to "; + PrintTo(expected_, os); + } + + private: + Status expected_; +}; + +class StatusIsOkMatcher : public testing::MatcherInterface { + bool MatchAndExplain(Status status, + testing::MatchResultListener* listener) const override { + return status.ok(); + } + + void DescribeTo(std::ostream* os) const override { *os << "is ok"; } +}; +} // namespace + +testing::Matcher StatusIsOk() { + return MakeMatcher(new StatusIsOkMatcher()); +} + +testing::Matcher StatusIs(Error error, size_t pos) { + return MakeMatcher(new StatusIsMatcher(Status(error, pos))); +} +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/crdtp/status_test_support.h b/tools/inspector_protocol/crdtp/status_test_support.h new file mode 100644 index 00000000000000..7543c394affcee --- /dev/null +++ b/tools/inspector_protocol/crdtp/status_test_support.h @@ -0,0 +1,32 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_STATUS_TEST_SUPPORT_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_STATUS_TEST_SUPPORT_H_ + +#include +#include "status.h" +#include "test_platform.h" + +namespace v8_inspector_protocol_crdtp { +// Supports gtest, to conveniently match Status objects and +// get useful error messages when tests fail. +// Typically used with EXPECT_THAT, e.g. +// +// EXPECT_THAT(status, StatusIs(Error::JSON_PARSER_COLON_EXPECTED, 42)); +// +// EXPECT_THAT(status, StatusIsOk()); + +// Prints a |status|, including its generated error message, error code, and +// position. This is used by gtest for pretty printing actual vs. expected. +void PrintTo(const Status& status, std::ostream* os); + +// Matches any status with |status.ok()|. +testing::Matcher StatusIsOk(); + +// Matches any status with |error| and |pos|. +testing::Matcher StatusIs(Error error, size_t pos); +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_STATUS_TEST_SUPPORT_H_ diff --git a/tools/inspector_protocol/crdtp/test_platform.h b/tools/inspector_protocol/crdtp/test_platform.h new file mode 100644 index 00000000000000..6ab4c69dd384a3 --- /dev/null +++ b/tools/inspector_protocol/crdtp/test_platform.h @@ -0,0 +1,26 @@ +// Copyright 2019 The V8 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is V8 specific. It's not rolled from the upstream project. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_ + +#include +#include + +#include "span.h" +#include "src/base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace v8_inspector_protocol_crdtp { + +std::string UTF16ToUTF8(span in); + +std::vector UTF8ToUTF16(span in); + +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_ diff --git a/tools/inspector_protocol/encoding/encoding_test_helper.h b/tools/inspector_protocol/crdtp/test_platform_v8.cc similarity index 50% rename from tools/inspector_protocol/encoding/encoding_test_helper.h rename to tools/inspector_protocol/crdtp/test_platform_v8.cc index 84da2e72e87f5c..0745c04afdc8fb 100644 --- a/tools/inspector_protocol/encoding/encoding_test_helper.h +++ b/tools/inspector_protocol/crdtp/test_platform_v8.cc @@ -2,21 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// This file is V8 specific, to make encoding_test.cc work. -// It is not rolled from the upstream project. +// This file is V8 specific. It's not rolled from the upstream project. -#ifndef V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_ -#define V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_ +#include "test_platform.h" -#include -#include - -#include "src/base/logging.h" #include "src/inspector/v8-string-conversions.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" -namespace v8_inspector_protocol_encoding { +namespace v8_inspector_protocol_crdtp { std::string UTF16ToUTF8(span in) { return v8_inspector::UTF16ToUTF8(in.data(), in.size()); @@ -28,6 +20,4 @@ std::vector UTF8ToUTF16(span in) { return std::vector(utf16.begin(), utf16.end()); } -} // namespace v8_inspector_protocol_encoding - -#endif // V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_TEST_HELPER_H_ +} // namespace v8_crdtp diff --git a/tools/inspector_protocol/encoding/encoding.cc b/tools/inspector_protocol/encoding/encoding.cc deleted file mode 100644 index f7858c9a22ba64..00000000000000 --- a/tools/inspector_protocol/encoding/encoding.cc +++ /dev/null @@ -1,2189 +0,0 @@ -// 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 "encoding.h" - -#include -#include -#include -#include -#include -#include - -namespace v8_inspector_protocol_encoding { -// ============================================================================= -// Status and Error codes -// ============================================================================= - -std::string Status::ToASCIIString() const { - switch (error) { - case Error::OK: - return "OK"; - case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS: - return ToASCIIString("JSON: unprocessed input remains"); - case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED: - return ToASCIIString("JSON: stack limit exceeded"); - case Error::JSON_PARSER_NO_INPUT: - return ToASCIIString("JSON: no input"); - case Error::JSON_PARSER_INVALID_TOKEN: - return ToASCIIString("JSON: invalid token"); - case Error::JSON_PARSER_INVALID_NUMBER: - return ToASCIIString("JSON: invalid number"); - case Error::JSON_PARSER_INVALID_STRING: - return ToASCIIString("JSON: invalid string"); - case Error::JSON_PARSER_UNEXPECTED_ARRAY_END: - return ToASCIIString("JSON: unexpected array end"); - case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED: - return ToASCIIString("JSON: comma or array end expected"); - case Error::JSON_PARSER_STRING_LITERAL_EXPECTED: - return ToASCIIString("JSON: string literal expected"); - case Error::JSON_PARSER_COLON_EXPECTED: - return ToASCIIString("JSON: colon expected"); - case Error::JSON_PARSER_UNEXPECTED_MAP_END: - return ToASCIIString("JSON: unexpected map end"); - case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED: - return ToASCIIString("JSON: comma or map end expected"); - case Error::JSON_PARSER_VALUE_EXPECTED: - return ToASCIIString("JSON: value expected"); - - case Error::CBOR_INVALID_INT32: - return ToASCIIString("CBOR: invalid int32"); - case Error::CBOR_INVALID_DOUBLE: - return ToASCIIString("CBOR: invalid double"); - case Error::CBOR_INVALID_ENVELOPE: - return ToASCIIString("CBOR: invalid envelope"); - case Error::CBOR_INVALID_STRING8: - return ToASCIIString("CBOR: invalid string8"); - case Error::CBOR_INVALID_STRING16: - return ToASCIIString("CBOR: invalid string16"); - case Error::CBOR_INVALID_BINARY: - return ToASCIIString("CBOR: invalid binary"); - case Error::CBOR_UNSUPPORTED_VALUE: - return ToASCIIString("CBOR: unsupported value"); - case Error::CBOR_NO_INPUT: - return ToASCIIString("CBOR: no input"); - case Error::CBOR_INVALID_START_BYTE: - return ToASCIIString("CBOR: invalid start byte"); - case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE: - return ToASCIIString("CBOR: unexpected eof expected value"); - case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY: - return ToASCIIString("CBOR: unexpected eof in array"); - case Error::CBOR_UNEXPECTED_EOF_IN_MAP: - return ToASCIIString("CBOR: unexpected eof in map"); - case Error::CBOR_INVALID_MAP_KEY: - return ToASCIIString("CBOR: invalid map key"); - case Error::CBOR_STACK_LIMIT_EXCEEDED: - return ToASCIIString("CBOR: stack limit exceeded"); - case Error::CBOR_TRAILING_JUNK: - return ToASCIIString("CBOR: trailing junk"); - case Error::CBOR_MAP_START_EXPECTED: - return ToASCIIString("CBOR: map start expected"); - case Error::CBOR_MAP_STOP_EXPECTED: - return ToASCIIString("CBOR: map stop expected"); - case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED: - return ToASCIIString("CBOR: envelope size limit exceeded"); - } - // Some compilers can't figure out that we can't get here. - return "INVALID ERROR CODE"; -} - -std::string Status::ToASCIIString(const char* msg) const { - return std::string(msg) + " at position " + std::to_string(pos); -} - -namespace cbor { -namespace { -// Indicates the number of bits the "initial byte" needs to be shifted to the -// right after applying |kMajorTypeMask| to produce the major type in the -// lowermost bits. -static constexpr uint8_t kMajorTypeBitShift = 5u; -// Mask selecting the low-order 5 bits of the "initial byte", which is where -// the additional information is encoded. -static constexpr uint8_t kAdditionalInformationMask = 0x1f; -// Mask selecting the high-order 3 bits of the "initial byte", which indicates -// the major type of the encoded value. -static constexpr uint8_t kMajorTypeMask = 0xe0; -// Indicates the integer is in the following byte. -static constexpr uint8_t kAdditionalInformation1Byte = 24u; -// Indicates the integer is in the next 2 bytes. -static constexpr uint8_t kAdditionalInformation2Bytes = 25u; -// Indicates the integer is in the next 4 bytes. -static constexpr uint8_t kAdditionalInformation4Bytes = 26u; -// Indicates the integer is in the next 8 bytes. -static constexpr uint8_t kAdditionalInformation8Bytes = 27u; - -// Encodes the initial byte, consisting of the |type| in the first 3 bits -// followed by 5 bits of |additional_info|. -constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) { - return (static_cast(type) << kMajorTypeBitShift) | - (additional_info & kAdditionalInformationMask); -} - -// TAG 24 indicates that what follows is a byte string which is -// encoded in CBOR format. We use this as a wrapper for -// maps and arrays, allowing us to skip them, because the -// byte string carries its size (byte length). -// https://tools.ietf.org/html/rfc7049#section-2.4.4.1 -static constexpr uint8_t kInitialByteForEnvelope = - EncodeInitialByte(MajorType::TAG, 24); -// The initial byte for a byte string with at most 2^32 bytes -// of payload. This is used for envelope encoding, even if -// the byte string is shorter. -static constexpr uint8_t kInitialByteFor32BitLengthByteString = - EncodeInitialByte(MajorType::BYTE_STRING, 26); - -// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional -// info = 31. -static constexpr uint8_t kInitialByteIndefiniteLengthArray = - EncodeInitialByte(MajorType::ARRAY, 31); -static constexpr uint8_t kInitialByteIndefiniteLengthMap = - EncodeInitialByte(MajorType::MAP, 31); -// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite -// length maps / arrays. -static constexpr uint8_t kStopByte = - EncodeInitialByte(MajorType::SIMPLE_VALUE, 31); - -// See RFC 7049 Section 2.3, Table 2. -static constexpr uint8_t kEncodedTrue = - 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); - -// 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); - -// 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, C* out) { - for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes) - out->push_back(0xff & (v >> (shift_bytes * 8))); -} - -// 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(in.size() >= sizeof(T)); - T result = 0; - for (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 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. -// TODO(johannes): change return type to size_t and use 0 for error. -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 (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 (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 (bytes.size() < 1 + sizeof(uint64_t)) - return -1; - *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); - return 9; - } - return -1; -} - -// 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. -template -void WriteTokenStartTmpl(MajorType type, uint64_t value, C* 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); -} -void WriteTokenStart(MajorType type, - uint64_t value, - std::vector* encoded) { - WriteTokenStartTmpl(type, value, encoded); -} -void WriteTokenStart(MajorType type, uint64_t value, std::string* encoded) { - WriteTokenStartTmpl(type, value, encoded); -} -} // namespace internals - -// ============================================================================= -// Detecting CBOR content -// ============================================================================= - -uint8_t InitialByteForEnvelope() { - return kInitialByteForEnvelope; -} -uint8_t InitialByteFor32BitLengthByteString() { - return kInitialByteFor32BitLengthByteString; -} -bool IsCBORMessage(span msg) { - return msg.size() >= 6 && msg[0] == InitialByteForEnvelope() && - msg[1] == InitialByteFor32BitLengthByteString(); -} - -// ============================================================================= -// Encoding invidiual CBOR items -// ============================================================================= - -uint8_t EncodeTrue() { - return kEncodedTrue; -} -uint8_t EncodeFalse() { - return kEncodedFalse; -} -uint8_t EncodeNull() { - return kEncodedNull; -} - -uint8_t EncodeIndefiniteLengthArrayStart() { - return kInitialByteIndefiniteLengthArray; -} - -uint8_t EncodeIndefiniteLengthMapStart() { - return kInitialByteIndefiniteLengthMap; -} - -uint8_t EncodeStop() { - return kStopByte; -} - -template -void EncodeInt32Tmpl(int32_t value, C* out) { - if (value >= 0) { - internals::WriteTokenStart(MajorType::UNSIGNED, value, out); - } else { - uint64_t representation = static_cast(-(value + 1)); - internals::WriteTokenStart(MajorType::NEGATIVE, representation, out); - } -} -void EncodeInt32(int32_t value, std::vector* out) { - EncodeInt32Tmpl(value, out); -} -void EncodeInt32(int32_t value, std::string* out) { - EncodeInt32Tmpl(value, out); -} - -template -void EncodeString16Tmpl(span in, C* out) { - uint64_t byte_length = static_cast(in.size_bytes()); - internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); - // When emitting UTF16 characters, we always write the least significant byte - // first; this is because it's the native representation for X86. - // TODO(johannes): Implement a more efficient thing here later, e.g. - // 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 EncodeString16(span in, std::vector* out) { - EncodeString16Tmpl(in, out); -} -void EncodeString16(span in, std::string* out) { - EncodeString16Tmpl(in, out); -} - -template -void EncodeString8Tmpl(span in, C* out) { - internals::WriteTokenStart(MajorType::STRING, - static_cast(in.size_bytes()), out); - out->insert(out->end(), in.begin(), in.end()); -} -void EncodeString8(span in, std::vector* out) { - EncodeString8Tmpl(in, out); -} -void EncodeString8(span in, std::string* out) { - EncodeString8Tmpl(in, out); -} - -template -void EncodeFromLatin1Tmpl(span latin1, C* out) { - for (size_t ii = 0; ii < latin1.size(); ++ii) { - if (latin1[ii] <= 127) - continue; - // If there's at least one non-ASCII char, convert to UTF8. - std::vector utf8(latin1.begin(), latin1.begin() + ii); - for (; ii < latin1.size(); ++ii) { - if (latin1[ii] <= 127) { - utf8.push_back(latin1[ii]); - } else { - // 0xC0 means it's a UTF8 sequence with 2 bytes. - utf8.push_back((latin1[ii] >> 6) | 0xc0); - utf8.push_back((latin1[ii] | 0x80) & 0xbf); - } - } - EncodeString8(SpanFrom(utf8), out); - return; - } - EncodeString8(latin1, out); -} -void EncodeFromLatin1(span latin1, std::vector* out) { - EncodeFromLatin1Tmpl(latin1, out); -} -void EncodeFromLatin1(span latin1, std::string* out) { - EncodeFromLatin1Tmpl(latin1, out); -} - -template -void EncodeFromUTF16Tmpl(span utf16, C* out) { - // If there's at least one non-ASCII char, encode as STRING16 (UTF16). - for (uint16_t ch : utf16) { - if (ch <= 127) - continue; - EncodeString16(utf16, out); - return; - } - // It's all US-ASCII, strip out every second byte and encode as UTF8. - internals::WriteTokenStart(MajorType::STRING, - static_cast(utf16.size()), out); - out->insert(out->end(), utf16.begin(), utf16.end()); -} -void EncodeFromUTF16(span utf16, std::vector* out) { - EncodeFromUTF16Tmpl(utf16, out); -} -void EncodeFromUTF16(span utf16, std::string* out) { - EncodeFromUTF16Tmpl(utf16, out); -} - -template -void EncodeBinaryTmpl(span in, C* out) { - out->push_back(kExpectedConversionToBase64Tag); - uint64_t byte_length = static_cast(in.size_bytes()); - internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); - out->insert(out->end(), in.begin(), in.end()); -} -void EncodeBinary(span in, std::vector* out) { - EncodeBinaryTmpl(in, out); -} -void EncodeBinary(span in, std::string* out) { - EncodeBinaryTmpl(in, out); -} - -// A double is encoded with a specific initial byte -// (kInitialByteForDouble) plus the 64 bits of payload for its value. -constexpr size_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 size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t); - -template -void EncodeDoubleTmpl(double value, C* 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 EncodeDouble(double value, std::vector* out) { - EncodeDoubleTmpl(value, out); -} -void EncodeDouble(double value, std::string* out) { - EncodeDoubleTmpl(value, out); -} - -// ============================================================================= -// cbor::EnvelopeEncoder - for wrapping submessages -// ============================================================================= - -template -void EncodeStartTmpl(C* out, size_t* byte_size_pos) { - 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)); -} - -void EnvelopeEncoder::EncodeStart(std::vector* out) { - EncodeStartTmpl>(out, &byte_size_pos_); -} - -void EnvelopeEncoder::EncodeStart(std::string* out) { - EncodeStartTmpl(out, &byte_size_pos_); -} - -template -bool EncodeStopTmpl(C* out, size_t* byte_size_pos) { - 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; -} - -bool EnvelopeEncoder::EncodeStop(std::vector* out) { - return EncodeStopTmpl(out, &byte_size_pos_); -} - -bool EnvelopeEncoder::EncodeStop(std::string* out) { - return EncodeStopTmpl(out, &byte_size_pos_); -} - -// ============================================================================= -// cbor::NewCBOREncoder - for encoding from a streaming parser -// ============================================================================= - -namespace { -template -class CBOREncoder : public StreamingParserHandler { - public: - CBOREncoder(C* out, Status* status) : out_(out), status_(status) { - *status_ = Status(); - } - - void HandleMapBegin() override { - if (!status_->ok()) - return; - envelopes_.emplace_back(); - envelopes_.back().EncodeStart(out_); - out_->push_back(kInitialByteIndefiniteLengthMap); - } - - void HandleMapEnd() override { - if (!status_->ok()) - return; - out_->push_back(kStopByte); - assert(!envelopes_.empty()); - if (!envelopes_.back().EncodeStop(out_)) { - HandleError( - Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size())); - return; - } - envelopes_.pop_back(); - } - - void HandleArrayBegin() override { - if (!status_->ok()) - return; - envelopes_.emplace_back(); - envelopes_.back().EncodeStart(out_); - out_->push_back(kInitialByteIndefiniteLengthArray); - } - - void HandleArrayEnd() override { - if (!status_->ok()) - return; - out_->push_back(kStopByte); - assert(!envelopes_.empty()); - if (!envelopes_.back().EncodeStop(out_)) { - HandleError( - Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size())); - return; - } - envelopes_.pop_back(); - } - - void HandleString8(span chars) override { - if (!status_->ok()) - return; - EncodeString8(chars, out_); - } - - void HandleString16(span chars) override { - if (!status_->ok()) - return; - EncodeFromUTF16(chars, out_); - } - - void HandleBinary(span bytes) override { - if (!status_->ok()) - return; - EncodeBinary(bytes, out_); - } - - void HandleDouble(double value) override { - if (!status_->ok()) - return; - EncodeDouble(value, out_); - } - - void HandleInt32(int32_t value) override { - if (!status_->ok()) - return; - EncodeInt32(value, out_); - } - - void HandleBool(bool value) override { - if (!status_->ok()) - return; - // See RFC 7049 Section 2.3, Table 2. - out_->push_back(value ? kEncodedTrue : kEncodedFalse); - } - - void HandleNull() override { - if (!status_->ok()) - return; - // See RFC 7049 Section 2.3, Table 2. - out_->push_back(kEncodedNull); - } - - void HandleError(Status error) override { - if (!status_->ok()) - return; - *status_ = error; - out_->clear(); - } - - private: - C* out_; - std::vector envelopes_; - Status* status_; -}; -} // namespace - -std::unique_ptr NewCBOREncoder( - std::vector* out, - Status* status) { - return std::unique_ptr( - new CBOREncoder>(out, status)); -} -std::unique_ptr NewCBOREncoder(std::string* out, - Status* status) { - return std::unique_ptr( - new CBOREncoder(out, status)); -} - -// ============================================================================= -// cbor::CBORTokenizer - for parsing individual CBOR items -// ============================================================================= - -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_; -} - -// The following accessor functions ::GetInt32, ::GetDouble, -// ::GetString8, ::GetString16WireRep, ::GetBinary, ::GetEnvelopeContents -// assume that a particular token was recognized in ::ReadNextToken. -// That's where all the error checking is done. By design, -// the accessors (assuming the token was recognized) never produce -// an error. - -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); -} - -span CBORTokenizer::GetEnvelopeContents() const { - assert(token_tag_ == CBORTokenTag::ENVELOPE); - auto length = static_cast(token_start_internal_value_); - return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length); -} - -// All error checking happens in ::ReadNextToken, so that the accessors -// can avoid having to carry an error return value. -// -// With respect to checking the encoded lengths of strings, arrays, etc: -// On the wire, CBOR uses 1,2,4, and 8 byte unsigned integers, so -// we initially read them as uint64_t, usually into token_start_internal_value_. -// -// However, since these containers have a representation on the machine, -// we need to do corresponding size computations on the input byte array, -// output span (e.g. the payload for a string), etc., and size_t is -// machine specific (in practice either 32 bit or 64 bit). -// -// Further, we must avoid overflowing size_t. Therefore, we use this -// kMaxValidLength constant to: -// - Reject values that are larger than the architecture specific -// max size_t (differs between 32 bit and 64 bit arch). -// - Reserve at least one bit so that we can check against overflows -// when adding lengths (array / string length / etc.); we do this by -// ensuring that the inputs to an addition are <= kMaxValidLength, -// and then checking whether the sum went past it. -// -// See also -// https://chromium.googlesource.com/chromium/src/+/master/docs/security/integer-semantics.md -static const uint64_t kMaxValidLength = - std::min(std::numeric_limits::max() >> 2, - std::numeric_limits::max()); - -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; - } - const size_t remaining_bytes = bytes_.size() - status_.pos; - 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 - const int8_t bytes_read = internals::ReadTokenStart( - bytes_.subspan(status_.pos + 1), &token_start_type_, - &token_start_internal_value_); - if (bytes_read < 0 || token_start_type_ != MajorType::BYTE_STRING || - token_start_internal_value_ > kMaxValidLength) { - SetError(Error::CBOR_INVALID_BINARY); - return; - } - const uint64_t token_byte_length = token_start_internal_value_ + - /* tag before token start: */ 1 + - /* token start: */ bytes_read; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_BINARY); - return; - } - SetToken(CBORTokenTag::BINARY, static_cast(token_byte_length)); - return; - } - case kInitialByteForDouble: { // DOUBLE - if (kEncodedDoubleSize > remaining_bytes) { - SetError(Error::CBOR_INVALID_DOUBLE); - return; - } - SetToken(CBORTokenTag::DOUBLE, kEncodedDoubleSize); - return; - } - case kInitialByteForEnvelope: { // ENVELOPE - if (kEncodedEnvelopeHeaderSize > remaining_bytes) { - 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)); - if (token_start_internal_value_ > kMaxValidLength) { - SetError(Error::CBOR_INVALID_ENVELOPE); - return; - } - uint64_t token_byte_length = - token_start_internal_value_ + kEncodedEnvelopeHeaderSize; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_ENVELOPE); - return; - } - SetToken(CBORTokenTag::ENVELOPE, static_cast(token_byte_length)); - return; - } - default: { - const int8_t token_start_length = internals::ReadTokenStart( - bytes_.subspan(status_.pos), &token_start_type_, - &token_start_internal_value_); - const bool success = token_start_length >= 0; - switch (token_start_type_) { - case MajorType::UNSIGNED: // INT32. - // INT32 is a signed int32 (int32 makes sense for the - // inspector_protocol, it's not a CBOR limitation), so we check - // against the signed max, so that the allowable values are - // 0, 1, 2, ... 2^31 - 1. - 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. - // INT32 is a signed int32 (int32 makes sense for the - // inspector_protocol, it's not a CBOR limitation); in CBOR, - // the negative values for INT32 are represented as NEGATIVE, - // that is, -1 INT32 is represented as 1 << 5 | 0 (major type 1, - // additional info value 0). So here, we compute the INT32 value - // and then check it against the INT32 min. - int64_t actual_value = - -static_cast(token_start_internal_value_) - 1; - if (!success || actual_value < std::numeric_limits::min()) { - SetError(Error::CBOR_INVALID_INT32); - return; - } - SetToken(CBORTokenTag::INT32, token_start_length); - return; - } - case MajorType::STRING: { // STRING8. - if (!success || token_start_internal_value_ > kMaxValidLength) { - SetError(Error::CBOR_INVALID_STRING8); - return; - } - uint64_t token_byte_length = - token_start_internal_value_ + token_start_length; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_STRING8); - return; - } - SetToken(CBORTokenTag::STRING8, - static_cast(token_byte_length)); - return; - } - case MajorType::BYTE_STRING: { // STRING16. - // Length must be divisible by 2 since UTF16 is 2 bytes per - // character, hence the &1 check. - if (!success || token_start_internal_value_ > kMaxValidLength || - token_start_internal_value_ & 1) { - SetError(Error::CBOR_INVALID_STRING16); - return; - } - uint64_t token_byte_length = - token_start_internal_value_ + token_start_length; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_STRING16); - return; - } - SetToken(CBORTokenTag::STRING16, - static_cast(token_byte_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, size_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; -} - -// ============================================================================= -// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages -// ============================================================================= - -namespace { -// When parsing CBOR, we limit recursion depth for objects and arrays -// to this constant. -static constexpr int kStackLimit = 300; - -// Below are three parsing routines for CBOR, which cover enough -// to roundtrip JSON messages. -bool ParseMap(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out); -bool ParseArray(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out); -bool ParseValue(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out); - -void ParseUTF16String(CBORTokenizer* tokenizer, StreamingParserHandler* out) { - std::vector value; - span rep = tokenizer->GetString16WireRep(); - for (size_t ii = 0; ii < rep.size(); ii += 2) - value.push_back((rep[ii + 1] << 8) | rep[ii]); - out->HandleString16(span(value.data(), value.size())); - tokenizer->Next(); -} - -bool ParseUTF8String(CBORTokenizer* tokenizer, StreamingParserHandler* out) { - assert(tokenizer->TokenTag() == CBORTokenTag::STRING8); - out->HandleString8(tokenizer->GetString8()); - tokenizer->Next(); - return true; -} - -bool ParseValue(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out) { - if (stack_depth > kStackLimit) { - out->HandleError( - Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos}); - return false; - } - // Skip past the envelope to get to what's inside. - if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE) - tokenizer->EnterEnvelope(); - switch (tokenizer->TokenTag()) { - case CBORTokenTag::ERROR_VALUE: - out->HandleError(tokenizer->Status()); - return false; - case CBORTokenTag::DONE: - out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, - tokenizer->Status().pos}); - return false; - case CBORTokenTag::TRUE_VALUE: - out->HandleBool(true); - tokenizer->Next(); - return true; - case CBORTokenTag::FALSE_VALUE: - out->HandleBool(false); - tokenizer->Next(); - return true; - case CBORTokenTag::NULL_VALUE: - out->HandleNull(); - tokenizer->Next(); - return true; - case CBORTokenTag::INT32: - out->HandleInt32(tokenizer->GetInt32()); - tokenizer->Next(); - return true; - case CBORTokenTag::DOUBLE: - out->HandleDouble(tokenizer->GetDouble()); - tokenizer->Next(); - return true; - case CBORTokenTag::STRING8: - return ParseUTF8String(tokenizer, out); - case CBORTokenTag::STRING16: - ParseUTF16String(tokenizer, out); - return true; - case CBORTokenTag::BINARY: { - out->HandleBinary(tokenizer->GetBinary()); - tokenizer->Next(); - return true; - } - case CBORTokenTag::MAP_START: - return ParseMap(stack_depth + 1, tokenizer, out); - case CBORTokenTag::ARRAY_START: - return ParseArray(stack_depth + 1, tokenizer, out); - default: - out->HandleError( - Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos}); - return false; - } -} - -// |bytes| must start with the indefinite length array byte, so basically, -// ParseArray may only be called after an indefinite length array has been -// detected. -bool ParseArray(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out) { - assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START); - tokenizer->Next(); - out->HandleArrayBegin(); - while (tokenizer->TokenTag() != CBORTokenTag::STOP) { - if (tokenizer->TokenTag() == CBORTokenTag::DONE) { - out->HandleError( - Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos}); - return false; - } - if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer->Status()); - return false; - } - // Parse value. - if (!ParseValue(stack_depth, tokenizer, out)) - return false; - } - out->HandleArrayEnd(); - tokenizer->Next(); - return true; -} - -// |bytes| must start with the indefinite length array byte, so basically, -// ParseArray may only be called after an indefinite length array has been -// detected. -bool ParseMap(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out) { - assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START); - out->HandleMapBegin(); - tokenizer->Next(); - while (tokenizer->TokenTag() != CBORTokenTag::STOP) { - if (tokenizer->TokenTag() == CBORTokenTag::DONE) { - out->HandleError( - Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos}); - return false; - } - if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer->Status()); - return false; - } - // Parse key. - if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { - if (!ParseUTF8String(tokenizer, out)) - return false; - } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { - ParseUTF16String(tokenizer, out); - } else { - out->HandleError( - Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos}); - return false; - } - // Parse value. - if (!ParseValue(stack_depth, tokenizer, out)) - return false; - } - out->HandleMapEnd(); - tokenizer->Next(); - return true; -} -} // namespace - -void ParseCBOR(span bytes, StreamingParserHandler* out) { - if (bytes.empty()) { - out->HandleError(Status{Error::CBOR_NO_INPUT, 0}); - return; - } - if (bytes[0] != kInitialByteForEnvelope) { - out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0}); - return; - } - CBORTokenizer tokenizer(bytes); - if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer.Status()); - return; - } - // We checked for the envelope start byte above, so the tokenizer - // must agree here, since it's not an error. - assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE); - tokenizer.EnterEnvelope(); - if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) { - out->HandleError( - Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos}); - return; - } - if (!ParseMap(/*stack_depth=*/1, &tokenizer, out)) - return; - if (tokenizer.TokenTag() == CBORTokenTag::DONE) - return; - if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer.Status()); - return; - } - out->HandleError(Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos}); -} - -// ============================================================================= -// cbor::AppendString8EntryToMap - for limited in-place editing of messages -// ============================================================================= - -template -Status AppendString8EntryToCBORMapTmpl(span string8_key, - span string8_value, - C* cbor) { - // Careful below: Don't compare (*cbor)[idx] with a uint8_t, since - // it could be a char (signed!). Instead, use bytes. - span bytes(reinterpret_cast(cbor->data()), - cbor->size()); - CBORTokenizer tokenizer(bytes); - if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) - return tokenizer.Status(); - if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE) - return Status(Error::CBOR_INVALID_ENVELOPE, 0); - size_t envelope_size = tokenizer.GetEnvelopeContents().size(); - size_t old_size = cbor->size(); - if (old_size != envelope_size + kEncodedEnvelopeHeaderSize) - return Status(Error::CBOR_INVALID_ENVELOPE, 0); - if (envelope_size == 0 || - (tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart())) - return Status(Error::CBOR_MAP_START_EXPECTED, kEncodedEnvelopeHeaderSize); - if (bytes[bytes.size() - 1] != EncodeStop()) - return Status(Error::CBOR_MAP_STOP_EXPECTED, cbor->size() - 1); - cbor->pop_back(); - EncodeString8(string8_key, cbor); - EncodeString8(string8_value, cbor); - cbor->push_back(EncodeStop()); - size_t new_envelope_size = envelope_size + (cbor->size() - old_size); - if (new_envelope_size > std::numeric_limits::max()) - return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0); - size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t); - uint8_t* out = reinterpret_cast(&cbor->at(size_pos)); - *(out++) = (new_envelope_size >> 24) & 0xff; - *(out++) = (new_envelope_size >> 16) & 0xff; - *(out++) = (new_envelope_size >> 8) & 0xff; - *(out) = new_envelope_size & 0xff; - return Status(); -} -Status AppendString8EntryToCBORMap(span string8_key, - span string8_value, - std::vector* cbor) { - return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor); -} -Status AppendString8EntryToCBORMap(span string8_key, - span string8_value, - std::string* cbor) { - return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor); -} -} // namespace cbor - -namespace json { - -// ============================================================================= -// json::NewJSONEncoder - for encoding streaming parser events as JSON -// ============================================================================= - -namespace { -// Prints |value| to |out| with 4 hex digits, most significant chunk first. -template -void PrintHex(uint16_t value, C* out) { - for (int ii = 3; ii >= 0; --ii) { - int four_bits = 0xf & (value >> (4 * ii)); - out->push_back(four_bits + ((four_bits <= 9) ? '0' : ('a' - 10))); - } -} - -// In the writer below, we maintain a stack of State instances. -// It is just enough to emit the appropriate delimiters and brackets -// in JSON. -enum class Container { - // Used for the top-level, initial state. - NONE, - // Inside a JSON object. - MAP, - // Inside a JSON array. - ARRAY -}; -class State { - public: - explicit State(Container container) : container_(container) {} - void StartElement(std::vector* out) { StartElementTmpl(out); } - void StartElement(std::string* out) { StartElementTmpl(out); } - Container container() const { return container_; } - - private: - template - void StartElementTmpl(C* out) { - assert(container_ != Container::NONE || size_ == 0); - if (size_ != 0) { - char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':'; - out->push_back(delim); - } - ++size_; - } - - Container container_ = Container::NONE; - int size_ = 0; -}; - -constexpr char kBase64Table[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz0123456789+/"; - -template -void Base64Encode(const span& in, C* out) { - // The following three cases are based on the tables in the example - // section in https://en.wikipedia.org/wiki/Base64. We process three - // input bytes at a time, emitting 4 output bytes at a time. - size_t ii = 0; - - // While possible, process three input bytes. - for (; ii + 3 <= in.size(); ii += 3) { - uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8) | in[ii + 2]; - out->push_back(kBase64Table[(twentyfour_bits >> 18)]); - out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); - out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]); - out->push_back(kBase64Table[twentyfour_bits & 0x3f]); - } - if (ii + 2 <= in.size()) { // Process two input bytes. - uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8); - out->push_back(kBase64Table[(twentyfour_bits >> 18)]); - out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); - out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]); - out->push_back('='); // Emit padding. - return; - } - if (ii + 1 <= in.size()) { // Process a single input byte. - uint32_t twentyfour_bits = (in[ii] << 16); - out->push_back(kBase64Table[(twentyfour_bits >> 18)]); - out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); - out->push_back('='); // Emit padding. - out->push_back('='); // Emit padding. - } -} - -// Implements a handler for JSON parser events to emit a JSON string. -template -class JSONEncoder : public StreamingParserHandler { - public: - JSONEncoder(const Platform* platform, C* out, Status* status) - : platform_(platform), out_(out), status_(status) { - *status_ = Status(); - state_.emplace(Container::NONE); - } - - void HandleMapBegin() override { - if (!status_->ok()) - return; - assert(!state_.empty()); - state_.top().StartElement(out_); - state_.emplace(Container::MAP); - Emit('{'); - } - - void HandleMapEnd() override { - if (!status_->ok()) - return; - assert(state_.size() >= 2 && state_.top().container() == Container::MAP); - state_.pop(); - Emit('}'); - } - - void HandleArrayBegin() override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - state_.emplace(Container::ARRAY); - Emit('['); - } - - void HandleArrayEnd() override { - if (!status_->ok()) - return; - assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY); - state_.pop(); - Emit(']'); - } - - void HandleString16(span chars) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit('"'); - for (const uint16_t ch : chars) { - if (ch == '"') { - Emit("\\\""); - } else if (ch == '\\') { - Emit("\\\\"); - } else if (ch == '\b') { - Emit("\\b"); - } else if (ch == '\f') { - Emit("\\f"); - } else if (ch == '\n') { - Emit("\\n"); - } else if (ch == '\r') { - Emit("\\r"); - } else if (ch == '\t') { - Emit("\\t"); - } else if (ch >= 32 && ch <= 126) { - Emit(ch); - } else { - Emit("\\u"); - PrintHex(ch, out_); - } - } - Emit('"'); - } - - void HandleString8(span chars) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit('"'); - for (size_t ii = 0; ii < chars.size(); ++ii) { - uint8_t c = chars[ii]; - if (c == '"') { - Emit("\\\""); - } else if (c == '\\') { - Emit("\\\\"); - } else if (c == '\b') { - Emit("\\b"); - } else if (c == '\f') { - Emit("\\f"); - } else if (c == '\n') { - Emit("\\n"); - } else if (c == '\r') { - Emit("\\r"); - } else if (c == '\t') { - Emit("\\t"); - } else if (c >= 32 && c <= 126) { - Emit(c); - } else if (c < 32) { - Emit("\\u"); - PrintHex(static_cast(c), out_); - } else { - // Inspect the leading byte to figure out how long the utf8 - // byte sequence is; while doing this initialize |codepoint| - // with the first few bits. - // See table in: https://en.wikipedia.org/wiki/UTF-8 - // byte one is 110x xxxx -> 2 byte utf8 sequence - // byte one is 1110 xxxx -> 3 byte utf8 sequence - // byte one is 1111 0xxx -> 4 byte utf8 sequence - uint32_t codepoint; - int num_bytes_left; - if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence - num_bytes_left = 1; - codepoint = c & 0x1f; - } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence - num_bytes_left = 2; - codepoint = c & 0x0f; - } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence - codepoint = c & 0x07; - num_bytes_left = 3; - } else { - continue; // invalid leading byte - } - - // If we have enough bytes in our input, decode the remaining ones - // belonging to this Unicode character into |codepoint|. - if (ii + num_bytes_left > chars.size()) - continue; - while (num_bytes_left > 0) { - c = chars[++ii]; - --num_bytes_left; - // Check the next byte is a continuation byte, that is 10xx xxxx. - if ((c & 0xc0) != 0x80) - continue; - codepoint = (codepoint << 6) | (c & 0x3f); - } - - // Disallow overlong encodings for ascii characters, as these - // would include " and other characters significant to JSON - // string termination / control. - if (codepoint < 0x7f) - continue; - // Invalid in UTF8, and can't be represented in UTF16 anyway. - if (codepoint > 0x10ffff) - continue; - - // So, now we transcode to UTF16, - // using the math described at https://en.wikipedia.org/wiki/UTF-16, - // for either one or two 16 bit characters. - if (codepoint < 0xffff) { - Emit("\\u"); - PrintHex(static_cast(codepoint), out_); - continue; - } - codepoint -= 0x10000; - // high surrogate - Emit("\\u"); - PrintHex(static_cast((codepoint >> 10) + 0xd800), out_); - // low surrogate - Emit("\\u"); - PrintHex(static_cast((codepoint & 0x3ff) + 0xdc00), out_); - } - } - Emit('"'); - } - - void HandleBinary(span bytes) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit('"'); - Base64Encode(bytes, out_); - Emit('"'); - } - - void HandleDouble(double value) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - // JSON cannot represent NaN or Infinity. So, for compatibility, - // we behave like the JSON object in web browsers: emit 'null'. - if (!std::isfinite(value)) { - Emit("null"); - return; - } - std::unique_ptr str_value = platform_->DToStr(value); - - // DToStr may fail to emit a 0 before the decimal dot. E.g. this is - // the case in base::NumberToString in Chromium (which is based on - // dmg_fp). So, much like - // https://cs.chromium.org/chromium/src/base/json/json_writer.cc - // we probe for this and emit the leading 0 anyway if necessary. - const char* chars = str_value.get(); - if (chars[0] == '.') { - Emit('0'); - } else if (chars[0] == '-' && chars[1] == '.') { - Emit("-0"); - ++chars; - } - Emit(chars); - } - - void HandleInt32(int32_t value) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit(std::to_string(value)); - } - - void HandleBool(bool value) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit(value ? "true" : "false"); - } - - void HandleNull() override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit("null"); - } - - void HandleError(Status error) override { - assert(!error.ok()); - *status_ = error; - out_->clear(); - } - - private: - void Emit(char c) { out_->push_back(c); } - void Emit(const char* str) { - out_->insert(out_->end(), str, str + strlen(str)); - } - void Emit(const std::string& str) { - out_->insert(out_->end(), str.begin(), str.end()); - } - - const Platform* platform_; - C* out_; - Status* status_; - std::stack state_; -}; -} // namespace - -std::unique_ptr NewJSONEncoder( - const Platform* platform, - std::vector* out, - Status* status) { - return std::unique_ptr( - new JSONEncoder>(platform, out, status)); -} -std::unique_ptr NewJSONEncoder(const Platform* platform, - std::string* out, - Status* status) { - return std::unique_ptr( - new JSONEncoder(platform, out, status)); -} - -// ============================================================================= -// json::ParseJSON - for receiving streaming parser events for JSON. -// ============================================================================= - -namespace { -const int kStackLimit = 300; - -enum Token { - ObjectBegin, - ObjectEnd, - ArrayBegin, - ArrayEnd, - StringLiteral, - Number, - BoolTrue, - BoolFalse, - NullToken, - ListSeparator, - ObjectPairSeparator, - InvalidToken, - NoInput -}; - -const char* const kNullString = "null"; -const char* const kTrueString = "true"; -const char* const kFalseString = "false"; - -template -class JsonParser { - public: - JsonParser(const Platform* platform, StreamingParserHandler* handler) - : platform_(platform), handler_(handler) {} - - void Parse(const Char* start, size_t length) { - start_pos_ = start; - const Char* end = start + length; - const Char* tokenEnd = nullptr; - ParseValue(start, end, &tokenEnd, 0); - if (error_) - return; - if (tokenEnd != end) { - HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd); - } - } - - private: - bool CharsToDouble(const uint16_t* chars, size_t length, double* result) { - std::string buffer; - buffer.reserve(length + 1); - for (size_t ii = 0; ii < length; ++ii) { - bool is_ascii = !(chars[ii] & ~0x7F); - if (!is_ascii) - return false; - buffer.push_back(static_cast(chars[ii])); - } - return platform_->StrToD(buffer.c_str(), result); - } - - bool CharsToDouble(const uint8_t* chars, size_t length, double* result) { - std::string buffer(reinterpret_cast(chars), length); - return platform_->StrToD(buffer.c_str(), result); - } - - static bool ParseConstToken(const Char* start, - const Char* end, - const Char** token_end, - const char* token) { - // |token| is \0 terminated, it's one of the constants at top of the file. - while (start < end && *token != '\0' && *start++ == *token++) { - } - if (*token != '\0') - return false; - *token_end = start; - return true; - } - - static bool ReadInt(const Char* start, - const Char* end, - const Char** token_end, - bool allow_leading_zeros) { - if (start == end) - return false; - bool has_leading_zero = '0' == *start; - int length = 0; - while (start < end && '0' <= *start && *start <= '9') { - ++start; - ++length; - } - if (!length) - return false; - if (!allow_leading_zeros && length > 1 && has_leading_zero) - return false; - *token_end = start; - return true; - } - - static bool ParseNumberToken(const Char* start, - const Char* end, - const Char** token_end) { - // We just grab the number here. We validate the size in DecodeNumber. - // According to RFC4627, a valid number is: [minus] int [frac] [exp] - if (start == end) - return false; - Char c = *start; - if ('-' == c) - ++start; - - if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/false)) - return false; - if (start == end) { - *token_end = start; - return true; - } - - // Optional fraction part - c = *start; - if ('.' == c) { - ++start; - if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true)) - return false; - if (start == end) { - *token_end = start; - return true; - } - c = *start; - } - - // Optional exponent part - if ('e' == c || 'E' == c) { - ++start; - if (start == end) - return false; - c = *start; - if ('-' == c || '+' == c) { - ++start; - if (start == end) - return false; - } - if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true)) - return false; - } - - *token_end = start; - return true; - } - - static bool ReadHexDigits(const Char* start, - const Char* end, - const Char** token_end, - int digits) { - if (end - start < digits) - return false; - for (int i = 0; i < digits; ++i) { - Char c = *start++; - if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || - ('A' <= c && c <= 'F'))) - return false; - } - *token_end = start; - return true; - } - - static bool ParseStringToken(const Char* start, - const Char* end, - const Char** token_end) { - while (start < end) { - Char c = *start++; - if ('\\' == c) { - if (start == end) - return false; - c = *start++; - // Make sure the escaped char is valid. - switch (c) { - case 'x': - if (!ReadHexDigits(start, end, &start, 2)) - return false; - break; - case 'u': - if (!ReadHexDigits(start, end, &start, 4)) - return false; - break; - case '\\': - case '/': - case 'b': - case 'f': - case 'n': - case 'r': - case 't': - case 'v': - case '"': - break; - default: - return false; - } - } else if ('"' == c) { - *token_end = start; - return true; - } - } - return false; - } - - static bool SkipComment(const Char* start, - const Char* end, - const Char** comment_end) { - if (start == end) - return false; - - if (*start != '/' || start + 1 >= end) - return false; - ++start; - - if (*start == '/') { - // Single line comment, read to newline. - for (++start; start < end; ++start) { - if (*start == '\n' || *start == '\r') { - *comment_end = start + 1; - return true; - } - } - *comment_end = end; - // Comment reaches end-of-input, which is fine. - return true; - } - - if (*start == '*') { - Char previous = '\0'; - // Block comment, read until end marker. - for (++start; start < end; previous = *start++) { - if (previous == '*' && *start == '/') { - *comment_end = start + 1; - return true; - } - } - // Block comment must close before end-of-input. - return false; - } - - return false; - } - - static bool IsSpaceOrNewLine(Char c) { - // \v = vertial tab; \f = form feed page break. - return c == ' ' || c == '\n' || c == '\v' || c == '\f' || c == '\r' || - c == '\t'; - } - - static void SkipWhitespaceAndComments(const Char* start, - const Char* end, - const Char** whitespace_end) { - while (start < end) { - if (IsSpaceOrNewLine(*start)) { - ++start; - } else if (*start == '/') { - const Char* comment_end = nullptr; - if (!SkipComment(start, end, &comment_end)) - break; - start = comment_end; - } else { - break; - } - } - *whitespace_end = start; - } - - static Token ParseToken(const Char* start, - const Char* end, - const Char** tokenStart, - const Char** token_end) { - SkipWhitespaceAndComments(start, end, tokenStart); - start = *tokenStart; - - if (start == end) - return NoInput; - - switch (*start) { - case 'n': - if (ParseConstToken(start, end, token_end, kNullString)) - return NullToken; - break; - case 't': - if (ParseConstToken(start, end, token_end, kTrueString)) - return BoolTrue; - break; - case 'f': - if (ParseConstToken(start, end, token_end, kFalseString)) - return BoolFalse; - break; - case '[': - *token_end = start + 1; - return ArrayBegin; - case ']': - *token_end = start + 1; - return ArrayEnd; - case ',': - *token_end = start + 1; - return ListSeparator; - case '{': - *token_end = start + 1; - return ObjectBegin; - case '}': - *token_end = start + 1; - return ObjectEnd; - case ':': - *token_end = start + 1; - return ObjectPairSeparator; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - if (ParseNumberToken(start, end, token_end)) - return Number; - break; - case '"': - if (ParseStringToken(start + 1, end, token_end)) - return StringLiteral; - break; - } - return InvalidToken; - } - - static int HexToInt(Char c) { - if ('0' <= c && c <= '9') - return c - '0'; - if ('A' <= c && c <= 'F') - return c - 'A' + 10; - if ('a' <= c && c <= 'f') - return c - 'a' + 10; - assert(false); // Unreachable. - return 0; - } - - static bool DecodeString(const Char* start, - const Char* end, - std::vector* output) { - if (start == end) - return true; - if (start > end) - return false; - output->reserve(end - start); - while (start < end) { - uint16_t c = *start++; - // If the |Char| we're dealing with is really a byte, then - // we have utf8 here, and we need to check for multibyte characters - // and transcode them to utf16 (either one or two utf16 chars). - if (sizeof(Char) == sizeof(uint8_t) && c >= 0x7f) { - // Inspect the leading byte to figure out how long the utf8 - // byte sequence is; while doing this initialize |codepoint| - // with the first few bits. - // See table in: https://en.wikipedia.org/wiki/UTF-8 - // byte one is 110x xxxx -> 2 byte utf8 sequence - // byte one is 1110 xxxx -> 3 byte utf8 sequence - // byte one is 1111 0xxx -> 4 byte utf8 sequence - uint32_t codepoint; - int num_bytes_left; - if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence - num_bytes_left = 1; - codepoint = c & 0x1f; - } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence - num_bytes_left = 2; - codepoint = c & 0x0f; - } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence - codepoint = c & 0x07; - num_bytes_left = 3; - } else { - return false; // invalid leading byte - } - - // If we have enough bytes in our inpput, decode the remaining ones - // belonging to this Unicode character into |codepoint|. - if (start + num_bytes_left > end) - return false; - while (num_bytes_left > 0) { - c = *start++; - --num_bytes_left; - // Check the next byte is a continuation byte, that is 10xx xxxx. - if ((c & 0xc0) != 0x80) - return false; - codepoint = (codepoint << 6) | (c & 0x3f); - } - - // Disallow overlong encodings for ascii characters, as these - // would include " and other characters significant to JSON - // string termination / control. - if (codepoint < 0x7f) - return false; - // Invalid in UTF8, and can't be represented in UTF16 anyway. - if (codepoint > 0x10ffff) - return false; - - // So, now we transcode to UTF16, - // using the math described at https://en.wikipedia.org/wiki/UTF-16, - // for either one or two 16 bit characters. - if (codepoint < 0xffff) { - output->push_back(codepoint); - continue; - } - codepoint -= 0x10000; - output->push_back((codepoint >> 10) + 0xd800); // high surrogate - output->push_back((codepoint & 0x3ff) + 0xdc00); // low surrogate - continue; - } - if ('\\' != c) { - output->push_back(c); - continue; - } - if (start == end) - return false; - c = *start++; - - if (c == 'x') { - // \x is not supported. - return false; - } - - switch (c) { - case '"': - case '/': - case '\\': - break; - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case 'v': - c = '\v'; - break; - case 'u': - c = (HexToInt(*start) << 12) + (HexToInt(*(start + 1)) << 8) + - (HexToInt(*(start + 2)) << 4) + HexToInt(*(start + 3)); - start += 4; - break; - default: - return false; - } - output->push_back(c); - } - return true; - } - - void ParseValue(const Char* start, - const Char* end, - const Char** value_token_end, - int depth) { - if (depth > kStackLimit) { - HandleError(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, start); - return; - } - const Char* token_start = nullptr; - const Char* token_end = nullptr; - Token token = ParseToken(start, end, &token_start, &token_end); - switch (token) { - case NoInput: - HandleError(Error::JSON_PARSER_NO_INPUT, token_start); - return; - case InvalidToken: - HandleError(Error::JSON_PARSER_INVALID_TOKEN, token_start); - return; - case NullToken: - handler_->HandleNull(); - break; - case BoolTrue: - handler_->HandleBool(true); - break; - case BoolFalse: - handler_->HandleBool(false); - break; - case Number: { - double value; - if (!CharsToDouble(token_start, token_end - token_start, &value)) { - HandleError(Error::JSON_PARSER_INVALID_NUMBER, token_start); - return; - } - if (value >= std::numeric_limits::min() && - value <= std::numeric_limits::max() && - static_cast(value) == value) - handler_->HandleInt32(static_cast(value)); - else - handler_->HandleDouble(value); - break; - } - case StringLiteral: { - std::vector value; - bool ok = DecodeString(token_start + 1, token_end - 1, &value); - if (!ok) { - HandleError(Error::JSON_PARSER_INVALID_STRING, token_start); - return; - } - handler_->HandleString16(span(value.data(), value.size())); - break; - } - case ArrayBegin: { - handler_->HandleArrayBegin(); - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - while (token != ArrayEnd) { - ParseValue(start, end, &token_end, depth + 1); - if (error_) - return; - - // After a list value, we expect a comma or the end of the list. - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - if (token == ListSeparator) { - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - if (token == ArrayEnd) { - HandleError(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, token_start); - return; - } - } else if (token != ArrayEnd) { - // Unexpected value after list value. Bail out. - HandleError(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED, - token_start); - return; - } - } - handler_->HandleArrayEnd(); - break; - } - case ObjectBegin: { - handler_->HandleMapBegin(); - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - while (token != ObjectEnd) { - if (token != StringLiteral) { - HandleError(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, - token_start); - return; - } - std::vector key; - if (!DecodeString(token_start + 1, token_end - 1, &key)) { - HandleError(Error::JSON_PARSER_INVALID_STRING, token_start); - return; - } - handler_->HandleString16(span(key.data(), key.size())); - start = token_end; - - token = ParseToken(start, end, &token_start, &token_end); - if (token != ObjectPairSeparator) { - HandleError(Error::JSON_PARSER_COLON_EXPECTED, token_start); - return; - } - start = token_end; - - ParseValue(start, end, &token_end, depth + 1); - if (error_) - return; - start = token_end; - - // After a key/value pair, we expect a comma or the end of the - // object. - token = ParseToken(start, end, &token_start, &token_end); - if (token == ListSeparator) { - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - if (token == ObjectEnd) { - HandleError(Error::JSON_PARSER_UNEXPECTED_MAP_END, token_start); - return; - } - } else if (token != ObjectEnd) { - // Unexpected value after last object value. Bail out. - HandleError(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, - token_start); - return; - } - } - handler_->HandleMapEnd(); - break; - } - - default: - // We got a token that's not a value. - HandleError(Error::JSON_PARSER_VALUE_EXPECTED, token_start); - return; - } - - SkipWhitespaceAndComments(token_end, end, value_token_end); - } - - void HandleError(Error error, const Char* pos) { - assert(error != Error::OK); - if (!error_) { - handler_->HandleError( - Status{error, static_cast(pos - start_pos_)}); - error_ = true; - } - } - - const Char* start_pos_ = nullptr; - bool error_ = false; - const Platform* platform_; - StreamingParserHandler* handler_; -}; -} // namespace - -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler) { - JsonParser parser(&platform, handler); - parser.Parse(chars.data(), chars.size()); -} - -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler) { - JsonParser parser(&platform, handler); - parser.Parse(chars.data(), chars.size()); -} - -// ============================================================================= -// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding -// ============================================================================= -template -Status ConvertCBORToJSONTmpl(const Platform& platform, - span cbor, - C* json) { - Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&platform, json, &status); - cbor::ParseCBOR(cbor, json_writer.get()); - return status; -} - -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::vector* json) { - return ConvertCBORToJSONTmpl(platform, cbor, json); -} -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::string* json) { - return ConvertCBORToJSONTmpl(platform, cbor, json); -} - -template -Status ConvertJSONToCBORTmpl(const Platform& platform, span json, C* cbor) { - Status status; - std::unique_ptr encoder = - cbor::NewCBOREncoder(cbor, &status); - ParseJSON(platform, json, encoder.get()); - return status; -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -} // namespace json -} // namespace v8_inspector_protocol_encoding diff --git a/tools/inspector_protocol/inspector_protocol.gni b/tools/inspector_protocol/inspector_protocol.gni index d612fb6aebb52c..2d241d6546ec72 100644 --- a/tools/inspector_protocol/inspector_protocol.gni +++ b/tools/inspector_protocol/inspector_protocol.gni @@ -33,22 +33,11 @@ template("inspector_protocol_generate") { invoker.config_file, "$inspector_protocol_dir/lib/base_string_adapter_cc.template", "$inspector_protocol_dir/lib/base_string_adapter_h.template", - "$inspector_protocol_dir/lib/encoding_h.template", - "$inspector_protocol_dir/lib/encoding_cpp.template", - "$inspector_protocol_dir/lib/Allocator_h.template", - "$inspector_protocol_dir/lib/Array_h.template", - "$inspector_protocol_dir/lib/DispatcherBase_cpp.template", - "$inspector_protocol_dir/lib/DispatcherBase_h.template", - "$inspector_protocol_dir/lib/ErrorSupport_cpp.template", - "$inspector_protocol_dir/lib/ErrorSupport_h.template", "$inspector_protocol_dir/lib/Forward_h.template", - "$inspector_protocol_dir/lib/FrontendChannel_h.template", - "$inspector_protocol_dir/lib/Maybe_h.template", "$inspector_protocol_dir/lib/Object_cpp.template", "$inspector_protocol_dir/lib/Object_h.template", - "$inspector_protocol_dir/lib/Parser_cpp.template", - "$inspector_protocol_dir/lib/Parser_h.template", "$inspector_protocol_dir/lib/Protocol_cpp.template", + "$inspector_protocol_dir/lib/ValueConversions_cpp.template", "$inspector_protocol_dir/lib/ValueConversions_h.template", "$inspector_protocol_dir/lib/Values_cpp.template", "$inspector_protocol_dir/lib/Values_h.template", @@ -63,11 +52,15 @@ template("inspector_protocol_generate") { args = [ "--jinja_dir", - rebase_path("//third_party/", root_build_dir), # jinja is in chromium's third_party + rebase_path("//third_party/", root_build_dir), # jinja is in chromium's + # third_party + "--output_base", rebase_path(invoker.out_dir, root_build_dir), "--config", rebase_path(invoker.config_file, root_build_dir), + "--inspector_protocol_dir", + "$inspector_protocol_dir", ] if (defined(invoker.config_values)) { diff --git a/tools/inspector_protocol/inspector_protocol.gypi b/tools/inspector_protocol/inspector_protocol.gypi deleted file mode 100644 index d614474e69c32e..00000000000000 --- a/tools/inspector_protocol/inspector_protocol.gypi +++ /dev/null @@ -1,34 +0,0 @@ -# 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. - -{ - 'variables': { - 'inspector_protocol_files': [ - 'lib/encoding_h.template', - 'lib/encoding_cpp.template', - 'lib/Allocator_h.template', - 'lib/Array_h.template', - 'lib/DispatcherBase_cpp.template', - 'lib/DispatcherBase_h.template', - 'lib/ErrorSupport_cpp.template', - 'lib/ErrorSupport_h.template', - 'lib/Forward_h.template', - 'lib/FrontendChannel_h.template', - 'lib/Maybe_h.template', - 'lib/Object_cpp.template', - 'lib/Object_h.template', - 'lib/Parser_cpp.template', - 'lib/Parser_h.template', - 'lib/Protocol_cpp.template', - 'lib/ValueConversions_h.template', - 'lib/Values_cpp.template', - 'lib/Values_h.template', - 'templates/Exported_h.template', - 'templates/Imported_h.template', - 'templates/TypeBuilder_cpp.template', - 'templates/TypeBuilder_h.template', - 'code_generator.py', - ] - } -} diff --git a/tools/inspector_protocol/lib/Allocator_h.template b/tools/inspector_protocol/lib/Allocator_h.template deleted file mode 100644 index 15eaaaff0236d2..00000000000000 --- a/tools/inspector_protocol/lib/Allocator_h.template +++ /dev/null @@ -1,25 +0,0 @@ -// 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. - -#ifndef {{"_".join(config.protocol.namespace)}}_Allocator_h -#define {{"_".join(config.protocol.namespace)}}_Allocator_h - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -enum NotNullTagEnum { NotNullLiteral }; - -#define PROTOCOL_DISALLOW_COPY(ClassName) \ - private: \ - ClassName(const ClassName&) = delete; \ - ClassName& operator=(const ClassName&) = delete - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#endif // !defined({{"_".join(config.protocol.namespace)}}_Allocator_h) diff --git a/tools/inspector_protocol/lib/Array_h.template b/tools/inspector_protocol/lib/Array_h.template deleted file mode 100644 index c420a0f7e9650a..00000000000000 --- a/tools/inspector_protocol/lib/Array_h.template +++ /dev/null @@ -1,138 +0,0 @@ -// 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. - -#ifndef {{"_".join(config.protocol.namespace)}}_Array_h -#define {{"_".join(config.protocol.namespace)}}_Array_h - -//#include "ErrorSupport.h" -//#include "Forward.h" -//#include "ValueConversions.h" -//#include "Values.h" - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -template -class Array { -public: - static std::unique_ptr> create() - { - return std::unique_ptr>(new Array()); - } - - static std::unique_ptr> fromValue(protocol::Value* value, ErrorSupport* errors) - { - protocol::ListValue* array = ListValue::cast(value); - if (!array) { - errors->addError("array expected"); - return nullptr; - } - std::unique_ptr> result(new Array()); - errors->push(); - for (size_t i = 0; i < array->size(); ++i) { - errors->setName(StringUtil::fromInteger(i)); - std::unique_ptr item = ValueConversions::fromValue(array->at(i), errors); - result->m_vector.push_back(std::move(item)); - } - errors->pop(); - if (errors->hasErrors()) - return nullptr; - return result; - } - - void addItem(std::unique_ptr value) - { - m_vector.push_back(std::move(value)); - } - - size_t length() - { - return m_vector.size(); - } - - T* get(size_t index) - { - return m_vector[index].get(); - } - - std::unique_ptr toValue() - { - std::unique_ptr result = ListValue::create(); - for (auto& item : m_vector) - result->pushValue(ValueConversions::toValue(item)); - return result; - } - -private: - std::vector> m_vector; -}; - -template -class ArrayBase { -public: - static std::unique_ptr> create() - { - return std::unique_ptr>(new Array()); - } - - static std::unique_ptr> fromValue(protocol::Value* value, ErrorSupport* errors) - { - protocol::ListValue* array = ListValue::cast(value); - if (!array) { - errors->addError("array expected"); - return nullptr; - } - errors->push(); - std::unique_ptr> result(new Array()); - for (size_t i = 0; i < array->size(); ++i) { - errors->setName(StringUtil::fromInteger(i)); - T item = ValueConversions::fromValue(array->at(i), errors); - result->m_vector.push_back(item); - } - errors->pop(); - if (errors->hasErrors()) - return nullptr; - return result; - } - - void addItem(const T& value) - { - m_vector.push_back(value); - } - - size_t length() - { - return m_vector.size(); - } - - T get(size_t index) - { - return m_vector[index]; - } - - std::unique_ptr toValue() - { - std::unique_ptr result = ListValue::create(); - for (auto& item : m_vector) - result->pushValue(ValueConversions::toValue(item)); - return result; - } - -private: - std::vector m_vector; -}; - -template<> class Array : public ArrayBase {}; -template<> class Array : public ArrayBase {}; -template<> class Array : public ArrayBase {}; -template<> class Array : public ArrayBase {}; - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#endif // !defined({{"_".join(config.protocol.namespace)}}_Array_h) diff --git a/tools/inspector_protocol/lib/DispatcherBase_cpp.template b/tools/inspector_protocol/lib/DispatcherBase_cpp.template deleted file mode 100644 index 11843f433007fc..00000000000000 --- a/tools/inspector_protocol/lib/DispatcherBase_cpp.template +++ /dev/null @@ -1,353 +0,0 @@ -// 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. - -//#include "DispatcherBase.h" -//#include "Parser.h" - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -// static -DispatchResponse DispatchResponse::OK() -{ - DispatchResponse result; - result.m_status = kSuccess; - result.m_errorCode = kParseError; - return result; -} - -// static -DispatchResponse DispatchResponse::Error(const String& error) -{ - DispatchResponse result; - result.m_status = kError; - result.m_errorCode = kServerError; - result.m_errorMessage = error; - return result; -} - -// static -DispatchResponse DispatchResponse::InternalError() -{ - DispatchResponse result; - result.m_status = kError; - result.m_errorCode = kInternalError; - result.m_errorMessage = "Internal error"; - return result; -} - -// static -DispatchResponse DispatchResponse::InvalidParams(const String& error) -{ - DispatchResponse result; - result.m_status = kError; - result.m_errorCode = kInvalidParams; - result.m_errorMessage = error; - return result; -} - -// static -DispatchResponse DispatchResponse::FallThrough() -{ - DispatchResponse result; - result.m_status = kFallThrough; - result.m_errorCode = kParseError; - return result; -} - -// static -const char DispatcherBase::kInvalidParamsString[] = "Invalid parameters"; - -DispatcherBase::WeakPtr::WeakPtr(DispatcherBase* dispatcher) : m_dispatcher(dispatcher) { } - -DispatcherBase::WeakPtr::~WeakPtr() -{ - if (m_dispatcher) - m_dispatcher->m_weakPtrs.erase(this); -} - -DispatcherBase::Callback::Callback(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message) - : m_backendImpl(std::move(backendImpl)) - , m_callId(callId) - , m_method(method) - , m_message(message) { } - -DispatcherBase::Callback::~Callback() = default; - -void DispatcherBase::Callback::dispose() -{ - m_backendImpl = nullptr; -} - -void DispatcherBase::Callback::sendIfActive(std::unique_ptr partialMessage, const DispatchResponse& response) -{ - if (!m_backendImpl || !m_backendImpl->get()) - return; - m_backendImpl->get()->sendResponse(m_callId, response, std::move(partialMessage)); - m_backendImpl = nullptr; -} - -void DispatcherBase::Callback::fallThroughIfActive() -{ - if (!m_backendImpl || !m_backendImpl->get()) - return; - m_backendImpl->get()->channel()->fallThrough(m_callId, m_method, m_message); - m_backendImpl = nullptr; -} - -DispatcherBase::DispatcherBase(FrontendChannel* frontendChannel) - : m_frontendChannel(frontendChannel) { } - -DispatcherBase::~DispatcherBase() -{ - clearFrontend(); -} - -void DispatcherBase::sendResponse(int callId, const DispatchResponse& response, std::unique_ptr result) -{ - if (!m_frontendChannel) - return; - if (response.status() == DispatchResponse::kError) { - reportProtocolError(callId, response.errorCode(), response.errorMessage(), nullptr); - return; - } - m_frontendChannel->sendProtocolResponse(callId, InternalResponse::createResponse(callId, std::move(result))); -} - -void DispatcherBase::sendResponse(int callId, const DispatchResponse& response) -{ - sendResponse(callId, response, DictionaryValue::create()); -} - -namespace { - -class ProtocolError : public Serializable { -public: - static std::unique_ptr createErrorResponse(int callId, DispatchResponse::ErrorCode code, const String& errorMessage, ErrorSupport* errors) - { - std::unique_ptr protocolError(new ProtocolError(code, errorMessage)); - protocolError->m_callId = callId; - protocolError->m_hasCallId = true; - if (errors && errors->hasErrors()) - protocolError->m_data = errors->errors(); - return protocolError; - } - - static std::unique_ptr createErrorNotification(DispatchResponse::ErrorCode code, const String& errorMessage) - { - return std::unique_ptr(new ProtocolError(code, errorMessage)); - } - - String serializeToJSON() override - { - return serialize()->serializeToJSON(); - } - - std::vector serializeToBinary() override - { - return serialize()->serializeToBinary(); - } - - ~ProtocolError() override {} - -private: - ProtocolError(DispatchResponse::ErrorCode code, const String& errorMessage) - : m_code(code) - , m_errorMessage(errorMessage) - { - } - - 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; - int m_callId = 0; - bool m_hasCallId = false; -}; - -} // namespace - -static void reportProtocolErrorTo(FrontendChannel* frontendChannel, int callId, DispatchResponse::ErrorCode code, const String& errorMessage, ErrorSupport* errors) -{ - if (frontendChannel) - frontendChannel->sendProtocolResponse(callId, ProtocolError::createErrorResponse(callId, code, errorMessage, errors)); -} - -static void reportProtocolErrorTo(FrontendChannel* frontendChannel, DispatchResponse::ErrorCode code, const String& errorMessage) -{ - if (frontendChannel) - frontendChannel->sendProtocolNotification(ProtocolError::createErrorNotification(code, errorMessage)); -} - -void DispatcherBase::reportProtocolError(int callId, DispatchResponse::ErrorCode code, const String& errorMessage, ErrorSupport* errors) -{ - reportProtocolErrorTo(m_frontendChannel, callId, code, errorMessage, errors); -} - -void DispatcherBase::clearFrontend() -{ - m_frontendChannel = nullptr; - for (auto& weak : m_weakPtrs) - weak->dispose(); - m_weakPtrs.clear(); -} - -std::unique_ptr DispatcherBase::weakPtr() -{ - std::unique_ptr weak(new DispatcherBase::WeakPtr(this)); - m_weakPtrs.insert(weak.get()); - return weak; -} - -UberDispatcher::UberDispatcher(FrontendChannel* frontendChannel) - : m_frontendChannel(frontendChannel) { } - -void UberDispatcher::registerBackend(const String& name, std::unique_ptr dispatcher) -{ - m_dispatchers[name] = std::move(dispatcher); -} - -void UberDispatcher::setupRedirects(const std::unordered_map& redirects) -{ - for (const auto& pair : redirects) - m_redirects[pair.first] = pair.second; -} - -bool UberDispatcher::parseCommand(Value* parsedMessage, int* outCallId, String* outMethod) { - if (!parsedMessage) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kParseError, "Message must be a valid JSON"); - return false; - } - protocol::DictionaryValue* messageObject = DictionaryValue::cast(parsedMessage); - if (!messageObject) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must be an object"); - return false; - } - - int callId = 0; - protocol::Value* callIdValue = messageObject->get("id"); - bool success = callIdValue && callIdValue->asInteger(&callId); - if (!success) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must have integer 'id' property"); - return false; - } - if (outCallId) - *outCallId = callId; - - protocol::Value* methodValue = messageObject->get("method"); - String method; - success = methodValue && methodValue->asString(&method); - if (!success) { - reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kInvalidRequest, "Message must have string 'method' property", nullptr); - return false; - } - if (outMethod) - *outMethod = method; - return true; -} - -protocol::DispatcherBase* UberDispatcher::findDispatcher(const String& method) { - size_t dotIndex = StringUtil::find(method, "."); - if (dotIndex == StringUtil::kNotFound) - return nullptr; - String domain = StringUtil::substring(method, 0, dotIndex); - auto it = m_dispatchers.find(domain); - if (it == m_dispatchers.end()) - return nullptr; - if (!it->second->canDispatch(method)) - return nullptr; - return it->second.get(); -} - -bool UberDispatcher::canDispatch(const String& in_method) -{ - String method = in_method; - auto redirectIt = m_redirects.find(method); - if (redirectIt != m_redirects.end()) - method = redirectIt->second; - return !!findDispatcher(method); -} - -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; - } - std::unique_ptr messageObject = DictionaryValue::cast(std::move(parsedMessage)); - dispatcher->dispatch(callId, method, rawMessage, std::move(messageObject)); -} - -UberDispatcher::~UberDispatcher() = default; - -// static -std::unique_ptr InternalResponse::createResponse(int callId, std::unique_ptr params) -{ - return std::unique_ptr(new InternalResponse(callId, String(), std::move(params))); -} - -// static -std::unique_ptr InternalResponse::createNotification(const String& notification, std::unique_ptr params) -{ - return std::unique_ptr(new InternalResponse(0, notification, std::move(params))); -} - -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::fromBinary(params->serializeToBinary())); - } else { - result->setInteger("id", m_callId); - result->setValue("result", SerializedValue::fromBinary(params->serializeToBinary())); - } - return result->serializeToBinary(); -} - -InternalResponse::InternalResponse(int callId, const String& notification, std::unique_ptr params) - : m_callId(callId) - , m_notification(notification) - , m_params(params ? std::move(params) : nullptr) -{ -} - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} diff --git a/tools/inspector_protocol/lib/DispatcherBase_h.template b/tools/inspector_protocol/lib/DispatcherBase_h.template deleted file mode 100644 index 7d859c4f2753bb..00000000000000 --- a/tools/inspector_protocol/lib/DispatcherBase_h.template +++ /dev/null @@ -1,182 +0,0 @@ -// 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. - -#ifndef {{"_".join(config.protocol.namespace)}}_DispatcherBase_h -#define {{"_".join(config.protocol.namespace)}}_DispatcherBase_h - -//#include "Forward.h" -//#include "ErrorSupport.h" -//#include "Values.h" - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -class WeakPtr; - -class {{config.lib.export_macro}} DispatchResponse { -public: - enum Status { - kSuccess = 0, - kError = 1, - kFallThrough = 2, - }; - - enum ErrorCode { - kParseError = -32700, - kInvalidRequest = -32600, - kMethodNotFound = -32601, - kInvalidParams = -32602, - kInternalError = -32603, - kServerError = -32000, - }; - - Status status() const { return m_status; } - const String& errorMessage() const { return m_errorMessage; } - ErrorCode errorCode() const { return m_errorCode; } - bool isSuccess() const { return m_status == kSuccess; } - - static DispatchResponse OK(); - static DispatchResponse Error(const String&); - static DispatchResponse InternalError(); - static DispatchResponse InvalidParams(const String&); - static DispatchResponse FallThrough(); - -private: - Status m_status; - String m_errorMessage; - ErrorCode m_errorCode; -}; - -class {{config.lib.export_macro}} DispatcherBase { - PROTOCOL_DISALLOW_COPY(DispatcherBase); -public: - static const char kInvalidParamsString[]; - class {{config.lib.export_macro}} WeakPtr { - public: - explicit WeakPtr(DispatcherBase*); - ~WeakPtr(); - DispatcherBase* get() { return m_dispatcher; } - void dispose() { m_dispatcher = nullptr; } - - private: - DispatcherBase* m_dispatcher; - }; - - class {{config.lib.export_macro}} Callback { - public: - Callback(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message); - virtual ~Callback(); - void dispose(); - - protected: - void sendIfActive(std::unique_ptr partialMessage, const DispatchResponse& response); - void fallThroughIfActive(); - - private: - std::unique_ptr m_backendImpl; - int m_callId; - String m_method; - ProtocolMessage m_message; - }; - - explicit DispatcherBase(FrontendChannel*); - virtual ~DispatcherBase(); - - 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&); - - void reportProtocolError(int callId, DispatchResponse::ErrorCode, const String& errorMessage, ErrorSupport* errors); - void clearFrontend(); - - std::unique_ptr weakPtr(); - -private: - FrontendChannel* m_frontendChannel; - std::unordered_set m_weakPtrs; -}; - -class {{config.lib.export_macro}} UberDispatcher { - PROTOCOL_DISALLOW_COPY(UberDispatcher); -public: - explicit UberDispatcher(FrontendChannel*); - void registerBackend(const String& name, std::unique_ptr); - 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; } - virtual ~UberDispatcher(); - -private: - protocol::DispatcherBase* findDispatcher(const String& method); - FrontendChannel* m_frontendChannel; - std::unordered_map m_redirects; - std::unordered_map> m_dispatchers; -}; - -class InternalResponse : public Serializable { - PROTOCOL_DISALLOW_COPY(InternalResponse); -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 serializeToJSON() override; - std::vector serializeToBinary() override; - - ~InternalResponse() override {} - -private: - InternalResponse(int callId, const String& notification, std::unique_ptr params); - - int m_callId; - String m_notification; - std::unique_ptr m_params; -}; - -class InternalRawNotification : public Serializable { -public: - 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(std::move(notification))); - } - - ~InternalRawNotification() override {} - - String serializeToJSON() override - { - return std::move(m_jsonNotification); - } - - std::vector serializeToBinary() override - { - return std::move(m_binaryNotification); - } - -private: - explicit InternalRawNotification(String notification) - : m_jsonNotification(std::move(notification)) { } - explicit InternalRawNotification(std::vector notification) - : m_binaryNotification(std::move(notification)) { } - - String m_jsonNotification; - std::vector m_binaryNotification; -}; - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#endif // !defined({{"_".join(config.protocol.namespace)}}_DispatcherBase_h) diff --git a/tools/inspector_protocol/lib/ErrorSupport_cpp.template b/tools/inspector_protocol/lib/ErrorSupport_cpp.template deleted file mode 100644 index a5c2a79bbd25c4..00000000000000 --- a/tools/inspector_protocol/lib/ErrorSupport_cpp.template +++ /dev/null @@ -1,73 +0,0 @@ -// 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. - -//#include "ErrorSupport.h" - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -ErrorSupport::ErrorSupport() { } -ErrorSupport::~ErrorSupport() { } - -void ErrorSupport::setName(const char* name) -{ - setName(String(name)); -} - -void ErrorSupport::setName(const String& name) -{ - DCHECK(m_path.size()); - m_path[m_path.size() - 1] = name; -} - -void ErrorSupport::push() -{ - m_path.push_back(String()); -} - -void ErrorSupport::pop() -{ - m_path.pop_back(); -} - -void ErrorSupport::addError(const char* error) -{ - addError(String(error)); -} - -void ErrorSupport::addError(const String& error) -{ - StringBuilder builder; - for (size_t i = 0; i < m_path.size(); ++i) { - if (i) - StringUtil::builderAppend(builder, '.'); - StringUtil::builderAppend(builder, m_path[i]); - } - StringUtil::builderAppend(builder, ": "); - StringUtil::builderAppend(builder, error); - m_errors.push_back(StringUtil::builderToString(builder)); -} - -bool ErrorSupport::hasErrors() -{ - return !!m_errors.size(); -} - -String ErrorSupport::errors() -{ - StringBuilder builder; - for (size_t i = 0; i < m_errors.size(); ++i) { - if (i) - StringUtil::builderAppend(builder, "; "); - StringUtil::builderAppend(builder, m_errors[i]); - } - return StringUtil::builderToString(builder); -} - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} diff --git a/tools/inspector_protocol/lib/ErrorSupport_h.template b/tools/inspector_protocol/lib/ErrorSupport_h.template deleted file mode 100644 index f317a3cfb411b5..00000000000000 --- a/tools/inspector_protocol/lib/ErrorSupport_h.template +++ /dev/null @@ -1,39 +0,0 @@ -// 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. - -#ifndef {{"_".join(config.protocol.namespace)}}_ErrorSupport_h -#define {{"_".join(config.protocol.namespace)}}_ErrorSupport_h - -#include {{format_include(config.protocol.package, "Forward")}} - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -class {{config.lib.export_macro}} ErrorSupport { -public: - ErrorSupport(); - ~ErrorSupport(); - - void push(); - void setName(const char*); - void setName(const String&); - void pop(); - void addError(const char*); - void addError(const String&); - bool hasErrors(); - String errors(); - -private: - std::vector m_path; - std::vector m_errors; -}; - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#endif // !defined({{"_".join(config.protocol.namespace)}}_ErrorSupport_h) diff --git a/tools/inspector_protocol/lib/Forward_h.template b/tools/inspector_protocol/lib/Forward_h.template index ff5e685863395b..54f25e1c9c0705 100644 --- a/tools/inspector_protocol/lib/Forward_h.template +++ b/tools/inspector_protocol/lib/Forward_h.template @@ -18,24 +18,61 @@ #include #include +#include "{{config.crdtp.dir}}/error_support.h" +#include "{{config.crdtp.dir}}/dispatch.h" +#include "{{config.crdtp.dir}}/frontend_channel.h" +#include "{{config.crdtp.dir}}/protocol_core.h" + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} -template class Array; class DictionaryValue; -class DispatchResponse; -class ErrorSupport; +using DispatchResponse = {{config.crdtp.namespace}}::DispatchResponse; +using ErrorSupport = {{config.crdtp.namespace}}::ErrorSupport; +using Serializable = {{config.crdtp.namespace}}::Serializable; +using FrontendChannel = {{config.crdtp.namespace}}::FrontendChannel; +using DomainDispatcher = {{config.crdtp.namespace}}::DomainDispatcher; +using UberDispatcher = {{config.crdtp.namespace}}::UberDispatcher; class FundamentalValue; class ListValue; -template class Maybe; class Object; using Response = DispatchResponse; class SerializedValue; class StringValue; -class UberDispatcher; class Value; +using {{config.crdtp.namespace}}::detail::PtrMaybe; +using {{config.crdtp.namespace}}::detail::ValueMaybe; + +template +using Maybe = {{config.crdtp.namespace}}::Maybe; + +namespace detail { + +template +struct ArrayTypedef { typedef std::vector> type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +} // namespace detail + +template +using Array = typename detail::ArrayTypedef::type; + {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} diff --git a/tools/inspector_protocol/lib/FrontendChannel_h.template b/tools/inspector_protocol/lib/FrontendChannel_h.template deleted file mode 100644 index df104debadbe85..00000000000000 --- a/tools/inspector_protocol/lib/FrontendChannel_h.template +++ /dev/null @@ -1,40 +0,0 @@ -// 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. - -#ifndef {{"_".join(config.protocol.namespace)}}_FrontendChannel_h -#define {{"_".join(config.protocol.namespace)}}_FrontendChannel_h - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -class {{config.lib.export_macro}} Serializable { -public: - 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; -}; - -class {{config.lib.export_macro}} FrontendChannel { -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; -}; - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#endif // !defined({{"_".join(config.protocol.namespace)}}_FrontendChannel_h) diff --git a/tools/inspector_protocol/lib/Maybe_h.template b/tools/inspector_protocol/lib/Maybe_h.template deleted file mode 100644 index 22cfac6b240bef..00000000000000 --- a/tools/inspector_protocol/lib/Maybe_h.template +++ /dev/null @@ -1,139 +0,0 @@ -// 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. - -#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 %} -namespace {{namespace}} { -{% endfor %} - -template -class Maybe { -public: - Maybe() : m_value() { } - Maybe(std::unique_ptr value) : m_value(std::move(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; } - bool isJust() const { return !!m_value; } - std::unique_ptr takeJust() { DCHECK(m_value); return std::move(m_value); } -private: - std::unique_ptr m_value; -}; - -template -class MaybeBase { -public: - MaybeBase() : m_isJust(false) { } - MaybeBase(T value) : m_isJust(true), m_value(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; } - bool isJust() const { return m_isJust; } - T takeJust() { DCHECK(m_isJust); return m_value; } - -protected: - bool m_isJust; - T m_value; -}; - -template<> -class Maybe : public MaybeBase { -public: - Maybe() { m_value = false; } - Maybe(bool value) : MaybeBase(value) { } - Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} - using MaybeBase::operator=; -}; - -template<> -class Maybe : public MaybeBase { -public: - Maybe() { m_value = 0; } - Maybe(int value) : MaybeBase(value) { } - Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} - using MaybeBase::operator=; -}; - -template<> -class Maybe : public MaybeBase { -public: - Maybe() { m_value = 0; } - Maybe(double value) : MaybeBase(value) { } - Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} - using MaybeBase::operator=; -}; - -template<> -class Maybe : public MaybeBase { -public: - Maybe() { } - Maybe(const String& value) : MaybeBase(value) { } - 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=; -}; - -{% for namespace in config.protocol.namespace %} -} // 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 1640a11127b442..b639b1bb776ad0 100644 --- a/tools/inspector_protocol/lib/Object_cpp.template +++ b/tools/inspector_protocol/lib/Object_cpp.template @@ -14,13 +14,18 @@ std::unique_ptr Object::fromValue(protocol::Value* value, ErrorSupport* { protocol::DictionaryValue* dictionary = DictionaryValue::cast(value); if (!dictionary) { - errors->addError("object expected"); + errors->AddError("object expected"); return nullptr; } dictionary = static_cast(dictionary->clone().release()); return std::unique_ptr(new Object(std::unique_ptr(dictionary))); } +// Implements Serializable. +void Object::AppendSerialized(std::vector* out) const { + m_object->AppendSerialized(out); +} + std::unique_ptr Object::toValue() const { return DictionaryValue::cast(m_object->clone()); diff --git a/tools/inspector_protocol/lib/Object_h.template b/tools/inspector_protocol/lib/Object_h.template index ec953d0d4836a4..f0dce5d1b7f198 100644 --- a/tools/inspector_protocol/lib/Object_h.template +++ b/tools/inspector_protocol/lib/Object_h.template @@ -11,19 +11,28 @@ //#include "Forward.h" //#include "Values.h" +#include "{{config.crdtp.dir}}/serializable.h" + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} -class {{config.lib.export_macro}} Object { +class {{config.lib.export_macro}} Object : public {{config.crdtp.namespace}}::Serializable { public: static std::unique_ptr fromValue(protocol::Value*, ErrorSupport*); explicit Object(std::unique_ptr); ~Object(); + // Implements Serializable. + void AppendSerialized(std::vector* out) const override; + std::unique_ptr toValue() const; std::unique_ptr clone() const; + private: + Object() = default; + friend struct {{config.crdtp.namespace}}::ProtocolTypeTraits, void>; + std::unique_ptr m_object; }; diff --git a/tools/inspector_protocol/lib/Parser_cpp.template b/tools/inspector_protocol/lib/Parser_cpp.template deleted file mode 100644 index ea7ecc5a1a4756..00000000000000 --- a/tools/inspector_protocol/lib/Parser_cpp.template +++ /dev/null @@ -1,548 +0,0 @@ -// 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. - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -namespace { - -const int stackLimit = 1000; - -enum Token { - ObjectBegin, - ObjectEnd, - ArrayBegin, - ArrayEnd, - StringLiteral, - Number, - BoolTrue, - BoolFalse, - NullToken, - ListSeparator, - ObjectPairSeparator, - InvalidToken, -}; - -const char* const nullString = "null"; -const char* const trueString = "true"; -const char* const falseString = "false"; - -bool isASCII(uint16_t c) -{ - return !(c & ~0x7F); -} - -bool isSpaceOrNewLine(uint16_t c) -{ - return isASCII(c) && c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9)); -} - -double charactersToDouble(const uint16_t* characters, size_t length, bool* ok) -{ - std::vector buffer; - buffer.reserve(length + 1); - for (size_t i = 0; i < length; ++i) { - if (!isASCII(characters[i])) { - *ok = false; - return 0; - } - buffer.push_back(static_cast(characters[i])); - } - buffer.push_back('\0'); - return StringUtil::toDouble(buffer.data(), length, ok); -} - -double charactersToDouble(const uint8_t* characters, size_t length, bool* ok) -{ - std::string buffer(reinterpret_cast(characters), length); - return StringUtil::toDouble(buffer.data(), length, ok); -} - -template -bool parseConstToken(const Char* start, const Char* end, const Char** tokenEnd, const char* token) -{ - while (start < end && *token != '\0' && *start++ == *token++) { } - if (*token != '\0') - return false; - *tokenEnd = start; - return true; -} - -template -bool readInt(const Char* start, const Char* end, const Char** tokenEnd, bool canHaveLeadingZeros) -{ - if (start == end) - return false; - bool haveLeadingZero = '0' == *start; - int length = 0; - while (start < end && '0' <= *start && *start <= '9') { - ++start; - ++length; - } - if (!length) - return false; - if (!canHaveLeadingZeros && length > 1 && haveLeadingZero) - return false; - *tokenEnd = start; - return true; -} - -template -bool parseNumberToken(const Char* start, const Char* end, const Char** tokenEnd) -{ - // We just grab the number here. We validate the size in DecodeNumber. - // According to RFC4627, a valid number is: [minus] int [frac] [exp] - if (start == end) - return false; - Char c = *start; - if ('-' == c) - ++start; - - if (!readInt(start, end, &start, false)) - return false; - if (start == end) { - *tokenEnd = start; - return true; - } - - // Optional fraction part - c = *start; - if ('.' == c) { - ++start; - if (!readInt(start, end, &start, true)) - return false; - if (start == end) { - *tokenEnd = start; - return true; - } - c = *start; - } - - // Optional exponent part - if ('e' == c || 'E' == c) { - ++start; - if (start == end) - return false; - c = *start; - if ('-' == c || '+' == c) { - ++start; - if (start == end) - return false; - } - if (!readInt(start, end, &start, true)) - return false; - } - - *tokenEnd = start; - return true; -} - -template -bool readHexDigits(const Char* start, const Char* end, const Char** tokenEnd, int digits) -{ - if (end - start < digits) - return false; - for (int i = 0; i < digits; ++i) { - Char c = *start++; - if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'))) - return false; - } - *tokenEnd = start; - return true; -} - -template -bool parseStringToken(const Char* start, const Char* end, const Char** tokenEnd) -{ - while (start < end) { - Char c = *start++; - if ('\\' == c) { - if (start == end) - return false; - c = *start++; - // Make sure the escaped char is valid. - switch (c) { - case 'x': - if (!readHexDigits(start, end, &start, 2)) - return false; - break; - case 'u': - if (!readHexDigits(start, end, &start, 4)) - return false; - break; - case '\\': - case '/': - case 'b': - case 'f': - case 'n': - case 'r': - case 't': - case 'v': - case '"': - break; - default: - return false; - } - } else if ('"' == c) { - *tokenEnd = start; - return true; - } - } - return false; -} - -template -bool skipComment(const Char* start, const Char* end, const Char** commentEnd) -{ - if (start == end) - return false; - - if (*start != '/' || start + 1 >= end) - return false; - ++start; - - if (*start == '/') { - // Single line comment, read to newline. - for (++start; start < end; ++start) { - if (*start == '\n' || *start == '\r') { - *commentEnd = start + 1; - return true; - } - } - *commentEnd = end; - // Comment reaches end-of-input, which is fine. - return true; - } - - if (*start == '*') { - Char previous = '\0'; - // Block comment, read until end marker. - for (++start; start < end; previous = *start++) { - if (previous == '*' && *start == '/') { - *commentEnd = start + 1; - return true; - } - } - // Block comment must close before end-of-input. - return false; - } - - return false; -} - -template -void skipWhitespaceAndComments(const Char* start, const Char* end, const Char** whitespaceEnd) -{ - while (start < end) { - if (isSpaceOrNewLine(*start)) { - ++start; - } else if (*start == '/') { - const Char* commentEnd; - if (!skipComment(start, end, &commentEnd)) - break; - start = commentEnd; - } else { - break; - } - } - *whitespaceEnd = start; -} - -template -Token parseToken(const Char* start, const Char* end, const Char** tokenStart, const Char** tokenEnd) -{ - skipWhitespaceAndComments(start, end, tokenStart); - start = *tokenStart; - - if (start == end) - return InvalidToken; - - switch (*start) { - case 'n': - if (parseConstToken(start, end, tokenEnd, nullString)) - return NullToken; - break; - case 't': - if (parseConstToken(start, end, tokenEnd, trueString)) - return BoolTrue; - break; - case 'f': - if (parseConstToken(start, end, tokenEnd, falseString)) - return BoolFalse; - break; - case '[': - *tokenEnd = start + 1; - return ArrayBegin; - case ']': - *tokenEnd = start + 1; - return ArrayEnd; - case ',': - *tokenEnd = start + 1; - return ListSeparator; - case '{': - *tokenEnd = start + 1; - return ObjectBegin; - case '}': - *tokenEnd = start + 1; - return ObjectEnd; - case ':': - *tokenEnd = start + 1; - return ObjectPairSeparator; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - if (parseNumberToken(start, end, tokenEnd)) - return Number; - break; - case '"': - if (parseStringToken(start + 1, end, tokenEnd)) - return StringLiteral; - break; - } - return InvalidToken; -} - -template -int hexToInt(Char c) -{ - if ('0' <= c && c <= '9') - return c - '0'; - if ('A' <= c && c <= 'F') - return c - 'A' + 10; - if ('a' <= c && c <= 'f') - return c - 'a' + 10; - DCHECK(false); - return 0; -} - -template -bool decodeString(const Char* start, const Char* end, StringBuilder* output) -{ - while (start < end) { - uint16_t c = *start++; - if ('\\' != c) { - StringUtil::builderAppend(*output, c); - continue; - } - if (start == end) - return false; - c = *start++; - - if (c == 'x') { - // \x is not supported. - return false; - } - - switch (c) { - case '"': - case '/': - case '\\': - break; - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case 'v': - c = '\v'; - break; - case 'u': - c = (hexToInt(*start) << 12) + - (hexToInt(*(start + 1)) << 8) + - (hexToInt(*(start + 2)) << 4) + - hexToInt(*(start + 3)); - start += 4; - break; - default: - return false; - } - StringUtil::builderAppend(*output, c); - } - return true; -} - -template -bool decodeString(const Char* start, const Char* end, String* output) -{ - if (start == end) { - *output = ""; - return true; - } - if (start > end) - return false; - StringBuilder buffer; - StringUtil::builderReserve(buffer, end - start); - if (!decodeString(start, end, &buffer)) - return false; - *output = StringUtil::builderToString(buffer); - return true; -} - -template -std::unique_ptr buildValue(const Char* start, const Char* end, const Char** valueTokenEnd, int depth) -{ - if (depth > stackLimit) - return nullptr; - - std::unique_ptr result; - const Char* tokenStart; - const Char* tokenEnd; - Token token = parseToken(start, end, &tokenStart, &tokenEnd); - switch (token) { - case InvalidToken: - return nullptr; - case NullToken: - result = Value::null(); - break; - case BoolTrue: - result = FundamentalValue::create(true); - break; - case BoolFalse: - result = FundamentalValue::create(false); - break; - case Number: { - bool ok; - double value = charactersToDouble(tokenStart, tokenEnd - tokenStart, &ok); - if (!ok) - return nullptr; - if (value >= INT_MIN && value <= INT_MAX && static_cast(value) == value) - result = FundamentalValue::create(static_cast(value)); - else - result = FundamentalValue::create(value); - break; - } - case StringLiteral: { - String value; - bool ok = decodeString(tokenStart + 1, tokenEnd - 1, &value); - if (!ok) - return nullptr; - result = StringValue::create(value); - break; - } - case ArrayBegin: { - std::unique_ptr array = ListValue::create(); - start = tokenEnd; - token = parseToken(start, end, &tokenStart, &tokenEnd); - while (token != ArrayEnd) { - std::unique_ptr arrayNode = buildValue(start, end, &tokenEnd, depth + 1); - if (!arrayNode) - return nullptr; - array->pushValue(std::move(arrayNode)); - - // After a list value, we expect a comma or the end of the list. - start = tokenEnd; - token = parseToken(start, end, &tokenStart, &tokenEnd); - if (token == ListSeparator) { - start = tokenEnd; - token = parseToken(start, end, &tokenStart, &tokenEnd); - if (token == ArrayEnd) - return nullptr; - } else if (token != ArrayEnd) { - // Unexpected value after list value. Bail out. - return nullptr; - } - } - if (token != ArrayEnd) - return nullptr; - result = std::move(array); - break; - } - case ObjectBegin: { - std::unique_ptr object = DictionaryValue::create(); - start = tokenEnd; - token = parseToken(start, end, &tokenStart, &tokenEnd); - while (token != ObjectEnd) { - if (token != StringLiteral) - return nullptr; - String key; - if (!decodeString(tokenStart + 1, tokenEnd - 1, &key)) - return nullptr; - start = tokenEnd; - - token = parseToken(start, end, &tokenStart, &tokenEnd); - if (token != ObjectPairSeparator) - return nullptr; - start = tokenEnd; - - std::unique_ptr value = buildValue(start, end, &tokenEnd, depth + 1); - if (!value) - return nullptr; - object->setValue(key, std::move(value)); - start = tokenEnd; - - // After a key/value pair, we expect a comma or the end of the - // object. - token = parseToken(start, end, &tokenStart, &tokenEnd); - if (token == ListSeparator) { - start = tokenEnd; - token = parseToken(start, end, &tokenStart, &tokenEnd); - if (token == ObjectEnd) - return nullptr; - } else if (token != ObjectEnd) { - // Unexpected value after last object value. Bail out. - return nullptr; - } - } - if (token != ObjectEnd) - return nullptr; - result = std::move(object); - break; - } - - default: - // We got a token that's not a value. - return nullptr; - } - - skipWhitespaceAndComments(tokenEnd, end, valueTokenEnd); - return result; -} - -template -std::unique_ptr parseJSONInternal(const Char* start, unsigned length) -{ - const Char* end = start + length; - const Char *tokenEnd; - std::unique_ptr value = buildValue(start, end, &tokenEnd, 0); - if (!value || tokenEnd != end) - return nullptr; - return value; -} - -} // anonymous namespace - -std::unique_ptr parseJSONCharacters(const uint16_t* characters, unsigned length) -{ - return parseJSONInternal(characters, length); -} - -std::unique_ptr parseJSONCharacters(const uint8_t* characters, unsigned length) -{ - return parseJSONInternal(characters, length); -} - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} diff --git a/tools/inspector_protocol/lib/Parser_h.template b/tools/inspector_protocol/lib/Parser_h.template deleted file mode 100644 index 1832c2e9724b4a..00000000000000 --- a/tools/inspector_protocol/lib/Parser_h.template +++ /dev/null @@ -1,24 +0,0 @@ -// 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. - -#ifndef {{"_".join(config.protocol.namespace)}}_Parser_h -#define {{"_".join(config.protocol.namespace)}}_Parser_h - -//#include "Forward.h" -//#include "Values.h" - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -{{config.lib.export_macro}} std::unique_ptr parseJSONCharacters(const uint8_t*, unsigned); -{{config.lib.export_macro}} std::unique_ptr parseJSONCharacters(const uint16_t*, unsigned); - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#endif // !defined({{"_".join(config.protocol.namespace)}}_Parser_h) diff --git a/tools/inspector_protocol/lib/ValueConversions_cpp.template b/tools/inspector_protocol/lib/ValueConversions_cpp.template new file mode 100644 index 00000000000000..36c8dcc356370c --- /dev/null +++ b/tools/inspector_protocol/lib/ValueConversions_cpp.template @@ -0,0 +1,123 @@ +// This file is generated by ValueConversions_cpp.template. + +// Copyright 2020 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, "Protocol")}} + +#include +#include +#include + +//#include "ValueConversions.h" +//#include "Values.h" + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +{% for namespace in config.protocol.namespace %} +} // namespce +{% endfor %} + + +namespace {{config.crdtp.namespace}} { + +namespace { + +using {{"::".join(config.protocol.namespace)}}::Binary; +using {{"::".join(config.protocol.namespace)}}::Object; +using {{"::".join(config.protocol.namespace)}}::Value; +using {{"::".join(config.protocol.namespace)}}::String; +using {{"::".join(config.protocol.namespace)}}::DictionaryValue; +using {{"::".join(config.protocol.namespace)}}::FundamentalValue; +using {{"::".join(config.protocol.namespace)}}::StringValue; +using {{"::".join(config.protocol.namespace)}}::StringUtil; +//using {{"::".join(config.protocol.namespace)}}::EncodeString; + +std::unique_ptr ReadValue(DeserializerState* state) { + cbor::CBORTokenizer* tokenizer = state->tokenizer(); + switch (tokenizer->TokenTag()) { + case cbor::CBORTokenTag::TRUE_VALUE: + return FundamentalValue::create(true); + case cbor::CBORTokenTag::FALSE_VALUE: + return FundamentalValue::create(false); + case cbor::CBORTokenTag::NULL_VALUE: + return Value::null(); + case cbor::CBORTokenTag::INT32: + return FundamentalValue::create(tokenizer->GetInt32()); + case cbor::CBORTokenTag::DOUBLE: + return FundamentalValue::create(tokenizer->GetDouble()); + case cbor::CBORTokenTag::STRING8: { + const auto str = tokenizer->GetString8(); + return StringValue::create(StringUtil::fromUTF8(str.data(), str.size())); + } + case cbor::CBORTokenTag::STRING16: { + const auto str = tokenizer->GetString16WireRep(); + return StringValue::create(StringUtil::fromUTF16LE(reinterpret_cast(str.data()), str.size() / 2)); + } + case cbor::CBORTokenTag::ENVELOPE: { + const auto env = tokenizer->GetEnvelope(); + return Value::parseBinary(env.data(), env.size()); + } + // Intentionally not supported. + case cbor::CBORTokenTag::BINARY: + // Should not be encountered outside of envelope. + case cbor::CBORTokenTag::MAP_START: + case cbor::CBORTokenTag::ARRAY_START: + default: + state->RegisterError(Error::CBOR_UNSUPPORTED_VALUE); + return nullptr; + } +} + +} // namespace + +// static +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, std::unique_ptr* value) { + auto result = ReadValue(state); + if (!result) + return false; + *value = std::move(result); + return true; +} + +// static +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +// static +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, std::unique_ptr* value) { + std::unique_ptr res; + if (!ProtocolTypeTraits>::Deserialize(state, &res)) + return false; + *value = DictionaryValue::cast(std::move(res)); + return true; +} + +// static +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +// static +bool ProtocolTypeTraits>::Deserialize(DeserializerState* state, std::unique_ptr* value) { + auto res = DictionaryValue::create(); + if (ProtocolTypeTraits>::Deserialize(state, &res)) { + *value = std::make_unique(std::move(res)); + return true; + } + return false; +} + +void ProtocolTypeTraits>::Serialize(const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +} // namespace {{config.crdtp.namespace}} diff --git a/tools/inspector_protocol/lib/ValueConversions_h.template b/tools/inspector_protocol/lib/ValueConversions_h.template index 2ee5b724545a33..0fac003d90cbc1 100644 --- a/tools/inspector_protocol/lib/ValueConversions_h.template +++ b/tools/inspector_protocol/lib/ValueConversions_h.template @@ -40,7 +40,7 @@ struct ValueConversions { bool result = false; bool success = value ? value->asBoolean(&result) : false; if (!success) - errors->addError("boolean value expected"); + errors->AddError("boolean value expected"); return result; } @@ -57,7 +57,7 @@ struct ValueConversions { int result = 0; bool success = value ? value->asInteger(&result) : false; if (!success) - errors->addError("integer value expected"); + errors->AddError("integer value expected"); return result; } @@ -74,7 +74,7 @@ struct ValueConversions { double result = 0; bool success = value ? value->asDouble(&result) : false; if (!success) - errors->addError("double value expected"); + errors->AddError("double value expected"); return result; } @@ -91,7 +91,7 @@ struct ValueConversions { String result; bool success = value ? value->asString(&result) : false; if (!success) - errors->addError("string value expected"); + errors->AddError("string value expected"); return result; } @@ -107,7 +107,7 @@ struct ValueConversions { { if (!value || (value->type() != Value::TypeBinary && value->type() != Value::TypeString)) { - errors->addError("Either string base64 or binary value expected"); + errors->AddError("Either string base64 or binary value expected"); return Binary(); } Binary binary; @@ -118,7 +118,7 @@ struct ValueConversions { bool success; Binary out = Binary::fromBase64(result, &success); if (!success) - errors->addError("base64 decoding error"); + errors->AddError("base64 decoding error"); return out; } @@ -128,13 +128,79 @@ struct ValueConversions { } }; +template +struct ValueConversions>> { + static std::unique_ptr>> fromValue(protocol::Value* value, ErrorSupport* errors) { + protocol::ListValue* array = ListValue::cast(value); + if (!array) { + errors->AddError("array expected"); + return nullptr; + } + errors->Push(); + std::unique_ptr>> result( + new std::vector>()); + result->reserve(array->size()); + for (size_t i = 0; i < array->size(); ++i) { + errors->SetIndex(i); + auto item = ValueConversions::fromValue(array->at(i), errors); + result->emplace_back(std::move(item)); + } + errors->Pop(); + if (!errors->Errors().empty()) + return nullptr; + return result; + } + + static std::unique_ptr toValue(std::vector>* v) + { + std::unique_ptr result = ListValue::create(); + result->reserve(v->size()); + for (auto& item : *v) + result->pushValue(ValueConversions::toValue(item.get())); + return result; + } + +}; + +template +struct ValueConversions> { + static std::unique_ptr> fromValue(protocol::Value* value, ErrorSupport* errors) { + protocol::ListValue* array = ListValue::cast(value); + if (!array) { + errors->AddError("array expected"); + return nullptr; + } + errors->Push(); + std::unique_ptr> result(new std::vector()); + result->reserve(array->size()); + for (size_t i = 0; i < array->size(); ++i) { + errors->SetIndex(i); + auto item = ValueConversions::fromValue(array->at(i), errors); + result->emplace_back(std::move(item)); + } + errors->Pop(); + if (!errors->Errors().empty()) + return nullptr; + return result; + } + + static std::unique_ptr toValue(std::vector* v) + { + std::unique_ptr result = ListValue::create(); + result->reserve(v->size()); + for (auto& item : *v) + result->pushValue(ValueConversions::toValue(item)); + return result; + } +}; + template<> struct ValueConversions { static std::unique_ptr fromValue(protocol::Value* value, ErrorSupport* errors) { bool success = !!value; if (!success) { - errors->addError("value expected"); + errors->AddError("value expected"); return nullptr; } return value->clone(); @@ -157,7 +223,7 @@ struct ValueConversions { { bool success = value && value->type() == protocol::Value::TypeObject; if (!success) - errors->addError("object expected"); + errors->AddError("object expected"); return DictionaryValue::cast(value->clone()); } @@ -178,7 +244,7 @@ struct ValueConversions { { bool success = value && value->type() == protocol::Value::TypeArray; if (!success) - errors->addError("list expected"); + errors->AddError("list expected"); return ListValue::cast(value->clone()); } @@ -193,8 +259,61 @@ struct ValueConversions { } }; +template struct ValueTypeConverter { + static std::unique_ptr FromValue(const protocol::Value& value) { + std::vector bytes; + value.AppendSerialized(&bytes); + return T::FromBinary(bytes.data(), bytes.size()); + } + + static std::unique_ptr ToValue(const T& obj) { + std::vector bytes; + obj.AppendSerialized(&bytes); + auto result = Value::parseBinary(bytes.data(), bytes.size()); + return DictionaryValue::cast(std::move(result)); + } +}; + {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} +namespace {{config.crdtp.namespace}} { + +template +struct ProtocolTypeTraits::value>::type> { + static void Serialize(const {{"::".join(config.protocol.namespace)}}::Value& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Value>* value); + static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Value>& value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::DictionaryValue>* value); + static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::DictionaryValue>& value, std::vector* bytes); +}; + +// TODO(caseq): get rid of it, it's just a DictionaryValue really. +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Object>* value); + static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Object>& value, std::vector* bytes); +}; + +template<> +struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::Object> { + static void Serialize(const {{"::".join(config.protocol.namespace)}}::Object& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +} // namespace {{config.crdtp.namespace}} + #endif // !defined({{"_".join(config.protocol.namespace)}}_ValueConversions_h) diff --git a/tools/inspector_protocol/lib/Values_cpp.template b/tools/inspector_protocol/lib/Values_cpp.template index be3149d50356f2..5b7db4a23c8cea 100644 --- a/tools/inspector_protocol/lib/Values_cpp.template +++ b/tools/inspector_protocol/lib/Values_cpp.template @@ -6,218 +6,189 @@ //#include "Values.h" +#include "{{config.crdtp.dir}}/cbor.h" + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} namespace { +using {{config.crdtp.namespace}}::Status; +using {{config.crdtp.namespace}}::ParserHandler; +using {{config.crdtp.namespace}}::span; +namespace cbor { +using {{config.crdtp.namespace}}::cbor::ParseCBOR; +using {{config.crdtp.namespace}}::cbor::EncodeBinary; +using {{config.crdtp.namespace}}::cbor::EncodeDouble; +using {{config.crdtp.namespace}}::cbor::EncodeFalse; +using {{config.crdtp.namespace}}::cbor::EncodeFromLatin1; +using {{config.crdtp.namespace}}::cbor::EncodeFromUTF16; +using {{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthArrayStart; +using {{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart; +using {{config.crdtp.namespace}}::cbor::EncodeInt32; +using {{config.crdtp.namespace}}::cbor::EncodeNull; +using {{config.crdtp.namespace}}::cbor::EncodeStop; +using {{config.crdtp.namespace}}::cbor::EncodeString8; +using {{config.crdtp.namespace}}::cbor::EncodeTrue; +using {{config.crdtp.namespace}}::cbor::EnvelopeEncoder; +using {{config.crdtp.namespace}}::cbor::InitialByteForEnvelope; +} // namespace cbor + +// Uses the parsing events received from driver of |ParserHandler| +// (e.g. cbor::ParseCBOR) into a protocol::Value instance. +class ValueParserHandler : public ParserHandler { + public: + // Provides the parsed protocol::Value. + std::unique_ptr ReleaseRoot() { return std::move(root_); } + + // The first parsing error encountered; |status().ok()| is the default. + Status status() const { return status_; } + + private: + // + // Implementation of ParserHandler. + // + void HandleMapBegin() override { + if (!status_.ok()) return; + std::unique_ptr dict = DictionaryValue::create(); + DictionaryValue* dict_ptr = dict.get(); + AddValueToParent(std::move(dict)); + stack_.emplace_back(dict_ptr); + } -const char* const nullValueString = "null"; -const char* const trueValueString = "true"; -const char* const falseValueString = "false"; + void HandleMapEnd() override { + if (!status_.ok()) return; + DCHECK(!stack_.empty()); + DCHECK(stack_.back().is_dict); + stack_.pop_back(); + } -inline bool escapeChar(uint16_t c, StringBuilder* dst) -{ - switch (c) { - case '\b': StringUtil::builderAppend(*dst, "\\b"); break; - case '\f': StringUtil::builderAppend(*dst, "\\f"); break; - case '\n': StringUtil::builderAppend(*dst, "\\n"); break; - case '\r': StringUtil::builderAppend(*dst, "\\r"); break; - case '\t': StringUtil::builderAppend(*dst, "\\t"); break; - case '\\': StringUtil::builderAppend(*dst, "\\\\"); break; - case '"': StringUtil::builderAppend(*dst, "\\\""); break; - default: - return false; - } - return true; -} + void HandleArrayBegin() override { + if (!status_.ok()) return; + std::unique_ptr list = ListValue::create(); + ListValue* list_ptr = list.get(); + AddValueToParent(std::move(list)); + stack_.emplace_back(list_ptr); + } -const char hexDigits[17] = "0123456789ABCDEF"; + void HandleArrayEnd() override { + if (!status_.ok()) return; + DCHECK(!stack_.empty()); + DCHECK(!stack_.back().is_dict); + stack_.pop_back(); + } -void appendUnsignedAsHex(uint16_t number, StringBuilder* dst) -{ - StringUtil::builderAppend(*dst, "\\u"); - for (size_t i = 0; i < 4; ++i) { - uint16_t c = hexDigits[(number & 0xF000) >> 12]; - StringUtil::builderAppend(*dst, c); - number <<= 4; - } -} + void HandleString8(span chars) override { + AddStringToParent(StringUtil::fromUTF8(chars.data(), chars.size())); + } -template -void escapeStringForJSONInternal(const Char* str, unsigned len, - StringBuilder* dst) -{ - for (unsigned i = 0; i < len; ++i) { - Char c = str[i]; - if (escapeChar(c, dst)) - continue; - if (c < 32 || c > 126) { - appendUnsignedAsHex(c, dst); - } else { - StringUtil::builderAppend(*dst, c); - } - } -} + void HandleString16(span chars) override { + AddStringToParent( + StringUtil::fromUTF16LE(chars.data(), chars.size())); + } -// 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, cbor::CBORTokenizer* tokenizer); -std::unique_ptr parseArray(int32_t stack_depth, cbor::CBORTokenizer* tokenizer); -std::unique_ptr parseValue(int32_t stack_depth, cbor::CBORTokenizer* tokenizer); - -// |bytes| must start with the indefinite length array byte, so basically, -// ParseArray may only be called after an indefinite length array has been -// detected. -std::unique_ptr parseArray(int32_t stack_depth, cbor::CBORTokenizer* tokenizer) { - DCHECK(tokenizer->TokenTag() == cbor::CBORTokenTag::ARRAY_START); - tokenizer->Next(); - auto list = ListValue::create(); - while (tokenizer->TokenTag() != cbor::CBORTokenTag::STOP) { - // Error::CBOR_UNEXPECTED_EOF_IN_ARRAY - if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE) return nullptr; - if (tokenizer->TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr; - // Parse value. - auto value = parseValue(stack_depth, tokenizer); - if (!value) return nullptr; - list->pushValue(std::move(value)); + void HandleBinary(span bytes) override { + AddValueToParent( + BinaryValue::create(Binary::fromSpan(bytes.data(), bytes.size()))); } - tokenizer->Next(); - return list; -} - -std::unique_ptr parseValue( - int32_t stack_depth, cbor::CBORTokenizer* tokenizer) { - // Error::CBOR_STACK_LIMIT_EXCEEDED - if (stack_depth > kStackLimitValues) return nullptr; - // Skip past the envelope to get to what's inside. - if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE) - tokenizer->EnterEnvelope(); - switch (tokenizer->TokenTag()) { - case cbor::CBORTokenTag::ERROR_VALUE: - return nullptr; - case cbor::CBORTokenTag::DONE: - // Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE - return nullptr; - case cbor::CBORTokenTag::TRUE_VALUE: { - std::unique_ptr value = FundamentalValue::create(true); - tokenizer->Next(); - return value; - } - case cbor::CBORTokenTag::FALSE_VALUE: { - std::unique_ptr value = FundamentalValue::create(false); - tokenizer->Next(); - return value; - } - case cbor::CBORTokenTag::NULL_VALUE: { - std::unique_ptr value = FundamentalValue::null(); - tokenizer->Next(); - return value; - } - case cbor::CBORTokenTag::INT32: { - std::unique_ptr value = FundamentalValue::create(tokenizer->GetInt32()); - tokenizer->Next(); - return value; - } - case cbor::CBORTokenTag::DOUBLE: { - std::unique_ptr value = FundamentalValue::create(tokenizer->GetDouble()); - tokenizer->Next(); - return value; - } - case cbor::CBORTokenTag::STRING8: { - span str = tokenizer->GetString8(); - std::unique_ptr value = - StringValue::create(StringUtil::fromUTF8(str.data(), str.size())); - tokenizer->Next(); - return value; - } - case cbor::CBORTokenTag::STRING16: { - span wire = tokenizer->GetString16WireRep(); - DCHECK_EQ(wire.size() & 1, 0u); - std::unique_ptr value = StringValue::create(StringUtil::fromUTF16( - reinterpret_cast(wire.data()), wire.size() / 2)); - tokenizer->Next(); - return value; - } - case cbor::CBORTokenTag::BINARY: { - span payload = tokenizer->GetBinary(); - tokenizer->Next(); - return BinaryValue::create(Binary::fromSpan(payload.data(), payload.size())); - } - case cbor::CBORTokenTag::MAP_START: - return parseMap(stack_depth + 1, tokenizer); - case cbor::CBORTokenTag::ARRAY_START: - return parseArray(stack_depth + 1, tokenizer); - default: - // Error::CBOR_UNSUPPORTED_VALUE - return nullptr; + + void HandleDouble(double value) override { + AddValueToParent(FundamentalValue::create(value)); } -} -// |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, cbor::CBORTokenizer* tokenizer) { - auto dict = DictionaryValue::create(); - tokenizer->Next(); - while (tokenizer->TokenTag() != cbor::CBORTokenTag::STOP) { - if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE) { - // Error::CBOR_UNEXPECTED_EOF_IN_MAP - return nullptr; + void HandleInt32(int32_t value) override { + AddValueToParent(FundamentalValue::create(value)); + } + + void HandleBool(bool value) override { + AddValueToParent(FundamentalValue::create(value)); + } + + void HandleNull() override { + AddValueToParent(Value::null()); + } + + void HandleError(Status error) override { + status_ = error; + } + + // + // Adding strings and values to the parent value. + // Strings are handled separately because they can be keys for + // dictionary values. + // + void AddStringToParent(String str) { + if (!status_.ok()) return; + if (!root_) { + DCHECK(!key_is_pending_); + root_ = StringValue::create(str); + } else if (stack_.back().is_dict) { + // If we already have a pending key, then this is the value of the + // key/value pair. Otherwise, it's the new pending key. + if (key_is_pending_) { + stack_.back().dict->setString(pending_key_, str); + key_is_pending_ = false; + } else { + pending_key_ = std::move(str); + key_is_pending_ = true; + } + } else { // Top of the stack is a list. + DCHECK(!key_is_pending_); + stack_.back().list->pushValue(StringValue::create(str)); } - if (tokenizer->TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr; - // Parse key. - String key; - if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) { - span key_span = tokenizer->GetString8(); - key = StringUtil::fromUTF8(key_span.data(), key_span.size()); - tokenizer->Next(); - } else if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) { - return nullptr; // STRING16 not supported yet. - } else { - // Error::CBOR_INVALID_MAP_KEY - return nullptr; + } + + void AddValueToParent(std::unique_ptr value) { + if (!status_.ok()) return; + if (!root_) { + DCHECK(!key_is_pending_); + root_ = std::move(value); + } else if (stack_.back().is_dict) { + DCHECK(key_is_pending_); + stack_.back().dict->setValue(pending_key_, std::move(value)); + key_is_pending_ = false; + } else { // Top of the stack is a list. + DCHECK(!key_is_pending_); + stack_.back().list->pushValue(std::move(value)); } - // Parse value. - auto value = parseValue(stack_depth, tokenizer); - if (!value) return nullptr; - dict->setValue(key, std::move(value)); } - tokenizer->Next(); - return dict; -} + // |status_.ok()| is the default; if we receive an error event + // we keep the first one and stop modifying any other state. + Status status_; + + // The root of the parsed protocol::Value tree. + std::unique_ptr root_; + + // If root_ is a list or a dictionary, this stack keeps track of + // the container we're currently parsing as well as its ancestors. + struct ContainerState { + ContainerState(DictionaryValue* dict) : is_dict(true), dict(dict) {} + ContainerState(ListValue* list) : is_dict(false), list(list) {} + + bool is_dict; + union { + DictionaryValue* dict; + ListValue* list; + }; + }; + std::vector stack_; + + // For maps, keys and values are alternating events, so we keep the + // key around and process it when the value arrives. + bool key_is_pending_ = false; + String pending_key_; +}; } // 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 - if (bytes[0] != cbor::InitialByteForEnvelope()) return nullptr; - - cbor::CBORTokenizer tokenizer(bytes); - if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr; - - // We checked for the envelope start byte above, so the tokenizer - // must agree here, since it's not an error. - DCHECK(tokenizer.TokenTag() == cbor::CBORTokenTag::ENVELOPE); - tokenizer.EnterEnvelope(); - // Error::MAP_START_EXPECTED - if (tokenizer.TokenTag() != cbor::CBORTokenTag::MAP_START) return nullptr; - std::unique_ptr result = parseMap(/*stack_depth=*/1, &tokenizer); - if (!result) return nullptr; - if (tokenizer.TokenTag() == cbor::CBORTokenTag::DONE) return result; - if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) return nullptr; - // Error::CBOR_TRAILING_JUNK + ValueParserHandler handler; + cbor::ParseCBOR(span(data, size), &handler); + // TODO(johannes): We have decent error info in handler.status(); provide + // a richer interface that makes this available to client code. + if (handler.status().ok()) + return handler.ReleaseRoot(); return nullptr; } @@ -246,13 +217,7 @@ bool Value::asBinary(Binary*) const return false; } -void Value::writeJSON(StringBuilder* output) const -{ - DCHECK(m_type == TypeNull); - StringUtil::builderAppend(*output, nullValueString, 4); -} - -void Value::writeBinary(std::vector* bytes) const { +void Value::AppendSerialized(std::vector* bytes) const { DCHECK(m_type == TypeNull); bytes->push_back(cbor::EncodeNull()); } @@ -262,24 +227,6 @@ std::unique_ptr Value::clone() const return Value::null(); } -String Value::toJSONString() const -{ - StringBuilder result; - StringUtil::builderReserve(result, 512); - writeJSON(&result); - 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) @@ -309,26 +256,7 @@ bool FundamentalValue::asInteger(int* output) const return true; } -void FundamentalValue::writeJSON(StringBuilder* output) const -{ - DCHECK(type() == TypeBoolean || type() == TypeInteger || type() == TypeDouble); - if (type() == TypeBoolean) { - if (m_boolValue) - StringUtil::builderAppend(*output, trueValueString, 4); - else - StringUtil::builderAppend(*output, falseValueString, 5); - } else if (type() == TypeDouble) { - if (!std::isfinite(m_doubleValue)) { - StringUtil::builderAppend(*output, nullValueString, 4); - return; - } - StringUtil::builderAppend(*output, StringUtil::fromDouble(m_doubleValue)); - } else if (type() == TypeInteger) { - StringUtil::builderAppend(*output, StringUtil::fromInteger(m_integerValue)); - } -} - -void FundamentalValue::writeBinary(std::vector* bytes) const { +void FundamentalValue::AppendSerialized(std::vector* bytes) const { switch (type()) { case TypeDouble: cbor::EncodeDouble(m_doubleValue, bytes); @@ -362,12 +290,6 @@ bool StringValue::asString(String* output) const return true; } -void StringValue::writeJSON(StringBuilder* output) const -{ - DCHECK(type() == TypeString); - StringUtil::builderAppendQuotedString(*output, m_stringValue); -} - namespace { // This routine distinguishes between the current encoding for a given // string |s|, and calls encoding routines that will @@ -396,8 +318,7 @@ void EncodeString(const String& s, std::vector* out) { } } } // namespace - -void StringValue::writeBinary(std::vector* bytes) const { +void StringValue::AppendSerialized(std::vector* bytes) const { EncodeString(m_stringValue, bytes); } @@ -412,13 +333,7 @@ bool BinaryValue::asBinary(Binary* output) const return true; } -void BinaryValue::writeJSON(StringBuilder* output) const -{ - DCHECK(type() == TypeBinary); - StringUtil::builderAppendQuotedString(*output, m_binaryValue.toBase64()); -} - -void BinaryValue::writeBinary(std::vector* bytes) const { +void BinaryValue::AppendSerialized(std::vector* bytes) const { cbor::EncodeBinary(span(m_binaryValue.data(), m_binaryValue.size()), bytes); } @@ -428,22 +343,6 @@ std::unique_ptr BinaryValue::clone() const return BinaryValue::create(m_binaryValue); } -void SerializedValue::writeJSON(StringBuilder* output) const -{ - DCHECK(type() == TypeSerialized); - 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 std::unique_ptr(new SerializedValue(m_serializedJSON, m_serializedBinary)); -} DictionaryValue::~DictionaryValue() { @@ -567,22 +466,7 @@ void DictionaryValue::remove(const String& name) m_order.erase(std::remove(m_order.begin(), m_order.end(), name), m_order.end()); } -void DictionaryValue::writeJSON(StringBuilder* output) const -{ - StringUtil::builderAppend(*output, '{'); - for (size_t i = 0; i < m_order.size(); ++i) { - Dictionary::const_iterator it = m_data.find(m_order[i]); - CHECK(it != m_data.end()); - if (i) - StringUtil::builderAppend(*output, ','); - StringUtil::builderAppendQuotedString(*output, it->first); - StringUtil::builderAppend(*output, ':'); - it->second->writeJSON(output); - } - StringUtil::builderAppend(*output, '}'); -} - -void DictionaryValue::writeBinary(std::vector* bytes) const { +void DictionaryValue::AppendSerialized(std::vector* bytes) const { cbor::EnvelopeEncoder encoder; encoder.EncodeStart(bytes); bytes->push_back(cbor::EncodeIndefiniteLengthMapStart()); @@ -591,7 +475,7 @@ void DictionaryValue::writeBinary(std::vector* bytes) const { Dictionary::const_iterator value = m_data.find(key); DCHECK(value != m_data.cend() && value->second); EncodeString(key, bytes); - value->second->writeBinary(bytes); + value->second->AppendSerialized(bytes); } bytes->push_back(cbor::EncodeStop()); encoder.EncodeStop(bytes); @@ -618,25 +502,12 @@ ListValue::~ListValue() { } -void ListValue::writeJSON(StringBuilder* output) const -{ - StringUtil::builderAppend(*output, '['); - bool first = true; - for (const std::unique_ptr& value : m_data) { - if (!first) - StringUtil::builderAppend(*output, ','); - value->writeJSON(output); - first = false; - } - StringUtil::builderAppend(*output, ']'); -} - -void ListValue::writeBinary(std::vector* bytes) const { +void ListValue::AppendSerialized(std::vector* bytes) const { cbor::EnvelopeEncoder encoder; encoder.EncodeStart(bytes); bytes->push_back(cbor::EncodeIndefiniteLengthArrayStart()); for (size_t i = 0; i < m_data.size(); ++i) { - m_data[i]->writeBinary(bytes); + m_data[i]->AppendSerialized(bytes); } bytes->push_back(cbor::EncodeStop()); encoder.EncodeStop(bytes); @@ -667,16 +538,6 @@ protocol::Value* ListValue::at(size_t index) return m_data[index].get(); } -void escapeLatinStringForJSON(const uint8_t* str, unsigned len, StringBuilder* dst) -{ - escapeStringForJSONInternal(str, len, dst); -} - -void escapeWideStringForJSON(const uint16_t* str, unsigned len, StringBuilder* dst) -{ - escapeStringForJSONInternal(str, len, dst); -} - {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} diff --git a/tools/inspector_protocol/lib/Values_h.template b/tools/inspector_protocol/lib/Values_h.template index 4a2e58f4cd6850..82080090098013 100644 --- a/tools/inspector_protocol/lib/Values_h.template +++ b/tools/inspector_protocol/lib/Values_h.template @@ -10,6 +10,8 @@ //#include "Allocator.h" //#include "Forward.h" +#include {{format_include(config.protocol.package, "Forward")}} + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} @@ -18,6 +20,11 @@ class ListValue; class DictionaryValue; class Value; +#define PROTOCOL_DISALLOW_COPY(ClassName) \ + private: \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + class {{config.lib.export_macro}} Value : public Serializable { PROTOCOL_DISALLOW_COPY(Value); public: @@ -39,7 +46,6 @@ public: TypeBinary, TypeObject, TypeArray, - TypeSerialized, TypeImported }; @@ -53,12 +59,8 @@ public: virtual bool asString(String* output) const; virtual bool asBinary(Binary* output) const; - virtual void writeJSON(StringBuilder* output) const; - virtual void writeBinary(std::vector* bytes) const; + virtual void AppendSerialized(std::vector* bytes) const override; virtual std::unique_ptr clone() const; - String toJSONString() const; - String serializeToJSON() override; - std::vector serializeToBinary() override; protected: Value() : m_type(TypeNull) { } @@ -91,8 +93,7 @@ public: bool asBoolean(bool* output) const override; 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; + void AppendSerialized(std::vector* bytes) const override; std::unique_ptr clone() const override; private: @@ -120,8 +121,7 @@ public: } bool asString(String* output) const override; - void writeJSON(StringBuilder* output) const override; - void writeBinary(std::vector* bytes) const override; + void AppendSerialized(std::vector* bytes) const override; std::unique_ptr clone() const override; private: @@ -139,8 +139,7 @@ public: } bool asBinary(Binary* output) const override; - void writeJSON(StringBuilder* output) const override; - void writeBinary(std::vector* bytes) const override; + void AppendSerialized(std::vector* bytes) const override; std::unique_ptr clone() const override; private: @@ -149,31 +148,6 @@ private: Binary m_binaryValue; }; -class {{config.lib.export_macro}} SerializedValue : public Value { -public: - static std::unique_ptr fromJSON(const String& value) - { - return std::unique_ptr(new SerializedValue(value)); - } - - 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& 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 { public: using Entry = std::pair; @@ -194,8 +168,7 @@ public: return std::unique_ptr(DictionaryValue::cast(value.release())); } - void writeJSON(StringBuilder* output) const override; - void writeBinary(std::vector* bytes) const override; + void AppendSerialized(std::vector* bytes) const override; std::unique_ptr clone() const override; size_t size() const { return m_data.size(); } @@ -263,23 +236,20 @@ public: ~ListValue() override; - void writeJSON(StringBuilder* output) const override; - void writeBinary(std::vector* bytes) const override; + void AppendSerialized(std::vector* bytes) const override; std::unique_ptr clone() const override; void pushValue(std::unique_ptr); Value* at(size_t index); size_t size() const { return m_data.size(); } + void reserve(size_t capacity) { m_data.reserve(capacity); } private: ListValue(); std::vector> m_data; }; -void escapeLatinStringForJSON(const uint8_t* str, unsigned len, StringBuilder* dst); -void escapeWideStringForJSON(const uint16_t* str, unsigned len, StringBuilder* dst); - {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} diff --git a/tools/inspector_protocol/lib/base_string_adapter_cc.template b/tools/inspector_protocol/lib/base_string_adapter_cc.template index 639b39bb520d85..e503f5c23e2f23 100644 --- a/tools/inspector_protocol/lib/base_string_adapter_cc.template +++ b/tools/inspector_protocol/lib/base_string_adapter_cc.template @@ -1,4 +1,4 @@ -// This file is generated by DispatcherBase_cpp.template. +// This file is generated by base_string_adapter_cc.template. // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -15,6 +15,14 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" +#include "{{config.crdtp.dir}}/cbor.h" +#include "{{config.crdtp.dir}}/protocol_core.h" + +using namespace {{config.crdtp.namespace}}; + +using {{"::".join(config.protocol.namespace)}}::Binary; +using {{"::".join(config.protocol.namespace)}}::String; +using {{"::".join(config.protocol.namespace)}}::StringUtil; {% for namespace in config.protocol.namespace %} namespace {{namespace}} { @@ -58,7 +66,7 @@ std::unique_ptr toProtocolValue( if (converted) result->pushValue(std::move(converted)); } - return std::move(result); + return result; } if (value->is_dict()) { const base::DictionaryValue* dictionary = nullptr; @@ -72,7 +80,7 @@ std::unique_ptr toProtocolValue( if (converted) result->setValue(it.key(), std::move(converted)); } - return std::move(result); + return result; } return nullptr; } @@ -111,7 +119,7 @@ std::unique_ptr toBaseValue(Value* value, int depth) { if (converted) result->Append(std::move(converted)); } - return std::move(result); + return result; } if (value->type() == Value::TypeObject) { DictionaryValue* dict = DictionaryValue::cast(value); @@ -123,73 +131,40 @@ std::unique_ptr toBaseValue(Value* value, int depth) { if (converted) result->SetWithoutPathExpansion(entry.first, std::move(converted)); } - return std::move(result); + return result; } return nullptr; } +// In Chromium, we do not support big endian architectures, so no conversion is needed +// to interpret UTF16LE. // 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::ReadDeprecated(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_; +String StringUtil::fromUTF16LE(const uint16_t* data, size_t length) { + std::string utf8; + base::UTF16ToUTF8(reinterpret_cast(data), length, &utf8); + return utf8; } -void StringBuilder::reserveCapacity(size_t capacity) { - string_.reserve(capacity); +bool StringUtil::ReadString(DeserializerState* state, String* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) { + const auto str = tokenizer->GetString8(); + *value = StringUtil::fromUTF8(str.data(), str.size()); + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) { + const auto str = tokenizer->GetString16WireRep(); + *value = StringUtil::fromUTF16LE(reinterpret_cast(str.data()), str.size() / 2); + return true; + } + state->RegisterError(Error::BINDINGS_STRING_VALUE_EXPECTED); + return false; } -// static -String StringUtil::fromUTF16(const uint16_t* data, size_t length) { - std::string utf8; - base::UTF16ToUTF8(reinterpret_cast(data), length, &utf8); - return utf8; +void StringUtil::WriteString(const String& str, std::vector* bytes) { + cbor::EncodeString8(span(StringUtil::CharactersUTF8(str), + StringUtil::CharacterCount(str)), + bytes); } Binary::Binary() : bytes_(new base::RefCountedBytes) {} @@ -197,6 +172,10 @@ Binary::Binary(const Binary& binary) : bytes_(binary.bytes_) {} Binary::Binary(scoped_refptr bytes) : bytes_(bytes) {} Binary::~Binary() {} +void Binary::AppendSerialized(std::vector* out) const { + crdtp::cbor::EncodeBinary(crdtp::span(data(), size()), out); +} + String Binary::toBase64() const { std::string encoded; base::Base64Encode( @@ -240,3 +219,31 @@ Binary Binary::fromSpan(const uint8_t* data, size_t size) { {% for namespace in config.protocol.namespace %} } // namespace {{namespace}} {% endfor %} + +namespace {{config.crdtp.namespace}} { + +// static +bool ProtocolTypeTraits::Deserialize(DeserializerState* state, Binary* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::BINARY) { + const span bin = tokenizer->GetBinary(); + *value = Binary::fromSpan(bin.data(), bin.size()); + return true; + } + if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) { + const auto str_span = tokenizer->GetString8(); + String str = StringUtil::fromUTF8(str_span.data(), str_span.size()); + bool success = false; + *value = Binary::fromBase64(str, &success); + return success; + } + state->RegisterError(Error::BINDINGS_BINARY_VALUE_EXPECTED); + return false; +} + +// static +void ProtocolTypeTraits::Serialize(const Binary& value, std::vector* bytes) { + value.AppendSerialized(bytes); +} + +} // namespace {{config.crdtp.namespace}} \ No newline at end of file diff --git a/tools/inspector_protocol/lib/base_string_adapter_h.template b/tools/inspector_protocol/lib/base_string_adapter_h.template index 8bf3c355c0e584..8c28ab033e26e6 100644 --- a/tools/inspector_protocol/lib/base_string_adapter_h.template +++ b/tools/inspector_protocol/lib/base_string_adapter_h.template @@ -1,4 +1,4 @@ -// This file is generated by Parser_h.template. +// This file is generated by base_string_adapter_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 @@ -14,7 +14,9 @@ #include "base/logging.h" #include "base/macros.h" #include "base/memory/ref_counted_memory.h" -#include "base/strings/string_number_conversions.h" +#include "{{config.crdtp.dir}}/serializable.h" +#include "{{config.crdtp.dir}}/protocol_core.h" + {% if config.lib.export_header %} #include "{{config.lib.export_header}}" {% endif %} @@ -23,6 +25,10 @@ namespace base { class Value; } +namespace {{config.crdtp.namespace}} { +class DeserializerState; +} + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} @@ -30,77 +36,14 @@ namespace {{namespace}} { class Value; using String = std::string; -using ProtocolMessage = std::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); } - static String fromUTF16(const uint16_t* data, size_t length); + static String fromUTF16LE(const uint16_t* data, size_t length); static const uint8_t* CharactersLatin1(const String& s) { return nullptr; } static const uint8_t* CharactersUTF8(const String& s) { @@ -108,15 +51,21 @@ class {{config.lib.export_macro}} StringUtil { } static const uint16_t* CharactersUTF16(const String& s) { return nullptr; } static size_t CharacterCount(const String& s) { return s.size(); } + + static bool ReadString({{config.crdtp.namespace}}::DeserializerState* state, String* str); + static void WriteString(const String& str, std::vector* bytes); }; // A read-only sequence of uninterpreted bytes with reference-counted storage. -class {{config.lib.export_macro}} Binary { +class {{config.lib.export_macro}} Binary : public {{config.crdtp.namespace}}::Serializable { public: Binary(const Binary&); Binary(); ~Binary(); + // Implements Serializable. + void AppendSerialized(std::vector* out) const override; + const uint8_t* data() const { return bytes_->front(); } size_t size() const { return bytes_->size(); } scoped_refptr bytes() const { return bytes_; } @@ -140,4 +89,29 @@ std::unique_ptr toBaseValue(Value* value, int depth); } // namespace {{namespace}} {% endfor %} +namespace {{config.crdtp.namespace}} { + +template <> +struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::String> { + static bool Deserialize(DeserializerState* state, {{"::".join(config.protocol.namespace)}}::String* value) { + return {{"::".join(config.protocol.namespace)}}::StringUtil::ReadString(state, value); + } + static void Serialize(const {{"::".join(config.protocol.namespace)}}::String& value, std::vector* bytes) { + {{"::".join(config.protocol.namespace)}}::StringUtil::WriteString(value, bytes); + } +}; + +template <> +struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::Binary> { + static bool Deserialize(DeserializerState* state, {{"::".join(config.protocol.namespace)}}::Binary* value); + static void Serialize(const {{"::".join(config.protocol.namespace)}}::Binary& value, std::vector* bytes); +}; + +template <> +struct {{config.crdtp.namespace}}::detail::MaybeTypedef<{{"::".join(config.protocol.namespace)}}::Binary> { + typedef ValueMaybe<{{"::".join(config.protocol.namespace)}}::Binary> type; +}; + +} // {{config.crdtp.namespace}} + #endif // !defined({{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H) diff --git a/tools/inspector_protocol/lib/encoding_cpp.template b/tools/inspector_protocol/lib/encoding_cpp.template deleted file mode 100644 index 54a46ecd203d4d..00000000000000 --- a/tools/inspector_protocol/lib/encoding_cpp.template +++ /dev/null @@ -1,2199 +0,0 @@ -{# This template is generated by gen_cbor_templates.py. #} -// Generated by lib/encoding_cpp.template. - -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - - -#include -#include -#include -#include -#include -#include - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -// ===== encoding/encoding.cc ===== - -// ============================================================================= -// Status and Error codes -// ============================================================================= - -std::string Status::ToASCIIString() const { - switch (error) { - case Error::OK: - return "OK"; - case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS: - return ToASCIIString("JSON: unprocessed input remains"); - case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED: - return ToASCIIString("JSON: stack limit exceeded"); - case Error::JSON_PARSER_NO_INPUT: - return ToASCIIString("JSON: no input"); - case Error::JSON_PARSER_INVALID_TOKEN: - return ToASCIIString("JSON: invalid token"); - case Error::JSON_PARSER_INVALID_NUMBER: - return ToASCIIString("JSON: invalid number"); - case Error::JSON_PARSER_INVALID_STRING: - return ToASCIIString("JSON: invalid string"); - case Error::JSON_PARSER_UNEXPECTED_ARRAY_END: - return ToASCIIString("JSON: unexpected array end"); - case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED: - return ToASCIIString("JSON: comma or array end expected"); - case Error::JSON_PARSER_STRING_LITERAL_EXPECTED: - return ToASCIIString("JSON: string literal expected"); - case Error::JSON_PARSER_COLON_EXPECTED: - return ToASCIIString("JSON: colon expected"); - case Error::JSON_PARSER_UNEXPECTED_MAP_END: - return ToASCIIString("JSON: unexpected map end"); - case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED: - return ToASCIIString("JSON: comma or map end expected"); - case Error::JSON_PARSER_VALUE_EXPECTED: - return ToASCIIString("JSON: value expected"); - - case Error::CBOR_INVALID_INT32: - return ToASCIIString("CBOR: invalid int32"); - case Error::CBOR_INVALID_DOUBLE: - return ToASCIIString("CBOR: invalid double"); - case Error::CBOR_INVALID_ENVELOPE: - return ToASCIIString("CBOR: invalid envelope"); - case Error::CBOR_INVALID_STRING8: - return ToASCIIString("CBOR: invalid string8"); - case Error::CBOR_INVALID_STRING16: - return ToASCIIString("CBOR: invalid string16"); - case Error::CBOR_INVALID_BINARY: - return ToASCIIString("CBOR: invalid binary"); - case Error::CBOR_UNSUPPORTED_VALUE: - return ToASCIIString("CBOR: unsupported value"); - case Error::CBOR_NO_INPUT: - return ToASCIIString("CBOR: no input"); - case Error::CBOR_INVALID_START_BYTE: - return ToASCIIString("CBOR: invalid start byte"); - case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE: - return ToASCIIString("CBOR: unexpected eof expected value"); - case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY: - return ToASCIIString("CBOR: unexpected eof in array"); - case Error::CBOR_UNEXPECTED_EOF_IN_MAP: - return ToASCIIString("CBOR: unexpected eof in map"); - case Error::CBOR_INVALID_MAP_KEY: - return ToASCIIString("CBOR: invalid map key"); - case Error::CBOR_STACK_LIMIT_EXCEEDED: - return ToASCIIString("CBOR: stack limit exceeded"); - case Error::CBOR_TRAILING_JUNK: - return ToASCIIString("CBOR: trailing junk"); - case Error::CBOR_MAP_START_EXPECTED: - return ToASCIIString("CBOR: map start expected"); - case Error::CBOR_MAP_STOP_EXPECTED: - return ToASCIIString("CBOR: map stop expected"); - case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED: - return ToASCIIString("CBOR: envelope size limit exceeded"); - } - // Some compilers can't figure out that we can't get here. - return "INVALID ERROR CODE"; -} - -std::string Status::ToASCIIString(const char* msg) const { - return std::string(msg) + " at position " + std::to_string(pos); -} - -namespace cbor { -namespace { -// Indicates the number of bits the "initial byte" needs to be shifted to the -// right after applying |kMajorTypeMask| to produce the major type in the -// lowermost bits. -static constexpr uint8_t kMajorTypeBitShift = 5u; -// Mask selecting the low-order 5 bits of the "initial byte", which is where -// the additional information is encoded. -static constexpr uint8_t kAdditionalInformationMask = 0x1f; -// Mask selecting the high-order 3 bits of the "initial byte", which indicates -// the major type of the encoded value. -static constexpr uint8_t kMajorTypeMask = 0xe0; -// Indicates the integer is in the following byte. -static constexpr uint8_t kAdditionalInformation1Byte = 24u; -// Indicates the integer is in the next 2 bytes. -static constexpr uint8_t kAdditionalInformation2Bytes = 25u; -// Indicates the integer is in the next 4 bytes. -static constexpr uint8_t kAdditionalInformation4Bytes = 26u; -// Indicates the integer is in the next 8 bytes. -static constexpr uint8_t kAdditionalInformation8Bytes = 27u; - -// Encodes the initial byte, consisting of the |type| in the first 3 bits -// followed by 5 bits of |additional_info|. -constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) { - return (static_cast(type) << kMajorTypeBitShift) | - (additional_info & kAdditionalInformationMask); -} - -// TAG 24 indicates that what follows is a byte string which is -// encoded in CBOR format. We use this as a wrapper for -// maps and arrays, allowing us to skip them, because the -// byte string carries its size (byte length). -// https://tools.ietf.org/html/rfc7049#section-2.4.4.1 -static constexpr uint8_t kInitialByteForEnvelope = - EncodeInitialByte(MajorType::TAG, 24); -// The initial byte for a byte string with at most 2^32 bytes -// of payload. This is used for envelope encoding, even if -// the byte string is shorter. -static constexpr uint8_t kInitialByteFor32BitLengthByteString = - EncodeInitialByte(MajorType::BYTE_STRING, 26); - -// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional -// info = 31. -static constexpr uint8_t kInitialByteIndefiniteLengthArray = - EncodeInitialByte(MajorType::ARRAY, 31); -static constexpr uint8_t kInitialByteIndefiniteLengthMap = - EncodeInitialByte(MajorType::MAP, 31); -// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite -// length maps / arrays. -static constexpr uint8_t kStopByte = - EncodeInitialByte(MajorType::SIMPLE_VALUE, 31); - -// See RFC 7049 Section 2.3, Table 2. -static constexpr uint8_t kEncodedTrue = - 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); - -// 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); - -// 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, C* out) { - for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes) - out->push_back(0xff & (v >> (shift_bytes * 8))); -} - -// 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(in.size() >= sizeof(T)); - T result = 0; - for (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 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. -// TODO(johannes): change return type to size_t and use 0 for error. -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 (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 (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 (bytes.size() < 1 + sizeof(uint64_t)) - return -1; - *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); - return 9; - } - return -1; -} - -// 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. -template -void WriteTokenStartTmpl(MajorType type, uint64_t value, C* 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); -} -void WriteTokenStart(MajorType type, - uint64_t value, - std::vector* encoded) { - WriteTokenStartTmpl(type, value, encoded); -} -void WriteTokenStart(MajorType type, uint64_t value, std::string* encoded) { - WriteTokenStartTmpl(type, value, encoded); -} -} // namespace internals - -// ============================================================================= -// Detecting CBOR content -// ============================================================================= - -uint8_t InitialByteForEnvelope() { - return kInitialByteForEnvelope; -} -uint8_t InitialByteFor32BitLengthByteString() { - return kInitialByteFor32BitLengthByteString; -} -bool IsCBORMessage(span msg) { - return msg.size() >= 6 && msg[0] == InitialByteForEnvelope() && - msg[1] == InitialByteFor32BitLengthByteString(); -} - -// ============================================================================= -// Encoding invidiual CBOR items -// ============================================================================= - -uint8_t EncodeTrue() { - return kEncodedTrue; -} -uint8_t EncodeFalse() { - return kEncodedFalse; -} -uint8_t EncodeNull() { - return kEncodedNull; -} - -uint8_t EncodeIndefiniteLengthArrayStart() { - return kInitialByteIndefiniteLengthArray; -} - -uint8_t EncodeIndefiniteLengthMapStart() { - return kInitialByteIndefiniteLengthMap; -} - -uint8_t EncodeStop() { - return kStopByte; -} - -template -void EncodeInt32Tmpl(int32_t value, C* out) { - if (value >= 0) { - internals::WriteTokenStart(MajorType::UNSIGNED, value, out); - } else { - uint64_t representation = static_cast(-(value + 1)); - internals::WriteTokenStart(MajorType::NEGATIVE, representation, out); - } -} -void EncodeInt32(int32_t value, std::vector* out) { - EncodeInt32Tmpl(value, out); -} -void EncodeInt32(int32_t value, std::string* out) { - EncodeInt32Tmpl(value, out); -} - -template -void EncodeString16Tmpl(span in, C* out) { - uint64_t byte_length = static_cast(in.size_bytes()); - internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); - // When emitting UTF16 characters, we always write the least significant byte - // first; this is because it's the native representation for X86. - // TODO(johannes): Implement a more efficient thing here later, e.g. - // 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 EncodeString16(span in, std::vector* out) { - EncodeString16Tmpl(in, out); -} -void EncodeString16(span in, std::string* out) { - EncodeString16Tmpl(in, out); -} - -template -void EncodeString8Tmpl(span in, C* out) { - internals::WriteTokenStart(MajorType::STRING, - static_cast(in.size_bytes()), out); - out->insert(out->end(), in.begin(), in.end()); -} -void EncodeString8(span in, std::vector* out) { - EncodeString8Tmpl(in, out); -} -void EncodeString8(span in, std::string* out) { - EncodeString8Tmpl(in, out); -} - -template -void EncodeFromLatin1Tmpl(span latin1, C* out) { - for (size_t ii = 0; ii < latin1.size(); ++ii) { - if (latin1[ii] <= 127) - continue; - // If there's at least one non-ASCII char, convert to UTF8. - std::vector utf8(latin1.begin(), latin1.begin() + ii); - for (; ii < latin1.size(); ++ii) { - if (latin1[ii] <= 127) { - utf8.push_back(latin1[ii]); - } else { - // 0xC0 means it's a UTF8 sequence with 2 bytes. - utf8.push_back((latin1[ii] >> 6) | 0xc0); - utf8.push_back((latin1[ii] | 0x80) & 0xbf); - } - } - EncodeString8(SpanFrom(utf8), out); - return; - } - EncodeString8(latin1, out); -} -void EncodeFromLatin1(span latin1, std::vector* out) { - EncodeFromLatin1Tmpl(latin1, out); -} -void EncodeFromLatin1(span latin1, std::string* out) { - EncodeFromLatin1Tmpl(latin1, out); -} - -template -void EncodeFromUTF16Tmpl(span utf16, C* out) { - // If there's at least one non-ASCII char, encode as STRING16 (UTF16). - for (uint16_t ch : utf16) { - if (ch <= 127) - continue; - EncodeString16(utf16, out); - return; - } - // It's all US-ASCII, strip out every second byte and encode as UTF8. - internals::WriteTokenStart(MajorType::STRING, - static_cast(utf16.size()), out); - out->insert(out->end(), utf16.begin(), utf16.end()); -} -void EncodeFromUTF16(span utf16, std::vector* out) { - EncodeFromUTF16Tmpl(utf16, out); -} -void EncodeFromUTF16(span utf16, std::string* out) { - EncodeFromUTF16Tmpl(utf16, out); -} - -template -void EncodeBinaryTmpl(span in, C* out) { - out->push_back(kExpectedConversionToBase64Tag); - uint64_t byte_length = static_cast(in.size_bytes()); - internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); - out->insert(out->end(), in.begin(), in.end()); -} -void EncodeBinary(span in, std::vector* out) { - EncodeBinaryTmpl(in, out); -} -void EncodeBinary(span in, std::string* out) { - EncodeBinaryTmpl(in, out); -} - -// A double is encoded with a specific initial byte -// (kInitialByteForDouble) plus the 64 bits of payload for its value. -constexpr size_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 size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t); - -template -void EncodeDoubleTmpl(double value, C* 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 EncodeDouble(double value, std::vector* out) { - EncodeDoubleTmpl(value, out); -} -void EncodeDouble(double value, std::string* out) { - EncodeDoubleTmpl(value, out); -} - -// ============================================================================= -// cbor::EnvelopeEncoder - for wrapping submessages -// ============================================================================= - -template -void EncodeStartTmpl(C* out, size_t* byte_size_pos) { - 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)); -} - -void EnvelopeEncoder::EncodeStart(std::vector* out) { - EncodeStartTmpl>(out, &byte_size_pos_); -} - -void EnvelopeEncoder::EncodeStart(std::string* out) { - EncodeStartTmpl(out, &byte_size_pos_); -} - -template -bool EncodeStopTmpl(C* out, size_t* byte_size_pos) { - 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; -} - -bool EnvelopeEncoder::EncodeStop(std::vector* out) { - return EncodeStopTmpl(out, &byte_size_pos_); -} - -bool EnvelopeEncoder::EncodeStop(std::string* out) { - return EncodeStopTmpl(out, &byte_size_pos_); -} - -// ============================================================================= -// cbor::NewCBOREncoder - for encoding from a streaming parser -// ============================================================================= - -namespace { -template -class CBOREncoder : public StreamingParserHandler { - public: - CBOREncoder(C* out, Status* status) : out_(out), status_(status) { - *status_ = Status(); - } - - void HandleMapBegin() override { - if (!status_->ok()) - return; - envelopes_.emplace_back(); - envelopes_.back().EncodeStart(out_); - out_->push_back(kInitialByteIndefiniteLengthMap); - } - - void HandleMapEnd() override { - if (!status_->ok()) - return; - out_->push_back(kStopByte); - assert(!envelopes_.empty()); - if (!envelopes_.back().EncodeStop(out_)) { - HandleError( - Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size())); - return; - } - envelopes_.pop_back(); - } - - void HandleArrayBegin() override { - if (!status_->ok()) - return; - envelopes_.emplace_back(); - envelopes_.back().EncodeStart(out_); - out_->push_back(kInitialByteIndefiniteLengthArray); - } - - void HandleArrayEnd() override { - if (!status_->ok()) - return; - out_->push_back(kStopByte); - assert(!envelopes_.empty()); - if (!envelopes_.back().EncodeStop(out_)) { - HandleError( - Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, out_->size())); - return; - } - envelopes_.pop_back(); - } - - void HandleString8(span chars) override { - if (!status_->ok()) - return; - EncodeString8(chars, out_); - } - - void HandleString16(span chars) override { - if (!status_->ok()) - return; - EncodeFromUTF16(chars, out_); - } - - void HandleBinary(span bytes) override { - if (!status_->ok()) - return; - EncodeBinary(bytes, out_); - } - - void HandleDouble(double value) override { - if (!status_->ok()) - return; - EncodeDouble(value, out_); - } - - void HandleInt32(int32_t value) override { - if (!status_->ok()) - return; - EncodeInt32(value, out_); - } - - void HandleBool(bool value) override { - if (!status_->ok()) - return; - // See RFC 7049 Section 2.3, Table 2. - out_->push_back(value ? kEncodedTrue : kEncodedFalse); - } - - void HandleNull() override { - if (!status_->ok()) - return; - // See RFC 7049 Section 2.3, Table 2. - out_->push_back(kEncodedNull); - } - - void HandleError(Status error) override { - if (!status_->ok()) - return; - *status_ = error; - out_->clear(); - } - - private: - C* out_; - std::vector envelopes_; - Status* status_; -}; -} // namespace - -std::unique_ptr NewCBOREncoder( - std::vector* out, - Status* status) { - return std::unique_ptr( - new CBOREncoder>(out, status)); -} -std::unique_ptr NewCBOREncoder(std::string* out, - Status* status) { - return std::unique_ptr( - new CBOREncoder(out, status)); -} - -// ============================================================================= -// cbor::CBORTokenizer - for parsing individual CBOR items -// ============================================================================= - -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_; -} - -// The following accessor functions ::GetInt32, ::GetDouble, -// ::GetString8, ::GetString16WireRep, ::GetBinary, ::GetEnvelopeContents -// assume that a particular token was recognized in ::ReadNextToken. -// That's where all the error checking is done. By design, -// the accessors (assuming the token was recognized) never produce -// an error. - -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); -} - -span CBORTokenizer::GetEnvelopeContents() const { - assert(token_tag_ == CBORTokenTag::ENVELOPE); - auto length = static_cast(token_start_internal_value_); - return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length); -} - -// All error checking happens in ::ReadNextToken, so that the accessors -// can avoid having to carry an error return value. -// -// With respect to checking the encoded lengths of strings, arrays, etc: -// On the wire, CBOR uses 1,2,4, and 8 byte unsigned integers, so -// we initially read them as uint64_t, usually into token_start_internal_value_. -// -// However, since these containers have a representation on the machine, -// we need to do corresponding size computations on the input byte array, -// output span (e.g. the payload for a string), etc., and size_t is -// machine specific (in practice either 32 bit or 64 bit). -// -// Further, we must avoid overflowing size_t. Therefore, we use this -// kMaxValidLength constant to: -// - Reject values that are larger than the architecture specific -// max size_t (differs between 32 bit and 64 bit arch). -// - Reserve at least one bit so that we can check against overflows -// when adding lengths (array / string length / etc.); we do this by -// ensuring that the inputs to an addition are <= kMaxValidLength, -// and then checking whether the sum went past it. -// -// See also -// https://chromium.googlesource.com/chromium/src/+/master/docs/security/integer-semantics.md -static const uint64_t kMaxValidLength = - std::min(std::numeric_limits::max() >> 2, - std::numeric_limits::max()); - -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; - } - const size_t remaining_bytes = bytes_.size() - status_.pos; - 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 - const int8_t bytes_read = internals::ReadTokenStart( - bytes_.subspan(status_.pos + 1), &token_start_type_, - &token_start_internal_value_); - if (bytes_read < 0 || token_start_type_ != MajorType::BYTE_STRING || - token_start_internal_value_ > kMaxValidLength) { - SetError(Error::CBOR_INVALID_BINARY); - return; - } - const uint64_t token_byte_length = token_start_internal_value_ + - /* tag before token start: */ 1 + - /* token start: */ bytes_read; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_BINARY); - return; - } - SetToken(CBORTokenTag::BINARY, static_cast(token_byte_length)); - return; - } - case kInitialByteForDouble: { // DOUBLE - if (kEncodedDoubleSize > remaining_bytes) { - SetError(Error::CBOR_INVALID_DOUBLE); - return; - } - SetToken(CBORTokenTag::DOUBLE, kEncodedDoubleSize); - return; - } - case kInitialByteForEnvelope: { // ENVELOPE - if (kEncodedEnvelopeHeaderSize > remaining_bytes) { - 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)); - if (token_start_internal_value_ > kMaxValidLength) { - SetError(Error::CBOR_INVALID_ENVELOPE); - return; - } - uint64_t token_byte_length = - token_start_internal_value_ + kEncodedEnvelopeHeaderSize; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_ENVELOPE); - return; - } - SetToken(CBORTokenTag::ENVELOPE, static_cast(token_byte_length)); - return; - } - default: { - const int8_t token_start_length = internals::ReadTokenStart( - bytes_.subspan(status_.pos), &token_start_type_, - &token_start_internal_value_); - const bool success = token_start_length >= 0; - switch (token_start_type_) { - case MajorType::UNSIGNED: // INT32. - // INT32 is a signed int32 (int32 makes sense for the - // inspector_protocol, it's not a CBOR limitation), so we check - // against the signed max, so that the allowable values are - // 0, 1, 2, ... 2^31 - 1. - 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. - // INT32 is a signed int32 (int32 makes sense for the - // inspector_protocol, it's not a CBOR limitation); in CBOR, - // the negative values for INT32 are represented as NEGATIVE, - // that is, -1 INT32 is represented as 1 << 5 | 0 (major type 1, - // additional info value 0). So here, we compute the INT32 value - // and then check it against the INT32 min. - int64_t actual_value = - -static_cast(token_start_internal_value_) - 1; - if (!success || actual_value < std::numeric_limits::min()) { - SetError(Error::CBOR_INVALID_INT32); - return; - } - SetToken(CBORTokenTag::INT32, token_start_length); - return; - } - case MajorType::STRING: { // STRING8. - if (!success || token_start_internal_value_ > kMaxValidLength) { - SetError(Error::CBOR_INVALID_STRING8); - return; - } - uint64_t token_byte_length = - token_start_internal_value_ + token_start_length; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_STRING8); - return; - } - SetToken(CBORTokenTag::STRING8, - static_cast(token_byte_length)); - return; - } - case MajorType::BYTE_STRING: { // STRING16. - // Length must be divisible by 2 since UTF16 is 2 bytes per - // character, hence the &1 check. - if (!success || token_start_internal_value_ > kMaxValidLength || - token_start_internal_value_ & 1) { - SetError(Error::CBOR_INVALID_STRING16); - return; - } - uint64_t token_byte_length = - token_start_internal_value_ + token_start_length; - if (token_byte_length > remaining_bytes) { - SetError(Error::CBOR_INVALID_STRING16); - return; - } - SetToken(CBORTokenTag::STRING16, - static_cast(token_byte_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, size_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; -} - -// ============================================================================= -// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages -// ============================================================================= - -namespace { -// When parsing CBOR, we limit recursion depth for objects and arrays -// to this constant. -static constexpr int kStackLimit = 300; - -// Below are three parsing routines for CBOR, which cover enough -// to roundtrip JSON messages. -bool ParseMap(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out); -bool ParseArray(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out); -bool ParseValue(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out); - -void ParseUTF16String(CBORTokenizer* tokenizer, StreamingParserHandler* out) { - std::vector value; - span rep = tokenizer->GetString16WireRep(); - for (size_t ii = 0; ii < rep.size(); ii += 2) - value.push_back((rep[ii + 1] << 8) | rep[ii]); - out->HandleString16(span(value.data(), value.size())); - tokenizer->Next(); -} - -bool ParseUTF8String(CBORTokenizer* tokenizer, StreamingParserHandler* out) { - assert(tokenizer->TokenTag() == CBORTokenTag::STRING8); - out->HandleString8(tokenizer->GetString8()); - tokenizer->Next(); - return true; -} - -bool ParseValue(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out) { - if (stack_depth > kStackLimit) { - out->HandleError( - Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos}); - return false; - } - // Skip past the envelope to get to what's inside. - if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE) - tokenizer->EnterEnvelope(); - switch (tokenizer->TokenTag()) { - case CBORTokenTag::ERROR_VALUE: - out->HandleError(tokenizer->Status()); - return false; - case CBORTokenTag::DONE: - out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, - tokenizer->Status().pos}); - return false; - case CBORTokenTag::TRUE_VALUE: - out->HandleBool(true); - tokenizer->Next(); - return true; - case CBORTokenTag::FALSE_VALUE: - out->HandleBool(false); - tokenizer->Next(); - return true; - case CBORTokenTag::NULL_VALUE: - out->HandleNull(); - tokenizer->Next(); - return true; - case CBORTokenTag::INT32: - out->HandleInt32(tokenizer->GetInt32()); - tokenizer->Next(); - return true; - case CBORTokenTag::DOUBLE: - out->HandleDouble(tokenizer->GetDouble()); - tokenizer->Next(); - return true; - case CBORTokenTag::STRING8: - return ParseUTF8String(tokenizer, out); - case CBORTokenTag::STRING16: - ParseUTF16String(tokenizer, out); - return true; - case CBORTokenTag::BINARY: { - out->HandleBinary(tokenizer->GetBinary()); - tokenizer->Next(); - return true; - } - case CBORTokenTag::MAP_START: - return ParseMap(stack_depth + 1, tokenizer, out); - case CBORTokenTag::ARRAY_START: - return ParseArray(stack_depth + 1, tokenizer, out); - default: - out->HandleError( - Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos}); - return false; - } -} - -// |bytes| must start with the indefinite length array byte, so basically, -// ParseArray may only be called after an indefinite length array has been -// detected. -bool ParseArray(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out) { - assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START); - tokenizer->Next(); - out->HandleArrayBegin(); - while (tokenizer->TokenTag() != CBORTokenTag::STOP) { - if (tokenizer->TokenTag() == CBORTokenTag::DONE) { - out->HandleError( - Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos}); - return false; - } - if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer->Status()); - return false; - } - // Parse value. - if (!ParseValue(stack_depth, tokenizer, out)) - return false; - } - out->HandleArrayEnd(); - tokenizer->Next(); - return true; -} - -// |bytes| must start with the indefinite length array byte, so basically, -// ParseArray may only be called after an indefinite length array has been -// detected. -bool ParseMap(int32_t stack_depth, - CBORTokenizer* tokenizer, - StreamingParserHandler* out) { - assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START); - out->HandleMapBegin(); - tokenizer->Next(); - while (tokenizer->TokenTag() != CBORTokenTag::STOP) { - if (tokenizer->TokenTag() == CBORTokenTag::DONE) { - out->HandleError( - Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos}); - return false; - } - if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer->Status()); - return false; - } - // Parse key. - if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { - if (!ParseUTF8String(tokenizer, out)) - return false; - } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { - ParseUTF16String(tokenizer, out); - } else { - out->HandleError( - Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos}); - return false; - } - // Parse value. - if (!ParseValue(stack_depth, tokenizer, out)) - return false; - } - out->HandleMapEnd(); - tokenizer->Next(); - return true; -} -} // namespace - -void ParseCBOR(span bytes, StreamingParserHandler* out) { - if (bytes.empty()) { - out->HandleError(Status{Error::CBOR_NO_INPUT, 0}); - return; - } - if (bytes[0] != kInitialByteForEnvelope) { - out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0}); - return; - } - CBORTokenizer tokenizer(bytes); - if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer.Status()); - return; - } - // We checked for the envelope start byte above, so the tokenizer - // must agree here, since it's not an error. - assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE); - tokenizer.EnterEnvelope(); - if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) { - out->HandleError( - Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos}); - return; - } - if (!ParseMap(/*stack_depth=*/1, &tokenizer, out)) - return; - if (tokenizer.TokenTag() == CBORTokenTag::DONE) - return; - if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { - out->HandleError(tokenizer.Status()); - return; - } - out->HandleError(Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos}); -} - -// ============================================================================= -// cbor::AppendString8EntryToMap - for limited in-place editing of messages -// ============================================================================= - -template -Status AppendString8EntryToCBORMapTmpl(span string8_key, - span string8_value, - C* cbor) { - // Careful below: Don't compare (*cbor)[idx] with a uint8_t, since - // it could be a char (signed!). Instead, use bytes. - span bytes(reinterpret_cast(cbor->data()), - cbor->size()); - CBORTokenizer tokenizer(bytes); - if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) - return tokenizer.Status(); - if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE) - return Status(Error::CBOR_INVALID_ENVELOPE, 0); - size_t envelope_size = tokenizer.GetEnvelopeContents().size(); - size_t old_size = cbor->size(); - if (old_size != envelope_size + kEncodedEnvelopeHeaderSize) - return Status(Error::CBOR_INVALID_ENVELOPE, 0); - if (envelope_size == 0 || - (tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart())) - return Status(Error::CBOR_MAP_START_EXPECTED, kEncodedEnvelopeHeaderSize); - if (bytes[bytes.size() - 1] != EncodeStop()) - return Status(Error::CBOR_MAP_STOP_EXPECTED, cbor->size() - 1); - cbor->pop_back(); - EncodeString8(string8_key, cbor); - EncodeString8(string8_value, cbor); - cbor->push_back(EncodeStop()); - size_t new_envelope_size = envelope_size + (cbor->size() - old_size); - if (new_envelope_size > std::numeric_limits::max()) - return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0); - size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t); - uint8_t* out = reinterpret_cast(&cbor->at(size_pos)); - *(out++) = (new_envelope_size >> 24) & 0xff; - *(out++) = (new_envelope_size >> 16) & 0xff; - *(out++) = (new_envelope_size >> 8) & 0xff; - *(out) = new_envelope_size & 0xff; - return Status(); -} -Status AppendString8EntryToCBORMap(span string8_key, - span string8_value, - std::vector* cbor) { - return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor); -} -Status AppendString8EntryToCBORMap(span string8_key, - span string8_value, - std::string* cbor) { - return AppendString8EntryToCBORMapTmpl(string8_key, string8_value, cbor); -} -} // namespace cbor - -namespace json { - -// ============================================================================= -// json::NewJSONEncoder - for encoding streaming parser events as JSON -// ============================================================================= - -namespace { -// Prints |value| to |out| with 4 hex digits, most significant chunk first. -template -void PrintHex(uint16_t value, C* out) { - for (int ii = 3; ii >= 0; --ii) { - int four_bits = 0xf & (value >> (4 * ii)); - out->push_back(four_bits + ((four_bits <= 9) ? '0' : ('a' - 10))); - } -} - -// In the writer below, we maintain a stack of State instances. -// It is just enough to emit the appropriate delimiters and brackets -// in JSON. -enum class Container { - // Used for the top-level, initial state. - NONE, - // Inside a JSON object. - MAP, - // Inside a JSON array. - ARRAY -}; -class State { - public: - explicit State(Container container) : container_(container) {} - void StartElement(std::vector* out) { StartElementTmpl(out); } - void StartElement(std::string* out) { StartElementTmpl(out); } - Container container() const { return container_; } - - private: - template - void StartElementTmpl(C* out) { - assert(container_ != Container::NONE || size_ == 0); - if (size_ != 0) { - char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':'; - out->push_back(delim); - } - ++size_; - } - - Container container_ = Container::NONE; - int size_ = 0; -}; - -constexpr char kBase64Table[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz0123456789+/"; - -template -void Base64Encode(const span& in, C* out) { - // The following three cases are based on the tables in the example - // section in https://en.wikipedia.org/wiki/Base64. We process three - // input bytes at a time, emitting 4 output bytes at a time. - size_t ii = 0; - - // While possible, process three input bytes. - for (; ii + 3 <= in.size(); ii += 3) { - uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8) | in[ii + 2]; - out->push_back(kBase64Table[(twentyfour_bits >> 18)]); - out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); - out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]); - out->push_back(kBase64Table[twentyfour_bits & 0x3f]); - } - if (ii + 2 <= in.size()) { // Process two input bytes. - uint32_t twentyfour_bits = (in[ii] << 16) | (in[ii + 1] << 8); - out->push_back(kBase64Table[(twentyfour_bits >> 18)]); - out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); - out->push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]); - out->push_back('='); // Emit padding. - return; - } - if (ii + 1 <= in.size()) { // Process a single input byte. - uint32_t twentyfour_bits = (in[ii] << 16); - out->push_back(kBase64Table[(twentyfour_bits >> 18)]); - out->push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]); - out->push_back('='); // Emit padding. - out->push_back('='); // Emit padding. - } -} - -// Implements a handler for JSON parser events to emit a JSON string. -template -class JSONEncoder : public StreamingParserHandler { - public: - JSONEncoder(const Platform* platform, C* out, Status* status) - : platform_(platform), out_(out), status_(status) { - *status_ = Status(); - state_.emplace(Container::NONE); - } - - void HandleMapBegin() override { - if (!status_->ok()) - return; - assert(!state_.empty()); - state_.top().StartElement(out_); - state_.emplace(Container::MAP); - Emit('{'); - } - - void HandleMapEnd() override { - if (!status_->ok()) - return; - assert(state_.size() >= 2 && state_.top().container() == Container::MAP); - state_.pop(); - Emit('}'); - } - - void HandleArrayBegin() override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - state_.emplace(Container::ARRAY); - Emit('['); - } - - void HandleArrayEnd() override { - if (!status_->ok()) - return; - assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY); - state_.pop(); - Emit(']'); - } - - void HandleString16(span chars) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit('"'); - for (const uint16_t ch : chars) { - if (ch == '"') { - Emit("\\\""); - } else if (ch == '\\') { - Emit("\\\\"); - } else if (ch == '\b') { - Emit("\\b"); - } else if (ch == '\f') { - Emit("\\f"); - } else if (ch == '\n') { - Emit("\\n"); - } else if (ch == '\r') { - Emit("\\r"); - } else if (ch == '\t') { - Emit("\\t"); - } else if (ch >= 32 && ch <= 126) { - Emit(ch); - } else { - Emit("\\u"); - PrintHex(ch, out_); - } - } - Emit('"'); - } - - void HandleString8(span chars) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit('"'); - for (size_t ii = 0; ii < chars.size(); ++ii) { - uint8_t c = chars[ii]; - if (c == '"') { - Emit("\\\""); - } else if (c == '\\') { - Emit("\\\\"); - } else if (c == '\b') { - Emit("\\b"); - } else if (c == '\f') { - Emit("\\f"); - } else if (c == '\n') { - Emit("\\n"); - } else if (c == '\r') { - Emit("\\r"); - } else if (c == '\t') { - Emit("\\t"); - } else if (c >= 32 && c <= 126) { - Emit(c); - } else if (c < 32) { - Emit("\\u"); - PrintHex(static_cast(c), out_); - } else { - // Inspect the leading byte to figure out how long the utf8 - // byte sequence is; while doing this initialize |codepoint| - // with the first few bits. - // See table in: https://en.wikipedia.org/wiki/UTF-8 - // byte one is 110x xxxx -> 2 byte utf8 sequence - // byte one is 1110 xxxx -> 3 byte utf8 sequence - // byte one is 1111 0xxx -> 4 byte utf8 sequence - uint32_t codepoint; - int num_bytes_left; - if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence - num_bytes_left = 1; - codepoint = c & 0x1f; - } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence - num_bytes_left = 2; - codepoint = c & 0x0f; - } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence - codepoint = c & 0x07; - num_bytes_left = 3; - } else { - continue; // invalid leading byte - } - - // If we have enough bytes in our input, decode the remaining ones - // belonging to this Unicode character into |codepoint|. - if (ii + num_bytes_left > chars.size()) - continue; - while (num_bytes_left > 0) { - c = chars[++ii]; - --num_bytes_left; - // Check the next byte is a continuation byte, that is 10xx xxxx. - if ((c & 0xc0) != 0x80) - continue; - codepoint = (codepoint << 6) | (c & 0x3f); - } - - // Disallow overlong encodings for ascii characters, as these - // would include " and other characters significant to JSON - // string termination / control. - if (codepoint < 0x7f) - continue; - // Invalid in UTF8, and can't be represented in UTF16 anyway. - if (codepoint > 0x10ffff) - continue; - - // So, now we transcode to UTF16, - // using the math described at https://en.wikipedia.org/wiki/UTF-16, - // for either one or two 16 bit characters. - if (codepoint < 0xffff) { - Emit("\\u"); - PrintHex(static_cast(codepoint), out_); - continue; - } - codepoint -= 0x10000; - // high surrogate - Emit("\\u"); - PrintHex(static_cast((codepoint >> 10) + 0xd800), out_); - // low surrogate - Emit("\\u"); - PrintHex(static_cast((codepoint & 0x3ff) + 0xdc00), out_); - } - } - Emit('"'); - } - - void HandleBinary(span bytes) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit('"'); - Base64Encode(bytes, out_); - Emit('"'); - } - - void HandleDouble(double value) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - // JSON cannot represent NaN or Infinity. So, for compatibility, - // we behave like the JSON object in web browsers: emit 'null'. - if (!std::isfinite(value)) { - Emit("null"); - return; - } - std::unique_ptr str_value = platform_->DToStr(value); - - // DToStr may fail to emit a 0 before the decimal dot. E.g. this is - // the case in base::NumberToString in Chromium (which is based on - // dmg_fp). So, much like - // https://cs.chromium.org/chromium/src/base/json/json_writer.cc - // we probe for this and emit the leading 0 anyway if necessary. - const char* chars = str_value.get(); - if (chars[0] == '.') { - Emit('0'); - } else if (chars[0] == '-' && chars[1] == '.') { - Emit("-0"); - ++chars; - } - Emit(chars); - } - - void HandleInt32(int32_t value) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit(std::to_string(value)); - } - - void HandleBool(bool value) override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit(value ? "true" : "false"); - } - - void HandleNull() override { - if (!status_->ok()) - return; - state_.top().StartElement(out_); - Emit("null"); - } - - void HandleError(Status error) override { - assert(!error.ok()); - *status_ = error; - out_->clear(); - } - - private: - void Emit(char c) { out_->push_back(c); } - void Emit(const char* str) { - out_->insert(out_->end(), str, str + strlen(str)); - } - void Emit(const std::string& str) { - out_->insert(out_->end(), str.begin(), str.end()); - } - - const Platform* platform_; - C* out_; - Status* status_; - std::stack state_; -}; -} // namespace - -std::unique_ptr NewJSONEncoder( - const Platform* platform, - std::vector* out, - Status* status) { - return std::unique_ptr( - new JSONEncoder>(platform, out, status)); -} -std::unique_ptr NewJSONEncoder(const Platform* platform, - std::string* out, - Status* status) { - return std::unique_ptr( - new JSONEncoder(platform, out, status)); -} - -// ============================================================================= -// json::ParseJSON - for receiving streaming parser events for JSON. -// ============================================================================= - -namespace { -const int kStackLimit = 300; - -enum Token { - ObjectBegin, - ObjectEnd, - ArrayBegin, - ArrayEnd, - StringLiteral, - Number, - BoolTrue, - BoolFalse, - NullToken, - ListSeparator, - ObjectPairSeparator, - InvalidToken, - NoInput -}; - -const char* const kNullString = "null"; -const char* const kTrueString = "true"; -const char* const kFalseString = "false"; - -template -class JsonParser { - public: - JsonParser(const Platform* platform, StreamingParserHandler* handler) - : platform_(platform), handler_(handler) {} - - void Parse(const Char* start, size_t length) { - start_pos_ = start; - const Char* end = start + length; - const Char* tokenEnd = nullptr; - ParseValue(start, end, &tokenEnd, 0); - if (error_) - return; - if (tokenEnd != end) { - HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd); - } - } - - private: - bool CharsToDouble(const uint16_t* chars, size_t length, double* result) { - std::string buffer; - buffer.reserve(length + 1); - for (size_t ii = 0; ii < length; ++ii) { - bool is_ascii = !(chars[ii] & ~0x7F); - if (!is_ascii) - return false; - buffer.push_back(static_cast(chars[ii])); - } - return platform_->StrToD(buffer.c_str(), result); - } - - bool CharsToDouble(const uint8_t* chars, size_t length, double* result) { - std::string buffer(reinterpret_cast(chars), length); - return platform_->StrToD(buffer.c_str(), result); - } - - static bool ParseConstToken(const Char* start, - const Char* end, - const Char** token_end, - const char* token) { - // |token| is \0 terminated, it's one of the constants at top of the file. - while (start < end && *token != '\0' && *start++ == *token++) { - } - if (*token != '\0') - return false; - *token_end = start; - return true; - } - - static bool ReadInt(const Char* start, - const Char* end, - const Char** token_end, - bool allow_leading_zeros) { - if (start == end) - return false; - bool has_leading_zero = '0' == *start; - int length = 0; - while (start < end && '0' <= *start && *start <= '9') { - ++start; - ++length; - } - if (!length) - return false; - if (!allow_leading_zeros && length > 1 && has_leading_zero) - return false; - *token_end = start; - return true; - } - - static bool ParseNumberToken(const Char* start, - const Char* end, - const Char** token_end) { - // We just grab the number here. We validate the size in DecodeNumber. - // According to RFC4627, a valid number is: [minus] int [frac] [exp] - if (start == end) - return false; - Char c = *start; - if ('-' == c) - ++start; - - if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/false)) - return false; - if (start == end) { - *token_end = start; - return true; - } - - // Optional fraction part - c = *start; - if ('.' == c) { - ++start; - if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true)) - return false; - if (start == end) { - *token_end = start; - return true; - } - c = *start; - } - - // Optional exponent part - if ('e' == c || 'E' == c) { - ++start; - if (start == end) - return false; - c = *start; - if ('-' == c || '+' == c) { - ++start; - if (start == end) - return false; - } - if (!ReadInt(start, end, &start, /*allow_leading_zeros=*/true)) - return false; - } - - *token_end = start; - return true; - } - - static bool ReadHexDigits(const Char* start, - const Char* end, - const Char** token_end, - int digits) { - if (end - start < digits) - return false; - for (int i = 0; i < digits; ++i) { - Char c = *start++; - if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || - ('A' <= c && c <= 'F'))) - return false; - } - *token_end = start; - return true; - } - - static bool ParseStringToken(const Char* start, - const Char* end, - const Char** token_end) { - while (start < end) { - Char c = *start++; - if ('\\' == c) { - if (start == end) - return false; - c = *start++; - // Make sure the escaped char is valid. - switch (c) { - case 'x': - if (!ReadHexDigits(start, end, &start, 2)) - return false; - break; - case 'u': - if (!ReadHexDigits(start, end, &start, 4)) - return false; - break; - case '\\': - case '/': - case 'b': - case 'f': - case 'n': - case 'r': - case 't': - case 'v': - case '"': - break; - default: - return false; - } - } else if ('"' == c) { - *token_end = start; - return true; - } - } - return false; - } - - static bool SkipComment(const Char* start, - const Char* end, - const Char** comment_end) { - if (start == end) - return false; - - if (*start != '/' || start + 1 >= end) - return false; - ++start; - - if (*start == '/') { - // Single line comment, read to newline. - for (++start; start < end; ++start) { - if (*start == '\n' || *start == '\r') { - *comment_end = start + 1; - return true; - } - } - *comment_end = end; - // Comment reaches end-of-input, which is fine. - return true; - } - - if (*start == '*') { - Char previous = '\0'; - // Block comment, read until end marker. - for (++start; start < end; previous = *start++) { - if (previous == '*' && *start == '/') { - *comment_end = start + 1; - return true; - } - } - // Block comment must close before end-of-input. - return false; - } - - return false; - } - - static bool IsSpaceOrNewLine(Char c) { - // \v = vertial tab; \f = form feed page break. - return c == ' ' || c == '\n' || c == '\v' || c == '\f' || c == '\r' || - c == '\t'; - } - - static void SkipWhitespaceAndComments(const Char* start, - const Char* end, - const Char** whitespace_end) { - while (start < end) { - if (IsSpaceOrNewLine(*start)) { - ++start; - } else if (*start == '/') { - const Char* comment_end = nullptr; - if (!SkipComment(start, end, &comment_end)) - break; - start = comment_end; - } else { - break; - } - } - *whitespace_end = start; - } - - static Token ParseToken(const Char* start, - const Char* end, - const Char** tokenStart, - const Char** token_end) { - SkipWhitespaceAndComments(start, end, tokenStart); - start = *tokenStart; - - if (start == end) - return NoInput; - - switch (*start) { - case 'n': - if (ParseConstToken(start, end, token_end, kNullString)) - return NullToken; - break; - case 't': - if (ParseConstToken(start, end, token_end, kTrueString)) - return BoolTrue; - break; - case 'f': - if (ParseConstToken(start, end, token_end, kFalseString)) - return BoolFalse; - break; - case '[': - *token_end = start + 1; - return ArrayBegin; - case ']': - *token_end = start + 1; - return ArrayEnd; - case ',': - *token_end = start + 1; - return ListSeparator; - case '{': - *token_end = start + 1; - return ObjectBegin; - case '}': - *token_end = start + 1; - return ObjectEnd; - case ':': - *token_end = start + 1; - return ObjectPairSeparator; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - if (ParseNumberToken(start, end, token_end)) - return Number; - break; - case '"': - if (ParseStringToken(start + 1, end, token_end)) - return StringLiteral; - break; - } - return InvalidToken; - } - - static int HexToInt(Char c) { - if ('0' <= c && c <= '9') - return c - '0'; - if ('A' <= c && c <= 'F') - return c - 'A' + 10; - if ('a' <= c && c <= 'f') - return c - 'a' + 10; - assert(false); // Unreachable. - return 0; - } - - static bool DecodeString(const Char* start, - const Char* end, - std::vector* output) { - if (start == end) - return true; - if (start > end) - return false; - output->reserve(end - start); - while (start < end) { - uint16_t c = *start++; - // If the |Char| we're dealing with is really a byte, then - // we have utf8 here, and we need to check for multibyte characters - // and transcode them to utf16 (either one or two utf16 chars). - if (sizeof(Char) == sizeof(uint8_t) && c >= 0x7f) { - // Inspect the leading byte to figure out how long the utf8 - // byte sequence is; while doing this initialize |codepoint| - // with the first few bits. - // See table in: https://en.wikipedia.org/wiki/UTF-8 - // byte one is 110x xxxx -> 2 byte utf8 sequence - // byte one is 1110 xxxx -> 3 byte utf8 sequence - // byte one is 1111 0xxx -> 4 byte utf8 sequence - uint32_t codepoint; - int num_bytes_left; - if ((c & 0xe0) == 0xc0) { // 2 byte utf8 sequence - num_bytes_left = 1; - codepoint = c & 0x1f; - } else if ((c & 0xf0) == 0xe0) { // 3 byte utf8 sequence - num_bytes_left = 2; - codepoint = c & 0x0f; - } else if ((c & 0xf8) == 0xf0) { // 4 byte utf8 sequence - codepoint = c & 0x07; - num_bytes_left = 3; - } else { - return false; // invalid leading byte - } - - // If we have enough bytes in our inpput, decode the remaining ones - // belonging to this Unicode character into |codepoint|. - if (start + num_bytes_left > end) - return false; - while (num_bytes_left > 0) { - c = *start++; - --num_bytes_left; - // Check the next byte is a continuation byte, that is 10xx xxxx. - if ((c & 0xc0) != 0x80) - return false; - codepoint = (codepoint << 6) | (c & 0x3f); - } - - // Disallow overlong encodings for ascii characters, as these - // would include " and other characters significant to JSON - // string termination / control. - if (codepoint < 0x7f) - return false; - // Invalid in UTF8, and can't be represented in UTF16 anyway. - if (codepoint > 0x10ffff) - return false; - - // So, now we transcode to UTF16, - // using the math described at https://en.wikipedia.org/wiki/UTF-16, - // for either one or two 16 bit characters. - if (codepoint < 0xffff) { - output->push_back(codepoint); - continue; - } - codepoint -= 0x10000; - output->push_back((codepoint >> 10) + 0xd800); // high surrogate - output->push_back((codepoint & 0x3ff) + 0xdc00); // low surrogate - continue; - } - if ('\\' != c) { - output->push_back(c); - continue; - } - if (start == end) - return false; - c = *start++; - - if (c == 'x') { - // \x is not supported. - return false; - } - - switch (c) { - case '"': - case '/': - case '\\': - break; - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case 'v': - c = '\v'; - break; - case 'u': - c = (HexToInt(*start) << 12) + (HexToInt(*(start + 1)) << 8) + - (HexToInt(*(start + 2)) << 4) + HexToInt(*(start + 3)); - start += 4; - break; - default: - return false; - } - output->push_back(c); - } - return true; - } - - void ParseValue(const Char* start, - const Char* end, - const Char** value_token_end, - int depth) { - if (depth > kStackLimit) { - HandleError(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, start); - return; - } - const Char* token_start = nullptr; - const Char* token_end = nullptr; - Token token = ParseToken(start, end, &token_start, &token_end); - switch (token) { - case NoInput: - HandleError(Error::JSON_PARSER_NO_INPUT, token_start); - return; - case InvalidToken: - HandleError(Error::JSON_PARSER_INVALID_TOKEN, token_start); - return; - case NullToken: - handler_->HandleNull(); - break; - case BoolTrue: - handler_->HandleBool(true); - break; - case BoolFalse: - handler_->HandleBool(false); - break; - case Number: { - double value; - if (!CharsToDouble(token_start, token_end - token_start, &value)) { - HandleError(Error::JSON_PARSER_INVALID_NUMBER, token_start); - return; - } - if (value >= std::numeric_limits::min() && - value <= std::numeric_limits::max() && - static_cast(value) == value) - handler_->HandleInt32(static_cast(value)); - else - handler_->HandleDouble(value); - break; - } - case StringLiteral: { - std::vector value; - bool ok = DecodeString(token_start + 1, token_end - 1, &value); - if (!ok) { - HandleError(Error::JSON_PARSER_INVALID_STRING, token_start); - return; - } - handler_->HandleString16(span(value.data(), value.size())); - break; - } - case ArrayBegin: { - handler_->HandleArrayBegin(); - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - while (token != ArrayEnd) { - ParseValue(start, end, &token_end, depth + 1); - if (error_) - return; - - // After a list value, we expect a comma or the end of the list. - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - if (token == ListSeparator) { - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - if (token == ArrayEnd) { - HandleError(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, token_start); - return; - } - } else if (token != ArrayEnd) { - // Unexpected value after list value. Bail out. - HandleError(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED, - token_start); - return; - } - } - handler_->HandleArrayEnd(); - break; - } - case ObjectBegin: { - handler_->HandleMapBegin(); - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - while (token != ObjectEnd) { - if (token != StringLiteral) { - HandleError(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, - token_start); - return; - } - std::vector key; - if (!DecodeString(token_start + 1, token_end - 1, &key)) { - HandleError(Error::JSON_PARSER_INVALID_STRING, token_start); - return; - } - handler_->HandleString16(span(key.data(), key.size())); - start = token_end; - - token = ParseToken(start, end, &token_start, &token_end); - if (token != ObjectPairSeparator) { - HandleError(Error::JSON_PARSER_COLON_EXPECTED, token_start); - return; - } - start = token_end; - - ParseValue(start, end, &token_end, depth + 1); - if (error_) - return; - start = token_end; - - // After a key/value pair, we expect a comma or the end of the - // object. - token = ParseToken(start, end, &token_start, &token_end); - if (token == ListSeparator) { - start = token_end; - token = ParseToken(start, end, &token_start, &token_end); - if (token == ObjectEnd) { - HandleError(Error::JSON_PARSER_UNEXPECTED_MAP_END, token_start); - return; - } - } else if (token != ObjectEnd) { - // Unexpected value after last object value. Bail out. - HandleError(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, - token_start); - return; - } - } - handler_->HandleMapEnd(); - break; - } - - default: - // We got a token that's not a value. - HandleError(Error::JSON_PARSER_VALUE_EXPECTED, token_start); - return; - } - - SkipWhitespaceAndComments(token_end, end, value_token_end); - } - - void HandleError(Error error, const Char* pos) { - assert(error != Error::OK); - if (!error_) { - handler_->HandleError( - Status{error, static_cast(pos - start_pos_)}); - error_ = true; - } - } - - const Char* start_pos_ = nullptr; - bool error_ = false; - const Platform* platform_; - StreamingParserHandler* handler_; -}; -} // namespace - -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler) { - JsonParser parser(&platform, handler); - parser.Parse(chars.data(), chars.size()); -} - -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler) { - JsonParser parser(&platform, handler); - parser.Parse(chars.data(), chars.size()); -} - -// ============================================================================= -// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding -// ============================================================================= -template -Status ConvertCBORToJSONTmpl(const Platform& platform, - span cbor, - C* json) { - Status status; - std::unique_ptr json_writer = - NewJSONEncoder(&platform, json, &status); - cbor::ParseCBOR(cbor, json_writer.get()); - return status; -} - -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::vector* json) { - return ConvertCBORToJSONTmpl(platform, cbor, json); -} -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::string* json) { - return ConvertCBORToJSONTmpl(platform, cbor, json); -} - -template -Status ConvertJSONToCBORTmpl(const Platform& platform, span json, C* cbor) { - Status status; - std::unique_ptr encoder = - cbor::NewCBOREncoder(cbor, &status); - ParseJSON(platform, json, encoder.get()); - return status; -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor) { - return ConvertJSONToCBORTmpl(platform, json, cbor); -} -} // namespace json - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} diff --git a/tools/inspector_protocol/lib/encoding_h.template b/tools/inspector_protocol/lib/encoding_h.template deleted file mode 100644 index f1a52a1958a14d..00000000000000 --- a/tools/inspector_protocol/lib/encoding_h.template +++ /dev/null @@ -1,520 +0,0 @@ -{# This template is generated by gen_cbor_templates.py. #} -// Generated by lib/encoding_h.template. - -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef {{"_".join(config.protocol.namespace)}}_encoding_h -#define {{"_".join(config.protocol.namespace)}}_encoding_h - -#include -#include -#include -#include -#include -#include -#include - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -// ===== encoding/encoding.h ===== - - -// ============================================================================= -// span - sequence of bytes -// ============================================================================= - -// This template is similar to std::span, which will be included in C++20. -template -class span { - public: - using index_type = size_t; - - span() : data_(nullptr), size_(0) {} - span(const T* data, index_type size) : data_(data), size_(size) {} - - const T* data() const { return data_; } - - const T* begin() const { return data_; } - const T* end() const { return data_ + size_; } - - const T& operator[](index_type idx) const { return data_[idx]; } - - span 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_; -}; - -template -span SpanFrom(const std::vector& v) { - return span(v.data(), v.size()); -} - -template -span SpanFrom(const char (&str)[N]) { - return span(reinterpret_cast(str), N - 1); -} - -inline span SpanFrom(const char* str) { - return str ? span(reinterpret_cast(str), strlen(str)) - : span(); -} - -inline span SpanFrom(const std::string& v) { - return span(reinterpret_cast(v.data()), v.size()); -} - -// ============================================================================= -// Status and Error codes -// ============================================================================= -enum class Error { - OK = 0, - // JSON parsing errors - json_parser.{h,cc}. - JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01, - JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02, - JSON_PARSER_NO_INPUT = 0x03, - JSON_PARSER_INVALID_TOKEN = 0x04, - JSON_PARSER_INVALID_NUMBER = 0x05, - JSON_PARSER_INVALID_STRING = 0x06, - JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07, - JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08, - JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09, - JSON_PARSER_COLON_EXPECTED = 0x0a, - JSON_PARSER_UNEXPECTED_MAP_END = 0x0b, - JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c, - JSON_PARSER_VALUE_EXPECTED = 0x0d, - - CBOR_INVALID_INT32 = 0x0e, - CBOR_INVALID_DOUBLE = 0x0f, - CBOR_INVALID_ENVELOPE = 0x10, - CBOR_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_TRAILING_JUNK = 0x1c, - CBOR_MAP_START_EXPECTED = 0x1d, - CBOR_MAP_STOP_EXPECTED = 0x1e, - CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x1f, -}; - -// A status value with position that can be copied. The default status -// is OK. Usually, error status values should come with a valid position. -struct Status { - static constexpr size_t npos() { return std::numeric_limits::max(); } - - bool ok() const { return error == Error::OK; } - - Error error = Error::OK; - size_t pos = npos(); - Status(Error error, size_t pos) : error(error), pos(pos) {} - Status() = default; - - // Returns a 7 bit US-ASCII string, either "OK" or an error message - // that includes the position. - std::string ToASCIIString() const; - - private: - std::string ToASCIIString(const char* msg) const; -}; - -// Handler interface for parser events emitted by a streaming parser. -// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder, -// json::ParseJSON. -class StreamingParserHandler { - public: - virtual ~StreamingParserHandler() = default; - virtual void HandleMapBegin() = 0; - virtual void HandleMapEnd() = 0; - virtual void HandleArrayBegin() = 0; - virtual void HandleArrayEnd() = 0; - virtual void HandleString8(span chars) = 0; - virtual void HandleString16(span chars) = 0; - virtual void HandleBinary(span bytes) = 0; - virtual void HandleDouble(double value) = 0; - virtual void HandleInt32(int32_t value) = 0; - virtual void HandleBool(bool value) = 0; - virtual void HandleNull() = 0; - - // The parser may send one error even after other events have already - // been received. Client code is reponsible to then discard the - // already processed events. - // |error| must be an eror, as in, |error.is_ok()| can't be true. - virtual void HandleError(Status error) = 0; -}; - -namespace 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) are supported. -// - 7 bit US-ASCII strings must always be encoded as UTF8 strings, never -// as UTF16 strings. -// - Arbitrary byte arrays, in the inspector protocol called 'binary', -// are encoded as BYTE_STRING (major type 2), prefixed with a byte -// indicating base64 when rendered as JSON. - -// ============================================================================= -// Detecting CBOR content -// ============================================================================= - -// The first byte for an envelope, which we use for wrapping dictionaries -// and arrays; and the byte that indicates a byte string with 32 bit length. -// These two bytes start an envelope, and thereby also any CBOR message -// produced or consumed by this protocol. See also |EnvelopeEncoder| below. -uint8_t InitialByteForEnvelope(); -uint8_t InitialByteFor32BitLengthByteString(); - -// Checks whether |msg| is a cbor message. -bool IsCBORMessage(span msg); - -// ============================================================================= -// Encoding individual CBOR items -// ============================================================================= - -// Some constants for CBOR tokens that only take a single byte on the wire. -uint8_t EncodeTrue(); -uint8_t EncodeFalse(); -uint8_t EncodeNull(); -uint8_t EncodeIndefiniteLengthArrayStart(); -uint8_t EncodeIndefiniteLengthMapStart(); -uint8_t EncodeStop(); - -// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE| -// (major type 1) iff < 0. -void EncodeInt32(int32_t value, std::vector* out); -void EncodeInt32(int32_t value, std::string* 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); -void EncodeString16(span in, std::string* out); - -// Encodes a UTF8 string |in| as STRING (major type 3). -void EncodeString8(span in, std::vector* out); -void EncodeString8(span in, std::string* out); - -// Encodes the given |latin1| string as STRING8. -// If any non-ASCII character is present, it will be represented -// as a 2 byte UTF8 sequence. -void EncodeFromLatin1(span latin1, std::vector* out); -void EncodeFromLatin1(span latin1, std::string* out); - -// Encodes the given |utf16| string as STRING8 if it's entirely US-ASCII. -// Otherwise, encodes as STRING16. -void EncodeFromUTF16(span utf16, std::vector* out); -void EncodeFromUTF16(span utf16, std::string* 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); -void EncodeBinary(span in, std::string* 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); -void EncodeDouble(double value, std::string* out); - -// ============================================================================= -// cbor::EnvelopeEncoder - for wrapping submessages -// ============================================================================= - -// An envelope indicates the byte length of a wrapped item. -// We use this for maps and array, which allows the decoder -// 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); - void EncodeStart(std::string* out); - // This records the current size in |out| at position byte_size_pos_. - // Returns true iff successful. - bool EncodeStop(std::vector* out); - bool EncodeStop(std::string* out); - - private: - size_t byte_size_pos_ = 0; -}; - -// ============================================================================= -// cbor::NewCBOREncoder - for encoding from a streaming parser -// ============================================================================= - -// This can be used to convert to CBOR, by passing the return value to a parser -// that drives it. The handler will encode into |out|, and iff an error occurs -// it will set |status| to an error and clear |out|. Otherwise, |status.ok()| -// will be |true|. -std::unique_ptr NewCBOREncoder( - std::vector* out, - Status* status); -std::unique_ptr NewCBOREncoder(std::string* out, - Status* status); - -// ============================================================================= -// cbor::CBORTokenizer - for parsing individual CBOR items -// ============================================================================= - -// Tags for the tokens within a CBOR message that CBORTokenizer understands. -// Note that this is not the same terminology as the CBOR spec (RFC 7049), -// but rather, our adaptation. For instance, we lump unsigned and signed -// major type into INT32 here (and disallow values outside the int32_t range). -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, -}; - -// The major types from RFC 7049 Section 2.1. -enum class MajorType { - UNSIGNED = 0, - NEGATIVE = 1, - BYTE_STRING = 2, - STRING = 3, - ARRAY = 4, - MAP = 5, - TAG = 6, - SIMPLE_VALUE = 7 -}; - -// CBORTokenizer segments a CBOR message, presenting the tokens therein as -// numbers, strings, etc. This is not a complete CBOR parser, but makes it much -// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse -// 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; - - // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE. - span GetEnvelopeContents() const; - - private: - void ReadNextToken(bool enter_envelope); - void SetToken(CBORTokenTag token, size_t token_byte_length); - void SetError(Error error); - - span bytes_; - CBORTokenTag token_tag_; - struct Status status_; - size_t token_byte_length_; - MajorType token_start_type_; - uint64_t token_start_internal_value_; -}; - -// ============================================================================= -// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages -// ============================================================================= - -// Parses a CBOR encoded message from |bytes|, sending events to -// |out|. If an error occurs, sends |out->HandleError|, and parsing stops. -// The client is responsible for discarding the already received information in -// that case. -void ParseCBOR(span bytes, StreamingParserHandler* out); - -// ============================================================================= -// cbor::AppendString8EntryToMap - for limited in-place editing of messages -// ============================================================================= - -// Modifies the |cbor| message by appending a new key/value entry at the end -// of the map. Patches up the envelope size; Status.ok() iff successful. -// If not successful, |cbor| may be corrupted after this call. -Status AppendString8EntryToCBORMap(span string8_key, - span string8_value, - std::vector* cbor); -Status AppendString8EntryToCBORMap(span string8_key, - span string8_value, - std::string* cbor); - -namespace internals { // Exposed only for writing tests. -int8_t ReadTokenStart(span bytes, - cbor::MajorType* type, - uint64_t* value); - -void WriteTokenStart(cbor::MajorType type, - uint64_t value, - std::vector* encoded); -void WriteTokenStart(cbor::MajorType type, - uint64_t value, - std::string* encoded); -} // namespace internals -} // namespace cbor - -namespace json { -// Client code must provide an instance. Implementation should delegate -// to whatever is appropriate. -class Platform { - public: - virtual ~Platform() = default; - // Parses |str| into |result|. Returns false iff there are - // leftover characters or parsing errors. - virtual bool StrToD(const char* str, double* result) const = 0; - - // Prints |value| in a format suitable for JSON. - virtual std::unique_ptr DToStr(double value) const = 0; -}; - -// ============================================================================= -// json::NewJSONEncoder - for encoding streaming parser events as JSON -// ============================================================================= - -// Returns a handler object which will write ascii characters to |out|. -// |status->ok()| will be false iff the handler routine HandleError() is called. -// In that case, we'll stop emitting output. -// Except for calling the HandleError routine at any time, the client -// code must call the Handle* methods in an order in which they'd occur -// in valid JSON; otherwise we may crash (the code uses assert). -std::unique_ptr NewJSONEncoder( - const Platform* platform, - std::vector* out, - Status* status); -std::unique_ptr NewJSONEncoder(const Platform* platform, - std::string* out, - Status* status); - -// ============================================================================= -// json::ParseJSON - for receiving streaming parser events for JSON -// ============================================================================= - -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler); -void ParseJSON(const Platform& platform, - span chars, - StreamingParserHandler* handler); - -// ============================================================================= -// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding -// ============================================================================= -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::string* json); -Status ConvertCBORToJSON(const Platform& platform, - span cbor, - std::vector* json); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::vector* cbor); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor); -Status ConvertJSONToCBOR(const Platform& platform, - span json, - std::string* cbor); -} // namespace json - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} -#endif // !defined({{"_".join(config.protocol.namespace)}}_encoding_h) diff --git a/tools/inspector_protocol/pdl.py b/tools/inspector_protocol/pdl.py index 03d11b39d636df..d7733634e58517 100644 --- a/tools/inspector_protocol/pdl.py +++ b/tools/inspector_protocol/pdl.py @@ -12,160 +12,167 @@ description = '' -primitiveTypes = ['integer', 'number', 'boolean', 'string', 'object', 'any', 'array', 'binary'] +primitiveTypes = ['integer', 'number', 'boolean', 'string', 'object', + 'any', 'array', 'binary'] 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, 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: - item['$ref'] = type + if is_array: + item['type'] = 'array' + item['items'] = collections.OrderedDict() + 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: + item['$ref'] = type def createItem(d, experimental, deprecated, name=None): - result = collections.OrderedDict(d) - if name: - result['name'] = name - global description - if description: - result['description'] = description.strip() - if experimental: - result['experimental'] = True - if deprecated: - result['deprecated'] = True - return result + result = collections.OrderedDict(d) + if name: + result['name'] = name + global description + if description: + result['description'] = description.strip() + if experimental: + result['experimental'] = True + if deprecated: + result['deprecated'] = True + return result def parse(data, file_name, map_binary_to_string=False): - protocol = collections.OrderedDict() - protocol['version'] = collections.OrderedDict() - protocol['domains'] = [] - domain = None - item = None - subitems = None - nukeDescription = False - global description - lines = data.split('\n') - for i in range(0, len(lines)): - if nukeDescription: - description = '' - nukeDescription = False - line = lines[i] - trimLine = line.strip() - - if trimLine.startswith('#'): - if len(description): - description += '\n' - description += trimLine[2:] - continue + protocol = collections.OrderedDict() + protocol['version'] = collections.OrderedDict() + protocol['domains'] = [] + domain = None + item = None + subitems = None + nukeDescription = False + global description + lines = data.split('\n') + for i in range(0, len(lines)): + if nukeDescription: + description = '' + nukeDescription = False + line = lines[i] + trimLine = line.strip() + + if trimLine.startswith('#'): + if len(description): + description += '\n' + description += trimLine[2:] + continue + else: + nukeDescription = True + + if len(trimLine) == 0: + continue + + match = re.compile( + r'^(experimental )?(deprecated )?domain (.*)').match(line) + if match: + domain = createItem({'domain' : match.group(3)}, match.group(1), + match.group(2)) + protocol['domains'].append(domain) + continue + + match = re.compile(r'^ depends on ([^\s]+)').match(line) + if match: + if 'dependencies' not in domain: + domain['dependencies'] = [] + domain['dependencies'].append(match.group(1)) + continue + + match = re.compile(r'^ (experimental )?(deprecated )?type (.*) ' + r'extends (array of )?([^\s]+)').match(line) + if match: + 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), map_binary_to_string) + domain['types'].append(item) + continue + + match = re.compile( + r'^ (experimental )?(deprecated )?(command|event) (.*)').match(line) + if match: + list = [] + if match.group(3) == 'command': + if 'commands' in domain: + list = domain['commands'] + else: + list = domain['commands'] = [] + else: + if 'events' in domain: + list = domain['events'] else: - nukeDescription = True - - if len(trimLine) == 0: - continue - - match = re.compile(r'^(experimental )?(deprecated )?domain (.*)').match(line) - if match: - domain = createItem({'domain' : match.group(3)}, match.group(1), match.group(2)) - protocol['domains'].append(domain) - continue - - match = re.compile(r'^ depends on ([^\s]+)').match(line) - if match: - if 'dependencies' not in domain: - domain['dependencies'] = [] - domain['dependencies'].append(match.group(1)) - continue - - match = re.compile(r'^ (experimental )?(deprecated )?type (.*) extends (array of )?([^\s]+)').match(line) - if match: - 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), map_binary_to_string) - domain['types'].append(item) - continue - - match = re.compile(r'^ (experimental )?(deprecated )?(command|event) (.*)').match(line) - if match: - list = [] - if match.group(3) == 'command': - if 'commands' in domain: - list = domain['commands'] - else: - list = domain['commands'] = [] - else: - if 'events' in domain: - list = domain['events'] - else: - list = domain['events'] = [] - - item = createItem({}, match.group(1), match.group(2), match.group(4)) - list.append(item) - continue - - match = re.compile(r'^ (experimental )?(deprecated )?(optional )?(array of )?([^\s]+) ([^\s]+)').match(line) - if match: - 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), map_binary_to_string) - if match.group(5) == 'enum': - enumliterals = param['enum'] = [] - subitems.append(param) - continue - - match = re.compile(r'^ (parameters|returns|properties)').match(line) - if match: - subitems = item[match.group(1)] = [] - continue - - match = re.compile(r'^ enum').match(line) - if match: - enumliterals = item['enum'] = [] - continue - - match = re.compile(r'^version').match(line) - if match: - continue - - match = re.compile(r'^ major (\d+)').match(line) - if match: - protocol['version']['major'] = match.group(1) - continue - - match = re.compile(r'^ minor (\d+)').match(line) - if match: - protocol['version']['minor'] = match.group(1) - continue - - match = re.compile(r'^ redirect ([^\s]+)').match(line) - if match: - item['redirect'] = match.group(1) - continue - - match = re.compile(r'^ ( )?[^\s]+$').match(line) - if match: - # enum literal - enumliterals.append(trimLine) - continue - - print('Error in %s:%s, illegal token: \t%s' % (file_name, i, line)) - sys.exit(1) - return protocol + list = domain['events'] = [] + + item = createItem({}, match.group(1), match.group(2), match.group(4)) + list.append(item) + continue + + match = re.compile( + r'^ (experimental )?(deprecated )?(optional )?' + r'(array of )?([^\s]+) ([^\s]+)').match(line) + if match: + 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), map_binary_to_string) + if match.group(5) == 'enum': + enumliterals = param['enum'] = [] + subitems.append(param) + continue + + match = re.compile(r'^ (parameters|returns|properties)').match(line) + if match: + subitems = item[match.group(1)] = [] + continue + + match = re.compile(r'^ enum').match(line) + if match: + enumliterals = item['enum'] = [] + continue + + match = re.compile(r'^version').match(line) + if match: + continue + + match = re.compile(r'^ major (\d+)').match(line) + if match: + protocol['version']['major'] = match.group(1) + continue + + match = re.compile(r'^ minor (\d+)').match(line) + if match: + protocol['version']['minor'] = match.group(1) + continue + + match = re.compile(r'^ redirect ([^\s]+)').match(line) + if match: + item['redirect'] = match.group(1) + continue + + match = re.compile(r'^ ( )?[^\s]+$').match(line) + if match: + # enum literal + enumliterals.append(trimLine) + continue + + print('Error in %s:%s, illegal token: \t%s' % (file_name, i, line)) + sys.exit(1) + return protocol 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) + if file_name.endswith(".pdl"): + return parse(data, file_name, map_binary_to_string) + return json.loads(data) diff --git a/tools/inspector_protocol/roll.py b/tools/inspector_protocol/roll.py old mode 100644 new mode 100755 index abe636e270b7cf..521f350242285e --- a/tools/inspector_protocol/roll.py +++ b/tools/inspector_protocol/roll.py @@ -18,9 +18,42 @@ 'code_generator.py', 'concatenate_protocols.py', 'convert_protocol_to_json.py', - 'encoding/encoding.h', - 'encoding/encoding.cc', - 'encoding/encoding_test.cc', + 'crdtp/cbor.cc', + 'crdtp/cbor.h', + 'crdtp/cbor_test.cc', + 'crdtp/dispatch.h', + 'crdtp/dispatch.cc', + 'crdtp/dispatch_test.cc', + 'crdtp/error_support.cc', + 'crdtp/error_support.h', + 'crdtp/error_support_test.cc', + 'crdtp/export_template.h', + 'crdtp/find_by_first.h', + 'crdtp/find_by_first_test.cc', + 'crdtp/frontend_channel.h', + 'crdtp/maybe.h', + 'crdtp/maybe_test.cc', + 'crdtp/json.cc', + 'crdtp/json.h', + 'crdtp/json_platform.h', + 'crdtp/json_test.cc', + 'crdtp/parser_handler.h', + 'crdtp/protocol_core_test.cc', + 'crdtp/protocol_core.cc', + 'crdtp/protocol_core.h', + 'crdtp/serializable.h', + 'crdtp/serializable.cc', + 'crdtp/serializable_test.cc', + 'crdtp/serializer_traits.h', + 'crdtp/serializer_traits_test.cc', + 'crdtp/span.cc', + 'crdtp/span.h', + 'crdtp/span_test.cc', + 'crdtp/status.cc', + 'crdtp/status.h', + 'crdtp/status_test.cc', + 'crdtp/status_test_support.cc', + 'crdtp/status_test_support.h', 'inspector_protocol.gni', 'inspector_protocol.gypi', 'lib/*', @@ -137,12 +170,13 @@ def main(argv): print('You said --force ... as you wish, modifying the destination.') for f in to_add + to_copy: contents = open(os.path.join(src_dir, f)).read() + contents = contents.replace('CRDTP_EXPORT ', '') contents = contents.replace( - 'INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_', - 'V8_INSPECTOR_PROTOCOL_ENCODING_ENCODING_H_') + 'CRDTP_', + 'V8_INSPECTOR_PROTOCOL_CRDTP_') contents = contents.replace( - 'namespace inspector_protocol_encoding', - 'namespace v8_inspector_protocol_encoding') + 'namespace crdtp', + 'namespace v8_inspector_protocol_crdtp') open(os.path.join(dest_dir, f), 'w').write(contents) shutil.copymode(os.path.join(src_dir, f), os.path.join(dest_dir, f)) for f in to_delete: diff --git a/tools/inspector_protocol/templates/Exported_h.template b/tools/inspector_protocol/templates/Exported_h.template index 765f6c2135b9c3..6481eef50c6889 100644 --- a/tools/inspector_protocol/templates/Exported_h.template +++ b/tools/inspector_protocol/templates/Exported_h.template @@ -20,8 +20,8 @@ namespace {{namespace}} { #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 void AppendSerialized(std::vector* out) const = 0; + virtual ~Exported() { } }; #endif // !defined({{"_".join(config.protocol.namespace)}}_exported_api_h) @@ -61,7 +61,6 @@ namespace {{param.name | to_title_case}}Enum { class {{config.exported.export_macro}} {{type.id}} : public Exported { public: - 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 f2e576a9c470ae..47f27ac1081a53 100644 --- a/tools/inspector_protocol/templates/Imported_h.template +++ b/tools/inspector_protocol/templates/Imported_h.template @@ -14,6 +14,53 @@ #include {{format_include(config.imported.package, domain.domain)}} {% endif %} +#ifndef {{"_".join(config.protocol.namespace)}}_imported_imported_h + +namespace {{config.crdtp.namespace}} { + +template +struct ProtocolTypeTraits< + std::unique_ptr, + typename std::enable_if< + std::is_base_of<{{"::".join(config.imported.namespace)}}::Exported, T>::value>::type> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value) { + if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::ENVELOPE) { + state->RegisterError(Error::CBOR_INVALID_ENVELOPE); + return false; + } + span env = state->tokenizer()->GetEnvelope(); + auto res = T::fromBinary(env.data(), env.size()); + if (!res) { + // TODO(caseq): properly plumb an error rather than returning a bogus code. + state->RegisterError(Error::MESSAGE_MUST_BE_AN_OBJECT); + return false; + } + *value = std::move(res); + return true; + } + static void Serialize(const std::unique_ptr& value, std::vector* bytes) { + // Use virtual method, so that outgoing protocol objects could be retained + // by a pointer to ProtocolObject. + value->AppendSerialized(bytes); + } +}; + +template +struct ProtocolTypeTraits< + T, + typename std::enable_if< + std::is_base_of<{{"::".join(config.imported.namespace)}}::Exported, T>::value>::type> { + static void Serialize(const T& value, std::vector* bytes) { + // Use virtual method, so that outgoing protocol objects could be retained + // by a pointer to ProtocolObject. + value.AppendSerialized(bytes); + } +}; + +} // namespace {{config.crdtp.namespace}} + +#endif // {{"_".join(config.protocol.namespace)}}_imported_imported_h + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} @@ -29,14 +76,10 @@ public: 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); + void AppendSerialized(std::vector* output) const override { + m_exported->AppendSerialized(output); } + std::unique_ptr clone() const override { return std::unique_ptr(new ImportedValue(m_exported)); } @@ -56,15 +99,15 @@ struct ValueConversions<{{"::".join(config.imported.namespace)}}::{{domain.domai static std::unique_ptr<{{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}> fromValue(protocol::Value* value, ErrorSupport* errors) { if (!value) { - errors->addError("value expected"); + errors->AddError("value expected"); return nullptr; } std::vector binary; - value->writeBinary(&binary); + value->AppendSerialized(&binary); auto result = {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}::fromBinary(binary.data(), binary.size()); if (!result) - errors->addError("cannot parse"); + errors->AddError("cannot parse"); return result; } @@ -72,11 +115,6 @@ struct ValueConversions<{{"::".join(config.imported.namespace)}}::{{domain.domai { return ImportedValue::fromExported(exported); } - - static std::unique_ptr toValue(const std::unique_ptr<{{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}>& value) - { - return toValue(value.get()); - } }; {% endfor %} diff --git a/tools/inspector_protocol/templates/TypeBuilder_cpp.template b/tools/inspector_protocol/templates/TypeBuilder_cpp.template index 982e2c61b8e916..0139ee2f7d0480 100644 --- a/tools/inspector_protocol/templates/TypeBuilder_cpp.template +++ b/tools/inspector_protocol/templates/TypeBuilder_cpp.template @@ -8,11 +8,18 @@ #include {{format_include(config.protocol.package, "Protocol")}} +#include "{{config.crdtp.dir}}/cbor.h" +#include "{{config.crdtp.dir}}/find_by_first.h" +#include "{{config.crdtp.dir}}/span.h" + {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} namespace {{domain.domain}} { +using {{config.crdtp.namespace}}::DeserializerState; +using {{config.crdtp.namespace}}::ProtocolTypeTraits; + // ------------- Enum values from types. const char Metainfo::domainName[] = "{{domain.domain}}"; @@ -38,6 +45,7 @@ const char* {{ literal | dash_to_camelcase}} = "{{literal}}"; } // namespace API {% endif %} {% endif %} + {% for property in type.properties %} {% if "enum" in property %} @@ -47,89 +55,28 @@ const char* {{type.id}}::{{property.name | to_title_case}}Enum::{{literal | dash {% endif %} {% endfor %} {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} - -std::unique_ptr<{{type.id}}> {{type.id}}::fromValue(protocol::Value* value, ErrorSupport* errors) -{ - if (!value || value->type() != protocol::Value::TypeObject) { - errors->addError("object expected"); - return nullptr; - } - - std::unique_ptr<{{type.id}}> result(new {{type.id}}()); - protocol::DictionaryValue* object = DictionaryValue::cast(value); - errors->push(); - {% for property in type.properties %} - protocol::Value* {{property.name}}Value = object->get("{{property.name}}"); +V8_CRDTP_BEGIN_DESERIALIZER({{type.id}}) + {% for property in type.properties | sort(attribute = 'name', case_sensitive=True) %} {% if property.optional %} - if ({{property.name}}Value) { - errors->setName("{{property.name}}"); - result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors); - } + V8_CRDTP_DESERIALIZE_FIELD_OPT("{{property.name}}", m_{{property.name}}), {% else %} - errors->setName("{{property.name}}"); - result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors); + V8_CRDTP_DESERIALIZE_FIELD("{{property.name}}", m_{{property.name}}), {% endif %} - {% endfor %} - errors->pop(); - if (errors->hasErrors()) - return nullptr; - return result; -} + {% endfor %} +V8_CRDTP_END_DESERIALIZER() -std::unique_ptr {{type.id}}::toValue() const -{ - std::unique_ptr result = DictionaryValue::create(); - {% for property in type.properties %} - {% set property_type = protocol.resolve_type(property) %} - {% set property_field = "m_" + property.name %} - {% if property.optional %} - if ({{property_field}}.isJust()) - result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_field}}.fromJust())); - {% else %} - result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_type.to_raw_type % property_field}})); - {% endif %} - {% endfor %} - return result; -} +V8_CRDTP_BEGIN_SERIALIZER({{type.id}}) + {% for property in type.properties %} + V8_CRDTP_SERIALIZE_FIELD("{{property.name}}", m_{{property.name}}); + {% endfor %} +V8_CRDTP_END_SERIALIZER(); -std::unique_ptr<{{type.id}}> {{type.id}}::clone() const -{ - ErrorSupport errors; - return fromValue(toValue().get(), &errors); -} {% if protocol.is_exported(domain.domain, type.id) %} - -{{config.exported.string_out}} {{type.id}}::toJSONString() const -{ - 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) -{ - ErrorSupport errors; - std::unique_ptr value = StringUtil::parseJSON(json); - if (!value) - 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); + return protocol::{{domain.domain}}::{{type.id}}::FromBinary(data, length); } - {% endif %} {% endfor %} @@ -175,90 +122,82 @@ void Frontend::{{event.name | to_method_case}}( {%- endif %} {{parameter.name}}{%- if not loop.last -%}, {% endif -%} {% endfor -%}) { - if (!m_frontendChannel) + if (!frontend_channel_) return; {% if event.parameters %} - std::unique_ptr<{{event.name | to_title_case}}Notification> messageData = {{event.name | to_title_case}}Notification::{{"create" | to_method_case}}() - {% for parameter in event.parameters %} - {% if not "optional" in parameter %} - .{{"set" | to_method_case}}{{parameter.name | to_title_case}}({{protocol.resolve_type(parameter).to_pass_type % parameter.name}}) - {% endif %} - {% endfor %} - .{{ "build" | to_method_case }}(); + {{config.crdtp.namespace}}::ObjectSerializer serializer; {% for parameter in event.parameters %} - {% if "optional" in parameter %} - if ({{parameter.name}}.isJust()) - messageData->{{"set" | to_method_case}}{{parameter.name | to_title_case}}(std::move({{parameter.name}}).takeJust()); - {% endif %} + serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), {{parameter.name}}); {% endfor %} - m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("{{domain.domain}}.{{event.name}}", std::move(messageData))); + frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}", serializer.Finish())); {% else %} - m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("{{domain.domain}}.{{event.name}}")); + frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}")); {% endif %} } {% endfor %} void Frontend::flush() { - m_frontendChannel->flushProtocolNotifications(); + frontend_channel_->FlushProtocolNotifications(); } -void Frontend::sendRawJSONNotification(String notification) +void Frontend::sendRawNotification(std::unique_ptr notification) { - m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromJSON(std::move(notification))); -} - -void Frontend::sendRawCBORNotification(std::vector notification) -{ - m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromBinary(std::move(notification))); + frontend_channel_->SendProtocolNotification(std::move(notification)); } // --------------------- Dispatcher. -class DispatcherImpl : public protocol::DispatcherBase { +class DomainDispatcherImpl : public protocol::DomainDispatcher { public: - DispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) - : DispatcherBase(frontendChannel) - , m_backend(backend) { - {% for command in domain.commands %} - {% if "redirect" in command %} - m_redirects["{{domain.domain}}.{{command.name}}"] = "{{command.redirect}}.{{command.name}}"; - {% continue %} - {% endif %} - {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %} - m_dispatchMap["{{domain.domain}}.{{command.name}}"] = &DispatcherImpl::{{command.name}}; - {% endfor %} - } - ~DispatcherImpl() override { } - 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; } + DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) + : DomainDispatcher(frontendChannel) + , m_backend(backend) {} + ~DomainDispatcherImpl() override { } -protected: - 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; - std::unordered_map m_redirects; + using CallHandler = void (DomainDispatcherImpl::*)(const {{config.crdtp.namespace}}::Dispatchable& dispatchable); + + std::function Dispatch({{config.crdtp.namespace}}::span command_name) override; {% for command in domain.commands %} {% if "redirect" in command %}{% continue %}{% endif %} {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %} - void {{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr requestMessageObject, ErrorSupport*); + void {{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable); {% endfor %} - + protected: Backend* m_backend; }; -bool DispatcherImpl::canDispatch(const String& method) { - return m_dispatchMap.find(method) != m_dispatchMap.end(); +namespace { +// This helper method with a static map of command methods (instance methods +// of DomainDispatcherImpl declared just above) by their name is used immediately below, +// in the DomainDispatcherImpl::Dispatch method. +DomainDispatcherImpl::CallHandler CommandByName({{config.crdtp.namespace}}::span command_name) { + static auto* commands = [](){ + auto* commands = new std::vector, + DomainDispatcherImpl::CallHandler>>{ + {% for command in domain.commands|sort(attribute="name",case_sensitive=True) %} + {% if "redirect" in command %}{% continue %}{% endif %} + {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %} + { + {{config.crdtp.namespace}}::SpanFrom("{{command.name}}"), + &DomainDispatcherImpl::{{command.name}} + }, + {% endfor %} + }; + return commands; + }(); + return {{config.crdtp.namespace}}::FindByFirst(*commands, command_name, nullptr); } +} // namespace -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; - (this->*(it->second))(callId, method, message, std::move(messageObject), &errors); +std::function DomainDispatcherImpl::Dispatch({{config.crdtp.namespace}}::span command_name) { + CallHandler handler = CommandByName(command_name); + if (!handler) return nullptr; + + return [this, handler](const {{config.crdtp.namespace}}::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); + }; } {% for command in domain.commands %} @@ -267,10 +206,11 @@ void DispatcherImpl::dispatch(int callId, const String& method, const ProtocolMe {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %} {% if protocol.is_async_command(domain.domain, command.name) %} -class {{command_name_title}}CallbackImpl : public Backend::{{command_name_title}}Callback, public DispatcherBase::Callback { +class {{command_name_title}}CallbackImpl : public Backend::{{command_name_title}}Callback, public DomainDispatcher::Callback { public: - {{command_name_title}}CallbackImpl(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message) - : DispatcherBase::Callback(std::move(backendImpl), callId, method, message) { } + {{command_name_title}}CallbackImpl(std::unique_ptr backendImpl, int callId, {{config.crdtp.namespace}}::span message) + : DomainDispatcher::Callback(std::move(backendImpl), callId, +{{config.crdtp.namespace}}::SpanFrom("{{domain.domain}}.{{command.name}}"), message) { } void sendSuccess( {%- for parameter in command.returns -%} @@ -282,16 +222,11 @@ public: {%- if not loop.last -%}, {% endif -%} {%- endfor -%}) override { - std::unique_ptr resultObject = DictionaryValue::create(); - {% for parameter in command.returns %} - {% if "optional" in parameter %} - if ({{parameter.name}}.isJust()) - resultObject->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue({{parameter.name}}.fromJust())); - {% else %} - resultObject->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue({{protocol.resolve_type(parameter).to_raw_type % parameter.name}})); - {% endif %} - {% endfor %} - sendIfActive(std::move(resultObject), DispatchResponse::OK()); + {{config.crdtp.namespace}}::ObjectSerializer serializer; + {% for parameter in command.returns %} + serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), {{parameter.name}}); + {% endfor %} + sendIfActive(serializer.Finish(), DispatchResponse::Success()); } void fallThrough() override @@ -301,38 +236,51 @@ public: void sendFailure(const DispatchResponse& response) override { - DCHECK(response.status() == DispatchResponse::kError); + DCHECK(response.IsError()); sendIfActive(nullptr, response); } }; {% endif %} -void DispatcherImpl::{{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr requestMessageObject, ErrorSupport* errors) -{ +namespace { + {% if "parameters" in command %} - // Prepare input parameters. - protocol::DictionaryValue* object = DictionaryValue::cast(requestMessageObject->get("params")); - errors->push(); +struct {{command.name}}Params : public {{config.crdtp.namespace}}::DeserializableProtocolObject<{{command.name}}Params> { {% for parameter in command.parameters %} {% set parameter_type = protocol.resolve_type(parameter) %} - protocol::Value* {{parameter.name}}Value = object ? object->get("{{parameter.name}}") : nullptr; {% if parameter.optional %} - Maybe<{{parameter_type.raw_type}}> in_{{parameter.name}}; - if ({{parameter.name}}Value) { - errors->setName("{{parameter.name}}"); - in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors); - } + Maybe<{{parameter_type.raw_type}}> {{parameter.name}}; {% else %} - errors->setName("{{parameter.name}}"); - {{parameter_type.type}} in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors); + {{parameter_type.type}} {{parameter.name}}; {% endif %} {% endfor %} - errors->pop(); - if (errors->hasErrors()) { - reportProtocolError(callId, DispatchResponse::kInvalidParams, kInvalidParamsString, errors); - return; - } + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER({{command.name}}Params) + {% for parameter in command.parameters | sort(attribute = 'name', case_sensitive=True) %} + {% if parameter.optional %} + V8_CRDTP_DESERIALIZE_FIELD_OPT("{{parameter.name}}", {{parameter.name}}), + {% else %} + V8_CRDTP_DESERIALIZE_FIELD("{{parameter.name}}", {{parameter.name}}), + {% endif %} + {% endfor %} +V8_CRDTP_END_DESERIALIZER() + {% endif %} + +} // namespace + +void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable) +{ + // Prepare input parameters. + {% if "parameters" in command %} + auto deserializer = {{config.crdtp.namespace}}::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + {{command.name}}Params params; + {{command.name}}Params::Deserialize(&deserializer, ¶ms); + if (MaybeReportInvalidParams(dispatchable, deserializer)) + return; {% endif %} + {% if "returns" in command and not protocol.is_async_command(domain.domain, command.name) %} // Declare output parameters. {% for parameter in command.returns %} @@ -345,14 +293,14 @@ void DispatcherImpl::{{command.name}}(int callId, const String& method, const Pr {% endif %} {% if not protocol.is_async_command(domain.domain, command.name) %} - std::unique_ptr weak = weakPtr(); + std::unique_ptr weak = weakPtr(); DispatchResponse response = m_backend->{{command.name | to_method_case}}( {%- for parameter in command.parameters -%} {%- if not loop.first -%}, {% endif -%} {%- if "optional" in parameter -%} - std::move(in_{{parameter.name}}) + std::move(params.{{parameter.name}}) {%- else -%} - {{protocol.resolve_type(parameter).to_pass_type % ("in_" + parameter.name)}} + {{protocol.resolve_type(parameter).to_pass_type % ("params." + parameter.name)}} {%- endif -%} {%- endfor %} {%- if "returns" in command %} @@ -361,54 +309,68 @@ void DispatcherImpl::{{command.name}}(int callId, const String& method, const Pr &out_{{parameter.name}} {%- endfor %} {% endif %}); - if (response.status() == DispatchResponse::kFallThrough) { - channel()->fallThrough(callId, method, message); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), {{config.crdtp.namespace}}::SpanFrom("{{domain.domain}}.{{command.name}}"), dispatchable.Serialized()); return; } {% if "returns" in command %} - std::unique_ptr result = DictionaryValue::create(); - if (response.status() == DispatchResponse::kSuccess) { - {% for parameter in command.returns %} - {% if "optional" in parameter %} - if (out_{{parameter.name}}.isJust()) - result->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue(out_{{parameter.name}}.fromJust())); - {% else %} - result->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue({{protocol.resolve_type(parameter).to_raw_type % ("out_" + parameter.name)}})); - {% endif %} - {% endfor %} - } - if (weak->get()) - weak->get()->sendResponse(callId, response, std::move(result)); + if (weak->get()) { + std::unique_ptr<{{config.crdtp.namespace}}::Serializable> result; + if (response.IsSuccess()) { + {{config.crdtp.namespace}}::ObjectSerializer serializer; + {% for parameter in command.returns %} + serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), out_{{parameter.name}}); + {% endfor %} + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } {% else %} if (weak->get()) - weak->get()->sendResponse(callId, response); + weak->get()->sendResponse(dispatchable.CallId(), response); {% endif %} return; {% else %} - std::unique_ptr weak = weakPtr(); - 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 -%} {%- if "optional" in property -%} - std::move(in_{{property.name}}) + std::move(params.{{property.name}}) {%- else -%} - {{protocol.resolve_type(property).to_pass_type % ("in_" + property.name)}} + {{protocol.resolve_type(property).to_pass_type % ("params." + property.name)}} {%- endif -%} {%- endfor -%} {%- if command.parameters -%}, {% endif -%} - std::move(callback)); - return; + std::make_unique<{{command_name_title}}CallbackImpl>(weakPtr(), dispatchable.CallId(), dispatchable.Serialized())); {% endif %} } {% endfor %} +namespace { +// This helper method (with a static map of redirects) is used from Dispatcher::wire +// immediately below. +const std::vector, {{config.crdtp.namespace}}::span>>& SortedRedirects() { + static auto* redirects = [](){ + auto* redirects = new std::vector, {{config.crdtp.namespace}}::span>>{ + {% for command in domain.commands|sort(attribute="name",case_sensitive=True) %} + {% if "redirect" in command %} + { {{config.crdtp.namespace}}::SpanFrom("{{domain.domain}}.{{command.name}}"), {{config.crdtp.namespace}}::SpanFrom("{{command.redirect}}.{{command.name}}") }, + {% endif %} + {% endfor %} + }; + return redirects; + }(); + return *redirects; +} +} // namespace + // static void Dispatcher::wire(UberDispatcher* uber, Backend* backend) { - std::unique_ptr dispatcher(new DispatcherImpl(uber->channel(), backend)); - uber->setupRedirects(dispatcher->redirects()); - uber->registerBackend("{{domain.domain}}", std::move(dispatcher)); + auto dispatcher = std::make_unique(uber->channel(), backend); + uber->WireBackend({{config.crdtp.namespace}}::SpanFrom("{{domain.domain}}"), SortedRedirects(), std::move(dispatcher)); } } // {{domain.domain}} diff --git a/tools/inspector_protocol/templates/TypeBuilder_h.template b/tools/inspector_protocol/templates/TypeBuilder_h.template index 9d86d7a4ac0a5c..c2e21a2d31eea8 100644 --- a/tools/inspector_protocol/templates/TypeBuilder_h.template +++ b/tools/inspector_protocol/templates/TypeBuilder_h.template @@ -26,8 +26,6 @@ namespace {{namespace}} { {% endfor %} namespace {{domain.domain}} { - -// ------------- Forward and enum declarations. {% for type in domain.types %} {% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %} {% if type.type == "object" %} @@ -40,6 +38,8 @@ using {{type.id}} = Object; using {{type.id}} = {{protocol.resolve_type(type).type}}; {% endif %} {% endfor %} + +// ------------- Forward and enum declarations. {% for type in domain.types %} {% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %} {% if "enum" in type %} @@ -71,11 +71,9 @@ namespace {{param.name | to_title_case}}Enum { {% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %} {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %} -class {{config.protocol.export_macro}} {{type.id}} : public Serializable{% if protocol.is_exported(domain.domain, type.id) %}, public API::{{type.id}}{% endif %}{ - PROTOCOL_DISALLOW_COPY({{type.id}}); +class {{config.protocol.export_macro}} {{type.id}} : public ::{{config.crdtp.namespace}}::ProtocolObject<{{type.id}}>{% if protocol.is_exported(domain.domain, type.id) %}, + public API::{{type.id}}{% endif %} { public: - static std::unique_ptr<{{type.id}}> fromValue(protocol::Value* value, ErrorSupport* errors); - ~{{type.id}}() override { } {% for property in type.properties %} {% set property_type = protocol.resolve_type(property) %} @@ -99,16 +97,6 @@ public: void {{"set" | to_method_case}}{{property_name}}({{property_type.pass_type}} value) { {{property_field}} = {{property_type.to_rvalue % "value"}}; } {% endfor %} - std::unique_ptr toValue() const; - 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 class {{type.id}}Builder { public: @@ -166,6 +154,8 @@ public: } private: + DECLARE_SERIALIZATION_SUPPORT(); + {{type.id}}() { {% for property in type.properties %} @@ -245,7 +235,7 @@ public: {% if protocol.generate_disable(domain) %} virtual DispatchResponse {{"disable" | to_method_case}}() { - return DispatchResponse::OK(); + return DispatchResponse::Success(); } {% endif %} }; @@ -254,7 +244,7 @@ public: class {{config.protocol.export_macro}} Frontend { public: - explicit Frontend(FrontendChannel* frontendChannel) : m_frontendChannel(frontendChannel) { } + explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {} {% for event in domain.events %} {% if not protocol.generate_event(domain.domain, event.name) %}{% continue %}{% endif %} void {{event.name | to_method_case}}( @@ -268,11 +258,10 @@ public: ); {% endfor %} - void flush(); - void sendRawJSONNotification(String); - void sendRawCBORNotification(std::vector); -private: - FrontendChannel* m_frontendChannel; + void flush(); + void sendRawNotification(std::unique_ptr); + private: + FrontendChannel* frontend_channel_; }; // ------------- Dispatcher.