From 13e9fe37beb2c7c2e74c0cf5d7148647a7dd6c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 30 Jan 2019 23:04:00 +0100 Subject: [PATCH] tools: update inspector_protocol to f67ec518 Fixes: https://github.com/nodejs/node/issues/25808 Refs: https://chromium.googlesource.com/deps/inspector_protocol/+/f67ec5180f476830e839226b5ca948e43070fdab --- src/inspector/node_inspector.gypi | 13 +- tools/inspector_protocol/.clang-format | 5 + tools/inspector_protocol/.gitignore | 6 + tools/inspector_protocol/BUILD.gn | 106 ++ tools/inspector_protocol/BUILDCONFIG.gn | 64 ++ tools/inspector_protocol/DEPS | 67 ++ tools/inspector_protocol/OWNERS | 3 +- tools/inspector_protocol/README.md | 33 + tools/inspector_protocol/README.v8 | 16 - tools/inspector_protocol/WATCHLISTS | 19 + tools/inspector_protocol/base/BUILD.gn | 12 + ...ity.py => check_protocol_compatibility.py} | 17 +- .../{CodeGenerator.py => code_generator.py} | 74 +- tools/inspector_protocol/codereview.settings | 6 + ...eProtocols.py => concatenate_protocols.py} | 4 +- .../convert_protocol_to_json.py | 37 + tools/inspector_protocol/encoding/README.md | 129 +++ tools/inspector_protocol/encoding/cbor.cc | 794 +++++++++++++++ tools/inspector_protocol/encoding/cbor.h | 270 +++++ .../encoding/cbor_internals.h | 37 + .../inspector_protocol/encoding/cbor_test.cc | 937 ++++++++++++++++++ .../encoding/json_parser.cc | 586 +++++++++++ .../inspector_protocol/encoding/json_parser.h | 22 + .../encoding/json_parser_handler.h | 38 + .../encoding/json_parser_test.cc | 392 ++++++++ .../encoding/json_std_string_writer.cc | 215 ++++ .../encoding/json_std_string_writer.h | 25 + .../encoding/json_std_string_writer_test.cc | 150 +++ .../encoding/linux_dev_platform.cc | 45 + .../encoding/linux_dev_platform.h | 17 + tools/inspector_protocol/encoding/platform.h | 25 + tools/inspector_protocol/encoding/span.h | 47 + .../inspector_protocol/encoding/span_test.cc | 57 ++ tools/inspector_protocol/encoding/status.h | 61 ++ tools/inspector_protocol/encoding/str_util.cc | 20 + tools/inspector_protocol/encoding/str_util.h | 17 + .../inspector_protocol/gen_cbor_templates.py | 81 ++ .../inspector_protocol/inspector_protocol.gni | 7 +- .../inspector_protocol.gypi | 5 +- .../lib/Allocator_h.template | 9 +- tools/inspector_protocol/lib/Array_h.template | 2 + .../inspector_protocol/lib/CBOR_cpp.template | 804 +++++++++++++++ tools/inspector_protocol/lib/CBOR_h.template | 416 ++++++++ .../lib/Collections_h.template | 43 - .../lib/DispatcherBase_cpp.template | 173 ++-- .../lib/DispatcherBase_h.template | 71 +- .../lib/ErrorSupport_cpp.template | 2 + .../lib/ErrorSupport_h.template | 4 +- .../inspector_protocol/lib/Forward_h.template | 6 + .../lib/FrontendChannel_h.template | 12 +- tools/inspector_protocol/lib/Maybe_h.template | 71 +- .../lib/Object_cpp.template | 2 + .../inspector_protocol/lib/Object_h.template | 4 +- .../lib/Parser_cpp.template | 7 +- .../inspector_protocol/lib/Parser_h.template | 2 + .../lib/Protocol_cpp.template | 4 +- .../lib/ValueConversions_h.template | 29 + .../lib/Values_cpp.template | 254 ++++- .../inspector_protocol/lib/Values_h.template | 58 +- .../lib/base_string_adapter_cc.template | 304 ++++++ .../lib/base_string_adapter_h.template | 150 +++ .../{ConvertProtocolToJSON.py => pdl.py} | 48 +- tools/inspector_protocol/sample_config.json | 55 + .../sample_expected_errors.json | 7 + .../templates/Exported_h.template | 18 +- .../templates/Imported_h.template | 45 +- .../templates/TypeBuilder_cpp.template | 92 +- .../templates/TypeBuilder_h.template | 14 +- .../inspector_protocol/testing/gmock/BUILD.gn | 12 + .../inspector_protocol/testing/gtest/BUILD.gn | 25 + .../testing/gtest/gtest_main.cc | 11 + tools/inspector_protocol/testing/test.gni | 19 + .../third_party/gtest/BUILD.gn | 349 +++++++ 73 files changed, 7248 insertions(+), 333 deletions(-) create mode 100644 tools/inspector_protocol/.clang-format create mode 100644 tools/inspector_protocol/.gitignore create mode 100644 tools/inspector_protocol/BUILD.gn create mode 100644 tools/inspector_protocol/BUILDCONFIG.gn create mode 100644 tools/inspector_protocol/DEPS create mode 100644 tools/inspector_protocol/README.md delete mode 100644 tools/inspector_protocol/README.v8 create mode 100644 tools/inspector_protocol/WATCHLISTS create mode 100644 tools/inspector_protocol/base/BUILD.gn rename tools/inspector_protocol/{CheckProtocolCompatibility.py => check_protocol_compatibility.py} (97%) rename tools/inspector_protocol/{CodeGenerator.py => code_generator.py} (90%) mode change 100644 => 100755 create mode 100644 tools/inspector_protocol/codereview.settings rename tools/inspector_protocol/{ConcatenateProtocols.py => concatenate_protocols.py} (92%) create mode 100755 tools/inspector_protocol/convert_protocol_to_json.py create mode 100644 tools/inspector_protocol/encoding/README.md create mode 100644 tools/inspector_protocol/encoding/cbor.cc create mode 100644 tools/inspector_protocol/encoding/cbor.h create mode 100644 tools/inspector_protocol/encoding/cbor_internals.h create mode 100644 tools/inspector_protocol/encoding/cbor_test.cc create mode 100644 tools/inspector_protocol/encoding/json_parser.cc create mode 100644 tools/inspector_protocol/encoding/json_parser.h create mode 100644 tools/inspector_protocol/encoding/json_parser_handler.h create mode 100644 tools/inspector_protocol/encoding/json_parser_test.cc create mode 100644 tools/inspector_protocol/encoding/json_std_string_writer.cc create mode 100644 tools/inspector_protocol/encoding/json_std_string_writer.h create mode 100644 tools/inspector_protocol/encoding/json_std_string_writer_test.cc create mode 100644 tools/inspector_protocol/encoding/linux_dev_platform.cc create mode 100644 tools/inspector_protocol/encoding/linux_dev_platform.h create mode 100644 tools/inspector_protocol/encoding/platform.h create mode 100644 tools/inspector_protocol/encoding/span.h create mode 100644 tools/inspector_protocol/encoding/span_test.cc create mode 100644 tools/inspector_protocol/encoding/status.h create mode 100644 tools/inspector_protocol/encoding/str_util.cc create mode 100644 tools/inspector_protocol/encoding/str_util.h create mode 100755 tools/inspector_protocol/gen_cbor_templates.py create mode 100644 tools/inspector_protocol/lib/CBOR_cpp.template create mode 100644 tools/inspector_protocol/lib/CBOR_h.template delete mode 100644 tools/inspector_protocol/lib/Collections_h.template create mode 100644 tools/inspector_protocol/lib/base_string_adapter_cc.template create mode 100644 tools/inspector_protocol/lib/base_string_adapter_h.template rename tools/inspector_protocol/{ConvertProtocolToJSON.py => pdl.py} (80%) create mode 100644 tools/inspector_protocol/sample_config.json create mode 100644 tools/inspector_protocol/sample_expected_errors.json create mode 100644 tools/inspector_protocol/testing/gmock/BUILD.gn create mode 100644 tools/inspector_protocol/testing/gtest/BUILD.gn create mode 100644 tools/inspector_protocol/testing/gtest/gtest_main.cc create mode 100644 tools/inspector_protocol/testing/test.gni create mode 100644 tools/inspector_protocol/third_party/gtest/BUILD.gn diff --git a/src/inspector/node_inspector.gypi b/src/inspector/node_inspector.gypi index 21f98cd9002cab..5cade72bfb3bf4 100644 --- a/src/inspector/node_inspector.gypi +++ b/src/inspector/node_inspector.gypi @@ -14,7 +14,8 @@ 'node_protocol_files': [ '<(protocol_tool_path)/lib/Allocator_h.template', '<(protocol_tool_path)/lib/Array_h.template', - '<(protocol_tool_path)/lib/Collections_h.template', + '<(protocol_tool_path)/lib/CBOR_cpp.template', + '<(protocol_tool_path)/lib/CBOR_h.template', '<(protocol_tool_path)/lib/DispatcherBase_cpp.template', '<(protocol_tool_path)/lib/DispatcherBase_h.template', '<(protocol_tool_path)/lib/ErrorSupport_cpp.template', @@ -30,11 +31,13 @@ '<(protocol_tool_path)/lib/ValueConversions_h.template', '<(protocol_tool_path)/lib/Values_cpp.template', '<(protocol_tool_path)/lib/Values_h.template', + '<(protocol_tool_path)/lib/base_string_adapter_cc.template', + '<(protocol_tool_path)/lib/base_string_adapter_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)/CodeGenerator.py', + '<(protocol_tool_path)/code_generator.py', ] }, 'defines': [ @@ -86,7 +89,7 @@ ], 'action': [ 'python', - 'tools/inspector_protocol/ConvertProtocolToJSON.py', + 'tools/inspector_protocol/convert_protocol_to_json.py', '<@(_inputs)', '<@(_outputs)', ], @@ -104,7 +107,7 @@ 'process_outputs_as_sources': 1, 'action': [ 'python', - 'tools/inspector_protocol/CodeGenerator.py', + 'tools/inspector_protocol/code_generator.py', '--jinja_dir', '<@(protocol_tool_path)/..', '--output_base', '<(SHARED_INTERMEDIATE_DIR)/src/', '--config', '<(SHARED_INTERMEDIATE_DIR)/node_protocol_config.json', @@ -122,7 +125,7 @@ ], 'action': [ 'python', - 'tools/inspector_protocol/ConcatenateProtocols.py', + 'tools/inspector_protocol/concatenate_protocols.py', '<@(_inputs)', '<@(_outputs)', ], diff --git a/tools/inspector_protocol/.clang-format b/tools/inspector_protocol/.clang-format new file mode 100644 index 00000000000000..55479a2f760845 --- /dev/null +++ b/tools/inspector_protocol/.clang-format @@ -0,0 +1,5 @@ +# Defines the Google C++ style for automatic reformatting. +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Google +DerivePointerAlignment: false +MaxEmptyLinesToKeep: 1 diff --git a/tools/inspector_protocol/.gitignore b/tools/inspector_protocol/.gitignore new file mode 100644 index 00000000000000..9f8235b0ff0c43 --- /dev/null +++ b/tools/inspector_protocol/.gitignore @@ -0,0 +1,6 @@ +# Generated files. +out*/ + +# Third party files which are pulled with gclient (see DEPS). +third_party/gtest/gtest +third_party/mini_chromium/mini_chromium diff --git a/tools/inspector_protocol/BUILD.gn b/tools/inspector_protocol/BUILD.gn new file mode 100644 index 00000000000000..6b82c845fab297 --- /dev/null +++ b/tools/inspector_protocol/BUILD.gn @@ -0,0 +1,106 @@ +# 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. + +# All build targets below are experimental and not used within the +# Chromium / V8 / etc. trees yet thus far. Do not depend on anything. + +import("//testing/test.gni") + +static_library("json_parser") { + sources = [ + "encoding/json_parser.cc", + "encoding/json_parser.h", + "encoding/json_parser_handler.h", + "encoding/platform.h", + "encoding/span.h", + "encoding/status.h", + "encoding/str_util.cc", + "encoding/str_util.h", + ] +} + +static_library("linux_dev_platform") { + sources = [ + "encoding/linux_dev_platform.cc", + "encoding/linux_dev_platform.h", + "encoding/platform.h", + ] +} + +test("json_parser_test") { + sources = [ + "encoding/json_parser_test.cc", + ] + deps = [ + ":json_parser", + ":linux_dev_platform", + "//base", + "//testing/gmock:gmock", + "//testing/gtest:gtest", + "//testing/gtest:gtest_main", + ] +} + +static_library("cbor") { + sources = [ + "encoding/cbor.cc", + "encoding/cbor.h", + "encoding/json_parser_handler.h", + "encoding/span.h", + "encoding/status.h", + ] + deps = [ + ":json_parser", + ] +} + +test("cbor_test") { + sources = [ + "encoding/cbor_test.cc", + ] + deps = [ + ":cbor", + ":json_std_string_writer", + ":linux_dev_platform", + "//base", + "//testing/gmock:gmock", + "//testing/gtest:gtest", + "//testing/gtest:gtest_main", + ] +} + +test("span_test") { + sources = [ + "encoding/span.h", + "encoding/span_test.cc", + ] + deps = [ + "//base", + "//testing/gmock:gmock", + "//testing/gtest:gtest", + "//testing/gtest:gtest_main", + ] +} + +static_library("json_std_string_writer") { + sources = [ + "encoding/json_parser_handler.h", + "encoding/json_std_string_writer.cc", + "encoding/json_std_string_writer.h", + ] +} + +test("json_std_string_writer_test") { + sources = [ + "encoding/json_std_string_writer_test.cc", + ] + deps = [ + ":json_std_string_writer", + ":linux_dev_platform", + "//base", + "//testing/gmock:gmock", + "//testing/gtest:gtest", + "//testing/gtest:gtest_main", + ] +} diff --git a/tools/inspector_protocol/BUILDCONFIG.gn b/tools/inspector_protocol/BUILDCONFIG.gn new file mode 100644 index 00000000000000..24c71a0db64527 --- /dev/null +++ b/tools/inspector_protocol/BUILDCONFIG.gn @@ -0,0 +1,64 @@ +# 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. + +# The global configuration visible in all BUILD.gn files. See also +# ".gn" in this directory. + +if (target_os == "") { + target_os = host_os +} +if (target_cpu == "") { + target_cpu = host_cpu +} +if (current_cpu == "") { + current_cpu = target_cpu +} +if (current_os == "") { + current_os = target_os +} + +# This configures Clang as the C++ compiler. mini_chromium is an +# external dependency we pull in via the DEPS file / gclient sync / fetch. +set_default_toolchain( + "//third_party/mini_chromium/mini_chromium/build:gcc_like_toolchain") + +declare_args() { + # When true, enables the debug configuration, with additional run-time checks + # and logging. When false, enables the release configuration, with additional + # optimizations. + is_debug = false +} + +_default_configs = [ + "//third_party/mini_chromium/mini_chromium/build:default", + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", +] + +_default_executable_configs = + _default_configs + + [ "//third_party/mini_chromium/mini_chromium/build:executable" ] + +set_defaults("source_set") { + configs = _default_configs +} + +set_defaults("static_library") { + configs = _default_configs +} + +set_defaults("executable") { + configs = _default_executable_configs +} + +set_defaults("loadable_module") { + configs = _default_configs +} + +set_defaults("shared_library") { + configs = _default_configs +} + +set_defaults("test") { + configs = _default_executable_configs +} diff --git a/tools/inspector_protocol/DEPS b/tools/inspector_protocol/DEPS new file mode 100644 index 00000000000000..fe823bab3562cc --- /dev/null +++ b/tools/inspector_protocol/DEPS @@ -0,0 +1,67 @@ +# 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. +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# ! DO NOT ROLL THIS FILE INTO CHROMIUM (or other repositories). ! +# ! It's only useful for the standalone configuration in ! +# ! https://chromium.googlesource.com/deps/inspector_protocol/ ! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# +# This file configures gclient, a tool that installs dependencies +# at particular versions into this source tree. See +# https://chromium.googlesource.com/chromium/tools/depot_tools.git +# To fetch these dependencies, run "gclient sync". The fetch +# command (from depot_tools) will also run gclient sync. + +vars = { + 'chromium_git': 'https://chromium.googlesource.com', +} + +# The keys in this dictionary define where the external dependencies (values) +# will be mapped into this gclient. The root of the gclient will +# be the parent directory of the directory in which this DEPS file is. +deps = { + # gn (the build tool) and clang-format. + 'buildtools': + Var('chromium_git') + '/chromium/buildtools.git@' + + '6fe4a3251488f7af86d64fc25cf442e817cf6133', + # The toolchain definitions (clang C++ compiler etc.) + 'src/third_party/mini_chromium/mini_chromium': + Var('chromium_git') + '/chromium/mini_chromium@' + + '737433ebade4d446643c6c07daae02a67e8decca', + # For writing unittests. + 'src/third_party/gtest/gtest': + Var('chromium_git') + '/external/github.com/google/googletest@' + + 'c091b0469ab4c04ee9411ef770f32360945f4c53', +} + +hooks = [ + { + 'name': 'clang_format_linux', + 'pattern': '.', + 'condition': 'host_os == "linux"', + 'action': [ + 'download_from_google_storage', + '--no_resume', + '--no_auth', + '--bucket=chromium-clang-format', + '--sha1_file', + 'buildtools/linux64/clang-format.sha1', + ], + }, + { + 'name': 'gn_linux', + 'pattern': '.', + 'condition': 'host_os == "linux"', + 'action': [ + 'download_from_google_storage', + '--no_resume', + '--no_auth', + '--bucket=chromium-gn', + '--sha1_file', + 'buildtools/linux64/gn.sha1', + ], + }, +] +recursedeps = ['buildtools'] diff --git a/tools/inspector_protocol/OWNERS b/tools/inspector_protocol/OWNERS index 8d0b6d90cb233b..40cea0c268ac20 100644 --- a/tools/inspector_protocol/OWNERS +++ b/tools/inspector_protocol/OWNERS @@ -1,8 +1,7 @@ set noparent - alph@chromium.org caseq@chromium.org 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 new file mode 100644 index 00000000000000..da3f93f3f3b49e --- /dev/null +++ b/tools/inspector_protocol/README.md @@ -0,0 +1,33 @@ +# Chromium inspector (devtools) protocol + +This package contains code generators and templates for the Chromium +inspector protocol. + +The canonical location of this package is at +https://chromium.googlesource.com/deps/inspector_protocol/ + +In the Chromium tree, it's rolled into +https://cs.chromium.org/chromium/src/third_party/inspector_protocol/ + +In the V8 tree, it's rolled into +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 diff --git a/tools/inspector_protocol/README.v8 b/tools/inspector_protocol/README.v8 deleted file mode 100644 index 8a82f2a9c9d691..00000000000000 --- a/tools/inspector_protocol/README.v8 +++ /dev/null @@ -1,16 +0,0 @@ -Name: inspector protocol -Short Name: inspector_protocol -URL: https://chromium.googlesource.com/deps/inspector_protocol/ -Version: 0 -Revision: 752d4abd13119010cf30e454e8ef9b5fb7ef43a3 -License: BSD -License File: LICENSE -Security Critical: no - -Description: -src/inspector uses these scripts to generate handlers from protocol -description. - -Local modifications: -- This only includes the lib/ and templates/ directories, scripts, build - and the LICENSE files. diff --git a/tools/inspector_protocol/WATCHLISTS b/tools/inspector_protocol/WATCHLISTS new file mode 100644 index 00000000000000..651da10f1bcd9f --- /dev/null +++ b/tools/inspector_protocol/WATCHLISTS @@ -0,0 +1,19 @@ +# 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. + +# Watchlist Rules +# Refer: http://dev.chromium.org/developers/contributing-code/watchlists + +# IMPORTANT: The regular expression filepath is tested against each path using +# re.search, so it is not usually necessary to add .*. +{ + 'WATCHLIST_DEFINITIONS': { + 'devtools': { + 'filepath': '.*', + }, + }, + 'WATCHLISTS': { + 'devtools': ['devtools-reviews@chromium.org'] + }, +} diff --git a/tools/inspector_protocol/base/BUILD.gn b/tools/inspector_protocol/base/BUILD.gn new file mode 100644 index 00000000000000..78d0820bf3e65a --- /dev/null +++ b/tools/inspector_protocol/base/BUILD.gn @@ -0,0 +1,12 @@ +# 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 library simply delegates to the third party library; we're doing this +# to be compatible with the Chromium tree. +static_library("base") { + testonly = true # This dependency is only allowed for tests. + public_deps = [ + "//third_party/mini_chromium/mini_chromium/base", + ] +} diff --git a/tools/inspector_protocol/CheckProtocolCompatibility.py b/tools/inspector_protocol/check_protocol_compatibility.py similarity index 97% rename from tools/inspector_protocol/CheckProtocolCompatibility.py rename to tools/inspector_protocol/check_protocol_compatibility.py index c70162a2a44ef0..d2df244fa97154 100755 --- a/tools/inspector_protocol/CheckProtocolCompatibility.py +++ b/tools/inspector_protocol/check_protocol_compatibility.py @@ -45,11 +45,14 @@ # # Adding --show_changes to the command line prints out a list of valid public API changes. +from __future__ import print_function import copy import os.path import optparse import sys +import pdl + try: import json except ImportError: @@ -166,6 +169,11 @@ def compare_types(context, kind, type_1, type_2, types_map_1, types_map_2, depth base_type_1 = type_1["type"] base_type_2 = type_2["type"] + # Binary and string have the same wire representation in JSON. + if ((base_type_1 == "string" and base_type_2 == "binary") or + (base_type_2 == "string" and base_type_1 == "binary")): + return + if base_type_1 != base_type_2: errors.append("%s: %s base type mismatch, '%s' vs '%s'" % (context, kind, base_type_1, base_type_2)) elif base_type_1 == "object": @@ -228,8 +236,8 @@ def load_schema(file_name, domains): if not os.path.isfile(file_name): return input_file = open(file_name, "r") - json_string = input_file.read() - parsed_json = json.loads(json_string) + parsed_json = pdl.loads(input_file.read(), file_name) + input_file.close() domains += parsed_json["domains"] return parsed_json["version"] @@ -422,6 +430,7 @@ def load_domains_and_baselines(file_name, domains, baseline_domains): version = load_schema(os.path.normpath(file_name), domains) suffix = "-%s.%s.json" % (version["major"], version["minor"]) baseline_file = file_name.replace(".json", suffix) + baseline_file = file_name.replace(".pdl", suffix) load_schema(os.path.normpath(baseline_file), baseline_domains) return version @@ -467,9 +476,9 @@ def main(): if arg_options.show_changes: changes = compare_schemas(domains, baseline_domains, True) if len(changes) > 0: - print " Public changes since %s:" % version + print(" Public changes since %s:" % version) for change in changes: - print " %s" % change + print(" %s" % change) if arg_options.stamp: with open(arg_options.stamp, 'a') as _: diff --git a/tools/inspector_protocol/CodeGenerator.py b/tools/inspector_protocol/code_generator.py old mode 100644 new mode 100755 similarity index 90% rename from tools/inspector_protocol/CodeGenerator.py rename to tools/inspector_protocol/code_generator.py index e630b02985710f..fb9959d6082a7b --- a/tools/inspector_protocol/CodeGenerator.py +++ b/tools/inspector_protocol/code_generator.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Copyright 2016 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -14,6 +15,8 @@ except ImportError: import simplejson as json +import pdl + # Path handling for libraries and templates # Paths have to be normalized because Jinja uses the exact template path to # determine the hash used in the cache filename, and we need a pre-caching step @@ -30,14 +33,14 @@ def json_to_object(data, output_base, config_base): def json_object_hook(object_dict): items = [(k, os.path.join(config_base, v) if k == "path" else v) for (k, v) in object_dict.items()] items = [(k, os.path.join(output_base, v) if k == "output" else v) for (k, v) in items] - keys, values = zip(*items) + keys, values = list(zip(*items)) return collections.namedtuple('X', keys)(*values) return json.loads(data, object_hook=json_object_hook) def init_defaults(config_tuple, path, defaults): keys = list(config_tuple._fields) # pylint: disable=E1101 values = [getattr(config_tuple, k) for k in keys] - for i in xrange(len(keys)): + for i in range(len(keys)): if hasattr(values[i], "_fields"): values[i] = init_defaults(values[i], path + "." + keys[i], defaults) for optional in defaults: @@ -95,6 +98,7 @@ def init_defaults(config_tuple, path, defaults): ".protocol.export_macro": "", ".protocol.export_header": False, ".protocol.options": False, + ".protocol.file_name_prefix": "", ".exported": False, ".exported.export_macro": "", ".exported.export_header": False, @@ -130,7 +134,7 @@ def dash_to_camelcase(word): def to_snake_case(name): - return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxint).lower() + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name, sys.maxsize).lower() def to_method_case(config, name): @@ -156,6 +160,10 @@ def format_include(config, header, file_name=None): return header +def format_domain_include(config, header, file_name): + return format_include(config, header, config.protocol.file_name_prefix + file_name) + + def to_file_name(config, file_name): if config.use_snake_file_names: return to_snake_case(file_name).replace(".cpp", ".cc") @@ -258,6 +266,21 @@ def create_string_type_definition(): } +def create_binary_type_definition(): + # pylint: disable=W0622 + return { + "return_type": "Binary", + "pass_type": "const Binary&", + "to_pass_type": "%s", + "to_raw_type": "%s", + "to_rvalue": "%s", + "type": "Binary", + "raw_type": "Binary", + "raw_pass_type": "const Binary&", + "raw_return_type": "Binary", + } + + def create_primitive_type_definition(type): # pylint: disable=W0622 typedefs = { @@ -330,9 +353,8 @@ def __init__(self, config): def read_protocol_file(self, file_name): input_file = open(file_name, "r") - json_string = input_file.read() + parsed_json = pdl.loads(input_file.read(), file_name) input_file.close() - parsed_json = json.loads(json_string) version = parsed_json["version"]["major"] + "." + parsed_json["version"]["minor"] domains = [] for domain in parsed_json["domains"]: @@ -436,8 +458,10 @@ def create_type_definitions(self): self.type_definitions["boolean"] = create_primitive_type_definition("boolean") self.type_definitions["object"] = create_object_type_definition() self.type_definitions["any"] = create_any_type_definition() + self.type_definitions["binary"] = create_binary_type_definition() for domain in self.json_api["domains"]: self.type_definitions[domain["domain"] + ".string"] = create_string_type_definition() + self.type_definitions[domain["domain"] + ".binary"] = create_binary_type_definition() if not ("types" in domain): continue for type in domain["types"]: @@ -447,10 +471,11 @@ def create_type_definitions(self): elif type["type"] == "object": self.type_definitions[type_name] = create_user_type_definition(domain["domain"], type) elif type["type"] == "array": - items_type = type["items"]["type"] - self.type_definitions[type_name] = wrap_array_definition(self.type_definitions[items_type]) + self.type_definitions[type_name] = self.resolve_type(type) elif type["type"] == domain["domain"] + ".string": self.type_definitions[type_name] = create_string_type_definition() + elif type["type"] == domain["domain"] + ".binary": + self.type_definitions[type_name] = create_binary_type_definition() else: self.type_definitions[type_name] = create_primitive_type_definition(type["type"]) @@ -571,21 +596,23 @@ def main(): for domain in protocol.json_api["domains"]: class_name = domain["domain"] + file_name = config.protocol.file_name_prefix + class_name template_context = { "protocol": protocol, "config": config, "domain": domain, "join_arrays": join_arrays, "format_include": functools.partial(format_include, config), + "format_domain_include": functools.partial(format_domain_include, config), } if domain["domain"] in protocol.generate_domains: - outputs[os.path.join(config.protocol.output, to_file_name(config, class_name + ".h"))] = h_template.render(template_context) - outputs[os.path.join(config.protocol.output, to_file_name(config, class_name + ".cpp"))] = cpp_template.render(template_context) + outputs[os.path.join(config.protocol.output, to_file_name(config, file_name + ".h"))] = h_template.render(template_context) + outputs[os.path.join(config.protocol.output, to_file_name(config, file_name + ".cpp"))] = cpp_template.render(template_context) if domain["domain"] in protocol.exported_domains: - outputs[os.path.join(config.exported.output, to_file_name(config, class_name + ".h"))] = exported_template.render(template_context) + outputs[os.path.join(config.exported.output, to_file_name(config, file_name + ".h"))] = exported_template.render(template_context) if domain["domain"] in protocol.imported_domains: - outputs[os.path.join(config.protocol.output, to_file_name(config, class_name + ".h"))] = imported_template.render(template_context) + outputs[os.path.join(config.protocol.output, to_file_name(config, file_name + ".h"))] = imported_template.render(template_context) if config.lib: template_context = { @@ -596,8 +623,7 @@ def main(): lib_templates_dir = os.path.join(module_path, "lib") # Note these should be sorted in the right order. # TODO(dgozman): sort them programmatically based on commented includes. - lib_h_templates = [ - "Collections_h.template", + protocol_h_templates = [ "ErrorSupport_h.template", "Values_h.template", "Object_h.template", @@ -606,15 +632,17 @@ def main(): "Array_h.template", "DispatcherBase_h.template", "Parser_h.template", + "CBOR_h.template", ] - lib_cpp_templates = [ + protocol_cpp_templates = [ "Protocol_cpp.template", "ErrorSupport_cpp.template", "Values_cpp.template", "Object_cpp.template", "DispatcherBase_cpp.template", "Parser_cpp.template", + "CBOR_cpp.template", ] forward_h_templates = [ @@ -623,6 +651,14 @@ def main(): "FrontendChannel_h.template", ] + base_string_adapter_h_templates = [ + "base_string_adapter_h.template", + ] + + base_string_adapter_cc_templates = [ + "base_string_adapter_cc.template", + ] + def generate_lib_file(file_name, template_files): parts = [] for template_file in template_files: @@ -632,20 +668,22 @@ def generate_lib_file(file_name, template_files): outputs[file_name] = "\n\n".join(parts) generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Forward.h")), forward_h_templates) - generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.h")), lib_h_templates) - generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.cpp")), lib_cpp_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.h")), protocol_h_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "Protocol.cpp")), protocol_cpp_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "base_string_adapter.h")), base_string_adapter_h_templates) + generate_lib_file(os.path.join(config.lib.output, to_file_name(config, "base_string_adapter.cc")), base_string_adapter_cc_templates) # Make gyp / make generatos happy, otherwise make rebuilds world. inputs_ts = max(map(os.path.getmtime, inputs)) up_to_date = True - for output_file in outputs.iterkeys(): + for output_file in outputs.keys(): if not os.path.exists(output_file) or os.path.getmtime(output_file) < inputs_ts: up_to_date = False break if up_to_date: sys.exit() - for file_name, content in outputs.iteritems(): + for file_name, content in outputs.items(): out_file = open(file_name, "w") out_file.write(content) out_file.close() diff --git a/tools/inspector_protocol/codereview.settings b/tools/inspector_protocol/codereview.settings new file mode 100644 index 00000000000000..6ac8580b4ccd9f --- /dev/null +++ b/tools/inspector_protocol/codereview.settings @@ -0,0 +1,6 @@ +# This file is used by git-cl to get repository specific information. +CC_LIST: chromium-reviews@chromium.org +CODE_REVIEW_SERVER: codereview.chromium.org +GERRIT_HOST: True +PROJECT: inspector_protocol +VIEW_VC: https://chromium.googlesource.com/deps/inspector_protocol/+/ diff --git a/tools/inspector_protocol/ConcatenateProtocols.py b/tools/inspector_protocol/concatenate_protocols.py similarity index 92% rename from tools/inspector_protocol/ConcatenateProtocols.py rename to tools/inspector_protocol/concatenate_protocols.py index a7cbc992c76e40..e9f448efe72702 100755 --- a/tools/inspector_protocol/ConcatenateProtocols.py +++ b/tools/inspector_protocol/concatenate_protocols.py @@ -11,6 +11,7 @@ except ImportError: import simplejson as json +import pdl def main(argv): if len(argv) < 1: @@ -25,8 +26,7 @@ def main(argv): sys.stderr.write("Cannot find %s\n" % file_name) return 1 input_file = open(file_name, "r") - json_string = input_file.read() - parsed_json = json.loads(json_string) + parsed_json = pdl.loads(input_file.read(), file_name) domains += parsed_json["domains"] version = parsed_json["version"] diff --git a/tools/inspector_protocol/convert_protocol_to_json.py b/tools/inspector_protocol/convert_protocol_to_json.py new file mode 100755 index 00000000000000..96048f793d85a8 --- /dev/null +++ b/tools/inspector_protocol/convert_protocol_to_json.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import collections +import json +import os.path +import re +import sys + +import pdl + +def main(argv): + parser = argparse.ArgumentParser(description=( + "Converts from .pdl to .json by invoking the pdl Python module.")) + parser.add_argument('--map_binary_to_string', type=bool, + help=('If set, binary in the .pdl is mapped to a ' + 'string in .json. Client code will have to ' + 'base64 decode the string to get the payload.')) + parser.add_argument("pdl_file", help="The .pdl input file to parse.") + parser.add_argument("json_file", help="The .json output file write.") + args = parser.parse_args(argv) + file_name = os.path.normpath(args.pdl_file) + input_file = open(file_name, "r") + pdl_string = input_file.read() + protocol = pdl.loads(pdl_string, file_name, args.map_binary_to_string) + input_file.close() + + output_file = open(os.path.normpath(args.json_file), 'wb') + json.dump(protocol, output_file, indent=4, separators=(',', ': ')) + output_file.close() + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/tools/inspector_protocol/encoding/README.md b/tools/inspector_protocol/encoding/README.md new file mode 100644 index 00000000000000..beeb850f980807 --- /dev/null +++ b/tools/inspector_protocol/encoding/README.md @@ -0,0 +1,129 @@ +# Inspector Protocol Encoding Library + +### Notice + +Code in this directory (including tests and library) is not used in production +yet, and won't be for a little while. Below is a rough sketch as to the overall +plan as well as the goals. + +## Objective + +We're hoping for: + +- Support a binary protocol with a reasonably efficient wire format. + Efficient means that: + - We can send .png images and similar bytes over this wire. + JSON currently requires us to base64 encode such pieces. + - We can extract message ids etc. from the wire format without + fully parsing the entire message. With JSON This is not very + practical because strings etc. are not preceded with their length, + so one is forced to read through them. + - Perhaps also, we may want to append session ids and the like to a message. +- Reduce some runtime overhead: + - Reduce the overhead of conversions between utf8 / utf16 / etc. + E.g., for keys in dictionaries, we may want to assume they're utf8 + and do this encoding once so that we can efficiently look them up. + - Reduce the in-memory overhead, that is, don't require round-tripping + every message via base::Value and similar structures. +- Reduce / maintain code size: The current template approach duplicates some + compiled code in multiple namespaces; linking against this as a third party + library should allow us to reduce the code size a bit. E.g., we'll just have + one copy of the json parser (not a great example because the json parser isn't + quite used multiple times, I think the linker may eliminate the multiple + copies already). But overall, compiled code size should come down a bit for + existing functionality, and we'll try our best to keep the new functionality + (binary protocol) from adding more code bloat. +- Reduce complexity / make code easier to test / maintain / etc.: It should + become possible to test the inspector protocol layer prior to rolling it into + the various consuming projects. Having a library / code that's unittested, + should help toward this direction. We may also add some integration test, + e.g. some .pdl file with example protocol to drive the code generator + all the way to round / tripping / handling some messages. + The distinction between code that's part of the protocol layer and the other + parts of the inspector / devtools should become somewhat easier to see. + +## Approach + +To begin, we wish to make a library for the core part of digesting +inspector_protocol messages - the encoding library, in this directory. That is, +encoding / decoding as well as extracting some values that are needed for +dispatch (method, message id, session id). + +Our first input shall be JSON, the existing wire format. It's convenient for +testing, but also we'll later be able to use it to convert the existing wire +format as early as possible into binary, e.g. in the devtools pipe for headless, +and therefore we won't need to maintain much separate code between json and +binary inputs. + +With the approach described here, we will not parse the JSON into dictionaries +or base::Value / protocol::Value nested structures. Instead, we'll convert it +into a binary format, probably CBOR. + +For this, json_parser{.h,.cc} implements a JSON parser with a SAX style +handler interface, which will drive conversion into a binary buffer via a +specific handler. We'll call the abstraction for the binary format +MessageBuffer from here (most likely we'll have a C++ class with that name). + +For parts of the Chromium code that doesn't need to inspect deep into the +inspector_protocol messages, we'll be able to extract individual values (message +id, session id, method name) from this MessageBuffer. This is because +MessageBuffer owns the wire format bytes (e.g. std::vector in a private +field) and can return values as string pieces or int64_t as appropriate. + +This library does not involve code generation - it's just checked in C++. The +library will be unittested, and because we have a JSON parser, it's an excellent +way to drive examples into the CBOR buffer and check that they round trip OK. It +may be practical to develop a JSON serializer in the process as well, at first to +make testing simpler, and then later, to use it for compatibility in production. + +## Dependencies + +There is strtod (for json), there is character encoding, and there is +string / vector / etc. libraries. We'll try handle them to maximize +portability while making it easy to test the library. + +For strtod, clients of the library will need to provide their +implementation. We'll use virtual method dispatch (abstract class + +implementation), they'll have to pass in an object, e.g. a singleton. + +For UTF8 / UTF16 Unicode encoding, we'll try to avoid adding dependencies and +see how far we get with the approach, much like jsoncpp does it, basically very +minimal / light and no dependency on ICU. We'll depend on mini_chromium for +testing, so that gives us ICU there. + +strings. We'll try to keep this minimal as well, by primarily working with +string pieces on the public surface on the library. E.g., for the message id, +which can be extracted from the binary message, we'll return a string piece +which is backed by our binary buffer. + +The above library should be usable at least from the browser side, blink, and +v8, but likely also from other projects (e.g. Google internal). + +## Code Generation + +The code generator (not part of encoding library) will provide: + +* Types for parameters, results, notification objects. These are likely + generated classes just like now. +* It will be possible to instatiate these types directly from binary messages, + and it will be possible to directly serialize to binary messages. This will + be generated code, somewhat similar to the code that's in protocol buffers + when ParseFromString / ToString is invoked. It will probably involve + supporting return types / parameters in these routines that come from the + third party library, or if not, we'll instantiate it under the hood. It should + not involve protocol::Value except maybe for free-form parts of the messages + ("any" in the .pdl). +* The code generator may provide glue. E.g., convert between the + aforementioned string piece with whatever is the appropriate string piece in + blink / v8 / etc. +* The code generator is responsible for significant parts of the dispatching + logic. + +The code generator is currently implemented in Python / jinja2 templates; we can +probably adapt this / bend it to these needs. The templates for the existing +functionality should shrink, however. E.g., we will not have a json parser in a +template but rather use the one in the new library, and we may also move other +supporting code out of the templates and into the library. The benefit of this +should be that it's easier to understand / test / maintain, especially it should +become possible to unittest within this project and provide sufficient +confidence. diff --git a/tools/inspector_protocol/encoding/cbor.cc b/tools/inspector_protocol/encoding/cbor.cc new file mode 100644 index 00000000000000..9ff93dab3ee329 --- /dev/null +++ b/tools/inspector_protocol/encoding/cbor.cc @@ -0,0 +1,794 @@ +// 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 "cbor.h" + +#include +#include +#include "json_parser_handler.h" + +namespace inspector_protocol { +using namespace cbor; + +namespace { + +// See RFC 7049 Section 2.3, Table 2. +static constexpr uint8_t kEncodedTrue = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 21); +static constexpr uint8_t kEncodedFalse = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 20); +static constexpr uint8_t kEncodedNull = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 22); +static constexpr uint8_t kInitialByteForDouble = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 27); + +} // namespace + +uint8_t EncodeTrue() { return kEncodedTrue; } +uint8_t EncodeFalse() { return kEncodedFalse; } +uint8_t EncodeNull() { return kEncodedNull; } + +uint8_t EncodeIndefiniteLengthArrayStart() { + return kInitialByteIndefiniteLengthArray; +} + +uint8_t EncodeIndefiniteLengthMapStart() { + return kInitialByteIndefiniteLengthMap; +} + +uint8_t EncodeStop() { return kStopByte; } + +namespace { +// See RFC 7049 Table 3 and Section 2.4.4.2. This is used as a prefix for +// arbitrary binary data encoded as BYTE_STRING. +static constexpr uint8_t kExpectedConversionToBase64Tag = + EncodeInitialByte(MajorType::TAG, 22); + +// When parsing CBOR, we limit recursion depth for objects and arrays +// to this constant. +static constexpr int kStackLimit = 1000; + +// Writes the bytes for |v| to |out|, starting with the most significant byte. +// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html +template +void WriteBytesMostSignificantByteFirst(T v, std::vector* out) { + for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes) + out->push_back(0xff & (v >> (shift_bytes * 8))); +} +} // namespace + +namespace cbor_internals { +// Writes the start of a token with |type|. The |value| may indicate the size, +// or it may be the payload if the value is an unsigned integer. +void WriteTokenStart(MajorType type, uint64_t value, + std::vector* encoded) { + if (value < 24) { + // Values 0-23 are encoded directly into the additional info of the + // initial byte. + encoded->push_back(EncodeInitialByte(type, /*additional_info=*/value)); + return; + } + if (value <= std::numeric_limits::max()) { + // Values 24-255 are encoded with one initial byte, followed by the value. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation1Byte)); + encoded->push_back(value); + return; + } + if (value <= std::numeric_limits::max()) { + // Values 256-65535: 1 initial byte + 2 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation2Bytes)); + WriteBytesMostSignificantByteFirst(value, encoded); + return; + } + if (value <= std::numeric_limits::max()) { + // 32 bit uint: 1 initial byte + 4 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation4Bytes)); + WriteBytesMostSignificantByteFirst(static_cast(value), + encoded); + return; + } + // 64 bit uint: 1 initial byte + 8 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes)); + WriteBytesMostSignificantByteFirst(value, encoded); +} +} // namespace cbor_internals + +namespace { +// Extracts sizeof(T) bytes from |in| to extract a value of type T +// (e.g. uint64_t, uint32_t, ...), most significant byte first. +// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html +template +T ReadBytesMostSignificantByteFirst(span in) { + assert(static_cast(in.size()) >= sizeof(T)); + T result = 0; + for (std::size_t shift_bytes = 0; shift_bytes < sizeof(T); ++shift_bytes) + result |= T(in[sizeof(T) - 1 - shift_bytes]) << (shift_bytes * 8); + return result; +} +} // namespace + +namespace cbor_internals { +int8_t ReadTokenStart(span bytes, MajorType* type, uint64_t* value) { + if (bytes.empty()) return -1; + uint8_t initial_byte = bytes[0]; + *type = MajorType((initial_byte & kMajorTypeMask) >> kMajorTypeBitShift); + + uint8_t additional_information = initial_byte & kAdditionalInformationMask; + if (additional_information < 24) { + // Values 0-23 are encoded directly into the additional info of the + // initial byte. + *value = additional_information; + return 1; + } + if (additional_information == kAdditionalInformation1Byte) { + // Values 24-255 are encoded with one initial byte, followed by the value. + if (bytes.size() < 2) return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 2; + } + if (additional_information == kAdditionalInformation2Bytes) { + // Values 256-65535: 1 initial byte + 2 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint16_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 3; + } + if (additional_information == kAdditionalInformation4Bytes) { + // 32 bit uint: 1 initial byte + 4 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint32_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 5; + } + if (additional_information == kAdditionalInformation8Bytes) { + // 64 bit uint: 1 initial byte + 8 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint64_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 9; + } + return -1; +} +} // namespace cbor_internals + +using cbor_internals::WriteTokenStart; +using cbor_internals::ReadTokenStart; + +void EncodeInt32(int32_t value, std::vector* out) { + if (value >= 0) { + WriteTokenStart(MajorType::UNSIGNED, value, out); + } else { + uint64_t representation = static_cast(-(value + 1)); + WriteTokenStart(MajorType::NEGATIVE, representation, out); + } +} + +void EncodeString16(span in, std::vector* out) { + uint64_t byte_length = static_cast(in.size_bytes()); + WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); + // When emitting UTF16 characters, we always write the least significant byte + // first; this is because it's the native representation for X86. + // TODO(johannes): Implement a more efficient thing here later, e.g. + // casting *iff* the machine has this byte order. + // The wire format for UTF16 chars will probably remain the same + // (least significant byte first) since this way we can have + // golden files, unittests, etc. that port easily and universally. + // See also: + // https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html + for (const uint16_t two_bytes : in) { + out->push_back(two_bytes); + out->push_back(two_bytes >> 8); + } +} + +void EncodeString8(span in, std::vector* out) { + WriteTokenStart(MajorType::STRING, static_cast(in.size_bytes()), + out); + out->insert(out->end(), in.begin(), in.end()); +} + +void EncodeBinary(span in, std::vector* out) { + out->push_back(kExpectedConversionToBase64Tag); + uint64_t byte_length = static_cast(in.size_bytes()); + WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); + out->insert(out->end(), in.begin(), in.end()); +} + +// A double is encoded with a specific initial byte +// (kInitialByteForDouble) plus the 64 bits of payload for its value. +constexpr std::ptrdiff_t kEncodedDoubleSize = 1 + sizeof(uint64_t); + +// An envelope is encoded with a specific initial byte +// (kInitialByteForEnvelope), plus the start byte for a BYTE_STRING with a 32 +// bit wide length, plus a 32 bit length for that string. +constexpr std::ptrdiff_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t); + +void EncodeDouble(double value, std::vector* out) { + // The additional_info=27 indicates 64 bits for the double follow. + // See RFC 7049 Section 2.3, Table 1. + out->push_back(kInitialByteForDouble); + union { + double from_double; + uint64_t to_uint64; + } reinterpret; + reinterpret.from_double = value; + WriteBytesMostSignificantByteFirst(reinterpret.to_uint64, out); +} + +void EnvelopeEncoder::EncodeStart(std::vector* out) { + assert(byte_size_pos_ == 0); + out->push_back(kInitialByteForEnvelope); + out->push_back(kInitialByteFor32BitLengthByteString); + byte_size_pos_ = out->size(); + out->resize(out->size() + sizeof(uint32_t)); +} + +bool EnvelopeEncoder::EncodeStop(std::vector* out) { + assert(byte_size_pos_ != 0); + // The byte size is the size of the payload, that is, all the + // bytes that were written past the byte size position itself. + uint64_t byte_size = out->size() - (byte_size_pos_ + sizeof(uint32_t)); + // We store exactly 4 bytes, so at most INT32MAX, with most significant + // byte first. + if (byte_size > std::numeric_limits::max()) return false; + for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0; + --shift_bytes) { + (*out)[byte_size_pos_++] = 0xff & (byte_size >> (shift_bytes * 8)); + } + return true; +} + +namespace { +class JSONToCBOREncoder : public JSONParserHandler { + public: + JSONToCBOREncoder(std::vector* out, Status* status) + : out_(out), status_(status) { + *status_ = Status(); + } + + void HandleObjectBegin() override { + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthMap); + } + + void HandleObjectEnd() override { + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + envelopes_.back().EncodeStop(out_); + envelopes_.pop_back(); + } + + void HandleArrayBegin() override { + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthArray); + } + + void HandleArrayEnd() override { + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + envelopes_.back().EncodeStop(out_); + envelopes_.pop_back(); + } + + void HandleString16(std::vector chars) override { + for (uint16_t ch : chars) { + if (ch >= 0x7f) { + // If there's at least one non-7bit character, we encode as UTF16. + EncodeString16(span(chars.data(), chars.size()), out_); + return; + } + } + std::vector sevenbit_chars(chars.begin(), chars.end()); + EncodeString8(span(sevenbit_chars.data(), sevenbit_chars.size()), + out_); + } + + void HandleBinary(std::vector bytes) override { + EncodeBinary(span(bytes.data(), bytes.size()), out_); + } + + void HandleDouble(double value) override { EncodeDouble(value, out_); } + + void HandleInt32(int32_t value) override { EncodeInt32(value, out_); } + + void HandleBool(bool value) override { + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(value ? kEncodedTrue : kEncodedFalse); + } + + void HandleNull() override { + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(kEncodedNull); + } + + void HandleError(Status error) override { + assert(!error.ok()); + *status_ = error; + out_->clear(); + } + + private: + std::vector* out_; + std::vector envelopes_; + Status* status_; +}; +} // namespace + +std::unique_ptr NewJSONToCBOREncoder( + std::vector* out, Status* status) { + return std::unique_ptr(new JSONToCBOREncoder(out, status)); +} + +namespace { +// Below are three parsing routines for CBOR, which cover enough +// to roundtrip JSON messages. +bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); +bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); +bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); + +void ParseUTF16String(CBORTokenizer* tokenizer, JSONParserHandler* out) { + std::vector value; + span rep = tokenizer->GetString16WireRep(); + for (std::ptrdiff_t ii = 0; ii < rep.size(); ii += 2) + value.push_back((rep[ii + 1] << 8) | rep[ii]); + out->HandleString16(std::move(value)); + tokenizer->Next(); +} + +// For now this method only covers US-ASCII. Later, we may allow UTF8. +bool ParseASCIIString(CBORTokenizer* tokenizer, JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::STRING8); + std::vector value16; + for (uint8_t ch : tokenizer->GetString8()) { + // We only accept us-ascii (7 bit) strings here. Other strings must + // be encoded with 16 bit (the BYTE_STRING case). + if (ch >= 0x7f) { + out->HandleError( + Status{Error::CBOR_STRING8_MUST_BE_7BIT, tokenizer->Status().pos}); + return false; + } + value16.push_back(ch); + } + out->HandleString16(std::move(value16)); + tokenizer->Next(); + return true; +} + +bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + if (stack_depth > kStackLimit) { + out->HandleError( + Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos}); + return false; + } + // Skip past the envelope to get to what's inside. + if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + switch (tokenizer->TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + out->HandleError(tokenizer->Status()); + return false; + case CBORTokenTag::DONE: + out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, + tokenizer->Status().pos}); + return false; + case CBORTokenTag::TRUE_VALUE: + out->HandleBool(true); + tokenizer->Next(); + return true; + case CBORTokenTag::FALSE_VALUE: + out->HandleBool(false); + tokenizer->Next(); + return true; + case CBORTokenTag::NULL_VALUE: + out->HandleNull(); + tokenizer->Next(); + return true; + case CBORTokenTag::INT32: + out->HandleInt32(tokenizer->GetInt32()); + tokenizer->Next(); + return true; + case CBORTokenTag::DOUBLE: + out->HandleDouble(tokenizer->GetDouble()); + tokenizer->Next(); + return true; + case CBORTokenTag::STRING8: + return ParseASCIIString(tokenizer, out); + case CBORTokenTag::STRING16: + ParseUTF16String(tokenizer, out); + return true; + case CBORTokenTag::BINARY: { + span binary = tokenizer->GetBinary(); + out->HandleBinary(std::vector(binary.begin(), binary.end())); + tokenizer->Next(); + return true; + } + case CBORTokenTag::MAP_START: + return ParseMap(stack_depth + 1, tokenizer, out); + case CBORTokenTag::ARRAY_START: + return ParseArray(stack_depth + 1, tokenizer, out); + default: + out->HandleError( + Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos}); + return false; + } +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START); + tokenizer->Next(); + out->HandleArrayBegin(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + out->HandleError( + Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos}); + return false; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer->Status()); + return false; + } + // Parse value. + if (!ParseValue(stack_depth, tokenizer, out)) return false; + } + out->HandleArrayEnd(); + tokenizer->Next(); + return true; +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START); + out->HandleObjectBegin(); + tokenizer->Next(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + out->HandleError( + Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos}); + return false; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer->Status()); + return false; + } + // Parse key. + if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { + if (!ParseASCIIString(tokenizer, out)) return false; + } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { + ParseUTF16String(tokenizer, out); + } else { + out->HandleError( + Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos}); + return false; + } + // Parse value. + if (!ParseValue(stack_depth, tokenizer, out)) return false; + } + out->HandleObjectEnd(); + tokenizer->Next(); + return true; +} +} // namespace + +void ParseCBOR(span bytes, JSONParserHandler* json_out) { + if (bytes.empty()) { + json_out->HandleError(Status{Error::CBOR_NO_INPUT, 0}); + return; + } + if (bytes[0] != kInitialByteForEnvelope) { + json_out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0}); + return; + } + CBORTokenizer tokenizer(bytes); + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + json_out->HandleError(tokenizer.Status()); + return; + } + // We checked for the envelope start byte above, so the tokenizer + // must agree here, since it's not an error. + assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE); + tokenizer.EnterEnvelope(); + if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) { + json_out->HandleError( + Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos}); + return; + } + if (!ParseMap(/*stack_depth=*/1, &tokenizer, json_out)) return; + if (tokenizer.TokenTag() == CBORTokenTag::DONE) return; + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + json_out->HandleError(tokenizer.Status()); + return; + } + json_out->HandleError( + Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos}); +} + +CBORTokenizer::CBORTokenizer(span bytes) : bytes_(bytes) { + ReadNextToken(/*enter_envelope=*/false); +} +CBORTokenizer::~CBORTokenizer() {} + +CBORTokenTag CBORTokenizer::TokenTag() const { return token_tag_; } + +void CBORTokenizer::Next() { + if (token_tag_ == CBORTokenTag::ERROR_VALUE || token_tag_ == CBORTokenTag::DONE) + return; + ReadNextToken(/*enter_envelope=*/false); +} + +void CBORTokenizer::EnterEnvelope() { + assert(token_tag_ == CBORTokenTag::ENVELOPE); + ReadNextToken(/*enter_envelope=*/true); +} + +Status CBORTokenizer::Status() const { return status_; } + +int32_t CBORTokenizer::GetInt32() const { + assert(token_tag_ == CBORTokenTag::INT32); + // The range checks happen in ::ReadNextToken(). + return static_cast( + token_start_type_ == MajorType::UNSIGNED + ? token_start_internal_value_ + : -static_cast(token_start_internal_value_) - 1); +} + +double CBORTokenizer::GetDouble() const { + assert(token_tag_ == CBORTokenTag::DOUBLE); + union { + uint64_t from_uint64; + double to_double; + } reinterpret; + reinterpret.from_uint64 = ReadBytesMostSignificantByteFirst( + bytes_.subspan(status_.pos + 1)); + return reinterpret.to_double; +} + +span CBORTokenizer::GetString8() const { + assert(token_tag_ == CBORTokenTag::STRING8); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +span CBORTokenizer::GetString16WireRep() const { + assert(token_tag_ == CBORTokenTag::STRING16); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +span CBORTokenizer::GetBinary() const { + assert(token_tag_ == CBORTokenTag::BINARY); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +void CBORTokenizer::ReadNextToken(bool enter_envelope) { + if (enter_envelope) { + status_.pos += kEncodedEnvelopeHeaderSize; + } else { + status_.pos = + status_.pos == Status::npos() ? 0 : status_.pos + token_byte_length_; + } + status_.error = Error::OK; + if (status_.pos >= bytes_.size()) { + token_tag_ = CBORTokenTag::DONE; + return; + } + switch (bytes_[status_.pos]) { + case kStopByte: + SetToken(CBORTokenTag::STOP, 1); + return; + case kInitialByteIndefiniteLengthMap: + SetToken(CBORTokenTag::MAP_START, 1); + return; + case kInitialByteIndefiniteLengthArray: + SetToken(CBORTokenTag::ARRAY_START, 1); + return; + case kEncodedTrue: + SetToken(CBORTokenTag::TRUE_VALUE, 1); + return; + case kEncodedFalse: + SetToken(CBORTokenTag::FALSE_VALUE, 1); + return; + case kEncodedNull: + SetToken(CBORTokenTag::NULL_VALUE, 1); + return; + case kExpectedConversionToBase64Tag: { // BINARY + int8_t bytes_read = + ReadTokenStart(bytes_.subspan(status_.pos + 1), &token_start_type_, + &token_start_internal_value_); + int64_t token_byte_length = 1 + bytes_read + token_start_internal_value_; + if (-1 == bytes_read || token_start_type_ != MajorType::BYTE_STRING || + status_.pos + token_byte_length > bytes_.size()) { + SetError(Error::CBOR_INVALID_BINARY); + return; + } + SetToken(CBORTokenTag::BINARY, + static_cast(token_byte_length)); + return; + } + case kInitialByteForDouble: { // DOUBLE + if (status_.pos + kEncodedDoubleSize > bytes_.size()) { + SetError(Error::CBOR_INVALID_DOUBLE); + return; + } + SetToken(CBORTokenTag::DOUBLE, kEncodedDoubleSize); + return; + } + case kInitialByteForEnvelope: { // ENVELOPE + if (status_.pos + kEncodedEnvelopeHeaderSize > bytes_.size()) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + // The envelope must be a byte string with 32 bit length. + if (bytes_[status_.pos + 1] != kInitialByteFor32BitLengthByteString) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + // Read the length of the byte string. + token_start_internal_value_ = ReadBytesMostSignificantByteFirst( + bytes_.subspan(status_.pos + 2)); + // Make sure the payload is contained within the message. + if (token_start_internal_value_ + kEncodedEnvelopeHeaderSize + + status_.pos > + static_cast(bytes_.size())) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::ENVELOPE, + kEncodedEnvelopeHeaderSize + length); + return; + } + default: { + span remainder = + bytes_.subspan(status_.pos, bytes_.size() - status_.pos); + assert(!remainder.empty()); + int8_t token_start_length = ReadTokenStart(remainder, &token_start_type_, + &token_start_internal_value_); + bool success = token_start_length != -1; + switch (token_start_type_) { + case MajorType::UNSIGNED: // INT32. + if (!success || std::numeric_limits::max() < + token_start_internal_value_) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, token_start_length); + return; + case MajorType::NEGATIVE: // INT32. + if (!success || + std::numeric_limits::min() > + -static_cast(token_start_internal_value_) - 1) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, token_start_length); + return; + case MajorType::STRING: { // STRING8. + if (!success || remainder.size() < static_cast( + token_start_internal_value_)) { + SetError(Error::CBOR_INVALID_STRING8); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::STRING8, token_start_length + length); + return; + } + case MajorType::BYTE_STRING: { // STRING16. + if (!success || + remainder.size() < + static_cast(token_start_internal_value_) || + // Must be divisible by 2 since UTF16 is 2 bytes per character. + token_start_internal_value_ & 1) { + SetError(Error::CBOR_INVALID_STRING16); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::STRING16, token_start_length + length); + return; + } + case MajorType::ARRAY: + case MajorType::MAP: + case MajorType::TAG: + case MajorType::SIMPLE_VALUE: + SetError(Error::CBOR_UNSUPPORTED_VALUE); + return; + } + } + } +} + +void CBORTokenizer::SetToken(CBORTokenTag token_tag, + std::ptrdiff_t token_byte_length) { + token_tag_ = token_tag; + token_byte_length_ = token_byte_length; +} + +void CBORTokenizer::SetError(Error error) { + token_tag_ = CBORTokenTag::ERROR_VALUE; + status_.error = error; +} + +#if 0 +void DumpCBOR(span cbor) { + std::string indent; + CBORTokenizer tokenizer(cbor); + while (true) { + fprintf(stderr, "%s", indent.c_str()); + switch (tokenizer.TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + fprintf(stderr, "ERROR {status.error=%d, status.pos=%ld}\n", + tokenizer.Status().error, tokenizer.Status().pos); + return; + case CBORTokenTag::DONE: + fprintf(stderr, "DONE\n"); + return; + case CBORTokenTag::TRUE_VALUE: + fprintf(stderr, "TRUE_VALUE\n"); + break; + case CBORTokenTag::FALSE_VALUE: + fprintf(stderr, "FALSE_VALUE\n"); + break; + case CBORTokenTag::NULL_VALUE: + fprintf(stderr, "NULL_VALUE\n"); + break; + case CBORTokenTag::INT32: + fprintf(stderr, "INT32 [%d]\n", tokenizer.GetInt32()); + break; + case CBORTokenTag::DOUBLE: + fprintf(stderr, "DOUBLE [%lf]\n", tokenizer.GetDouble()); + break; + case CBORTokenTag::STRING8: { + span v = tokenizer.GetString8(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "STRING8 [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::STRING16: { + span v = tokenizer.GetString16WireRep(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "STRING16 [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::BINARY: { + span v = tokenizer.GetBinary(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "BINARY [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::MAP_START: + fprintf(stderr, "MAP_START\n"); + indent += " "; + break; + case CBORTokenTag::ARRAY_START: + fprintf(stderr, "ARRAY_START\n"); + indent += " "; + break; + case CBORTokenTag::STOP: + fprintf(stderr, "STOP\n"); + indent.erase(0, 2); + break; + case CBORTokenTag::ENVELOPE: + fprintf(stderr, "ENVELOPE\n"); + tokenizer.EnterEnvelope(); + continue; + } + tokenizer.Next(); + } +} +#endif + +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/cbor.h b/tools/inspector_protocol/encoding/cbor.h new file mode 100644 index 00000000000000..c4b3c309b3c0cd --- /dev/null +++ b/tools/inspector_protocol/encoding/cbor.h @@ -0,0 +1,270 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_CBOR_H_ +#define INSPECTOR_PROTOCOL_ENCODING_CBOR_H_ + +#include +#include +#include +#include "cbor_internals.h" +#include "json_parser_handler.h" +#include "span.h" +#include "status.h" + +namespace inspector_protocol { + +namespace cbor { + +// The major types from RFC 7049 Section 2.1. +enum class MajorType { + UNSIGNED = 0, + NEGATIVE = 1, + BYTE_STRING = 2, + STRING = 3, + ARRAY = 4, + MAP = 5, + TAG = 6, + SIMPLE_VALUE = 7 +}; + +// Indicates the number of bits the "initial byte" needs to be shifted to the +// right after applying |kMajorTypeMask| to produce the major type in the +// lowermost bits. +static constexpr uint8_t kMajorTypeBitShift = 5u; +// Mask selecting the low-order 5 bits of the "initial byte", which is where +// the additional information is encoded. +static constexpr uint8_t kAdditionalInformationMask = 0x1f; +// Mask selecting the high-order 3 bits of the "initial byte", which indicates +// the major type of the encoded value. +static constexpr uint8_t kMajorTypeMask = 0xe0; +// Indicates the integer is in the following byte. +static constexpr uint8_t kAdditionalInformation1Byte = 24u; +// Indicates the integer is in the next 2 bytes. +static constexpr uint8_t kAdditionalInformation2Bytes = 25u; +// Indicates the integer is in the next 4 bytes. +static constexpr uint8_t kAdditionalInformation4Bytes = 26u; +// Indicates the integer is in the next 8 bytes. +static constexpr uint8_t kAdditionalInformation8Bytes = 27u; + +// Encodes the initial byte, consisting of the |type| in the first 3 bits +// followed by 5 bits of |additional_info|. +constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) { + return (static_cast(type) << kMajorTypeBitShift) | + (additional_info & kAdditionalInformationMask); +} + +// TAG 24 indicates that what follows is a byte string which is +// encoded in CBOR format. We use this as a wrapper for +// maps and arrays, allowing us to skip them, because the +// byte string carries its size (byte length). +// https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +static constexpr uint8_t kInitialByteForEnvelope = + EncodeInitialByte(MajorType::TAG, 24); +// The initial byte for a byte string with at most 2^32 bytes +// of payload. This is used for envelope encoding, even if +// the byte string is shorter. +static constexpr uint8_t kInitialByteFor32BitLengthByteString = + EncodeInitialByte(MajorType::BYTE_STRING, 26); + +// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional +// info = 31. +static constexpr uint8_t kInitialByteIndefiniteLengthArray = + EncodeInitialByte(MajorType::ARRAY, 31); +static constexpr uint8_t kInitialByteIndefiniteLengthMap = + EncodeInitialByte(MajorType::MAP, 31); +// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite +// length maps / arrays. +static constexpr uint8_t kStopByte = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 31); + +} // namespace cbor + +// The binary encoding for the inspector protocol follows the CBOR specification +// (RFC 7049). Additional constraints: +// - Only indefinite length maps and arrays are supported. +// - Maps and arrays are wrapped with an envelope, that is, a +// CBOR tag with value 24 followed by a byte string specifying +// the byte length of the enclosed map / array. The byte string +// must use a 32 bit wide length. +// - At the top level, a message must be an indefinite length map +// wrapped by an envelope. +// - Maximal size for messages is 2^32 (4 GB). +// - For scalars, we support only the int32_t range, encoded as +// UNSIGNED/NEGATIVE (major types 0 / 1). +// - UTF16 strings, including with unbalanced surrogate pairs, are encoded +// as CBOR BYTE_STRING (major type 2). For such strings, the number of +// bytes encoded must be even. +// - UTF8 strings (major type 3) may only have ASCII characters +// (7 bit US-ASCII). +// - Arbitrary byte arrays, in the inspector protocol called 'binary', +// are encoded as BYTE_STRING (major type 2), prefixed with a byte +// indicating base64 when rendered as JSON. + +// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE| +// (major type 1) iff < 0. +void EncodeInt32(int32_t value, std::vector* out); + +// Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16 +// character in |in| is emitted with most significant byte first, +// appending to |out|. +void EncodeString16(span in, std::vector* out); + +// Encodes a UTF8 string |in| as STRING (major type 3). +void EncodeString8(span in, std::vector* out); + +// Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with +// definitive length, prefixed with tag 22 indicating expected conversion to +// base64 (see RFC 7049, Table 3 and Section 2.4.4.2). +void EncodeBinary(span in, std::vector* out); + +// Encodes / decodes a double as Major type 7 (SIMPLE_VALUE), +// with additional info = 27, followed by 8 bytes in big endian. +void EncodeDouble(double value, std::vector* out); + +// Some constants for CBOR tokens that only take a single byte on the wire. +uint8_t EncodeTrue(); +uint8_t EncodeFalse(); +uint8_t EncodeNull(); +uint8_t EncodeIndefiniteLengthArrayStart(); +uint8_t EncodeIndefiniteLengthMapStart(); +uint8_t EncodeStop(); + +// An envelope indicates the byte length of a wrapped item. +// We use this for maps and array, which allows the decoder +// to skip such (nested) values whole sale. +// It's implemented as a CBOR tag (major type 6) with additional +// info = 24, followed by a byte string with a 32 bit length value; +// so the maximal structure that we can wrap is 2^32 bits long. +// See also: https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +class EnvelopeEncoder { + public: + // Emits the envelope start bytes and records the position for the + // byte size in |byte_size_pos_|. Also emits empty bytes for the + // byte sisze so that encoding can continue. + void EncodeStart(std::vector* out); + // This records the current size in |out| at position byte_size_pos_. + // Returns true iff successful. + bool EncodeStop(std::vector* out); + + private: + std::size_t byte_size_pos_ = 0; +}; + +// This can be used to convert from JSON to CBOR, by passing the +// return value to the routines in json_parser.h. The handler will encode into +// |out|, and iff an error occurs it will set |status| to an error and clear +// |out|. Otherwise, |status.ok()| will be |true|. +std::unique_ptr NewJSONToCBOREncoder( + std::vector* out, Status* status); + +// Parses a CBOR encoded message from |bytes|, sending JSON events to +// |json_out|. If an error occurs, sends |out->HandleError|, and parsing stops. +// The client is responsible for discarding the already received information in +// that case. +void ParseCBOR(span bytes, JSONParserHandler* json_out); + +// Tags for the tokens within a CBOR message that CBORStream understands. +// Note that this is not the same terminology as the CBOR spec (RFC 7049), +// but rather, our adaptation. For instance, we lump unsigned and signed +// major type into INT32 here (and disallow values outside the int32_t range). +enum class CBORTokenTag { + // Encountered an error in the structure of the message. Consult + // status() for details. + ERROR_VALUE, + // Booleans and NULL. + TRUE_VALUE, + FALSE_VALUE, + NULL_VALUE, + // An int32_t (signed 32 bit integer). + INT32, + // A double (64 bit floating point). + DOUBLE, + // A UTF8 string. + STRING8, + // A UTF16 string. + STRING16, + // A binary string. + BINARY, + // Starts an indefinite length map; after the map start we expect + // alternating keys and values, followed by STOP. + MAP_START, + // Starts an indefinite length array; after the array start we + // expect values, followed by STOP. + ARRAY_START, + // Ends a map or an array. + STOP, + // An envelope indicator, wrapping a map or array. + // Internally this carries the byte length of the wrapped + // map or array. While CBORTokenizer::Next() will read / skip the entire + // envelope, CBORTokenizer::EnterEnvelope() reads the tokens + // inside of it. + ENVELOPE, + // We've reached the end there is nothing else to read. + DONE, +}; + +// CBORTokenizer segments a CBOR message, presenting the tokens therein as +// numbers, strings, etc. This is not a complete CBOR parser, but makes it much +// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse +// messages partially. +class CBORTokenizer { + public: + explicit CBORTokenizer(span bytes); + ~CBORTokenizer(); + + // Identifies the current token that we're looking at, + // or ERROR_VALUE (in which ase ::Status() has details) + // or DONE (if we're past the last token). + CBORTokenTag TokenTag() const; + + // Advances to the next token. + void Next(); + // Can only be called if TokenTag() == CBORTokenTag::ENVELOPE. + // While Next() would skip past the entire envelope / what it's + // wrapping, EnterEnvelope positions the cursor inside of the envelope, + // letting the client explore the nested structure. + void EnterEnvelope(); + + // If TokenTag() is CBORTokenTag::ERROR_VALUE, then Status().error describes + // the error more precisely; otherwise it'll be set to Error::OK. + // In either case, Status().pos is the current position. + struct Status Status() const; + + // The following methods retrieve the token values. They can only + // be called if TokenTag() matches. + + // To be called only if ::TokenTag() == CBORTokenTag::INT32. + int32_t GetInt32() const; + + // To be called only if ::TokenTag() == CBORTokenTag::DOUBLE. + double GetDouble() const; + + // To be called only if ::TokenTag() == CBORTokenTag::STRING8. + span GetString8() const; + + // Wire representation for STRING16 is low byte first (little endian). + // To be called only if ::TokenTag() == CBORTokenTag::STRING16. + span GetString16WireRep() const; + + // To be called only if ::TokenTag() == CBORTokenTag::BINARY. + span GetBinary() const; + + private: + void ReadNextToken(bool enter_envelope); + void SetToken(CBORTokenTag token, std::ptrdiff_t token_byte_length); + void SetError(Error error); + + span bytes_; + CBORTokenTag token_tag_; + struct Status status_; + std::ptrdiff_t token_byte_length_; + cbor::MajorType token_start_type_; + uint64_t token_start_internal_value_; +}; + +void DumpCBOR(span cbor); + +} // namespace inspector_protocol +#endif // INSPECTOR_PROTOCOL_ENCODING_CBOR_H_ diff --git a/tools/inspector_protocol/encoding/cbor_internals.h b/tools/inspector_protocol/encoding/cbor_internals.h new file mode 100644 index 00000000000000..ee732d65bb1cc4 --- /dev/null +++ b/tools/inspector_protocol/encoding/cbor_internals.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_CBOR_INTERNALS_H_ +#define INSPECTOR_PROTOCOL_ENCODING_CBOR_INTERNALS_H_ + +#include +#include +#include "span.h" +#include "status.h" + +// These internals are exposed for testing and implementing cbor.h. +// Never directly depend on them from other production code. +namespace inspector_protocol { +namespace cbor { +enum class MajorType; +} + +namespace cbor_internals { + +// Reads the start of a token with definitive size from |bytes|. +// |type| is the major type as specified in RFC 7049 Section 2.1. +// |value| is the payload (e.g. for MajorType::UNSIGNED) or is the size +// (e.g. for BYTE_STRING). +// If successful, returns the number of bytes read. Otherwise returns -1. +int8_t ReadTokenStart(span bytes, cbor::MajorType* type, + uint64_t* value); + +// Writes the start of a token with |type|. The |value| may indicate the size, +// or it may be the payload if the value is an unsigned integer. +void WriteTokenStart(cbor::MajorType type, uint64_t value, + std::vector* encoded); +} // namespace cbor_internals +} // namespace inspector_protocol + +#endif // INSPECTOR_PROTOCOL_ENCODING_CBOR_H_ diff --git a/tools/inspector_protocol/encoding/cbor_test.cc b/tools/inspector_protocol/encoding/cbor_test.cc new file mode 100644 index 00000000000000..d3264c46da8cb1 --- /dev/null +++ b/tools/inspector_protocol/encoding/cbor_test.cc @@ -0,0 +1,937 @@ +// 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 "cbor.h" + +#include +#include +#include +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "json_parser.h" +#include "json_std_string_writer.h" +#include "linux_dev_platform.h" + +using testing::ElementsAreArray; + +namespace inspector_protocol { + +using cbor::MajorType; + +// +// EncodeInt32 / CBORTokenTag::INT32 +// +TEST(EncodeDecodeInt32Test, Roundtrips23) { + // This roundtrips the int32_t value 23 through the pair of EncodeInt32 / + // CBORTokenizer; this is interesting since 23 is encoded as a single byte. + std::vector encoded; + EncodeInt32(23, &encoded); + // first three bits: major type = 0; remaining five bits: additional info = + // value 23. + EXPECT_THAT(encoded, ElementsAreArray(std::array{{23}})); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(23, tokenizer.GetInt32()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +TEST(EncodeDecodeInt32Test, RoundtripsUint8) { + // This roundtrips the int32_t value 42 through the pair of EncodeInt32 / + // CBORTokenizer. This is different from Roundtrip23 because 42 is encoded + // in an extra byte after the initial one. + std::vector encoded; + EncodeInt32(42, &encoded); + // first three bits: major type = 0; + // remaining five bits: additional info = 24, indicating payload is uint8. + EXPECT_THAT(encoded, ElementsAreArray(std::array{{24, 42}})); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(42, tokenizer.GetInt32()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +TEST(EncodeDecodeInt32Test, RoundtripsUint16) { + // 500 is encoded as a uint16 after the initial byte. + std::vector encoded; + EncodeInt32(500, &encoded); + // 1 for initial byte, 2 for uint16. + EXPECT_EQ(static_cast(3), encoded.size()); + // first three bits: major type = 0; + // remaining five bits: additional info = 25, indicating payload is uint16. + EXPECT_EQ(25, encoded[0]); + EXPECT_EQ(0x01, encoded[1]); + EXPECT_EQ(0xf4, encoded[2]); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(500, tokenizer.GetInt32()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +TEST(EncodeDecodeInt32Test, RoundtripsInt32Max) { + // std::numeric_limits is encoded as a uint32 after the initial byte. + std::vector encoded; + EncodeInt32(std::numeric_limits::max(), &encoded); + // 1 for initial byte, 4 for the uint32. + // first three bits: major type = 0; + // remaining five bits: additional info = 26, indicating payload is uint32. + EXPECT_THAT( + encoded, + ElementsAreArray(std::array{{26, 0x7f, 0xff, 0xff, 0xff}})); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(std::numeric_limits::max(), 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 + // it with EncodeInt32. However, CBOR does support this, so we + // encode it here manually with the internal routine, just to observe + // that it's considered an invalid int32 by CBORTokenizer. + std::vector encoded; + cbor_internals::WriteTokenStart(MajorType::UNSIGNED, 0xdeadbeef, &encoded); + // 1 for initial byte, 4 for the uint32. + // first three bits: major type = 0; + // remaining five bits: additional info = 26, indicating payload is uint32. + EXPECT_THAT( + encoded, + ElementsAreArray(std::array{{26, 0xde, 0xad, 0xbe, 0xef}})); + + // Now try to decode; we treat this as an invalid INT32. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + // 0xdeadbeef is > std::numerical_limits::max(). + EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); + EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error); +} + +TEST(EncodeDecodeInt32Test, DecodeErrorCases) { + struct TestCase { + 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"}}}; + + for (const TestCase& test : tests) { + SCOPED_TRACE(test.msg); + span encoded_bytes(&test.data[0], test.data.size()); + CBORTokenizer tokenizer( + span(&encoded_bytes[0], encoded_bytes.size())); + EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); + EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error); + } +} + +TEST(EncodeDecodeInt32Test, RoundtripsMinus24) { + // This roundtrips the int32_t value -24 through the pair of EncodeInt32 / + // CBORTokenizer; this is interesting since -24 is encoded as + // a single byte as NEGATIVE, and it tests the specific encoding + // (note how for unsigned the single byte covers values up to 23). + // Additional examples are covered in RoundtripsAdditionalExamples. + std::vector encoded; + EncodeInt32(-24, &encoded); + // first three bits: major type = 1; remaining five bits: additional info = + // value 23. + EXPECT_THAT(encoded, ElementsAreArray(std::array{{1 << 5 | 23}})); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(-24, tokenizer.GetInt32()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +TEST(EncodeDecodeInt32Test, RoundtripsAdditionalNegativeExamples) { + std::vector examples = {-1, + -10, + -24, + -25, + -300, + -30000, + -300 * 1000, + -1000 * 1000, + -1000 * 1000 * 1000, + std::numeric_limits::min()}; + for (int32_t example : examples) { + SCOPED_TRACE(base::StringPrintf("example %d", example)); + std::vector encoded; + EncodeInt32(example, &encoded); + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(example, tokenizer.GetInt32()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); + } +} + +// +// EncodeString16 / CBORTokenTag::STRING16 +// +TEST(EncodeDecodeString16Test, RoundtripsEmpty) { + // This roundtrips the empty utf16 string through the pair of EncodeString16 / + // CBORTokenizer. + std::vector encoded; + EncodeString16(span(), &encoded); + EXPECT_EQ(static_cast(1), encoded.size()); + // first three bits: major type = 2; remaining five bits: additional info = + // size 0. + EXPECT_EQ(2 << 5, encoded[0]); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag()); + span decoded_string16_wirerep = tokenizer.GetString16WireRep(); + EXPECT_TRUE(decoded_string16_wirerep.empty()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +// On the wire, we STRING16 is encoded as little endian (least +// significant byte first). The host may or may not be little endian, +// so this routine follows the advice in +// https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html. +std::vector String16WireRepToHost(span in) { + assert((in.size() & 1) == 0); // must be even number of bytes. + std::vector host_out; + for (std::ptrdiff_t ii = 0; ii < in.size(); ii += 2) + host_out.push_back(in[ii + 1] << 8 | in[ii]); + return host_out; +} + +TEST(EncodeDecodeString16Test, RoundtripsHelloWorld) { + // This roundtrips the hello world message which is given here in utf16 + // characters. 0xd83c, 0xdf0e: UTF16 encoding for the "Earth Globe Americas" + // character, 🌎. + std::array msg{ + {'H', 'e', 'l', 'l', 'o', ',', ' ', 0xd83c, 0xdf0e, '.'}}; + std::vector encoded; + EncodeString16(span(msg.data(), msg.size()), &encoded); + // This will be encoded as BYTE_STRING of length 20, so the 20 is encoded in + // the additional info part of the initial byte. Payload is two bytes for each + // UTF16 character. + uint8_t initial_byte = /*major type=*/2 << 5 | /*additional info=*/20; + std::array encoded_expected = { + {initial_byte, 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0, + ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}}; + EXPECT_THAT(encoded, ElementsAreArray(encoded_expected)); + + // Now decode to complete the roundtrip. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag()); + std::vector decoded = + String16WireRepToHost(tokenizer.GetString16WireRep()); + EXPECT_THAT(decoded, ElementsAreArray(msg)); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); + + // For bonus points, we look at the decoded message in UTF8 as well so we can + // easily see it on the terminal screen. + std::string utf8_decoded = + base::UTF16ToUTF8(base::StringPiece16(decoded.data(), decoded.size())); + EXPECT_EQ("Hello, 🌎.", utf8_decoded); +} + +TEST(EncodeDecodeString16Test, Roundtrips500) { + // We roundtrip a message that has 250 16 bit values. Each of these are just + // set to their index. 250 is interesting because the cbor spec uses a + // BYTE_STRING of length 500 for one of their examples of how to encode the + // start of it (section 2.1) so it's easy for us to look at the first three + // bytes closely. + std::vector two_fifty; + for (uint16_t ii = 0; ii < 250; ++ii) two_fifty.push_back(ii); + std::vector encoded; + EncodeString16(span(two_fifty.data(), two_fifty.size()), &encoded); + EXPECT_EQ(static_cast(3 + 250 * 2), encoded.size()); + // Now check the first three bytes: + // Major type: 2 (BYTE_STRING) + // Additional information: 25, indicating size is represented by 2 bytes. + // Bytes 1 and 2 encode 500 (0x01f4). + EXPECT_EQ(2 << 5 | 25, encoded[0]); + EXPECT_EQ(0x01, encoded[1]); + EXPECT_EQ(0xf4, encoded[2]); + + // Now decode to complete the roundtrip. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag()); + std::vector decoded = + String16WireRepToHost(tokenizer.GetString16WireRep()); + EXPECT_THAT(decoded, ElementsAreArray(two_fifty)); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +TEST(EncodeDecodeString16Test, ErrorCases) { + struct TestCase { + std::vector data; + std::string msg; + }; + std::vector tests{ + {TestCase{{2 << 5 | 1, 'a'}, + "length must be divisible by 2 (but it's 1)"}, + TestCase{{2 << 5 | 29}, "additional info = 29 isn't recognized"}}}; + for (const TestCase& test : tests) { + SCOPED_TRACE(test.msg); + CBORTokenizer tokenizer(span(&test.data[0], test.data.size())); + EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag()); + EXPECT_EQ(Error::CBOR_INVALID_STRING16, tokenizer.Status().error); + } +} + +// +// EncodeString8 / CBORTokenTag::STRING8 +// +TEST(EncodeDecodeString8Test, RoundtripsHelloWorld) { + // This roundtrips the hello world message which is given here in utf8 + // characters. 🌎 is a four byte utf8 character. + std::string utf8_msg = "Hello, 🌎."; + std::vector msg(utf8_msg.begin(), utf8_msg.end()); + std::vector encoded; + EncodeString8(span(msg.data(), msg.size()), &encoded); + // This will be encoded as STRING of length 12, so the 12 is encoded in + // the additional info part of the initial byte. Payload is one byte per + // utf8 byte. + uint8_t initial_byte = /*major type=*/3 << 5 | /*additional info=*/12; + std::array encoded_expected = {{initial_byte, 'H', 'e', 'l', 'l', + 'o', ',', ' ', 0xF0, 0x9f, 0x8c, + 0x8e, '.'}}; + EXPECT_THAT(encoded, ElementsAreArray(encoded_expected)); + + // Now decode to complete the roundtrip. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag()); + std::vector decoded(tokenizer.GetString8().begin(), + tokenizer.GetString8().end()); + EXPECT_THAT(decoded, ElementsAreArray(msg)); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +// +// EncodeBinary / CBORTokenTag::BINARY +// +TEST(EncodeDecodeBinaryTest, RoundtripsHelloWorld) { + std::vector binary = {'H', 'e', 'l', 'l', 'o', ',', ' ', + 'w', 'o', 'r', 'l', 'd', '.'}; + std::vector encoded; + EncodeBinary(span(binary.data(), binary.size()), &encoded); + // So, on the wire we see that the binary blob travels unmodified. + EXPECT_THAT( + encoded, + ElementsAreArray(std::array{ + {(6 << 5 | 22), // tag 22 indicating base64 interpretation in JSON + (2 << 5 | 13), // BYTE_STRING (type 2) of length 13 + 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'}})); + std::vector decoded; + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::BINARY, tokenizer.TokenTag()); + EXPECT_EQ(0, int(tokenizer.Status().error)); + decoded = std::vector(tokenizer.GetBinary().begin(), + tokenizer.GetBinary().end()); + EXPECT_THAT(decoded, ElementsAreArray(binary)); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +// +// EncodeDouble / CBORTokenTag::DOUBLE +// +TEST(EncodeDecodeDoubleTest, RoundtripsWikipediaExample) { + // https://en.wikipedia.org/wiki/Double-precision_floating-point_format + // provides the example of a hex representation 3FD5 5555 5555 5555, which + // approximates 1/3. + + const double kOriginalValue = 1.0 / 3; + std::vector encoded; + EncodeDouble(kOriginalValue, &encoded); + // first three bits: major type = 7; remaining five bits: additional info = + // value 27. This is followed by 8 bytes of payload (which match Wikipedia). + EXPECT_THAT( + encoded, + ElementsAreArray(std::array{ + {7 << 5 | 27, 0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}})); + + // Reverse direction: decode and compare with original value. + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::DOUBLE, tokenizer.TokenTag()); + EXPECT_THAT(tokenizer.GetDouble(), testing::DoubleEq(kOriginalValue)); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + +TEST(EncodeDecodeDoubleTest, RoundtripsAdditionalExamples) { + std::vector examples = {0.0, + 1.0, + -1.0, + 3.1415, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN()}; + for (double example : examples) { + SCOPED_TRACE(base::StringPrintf("example %lf", example)); + std::vector encoded; + EncodeDouble(example, &encoded); + span encoded_bytes(&encoded[0], encoded.size()); + CBORTokenizer tokenizer(span(&encoded[0], encoded.size())); + EXPECT_EQ(CBORTokenTag::DOUBLE, tokenizer.TokenTag()); + if (std::isnan(example)) + EXPECT_TRUE(std::isnan(tokenizer.GetDouble())); + else + EXPECT_THAT(tokenizer.GetDouble(), testing::DoubleEq(example)); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); + } +} + +// +// NewJSONToCBOREncoder +// +void EncodeSevenBitStringForTest(const std::string& key, + std::vector* out) { + for (char c : key) assert(c > 0); // Enforce 7 bits. Sadly, char is signed. + EncodeString8( + span(reinterpret_cast(key.data()), key.size()), + 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 = + NewJSONToCBOREncoder(&encoded, &status); + std::vector utf16 = {'f', 'o', 'o'}; + encoder->HandleString16(utf16); + EXPECT_EQ(Error::OK, status.error); + // Here we assert that indeed, seven bit strings are represented as + // bytes on the wire, "foo" is just "foo". + EXPECT_THAT(encoded, + ElementsAreArray(std::array{ + {/*major type 3*/ 3 << 5 | /*length*/ 3, 'f', 'o', 'o'}})); +} + +TEST(JsonCborRoundtrip, EncodingDecoding) { + // Hits all the cases except binary and error in JSONParserHandler, first + // parsing a JSON message into CBOR, then parsing it back from CBOR into JSON. + std::string json = + "{" + "\"string\":\"Hello, \\ud83c\\udf0e.\"," + "\"double\":3.1415," + "\"int\":1," + "\"negative int\":-1," + "\"bool\":true," + "\"null\":null," + "\"array\":[1,2,3]" + "}"; + std::vector encoded; + Status status; + std::unique_ptr encoder = + NewJSONToCBOREncoder(&encoded, &status); + span ascii_in(reinterpret_cast(json.data()), + json.size()); + ParseJSONChars(GetLinuxDevPlatform(), ascii_in, encoder.get()); + std::vector expected = { + 0xd8, // envelope + 0x5a, // byte string with 32 bit length + 0, 0, 0, 94, // length is 94 bytes + }; + expected.push_back(0xbf); // indef length map start + EncodeSevenBitStringForTest("string", &expected); + // This is followed by the encoded string for "Hello, 🌎." + // So, it's the same bytes that we tested above in + // EncodeDecodeString16Test.RoundtripsHelloWorld. + expected.push_back(/*major type=*/2 << 5 | /*additional info=*/20); + for (uint8_t ch : std::array{ + {'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0, + ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}}) + expected.push_back(ch); + EncodeSevenBitStringForTest("double", &expected); + EncodeDouble(3.1415, &expected); + EncodeSevenBitStringForTest("int", &expected); + EncodeInt32(1, &expected); + EncodeSevenBitStringForTest("negative int", &expected); + EncodeInt32(-1, &expected); + EncodeSevenBitStringForTest("bool", &expected); + expected.push_back(7 << 5 | 21); // RFC 7049 Section 2.3, Table 2: true + EncodeSevenBitStringForTest("null", &expected); + expected.push_back(7 << 5 | 22); // RFC 7049 Section 2.3, Table 2: null + EncodeSevenBitStringForTest("array", &expected); + expected.push_back(0xd8); // envelope + expected.push_back(0x5a); // byte string with 32 bit length + // the length is 5 bytes (that's up to end indef length array below). + for (uint8_t ch : std::array{{0, 0, 0, 5}}) + expected.push_back(ch); + expected.push_back(0x9f); // RFC 7049 Section 2.2.1, indef length array start + expected.push_back(1); // Three UNSIGNED values (easy since Major Type 0) + expected.push_back(2); + expected.push_back(3); + expected.push_back(0xff); // End indef length array + expected.push_back(0xff); // End indef length map + EXPECT_TRUE(status.ok()); + EXPECT_THAT(encoded, ElementsAreArray(expected)); + + // And now we roundtrip, decoding the message we just encoded. + std::string decoded; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &decoded, &status); + ParseCBOR(span(encoded.data(), encoded.size()), json_writer.get()); + EXPECT_EQ(Error::OK, status.error); + EXPECT_EQ(json, decoded); +} + +TEST(JsonCborRoundtrip, MoreRoundtripExamples) { + std::vector examples = { + // Tests that after closing a nested objects, additional key/value pairs + // are considered. + "{\"foo\":{\"bar\":1},\"baz\":2}", "{\"foo\":[1,2,3],\"baz\":2}"}; + for (const std::string& json : examples) { + SCOPED_TRACE(std::string("example: ") + json); + std::vector encoded; + Status status; + std::unique_ptr encoder = + NewJSONToCBOREncoder(&encoded, &status); + span ascii_in(reinterpret_cast(json.data()), + json.size()); + ParseJSONChars(GetLinuxDevPlatform(), ascii_in, encoder.get()); + std::string decoded; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &decoded, &status); + ParseCBOR(span(encoded.data(), encoded.size()), json_writer.get()); + EXPECT_EQ(Error::OK, status.error); + EXPECT_EQ(json, decoded); + } +} + +TEST(JSONToCBOREncoderTest, HelloWorldBinary_WithTripToJson) { + // The JSONParserHandler::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 calls to + // the encoder directly here, to construct a message, and one of these calls + // is ::HandleBinary, to which we pass a "binary" string containing "Hello, + // world.". + std::vector encoded; + Status status; + std::unique_ptr encoder = + NewJSONToCBOREncoder(&encoded, &status); + encoder->HandleObjectBegin(); + // Emit a key. + encoder->HandleString16(std::vector{'f', 'o', 'o'}); + // Emit the binary payload, an arbitrary array of bytes that happens to + // be the ascii message "Hello, world.". + encoder->HandleBinary(std::vector{'H', 'e', 'l', 'l', 'o', ',', ' ', + 'w', 'o', 'r', 'l', 'd', '.'}); + encoder->HandleObjectEnd(); + EXPECT_EQ(Error::OK, status.error); + + // Now drive the json writer via the CBOR decoder. + std::string decoded; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &decoded, &status); + ParseCBOR(span(encoded.data(), encoded.size()), json_writer.get()); + EXPECT_EQ(Error::OK, status.error); + EXPECT_EQ(Status::npos(), status.pos); + // "Hello, world." in base64 is "SGVsbG8sIHdvcmxkLg==". + EXPECT_EQ("{\"foo\":\"SGVsbG8sIHdvcmxkLg==\"}", decoded); +} + +// +// ParseCBOR +// +TEST(ParseCBORTest, ParseEmptyCBORMessage) { + // An envelope starting with 0xd8, 0x5a, with the byte length + // of 2, containing a map that's empty (0xbf for map + // start, and 0xff for map end). + std::vector in = {0xd8, 0x5a, 0, 0, 0, 2, 0xbf, 0xff}; + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(in.data(), in.size()), json_writer.get()); + EXPECT_EQ(Error::OK, status.error); + 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. + EncodeSevenBitStringForTest("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); + for (uint8_t ch : std::array{ + {'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0, + ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}}) + bytes.push_back(ch); + bytes.push_back(0xff); // stop byte + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::OK, status.error); + EXPECT_EQ("{\"msg\":\"Hello, \\ud83c\\udf0e.\"}", out); +} + +TEST(ParseCBORTest, NoInputError) { + std::vector in = {}; + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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 = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR( + span(reinterpret_cast(json.data()), json.size()), + json_writer.get()); + EXPECT_EQ(Error::CBOR_INVALID_START_BYTE, status.error); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, UnexpectedEofExpectedValueError) { + constexpr uint8_t kPayloadLen = 5; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); // A key; so value would be next. + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, status.error); + EXPECT_EQ(static_cast(bytes.size()), status.pos); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, UnexpectedEofInArrayError) { + constexpr uint8_t kPayloadLen = 8; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // The byte for starting a map. + EncodeSevenBitStringForTest("array", + &bytes); // A key; so value would be next. + bytes.push_back(0x9f); // byte for indefinite length array start. + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, status.error); + EXPECT_EQ(static_cast(bytes.size()), status.pos); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, UnexpectedEofInMapError) { + constexpr uint8_t kPayloadLen = 1; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // The byte for starting a map. + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_MAP, status.error); + EXPECT_EQ(7, status.pos); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, InvalidMapKeyError) { + constexpr uint8_t kPayloadLen = 2; + std::vector bytes = {0xd8, 0x5a, 0, + 0, 0, kPayloadLen, // envelope + 0xbf, // map start + 7 << 5 | 22}; // null (not a valid map key) + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::CBOR_INVALID_MAP_KEY, status.error); + EXPECT_EQ(7, status.pos); + EXPECT_EQ("", out); +} + +std::vector MakeNestedCBOR(int depth) { + std::vector bytes; + std::vector envelopes; + for (int ii = 0; ii < depth; ++ii) { + envelopes.emplace_back(); + envelopes.back().EncodeStart(&bytes); + bytes.push_back(0xbf); // indef length map start + EncodeSevenBitStringForTest("key", &bytes); + } + EncodeSevenBitStringForTest("innermost_value", &bytes); + for (int ii = 0; ii < depth; ++ii) { + bytes.push_back(0xff); // stop byte, finishes map. + envelopes.back().EncodeStop(&bytes); + envelopes.pop_back(); + } + return bytes; +} + +TEST(ParseCBORTest, StackLimitExceededError) { + { // Depth 3: no stack limit exceeded error and is easy to inspect. + std::vector bytes = MakeNestedCBOR(3); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::OK, status.error); + EXPECT_EQ(Status::npos(), status.pos); + EXPECT_EQ("{\"key\":{\"key\":{\"key\":\"innermost_value\"}}}", out); + } + { // Depth 1000: no stack limit exceeded. + std::vector bytes = MakeNestedCBOR(1000); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::OK, status.error); + EXPECT_EQ(Status::npos(), status.pos); + } + + // We just want to know the length of one opening map so we can compute + // where the error is encountered. So we look at a small example and find + // the second envelope start. + std::vector small_example = MakeNestedCBOR(3); + int64_t opening_segment_size = 1; // Start after the first envelope start. + while (opening_segment_size < static_cast(small_example.size()) && + small_example[opening_segment_size] != 0xd8) + opening_segment_size++; + + { // Depth 1001: limit exceeded. + std::vector bytes = MakeNestedCBOR(1001); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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 * 1001, status.pos); + } + { // Depth 1200: still limit exceeded, and at the same pos as for 1001 + std::vector bytes = MakeNestedCBOR(1200); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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 * 1001, status.pos); + } +} + +TEST(ParseCBORTest, UnsupportedValueError) { + constexpr uint8_t kPayloadLen = 6; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + int64_t error_pos = bytes.size(); + bytes.push_back(6 << 5 | 5); // tags aren't supported yet. + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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_EQ("", out); +} + +TEST(ParseCBORTest, InvalidString16Error) { + constexpr uint8_t kPayloadLen = 11; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + int64_t error_pos = bytes.size(); + // a BYTE_STRING of length 5 as value; since we interpret these as string16, + // it's going to be invalid as each character would need two bytes, but + // 5 isn't divisible by 2. + bytes.push_back(2 << 5 | 5); + for (int ii = 0; ii < 5; ++ii) bytes.push_back(' '); + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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_EQ("", out); +} + +TEST(ParseCBORTest, InvalidString8Error) { + constexpr uint8_t kPayloadLen = 6; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + int64_t error_pos = bytes.size(); + // a STRING of length 5 as value, but we're at the end of the bytes array + // so it can't be decoded successfully. + bytes.push_back(3 << 5 | 5); + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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_EQ("", out); +} + +TEST(ParseCBORTest, String8MustBe7BitError) { + constexpr uint8_t kPayloadLen = 11; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + int64_t error_pos = bytes.size(); + // a STRING of length 5 as value, with a payload that has bytes outside + // 7 bit (> 0x7f). + bytes.push_back(3 << 5 | 5); + for (int ii = 0; ii < 5; ++ii) bytes.push_back(0xf0); + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + ParseCBOR(span(bytes.data(), bytes.size()), json_writer.get()); + EXPECT_EQ(Error::CBOR_STRING8_MUST_BE_7BIT, status.error); + EXPECT_EQ(error_pos, status.pos); + EXPECT_EQ("", out); +} + +TEST(ParseCBORTest, InvalidBinaryError) { + constexpr uint8_t kPayloadLen = 9; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + int64_t error_pos = bytes.size(); + bytes.push_back(6 << 5 | 22); // base64 hint for JSON; indicates binary + bytes.push_back(2 << 5 | 10); // BYTE_STRING (major type 2) of length 10 + // Just two garbage bytes, not enough for the binary. + bytes.push_back(0x31); + bytes.push_back(0x23); + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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_EQ("", out); +} + +TEST(ParseCBORTest, InvalidDoubleError) { + constexpr uint8_t kPayloadLen = 8; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + int64_t error_pos = bytes.size(); + bytes.push_back(7 << 5 | 27); // initial byte for double + // Just two garbage bytes, not enough to represent an actual double. + bytes.push_back(0x31); + bytes.push_back(0x23); + EXPECT_EQ(kPayloadLen, bytes.size() - 6); + std::string out; + Status status; + std::unique_ptr json_writer = + NewJSONWriter(GetLinuxDevPlatform(), &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_EQ("", out); +} + +TEST(ParseCBORTest, InvalidSignedError) { + constexpr uint8_t kPayloadLen = 14; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + int64_t error_pos = bytes.size(); + // uint64_t max is a perfectly fine value to encode as CBOR unsigned, + // but we don't support this since we only cover the int32_t range. + cbor_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 = + NewJSONWriter(GetLinuxDevPlatform(), &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_EQ("", out); +} + +TEST(ParseCBORTest, TrailingJunk) { + constexpr uint8_t kPayloadLen = 35; + std::vector bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope + 0xbf}; // map start + EncodeSevenBitStringForTest("key", &bytes); + EncodeSevenBitStringForTest("value", &bytes); + bytes.push_back(0xff); // Up to here, it's a perfectly fine msg. + int64_t error_pos = bytes.size(); + EncodeSevenBitStringForTest("trailing junk", &bytes); + + cbor_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 = + NewJSONWriter(GetLinuxDevPlatform(), &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_EQ("", out); +} +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/json_parser.cc b/tools/inspector_protocol/encoding/json_parser.cc new file mode 100644 index 00000000000000..e1feffd335981f --- /dev/null +++ b/tools/inspector_protocol/encoding/json_parser.cc @@ -0,0 +1,586 @@ +// 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_parser.h" + +#include +#include +#include +#include "status.h" + +namespace inspector_protocol { +namespace { +const int kStackLimit = 1000; + +enum Token { + ObjectBegin, + ObjectEnd, + ArrayBegin, + ArrayEnd, + StringLiteral, + Number, + BoolTrue, + BoolFalse, + NullToken, + ListSeparator, + ObjectPairSeparator, + InvalidToken, + NoInput +}; + +const char* const kNullString = "null"; +const char* const kTrueString = "true"; +const char* const kFalseString = "false"; + +template +class JsonParser { + public: + JsonParser(const Platform* platform, JSONParserHandler* handler) + : platform_(platform), handler_(handler) {} + + void Parse(const Char* start, std::size_t length) { + start_pos_ = start; + const Char* end = start + length; + const Char* tokenEnd; + ParseValue(start, end, &tokenEnd, 0); + if (tokenEnd != end) { + HandleError(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, tokenEnd); + } + } + + private: + bool CharsToDouble(const uint16_t* chars, std::size_t length, + double* result) { + std::string buffer; + buffer.reserve(length + 1); + for (std::size_t ii = 0; ii < length; ++ii) { + bool is_ascii = !(chars[ii] & ~0x7F); + if (!is_ascii) return false; + buffer.push_back(static_cast(chars[ii])); + } + return platform_->StrToD(buffer.c_str(), result); + } + + bool CharsToDouble(const uint8_t* chars, std::size_t length, double* result) { + std::string buffer(reinterpret_cast(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'; + } + + static void SkipWhitespaceAndComments(const Char* start, const Char* end, + const Char** whitespace_end) { + while (start < end) { + if (IsSpaceOrNewLine(*start)) { + ++start; + } else if (*start == '/') { + const Char* comment_end; + if (!SkipComment(start, end, &comment_end)) break; + start = comment_end; + } else { + break; + } + } + *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; + const Char* token_end; + Token token = ParseToken(start, end, &token_start, &token_end); + switch (token) { + case NoInput: + HandleError(Error::JSON_PARSER_NO_INPUT, token_start); + return; + case InvalidToken: + HandleError(Error::JSON_PARSER_INVALID_TOKEN, token_start); + return; + case NullToken: + handler_->HandleNull(); + break; + case BoolTrue: + handler_->HandleBool(true); + break; + case BoolFalse: + handler_->HandleBool(false); + break; + case Number: { + double value; + if (!CharsToDouble(token_start, token_end - token_start, &value)) { + HandleError(Error::JSON_PARSER_INVALID_NUMBER, token_start); + return; + } + if (value >= std::numeric_limits::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(std::move(value)); + 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_->HandleObjectBegin(); + 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(std::move(key)); + 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_OBJECT_END, + token_start); + return; + } + } else if (token != ObjectEnd) { + // Unexpected value after last object value. Bail out. + HandleError(Error::JSON_PARSER_COMMA_OR_OBJECT_END_EXPECTED, + token_start); + return; + } + } + handler_->HandleObjectEnd(); + 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, pos - start_pos_}); + error_ = true; + } + } + + const Char* start_pos_ = nullptr; + bool error_ = false; + const Platform* platform_; + JSONParserHandler* handler_; +}; +} // namespace + +void ParseJSONChars(const Platform* platform, span chars, + JSONParserHandler* handler) { + JsonParser parser(platform, handler); + parser.Parse(chars.data(), chars.size()); +} + +void ParseJSONChars(const Platform* platform, span chars, + JSONParserHandler* handler) { + JsonParser parser(platform, handler); + parser.Parse(chars.data(), chars.size()); +} +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/json_parser.h b/tools/inspector_protocol/encoding/json_parser.h new file mode 100644 index 00000000000000..55384be17cc720 --- /dev/null +++ b/tools/inspector_protocol/encoding/json_parser.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_JSON_PARSER_H_ +#define INSPECTOR_PROTOCOL_ENCODING_JSON_PARSER_H_ + +#include +#include +#include "json_parser_handler.h" +#include "platform.h" +#include "span.h" + +namespace inspector_protocol { +// JSON parsing routines. +void ParseJSONChars(const Platform* platform, span chars, + JSONParserHandler* handler); +void ParseJSONChars(const Platform* platform, span chars, + JSONParserHandler* handler); +} // namespace inspector_protocol + +#endif // INSPECTOR_PROTOCOL_ENCODING_JSON_PARSER_H_ diff --git a/tools/inspector_protocol/encoding/json_parser_handler.h b/tools/inspector_protocol/encoding/json_parser_handler.h new file mode 100644 index 00000000000000..c501d99fd2447c --- /dev/null +++ b/tools/inspector_protocol/encoding/json_parser_handler.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_JSON_PARSER_HANDLER_H_ +#define INSPECTOR_PROTOCOL_ENCODING_JSON_PARSER_HANDLER_H_ + +#include +#include +#include "status.h" + +namespace inspector_protocol { +// Handler interface for JSON parser events. See also json_parser.h. +class JSONParserHandler { + public: + virtual ~JSONParserHandler() = default; + virtual void HandleObjectBegin() = 0; + virtual void HandleObjectEnd() = 0; + virtual void HandleArrayBegin() = 0; + virtual void HandleArrayEnd() = 0; + // TODO(johannes): Support utf8 (requires utf16->utf8 conversion + // internally, including handling mismatched surrogate pairs). + virtual void HandleString16(std::vector chars) = 0; + virtual void HandleBinary(std::vector bytes) = 0; + virtual void HandleDouble(double value) = 0; + virtual void HandleInt32(int32_t value) = 0; + virtual void HandleBool(bool value) = 0; + virtual void HandleNull() = 0; + + // The parser may send one error even after other events have already + // been received. Client code is reponsible to then discard the + // already processed events. + // |error| must be an eror, as in, |error.is_ok()| can't be true. + virtual void HandleError(Status error) = 0; +}; +} // namespace inspector_protocol + +#endif // INSPECTOR_PROTOCOL_ENCODING_JSON_PARSER_HANDLER_H_ diff --git a/tools/inspector_protocol/encoding/json_parser_test.cc b/tools/inspector_protocol/encoding/json_parser_test.cc new file mode 100644 index 00000000000000..babb3260a18190 --- /dev/null +++ b/tools/inspector_protocol/encoding/json_parser_test.cc @@ -0,0 +1,392 @@ +// 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_parser.h" + +#include +#include +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "gtest/gtest.h" +#include "linux_dev_platform.h" + +namespace inspector_protocol { +class Log : public JSONParserHandler { + public: + void HandleObjectBegin() override { log_ << "object begin\n"; } + + void HandleObjectEnd() override { log_ << "object end\n"; } + + void HandleArrayBegin() override { log_ << "array begin\n"; } + + void HandleArrayEnd() override { log_ << "array end\n"; } + + void HandleString16(std::vector chars) override { + base::StringPiece16 foo(reinterpret_cast(chars.data()), + chars.size()); + log_ << "string: " << base::UTF16ToUTF8(foo) << "\n"; + } + + void HandleBinary(std::vector bytes) override { + // JSON doesn't have native support for arbitrary bytes, so our parser will + // never call this. + assert(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}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "object begin\n" + "string: foo\n" + "int: 42\n" + "object end\n", + log_.str()); +} + +TEST_F(JsonParserTest, NestedDictionary) { + std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "object begin\n" + "string: foo\n" + "object begin\n" + "string: bar\n" + "object begin\n" + "string: baz\n" + "int: 1\n" + "object end\n" + "string: bar2\n" + "int: 2\n" + "object end\n" + "object end\n", + log_.str()); +} + +TEST_F(JsonParserTest, Doubles) { + std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "object begin\n" + "string: foo\n" + "double: 3.1415\n" + "string: bar\n" + "double: 3.1415\n" + "object 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.\"}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "object begin\n" + "string: msg\n" + "string: Hello, 🌎.\n" + "object 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()). + base::string16 json = base::UTF8ToUTF16("{\"space\": \"🌎 \\uD83C\\uDF19.\"}"); + ParseJSONChars(GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), + json.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "object begin\n" + "string: space\n" + "string: 🌎 🌙.\n" + "object 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\":\"🌎\"" + "}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "object begin\n" + "string: escapes\n" + "string: 🌙\n" + "string: 2 byte\n" + "string: гласность\n" + "string: 3 byte\n" + "string: 屋\n" + "string: 4 byte\n" + "string: 🌎\n" + "object end\n", + log_.str()); +} + +TEST_F(JsonParserTest, UnprocessedInputRemainsError) { + // Trailing junk after the valid JSON. + std::string json = "{\"foo\": 3.1415} junk"; + int64_t junk_idx = json.find("junk"); + EXPECT_GT(junk_idx, 0); + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &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) { + // kStackLimit is 1000 (see json_parser.cc). First let's + // try with a small nested example. + std::string json_3 = MakeNestedJson(3); + ParseJSONChars(GetLinuxDevPlatform(), + span(reinterpret_cast(json_3.data()), + json_3.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "object begin\n" + "string: foo\n" + "object begin\n" + "string: foo\n" + "object begin\n" + "string: foo\n" + "int: 42\n" + "object end\n" + "object end\n" + "object end\n", + log_.str()); + + // Now with kStackLimit (1000). + log_ = Log(); + std::string json_limit = MakeNestedJson(1000); + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json_limit.data()), + json_limit.size()), + &log_); + EXPECT_TRUE(log_.status().ok()); + // Now with kStackLimit + 1 (1001) - it exceeds in the innermost instance. + log_ = Log(); + std::string exceeded = MakeNestedJson(1001); + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(exceeded.data()), + exceeded.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error); + EXPECT_EQ(static_cast(strlen("{\"foo\":") * 1001), + log_.status().pos); + // Now way past the limit. Still, the point of exceeding is 1001. + log_ = Log(); + std::string far_out = MakeNestedJson(10000); + ParseJSONChars(GetLinuxDevPlatform(), + span(reinterpret_cast(far_out.data()), + far_out.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error); + EXPECT_EQ(static_cast(strlen("{\"foo\":") * 1001), + log_.status().pos); +} + +TEST_F(JsonParserTest, NoInputError) { + std::string json = ""; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_NO_INPUT, log_.status().error); + EXPECT_EQ(0, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, InvalidTokenError) { + std::string json = "|"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_INVALID_TOKEN, log_.status().error); + EXPECT_EQ(0, 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"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_INVALID_NUMBER, log_.status().error); + EXPECT_EQ(0, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, InvalidStringError) { + // \x22 is an unsupported escape sequence + std::string json = "\"foo\\x22\""; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_INVALID_STRING, log_.status().error); + EXPECT_EQ(0, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, UnexpectedArrayEndError) { + std::string json = "[1,2,]"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, log_.status().error); + EXPECT_EQ(5, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) { + std::string json = "[1,2 2"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED, + log_.status().error); + EXPECT_EQ(5, 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}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, log_.status().error); + EXPECT_EQ(16, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, ColonExpectedError) { + std::string json = "{\"foo\", 42}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_COLON_EXPECTED, log_.status().error); + EXPECT_EQ(6, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, UnexpectedObjectEndError) { + std::string json = "{\"foo\": 42, }"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_OBJECT_END, log_.status().error); + EXPECT_EQ(12, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, CommaOrObjectEndExpectedError) { + // The second separator should be a comma. + std::string json = "{\"foo\": 3.1415: \"bar\": 0}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_OBJECT_END_EXPECTED, + log_.status().error); + EXPECT_EQ(14, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +TEST_F(JsonParserTest, ValueExpectedError) { + std::string json = "}"; + ParseJSONChars( + GetLinuxDevPlatform(), + span(reinterpret_cast(json.data()), json.size()), + &log_); + EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, log_.status().error); + EXPECT_EQ(0, log_.status().pos); + EXPECT_EQ("", log_.str()); +} + +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/json_std_string_writer.cc b/tools/inspector_protocol/encoding/json_std_string_writer.cc new file mode 100644 index 00000000000000..5f5ed7869c7338 --- /dev/null +++ b/tools/inspector_protocol/encoding/json_std_string_writer.cc @@ -0,0 +1,215 @@ +// 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_std_string_writer.h" +#include +#include + +namespace inspector_protocol { +namespace { +// Prints |value| to |out| with 4 hex digits, most significant chunk first. +void PrintHex(uint16_t value, std::string* out) { + for (int ii = 3; ii >= 0; --ii) { + int four_bits = 0xf & (value >> (4 * ii)); + out->append(1, four_bits + ((four_bits <= 9) ? '0' : ('a' - 10))); + } +} + +// In the writer below, we maintain a stack of State instances. +// It is just enough to emit the appropriate delimiters and brackets +// in JSON. +enum class Container { + // Used for the top-level, initial state. + NONE, + // Inside a JSON object. + OBJECT, + // Inside a JSON array. + ARRAY +}; +class State { + public: + explicit State(Container container) : container_(container) {} + void StartElement(std::string* out) { + assert(container_ != Container::NONE || size_ == 0); + if (size_ != 0) { + char delim = (!(size_ & 1) || container_ == Container::ARRAY) ? ',' : ':'; + out->append(1, delim); + } + ++size_; + } + Container container() const { return container_; } + + private: + Container container_ = Container::NONE; + int size_ = 0; +}; + +constexpr char kBase64Table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/"; + +void Base64Encode(const std::vector& in, std::string* out) { + // The following three cases are based on the tables in the example + // section in https://en.wikipedia.org/wiki/Base64. We process three + // input bytes at a time, emitting 4 output bytes at a time. + std::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. +class Writer : public JSONParserHandler { + public: + Writer(Platform* platform, std::string* out, Status* status) + : platform_(platform), out_(out), status_(status) { + *status_ = Status(); + state_.emplace(Container::NONE); + } + + void HandleObjectBegin() override { + if (!status_->ok()) return; + assert(!state_.empty()); + state_.top().StartElement(out_); + state_.emplace(Container::OBJECT); + out_->append("{"); + } + + void HandleObjectEnd() override { + if (!status_->ok()) return; + assert(state_.size() >= 2 && state_.top().container() == Container::OBJECT); + state_.pop(); + out_->append("}"); + } + + void HandleArrayBegin() override { + if (!status_->ok()) return; + state_.top().StartElement(out_); + state_.emplace(Container::ARRAY); + out_->append("["); + } + + void HandleArrayEnd() override { + if (!status_->ok()) return; + assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY); + state_.pop(); + out_->append("]"); + } + + void HandleString16(std::vector chars) override { + if (!status_->ok()) return; + state_.top().StartElement(out_); + out_->append("\""); + for (const uint16_t ch : chars) { + if (ch == '"') { + out_->append("\\\""); + } else if (ch == '\\') { + out_->append("\\\\"); + } else if (ch == '\b') { + out_->append("\\b"); + } else if (ch == '\f') { + out_->append("\\f"); + } else if (ch == '\n') { + out_->append("\\n"); + } else if (ch == '\r') { + out_->append("\\r"); + } else if (ch == '\t') { + out_->append("\\t"); + } else if (ch >= 32 && ch <= 126) { + out_->append(1, ch); + } else { + out_->append("\\u"); + PrintHex(ch, out_); + } + } + out_->append("\""); + } + + void HandleBinary(std::vector bytes) override { + if (!status_->ok()) return; + state_.top().StartElement(out_); + out_->append("\""); + Base64Encode(bytes, out_); + out_->append("\""); + } + + void HandleDouble(double value) override { + if (!status_->ok()) return; + state_.top().StartElement(out_); + std::unique_ptr str_value = platform_->DToStr(value); + + // DToStr may fail to emit a 0 before the decimal dot. E.g. this is + // the case in base::NumberToString in Chromium (which is based on + // dmg_fp). So, much like + // https://cs.chromium.org/chromium/src/base/json/json_writer.cc + // we probe for this and emit the leading 0 anyway if necessary. + const char* chars = str_value.get(); + if (chars[0] == '.') { + out_->append("0"); + } else if (chars[0] == '-' && chars[1] == '.') { + out_->append("-0"); + ++chars; + } + out_->append(chars); + } + + void HandleInt32(int32_t value) override { + if (!status_->ok()) return; + state_.top().StartElement(out_); + out_->append(std::to_string(value)); + } + + void HandleBool(bool value) override { + if (!status_->ok()) return; + state_.top().StartElement(out_); + out_->append(value ? "true" : "false"); + } + + void HandleNull() override { + if (!status_->ok()) return; + state_.top().StartElement(out_); + out_->append("null"); + } + + void HandleError(Status error) override { + assert(!error.ok()); + *status_ = error; + out_->clear(); + } + + private: + Platform* platform_; + std::string* out_; + Status* status_; + std::stack state_; +}; +} // namespace + +std::unique_ptr NewJSONWriter(Platform* platform, + std::string* out, + Status* status) { + return std::unique_ptr(new Writer(platform, out, status)); +} +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/json_std_string_writer.h b/tools/inspector_protocol/encoding/json_std_string_writer.h new file mode 100644 index 00000000000000..b2c9b634faac49 --- /dev/null +++ b/tools/inspector_protocol/encoding/json_std_string_writer.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_JSON_STD_STRING_WRITER_H_ +#define INSPECTOR_PROTOCOL_ENCODING_JSON_STD_STRING_WRITER_H_ + +#include +#include +#include "json_parser_handler.h" +#include "platform.h" + +namespace inspector_protocol { +// 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 NewJSONWriter(Platform* platform, + std::string* out, + Status* status); +} // namespace inspector_protocol + +#endif // INSPECTOR_PROTOCOL_ENCODING_JSON_STD_STRING_WRITER_H_ diff --git a/tools/inspector_protocol/encoding/json_std_string_writer_test.cc b/tools/inspector_protocol/encoding/json_std_string_writer_test.cc new file mode 100644 index 00000000000000..0baa4d53d0e019 --- /dev/null +++ b/tools/inspector_protocol/encoding/json_std_string_writer_test.cc @@ -0,0 +1,150 @@ +// 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_std_string_writer.h" + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "gtest/gtest.h" +#include "linux_dev_platform.h" + +namespace inspector_protocol { +std::vector UTF16String(const std::string& utf8) { + base::string16 string16 = base::UTF8ToUTF16(utf8); + return std::vector(string16.data(), + string16.data() + string16.size()); +} + +TEST(JsonStdStringWriterTest, HelloWorld) { + std::string out; + Status status; + std::unique_ptr writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + writer->HandleObjectBegin(); + writer->HandleString16(UTF16String("msg1")); + writer->HandleString16(UTF16String("Hello, 🌎.")); + writer->HandleString16(UTF16String("msg2")); + writer->HandleString16(UTF16String("\\\b\r\n\t\f\"")); + writer->HandleString16(UTF16String("nested")); + writer->HandleObjectBegin(); + writer->HandleString16(UTF16String("double")); + writer->HandleDouble(3.1415); + writer->HandleString16(UTF16String("int")); + writer->HandleInt32(-42); + writer->HandleString16(UTF16String("bool")); + writer->HandleBool(false); + writer->HandleString16(UTF16String("null")); + writer->HandleNull(); + writer->HandleObjectEnd(); + writer->HandleString16(UTF16String("array")); + writer->HandleArrayBegin(); + writer->HandleInt32(1); + writer->HandleInt32(2); + writer->HandleInt32(3); + writer->HandleArrayEnd(); + writer->HandleObjectEnd(); + EXPECT_TRUE(status.ok()); + EXPECT_EQ( + "{\"msg1\":\"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, BinaryEncodedAsJsonString) { + // The encoder emits binary submitted to JSONParserHandler::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 = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + writer->HandleBinary({'M', 'a', 'n'}); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("\"TWFu\"", out); + } + { + std::string out; + Status status; + std::unique_ptr writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + writer->HandleBinary({'M', 'a'}); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("\"TWE=\"", out); + } + { + std::string out; + Status status; + std::unique_ptr writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + writer->HandleBinary({'M'}); + EXPECT_TRUE(status.ok()); + EXPECT_EQ("\"TQ==\"", out); + } + { // "Hello, world.", verified with base64decode.org. + std::string out; + Status status; + std::unique_ptr writer = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + writer->HandleBinary( + {'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 = + NewJSONWriter(GetLinuxDevPlatform(), &out, &status); + writer->HandleObjectBegin(); + writer->HandleString16(UTF16String("msg1")); + writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42}); + EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, status.error); + EXPECT_EQ(42, 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); + assert(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 = + NewJSONWriter(&platform, &out, &status); + writer->HandleArrayBegin(); + writer->HandleDouble(.1); + writer->HandleDouble(-.7); + writer->HandleArrayEnd(); + EXPECT_EQ("[0.1,-0.7]", out); +} +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/linux_dev_platform.cc b/tools/inspector_protocol/encoding/linux_dev_platform.cc new file mode 100644 index 00000000000000..146fd7e3f3e7e4 --- /dev/null +++ b/tools/inspector_protocol/encoding/linux_dev_platform.cc @@ -0,0 +1,45 @@ +// 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 "linux_dev_platform.h" + +#include +#include +#include +#include + +namespace inspector_protocol { +namespace { +class LinuxDevPlatform : public Platform { + bool StrToD(const char* str, double* result) const override { + locale_t new_locale = newlocale(LC_NUMERIC_MASK, "C", NULL); + char* end; + *result = strtod_l(str, &end, new_locale); + freelocale(new_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; + } +}; +} // namespace + +Platform* GetLinuxDevPlatform() { + static Platform* deps = new LinuxDevPlatform; + return deps; +} +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/linux_dev_platform.h b/tools/inspector_protocol/encoding/linux_dev_platform.h new file mode 100644 index 00000000000000..cf5dfd5b11e444 --- /dev/null +++ b/tools/inspector_protocol/encoding/linux_dev_platform.h @@ -0,0 +1,17 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_LINUX_DEV_PLATFORM_H_ +#define INSPECTOR_PROTOCOL_ENCODING_LINUX_DEV_PLATFORM_H_ + +#include "platform.h" + +namespace inspector_protocol { +// Returns an instance of the platform implementation that we're using for +// development on Linux. This is intended and appropriate for tests for this +// package, for now. +Platform* GetLinuxDevPlatform(); +} // namespace inspector_protocol + +#endif // INSPECTOR_PROTOCOL_ENCODING_LINUX_DEV_PLATFORM_H_ diff --git a/tools/inspector_protocol/encoding/platform.h b/tools/inspector_protocol/encoding/platform.h new file mode 100644 index 00000000000000..cfd2d5d6ea33ca --- /dev/null +++ b/tools/inspector_protocol/encoding/platform.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_PLATFORM_H_ +#define INSPECTOR_PROTOCOL_ENCODING_PLATFORM_H_ + +#include + +namespace inspector_protocol { +// 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; +}; +} // namespace inspector_protocol + +#endif // INSPECTOR_PROTOCOL_ENCODING_PLATFORM_H_ diff --git a/tools/inspector_protocol/encoding/span.h b/tools/inspector_protocol/encoding/span.h new file mode 100644 index 00000000000000..052062cf47c39d --- /dev/null +++ b/tools/inspector_protocol/encoding/span.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_SPAN_H_ +#define INSPECTOR_PROTOCOL_ENCODING_SPAN_H_ + +#include + +namespace inspector_protocol { +// This template is similar to std::span, which will be included in C++20. Like +// std::span it uses ptrdiff_t, which is signed (and thus a bit annoying +// sometimes when comparing with size_t), but other than this it's much simpler. +template +class span { + public: + using index_type = std::ptrdiff_t; + + span() : data_(nullptr), size_(0) {} + span(const T* data, index_type size) : data_(data), size_(size) {} + + const T* data() const { return data_; } + + const T* begin() const { return data_; } + const T* end() const { return data_ + size_; } + + const T& operator[](index_type idx) const { return data_[idx]; } + + span subspan(index_type offset, index_type count) const { + return span(data_ + offset, count); + } + + span subspan(index_type offset) const { + return span(data_ + offset, size_ - offset); + } + + bool empty() const { return size_ == 0; } + + index_type size() const { return size_; } + index_type size_bytes() const { return size_ * sizeof(T); } + + private: + const T* data_; + index_type size_; +}; +} // namespace inspector_protocol +#endif // INSPECTOR_PROTOCOL_ENCODING_SPAN_H_ diff --git a/tools/inspector_protocol/encoding/span_test.cc b/tools/inspector_protocol/encoding/span_test.cc new file mode 100644 index 00000000000000..85337464276258 --- /dev/null +++ b/tools/inspector_protocol/encoding/span_test.cc @@ -0,0 +1,57 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "span.h" + +#include "gtest/gtest.h" + +namespace inspector_protocol { +template +class SpanTest : public ::testing::Test {}; + +using TestTypes = ::testing::Types; +TYPED_TEST_CASE(SpanTest, TestTypes); + +TYPED_TEST(SpanTest, Empty) { + span empty; + EXPECT_TRUE(empty.empty()); + EXPECT_EQ(0, empty.size()); + EXPECT_EQ(0, 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(1, singular.size()); + EXPECT_EQ(sizeof(TypeParam), static_cast(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(5, five_items.size()); + EXPECT_EQ(sizeof(TypeParam) * 5, + static_cast(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(3, 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(2, two_items.size()); + EXPECT_EQ(33, two_items[0]); + EXPECT_EQ(34, two_items[1]); +} +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/status.h b/tools/inspector_protocol/encoding/status.h new file mode 100644 index 00000000000000..ea4a3daa212f73 --- /dev/null +++ b/tools/inspector_protocol/encoding/status.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_STATUS_H_ +#define INSPECTOR_PROTOCOL_ENCODING_STATUS_H_ + +#include + +namespace inspector_protocol { +// Error codes. +enum class Error { + OK = 0, + // JSON parsing errors - json_parser.{h,cc}. + JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01, + JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02, + JSON_PARSER_NO_INPUT = 0x03, + JSON_PARSER_INVALID_TOKEN = 0x04, + JSON_PARSER_INVALID_NUMBER = 0x05, + JSON_PARSER_INVALID_STRING = 0x06, + JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07, + JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08, + JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09, + JSON_PARSER_COLON_EXPECTED = 0x0a, + JSON_PARSER_UNEXPECTED_OBJECT_END = 0x0b, + JSON_PARSER_COMMA_OR_OBJECT_END_EXPECTED = 0x0c, + JSON_PARSER_VALUE_EXPECTED = 0x0d, + + CBOR_INVALID_INT32 = 0x0e, + CBOR_INVALID_DOUBLE = 0x0f, + CBOR_INVALID_ENVELOPE = 0x10, + CBOR_INVALID_STRING8 = 0x11, + CBOR_INVALID_STRING16 = 0x12, + CBOR_INVALID_BINARY = 0x13, + CBOR_UNSUPPORTED_VALUE = 0x14, + CBOR_NO_INPUT = 0x15, + CBOR_INVALID_START_BYTE = 0x16, + CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x17, + CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x18, + CBOR_UNEXPECTED_EOF_IN_MAP = 0x19, + CBOR_INVALID_MAP_KEY = 0x1a, + CBOR_STACK_LIMIT_EXCEEDED = 0x1b, + CBOR_STRING8_MUST_BE_7BIT = 0x1c, + CBOR_TRAILING_JUNK = 0x1d, + CBOR_MAP_START_EXPECTED = 0x1e, +}; + +// A status value with position that can be copied. The default status +// is OK. Usually, error status values should come with a valid position. +struct Status { + static constexpr std::ptrdiff_t npos() { return -1; } + + bool ok() const { return error == Error::OK; } + + Error error = Error::OK; + std::ptrdiff_t pos = npos(); + Status(Error error, std::ptrdiff_t pos) : error(error), pos(pos) {} + Status() = default; +}; +} // namespace inspector_protocol +#endif // INSPECTOR_PROTOCOL_ENCODING_STATUS_H_ diff --git a/tools/inspector_protocol/encoding/str_util.cc b/tools/inspector_protocol/encoding/str_util.cc new file mode 100644 index 00000000000000..1f4be7cda7563b --- /dev/null +++ b/tools/inspector_protocol/encoding/str_util.cc @@ -0,0 +1,20 @@ +// 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 "str_util.h" + +#include + +namespace inspector_protocol { +bool StrEq(span left, span right) { + return left.size() == right.size() && + 0 == memcmp(left.data(), right.data(), left.size()); +} + +bool StrEq(span left, const char* null_terminated_right) { + return static_cast(left.size()) == + strlen(null_terminated_right) && + 0 == memcmp(left.data(), null_terminated_right, left.size()); +} +} // namespace inspector_protocol diff --git a/tools/inspector_protocol/encoding/str_util.h b/tools/inspector_protocol/encoding/str_util.h new file mode 100644 index 00000000000000..f3775410a0622e --- /dev/null +++ b/tools/inspector_protocol/encoding/str_util.h @@ -0,0 +1,17 @@ +// 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. + +#ifndef INSPECTOR_PROTOCOL_ENCODING_STR_UTIL_H_ +#define INSPECTOR_PROTOCOL_ENCODING_STR_UTIL_H_ + +#include + +#include "span.h" + +namespace inspector_protocol { +// Returns true iff |left| and right have the same contents, byte for byte. +bool StrEq(span left, span right); +bool StrEq(span left, const char* null_terminated_right); +} // namespace inspector_protocol +#endif // INSPECTOR_PROTOCOL_ENCODING_STR_UTIL_H_ diff --git a/tools/inspector_protocol/gen_cbor_templates.py b/tools/inspector_protocol/gen_cbor_templates.py new file mode 100755 index 00000000000000..a3f1367234d115 --- /dev/null +++ b/tools/inspector_protocol/gen_cbor_templates.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# 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 is a very simple script to generate lib/CBOR_cc.template +and lib/CBOR_h.template from the C++ files in the encoding directory. +This lets us edit the .cc / .h files, which is more pleasant +(there are tests after all), and then update the templates +mechanically. +""" + +def ReadBody(filename, system_includes, bodies): + inside_namespace = False + body = ['\n', '// ===== %s =====\n' % filename, '\n'] + for line in open(filename): + if line == 'namespace inspector_protocol {\n': + inside_namespace = True + continue + if line == '} // namespace inspector_protocol\n': + inside_namespace = False + continue + if line.startswith('#include <'): + system_includes.append(line) + if inside_namespace: + body.append(line) + bodies.append(''.join(body)) + +LICENSE = """ +// 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. +""" + +NAMESPACE_START = """ +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} +""" + +NAMESPACE_END = """ +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} +""" + +def GenTemplate(with_header_guard, filename, from_cpp_files): + contents = [ + '{# This template is generated by gen_cbor_templates.py. #}\n', + '// Generated by %s.\n' % filename, + LICENSE, + '\n'] + if with_header_guard: + contents.append('#ifndef {{"_".join(config.protocol.namespace)}}_%s_h\n' % with_header_guard) + contents.append('#define {{"_".join(config.protocol.namespace)}}_%s_h\n' % with_header_guard) + system_includes=[] + bodies=[] + for cpp_file in from_cpp_files: + ReadBody(cpp_file, system_includes, bodies) + system_includes = list(set(system_includes)) # unique + system_includes.sort() + contents.append('\n') + contents += system_includes + contents.append(NAMESPACE_START) + contents += bodies + contents.append(NAMESPACE_END) + if with_header_guard: + contents.append('#endif // !defined({{"_".join(config.protocol.namespace)}}_%s_h)' % with_header_guard) + contents.append('\n') + + open(filename, 'w').write(''.join(contents)) + +if __name__ == '__main__': + GenTemplate(with_header_guard=None, filename='lib/CBOR_cpp.template', + from_cpp_files=['encoding/cbor.cc']) + GenTemplate(with_header_guard='CBOR', filename='lib/CBOR_h.template', + from_cpp_files=['encoding/status.h', 'encoding/span.h', + 'encoding/json_parser_handler.h', 'encoding/cbor_internals.h', + 'encoding/cbor.h']) diff --git a/tools/inspector_protocol/inspector_protocol.gni b/tools/inspector_protocol/inspector_protocol.gni index 5dcc1f522d8634..ecee9428d48048 100644 --- a/tools/inspector_protocol/inspector_protocol.gni +++ b/tools/inspector_protocol/inspector_protocol.gni @@ -27,13 +27,16 @@ template("inspector_protocol_generate") { inspector_protocol_dir = invoker.inspector_protocol_dir action(target_name) { - script = "$inspector_protocol_dir/CodeGenerator.py" + script = "$inspector_protocol_dir/code_generator.py" inputs = [ 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/Allocator_h.template", "$inspector_protocol_dir/lib/Array_h.template", - "$inspector_protocol_dir/lib/Collections_h.template", + "$inspector_protocol_dir/lib/CBOR_h.template", + "$inspector_protocol_dir/lib/CBOR_cpp.template", "$inspector_protocol_dir/lib/DispatcherBase_cpp.template", "$inspector_protocol_dir/lib/DispatcherBase_h.template", "$inspector_protocol_dir/lib/ErrorSupport_cpp.template", diff --git a/tools/inspector_protocol/inspector_protocol.gypi b/tools/inspector_protocol/inspector_protocol.gypi index 1fb7119b5fa567..3d0a60e1395241 100644 --- a/tools/inspector_protocol/inspector_protocol.gypi +++ b/tools/inspector_protocol/inspector_protocol.gypi @@ -7,7 +7,8 @@ 'inspector_protocol_files': [ 'lib/Allocator_h.template', 'lib/Array_h.template', - 'lib/Collections_h.template', + 'lib/CBOR_h.template', + 'lib/CBOR_cpp.template', 'lib/DispatcherBase_cpp.template', 'lib/DispatcherBase_h.template', 'lib/ErrorSupport_cpp.template', @@ -27,7 +28,7 @@ 'templates/Imported_h.template', 'templates/TypeBuilder_cpp.template', 'templates/TypeBuilder_h.template', - 'CodeGenerator.py', + 'code_generator.py', ] } } diff --git a/tools/inspector_protocol/lib/Allocator_h.template b/tools/inspector_protocol/lib/Allocator_h.template index 8f8109d695c597..15eaaaff0236d2 100644 --- a/tools/inspector_protocol/lib/Allocator_h.template +++ b/tools/inspector_protocol/lib/Allocator_h.template @@ -1,3 +1,5 @@ +// This file is generated by Allocator_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,13 +13,6 @@ namespace {{namespace}} { enum NotNullTagEnum { NotNullLiteral }; -#define PROTOCOL_DISALLOW_NEW() \ - private: \ - void* operator new(size_t) = delete; \ - void* operator new(size_t, NotNullTagEnum, void*) = delete; \ - void* operator new(size_t, void*) = delete; \ - public: - #define PROTOCOL_DISALLOW_COPY(ClassName) \ private: \ ClassName(const ClassName&) = delete; \ diff --git a/tools/inspector_protocol/lib/Array_h.template b/tools/inspector_protocol/lib/Array_h.template index 3854f6e5cd102e..c420a0f7e9650a 100644 --- a/tools/inspector_protocol/lib/Array_h.template +++ b/tools/inspector_protocol/lib/Array_h.template @@ -1,3 +1,5 @@ +// This file is generated by Array_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/tools/inspector_protocol/lib/CBOR_cpp.template b/tools/inspector_protocol/lib/CBOR_cpp.template new file mode 100644 index 00000000000000..97ae156b2226be --- /dev/null +++ b/tools/inspector_protocol/lib/CBOR_cpp.template @@ -0,0 +1,804 @@ +{# This template is generated by gen_cbor_templates.py. #} +// Generated by lib/CBOR_cpp.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#include +#include + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +// ===== encoding/cbor.cc ===== + +using namespace cbor; + +namespace { + +// See RFC 7049 Section 2.3, Table 2. +static constexpr uint8_t kEncodedTrue = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 21); +static constexpr uint8_t kEncodedFalse = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 20); +static constexpr uint8_t kEncodedNull = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 22); +static constexpr uint8_t kInitialByteForDouble = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 27); + +} // namespace + +uint8_t EncodeTrue() { return kEncodedTrue; } +uint8_t EncodeFalse() { return kEncodedFalse; } +uint8_t EncodeNull() { return kEncodedNull; } + +uint8_t EncodeIndefiniteLengthArrayStart() { + return kInitialByteIndefiniteLengthArray; +} + +uint8_t EncodeIndefiniteLengthMapStart() { + return kInitialByteIndefiniteLengthMap; +} + +uint8_t EncodeStop() { return kStopByte; } + +namespace { +// See RFC 7049 Table 3 and Section 2.4.4.2. This is used as a prefix for +// arbitrary binary data encoded as BYTE_STRING. +static constexpr uint8_t kExpectedConversionToBase64Tag = + EncodeInitialByte(MajorType::TAG, 22); + +// When parsing CBOR, we limit recursion depth for objects and arrays +// to this constant. +static constexpr int kStackLimit = 1000; + +// Writes the bytes for |v| to |out|, starting with the most significant byte. +// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html +template +void WriteBytesMostSignificantByteFirst(T v, std::vector* out) { + for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes) + out->push_back(0xff & (v >> (shift_bytes * 8))); +} +} // namespace + +namespace cbor_internals { +// Writes the start of a token with |type|. The |value| may indicate the size, +// or it may be the payload if the value is an unsigned integer. +void WriteTokenStart(MajorType type, uint64_t value, + std::vector* encoded) { + if (value < 24) { + // Values 0-23 are encoded directly into the additional info of the + // initial byte. + encoded->push_back(EncodeInitialByte(type, /*additional_info=*/value)); + return; + } + if (value <= std::numeric_limits::max()) { + // Values 24-255 are encoded with one initial byte, followed by the value. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation1Byte)); + encoded->push_back(value); + return; + } + if (value <= std::numeric_limits::max()) { + // Values 256-65535: 1 initial byte + 2 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation2Bytes)); + WriteBytesMostSignificantByteFirst(value, encoded); + return; + } + if (value <= std::numeric_limits::max()) { + // 32 bit uint: 1 initial byte + 4 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation4Bytes)); + WriteBytesMostSignificantByteFirst(static_cast(value), + encoded); + return; + } + // 64 bit uint: 1 initial byte + 8 bytes payload. + encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes)); + WriteBytesMostSignificantByteFirst(value, encoded); +} +} // namespace cbor_internals + +namespace { +// Extracts sizeof(T) bytes from |in| to extract a value of type T +// (e.g. uint64_t, uint32_t, ...), most significant byte first. +// See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html +template +T ReadBytesMostSignificantByteFirst(span in) { + assert(static_cast(in.size()) >= sizeof(T)); + T result = 0; + for (std::size_t shift_bytes = 0; shift_bytes < sizeof(T); ++shift_bytes) + result |= T(in[sizeof(T) - 1 - shift_bytes]) << (shift_bytes * 8); + return result; +} +} // namespace + +namespace cbor_internals { +int8_t ReadTokenStart(span bytes, MajorType* type, uint64_t* value) { + if (bytes.empty()) return -1; + uint8_t initial_byte = bytes[0]; + *type = MajorType((initial_byte & kMajorTypeMask) >> kMajorTypeBitShift); + + uint8_t additional_information = initial_byte & kAdditionalInformationMask; + if (additional_information < 24) { + // Values 0-23 are encoded directly into the additional info of the + // initial byte. + *value = additional_information; + return 1; + } + if (additional_information == kAdditionalInformation1Byte) { + // Values 24-255 are encoded with one initial byte, followed by the value. + if (bytes.size() < 2) return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 2; + } + if (additional_information == kAdditionalInformation2Bytes) { + // Values 256-65535: 1 initial byte + 2 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint16_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 3; + } + if (additional_information == kAdditionalInformation4Bytes) { + // 32 bit uint: 1 initial byte + 4 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint32_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 5; + } + if (additional_information == kAdditionalInformation8Bytes) { + // 64 bit uint: 1 initial byte + 8 bytes payload. + if (static_cast(bytes.size()) < 1 + sizeof(uint64_t)) + return -1; + *value = ReadBytesMostSignificantByteFirst(bytes.subspan(1)); + return 9; + } + return -1; +} +} // namespace cbor_internals + +using cbor_internals::WriteTokenStart; +using cbor_internals::ReadTokenStart; + +void EncodeInt32(int32_t value, std::vector* out) { + if (value >= 0) { + WriteTokenStart(MajorType::UNSIGNED, value, out); + } else { + uint64_t representation = static_cast(-(value + 1)); + WriteTokenStart(MajorType::NEGATIVE, representation, out); + } +} + +void EncodeString16(span in, std::vector* out) { + uint64_t byte_length = static_cast(in.size_bytes()); + WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); + // When emitting UTF16 characters, we always write the least significant byte + // first; this is because it's the native representation for X86. + // TODO(johannes): Implement a more efficient thing here later, e.g. + // casting *iff* the machine has this byte order. + // The wire format for UTF16 chars will probably remain the same + // (least significant byte first) since this way we can have + // golden files, unittests, etc. that port easily and universally. + // See also: + // https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html + for (const uint16_t two_bytes : in) { + out->push_back(two_bytes); + out->push_back(two_bytes >> 8); + } +} + +void EncodeString8(span in, std::vector* out) { + WriteTokenStart(MajorType::STRING, static_cast(in.size_bytes()), + out); + out->insert(out->end(), in.begin(), in.end()); +} + +void EncodeBinary(span in, std::vector* out) { + out->push_back(kExpectedConversionToBase64Tag); + uint64_t byte_length = static_cast(in.size_bytes()); + WriteTokenStart(MajorType::BYTE_STRING, byte_length, out); + out->insert(out->end(), in.begin(), in.end()); +} + +// A double is encoded with a specific initial byte +// (kInitialByteForDouble) plus the 64 bits of payload for its value. +constexpr std::ptrdiff_t kEncodedDoubleSize = 1 + sizeof(uint64_t); + +// An envelope is encoded with a specific initial byte +// (kInitialByteForEnvelope), plus the start byte for a BYTE_STRING with a 32 +// bit wide length, plus a 32 bit length for that string. +constexpr std::ptrdiff_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t); + +void EncodeDouble(double value, std::vector* out) { + // The additional_info=27 indicates 64 bits for the double follow. + // See RFC 7049 Section 2.3, Table 1. + out->push_back(kInitialByteForDouble); + union { + double from_double; + uint64_t to_uint64; + } reinterpret; + reinterpret.from_double = value; + WriteBytesMostSignificantByteFirst(reinterpret.to_uint64, out); +} + +void EnvelopeEncoder::EncodeStart(std::vector* out) { + assert(byte_size_pos_ == 0); + out->push_back(kInitialByteForEnvelope); + out->push_back(kInitialByteFor32BitLengthByteString); + byte_size_pos_ = out->size(); + out->resize(out->size() + sizeof(uint32_t)); +} + +bool EnvelopeEncoder::EncodeStop(std::vector* out) { + assert(byte_size_pos_ != 0); + // The byte size is the size of the payload, that is, all the + // bytes that were written past the byte size position itself. + uint64_t byte_size = out->size() - (byte_size_pos_ + sizeof(uint32_t)); + // We store exactly 4 bytes, so at most INT32MAX, with most significant + // byte first. + if (byte_size > std::numeric_limits::max()) return false; + for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0; + --shift_bytes) { + (*out)[byte_size_pos_++] = 0xff & (byte_size >> (shift_bytes * 8)); + } + return true; +} + +namespace { +class JSONToCBOREncoder : public JSONParserHandler { + public: + JSONToCBOREncoder(std::vector* out, Status* status) + : out_(out), status_(status) { + *status_ = Status(); + } + + void HandleObjectBegin() override { + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthMap); + } + + void HandleObjectEnd() override { + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + envelopes_.back().EncodeStop(out_); + envelopes_.pop_back(); + } + + void HandleArrayBegin() override { + envelopes_.emplace_back(); + envelopes_.back().EncodeStart(out_); + out_->push_back(kInitialByteIndefiniteLengthArray); + } + + void HandleArrayEnd() override { + out_->push_back(kStopByte); + assert(!envelopes_.empty()); + envelopes_.back().EncodeStop(out_); + envelopes_.pop_back(); + } + + void HandleString16(std::vector chars) override { + for (uint16_t ch : chars) { + if (ch >= 0x7f) { + // If there's at least one non-7bit character, we encode as UTF16. + EncodeString16(span(chars.data(), chars.size()), out_); + return; + } + } + std::vector sevenbit_chars(chars.begin(), chars.end()); + EncodeString8(span(sevenbit_chars.data(), sevenbit_chars.size()), + out_); + } + + void HandleBinary(std::vector bytes) override { + EncodeBinary(span(bytes.data(), bytes.size()), out_); + } + + void HandleDouble(double value) override { EncodeDouble(value, out_); } + + void HandleInt32(int32_t value) override { EncodeInt32(value, out_); } + + void HandleBool(bool value) override { + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(value ? kEncodedTrue : kEncodedFalse); + } + + void HandleNull() override { + // See RFC 7049 Section 2.3, Table 2. + out_->push_back(kEncodedNull); + } + + void HandleError(Status error) override { + assert(!error.ok()); + *status_ = error; + out_->clear(); + } + + private: + std::vector* out_; + std::vector envelopes_; + Status* status_; +}; +} // namespace + +std::unique_ptr NewJSONToCBOREncoder( + std::vector* out, Status* status) { + return std::unique_ptr(new JSONToCBOREncoder(out, status)); +} + +namespace { +// Below are three parsing routines for CBOR, which cover enough +// to roundtrip JSON messages. +bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); +bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); +bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out); + +void ParseUTF16String(CBORTokenizer* tokenizer, JSONParserHandler* out) { + std::vector value; + span rep = tokenizer->GetString16WireRep(); + for (std::ptrdiff_t ii = 0; ii < rep.size(); ii += 2) + value.push_back((rep[ii + 1] << 8) | rep[ii]); + out->HandleString16(std::move(value)); + tokenizer->Next(); +} + +// For now this method only covers US-ASCII. Later, we may allow UTF8. +bool ParseASCIIString(CBORTokenizer* tokenizer, JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::STRING8); + std::vector value16; + for (uint8_t ch : tokenizer->GetString8()) { + // We only accept us-ascii (7 bit) strings here. Other strings must + // be encoded with 16 bit (the BYTE_STRING case). + if (ch >= 0x7f) { + out->HandleError( + Status{Error::CBOR_STRING8_MUST_BE_7BIT, tokenizer->Status().pos}); + return false; + } + value16.push_back(ch); + } + out->HandleString16(std::move(value16)); + tokenizer->Next(); + return true; +} + +bool ParseValue(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + if (stack_depth > kStackLimit) { + out->HandleError( + Status{Error::CBOR_STACK_LIMIT_EXCEEDED, tokenizer->Status().pos}); + return false; + } + // Skip past the envelope to get to what's inside. + if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + switch (tokenizer->TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + out->HandleError(tokenizer->Status()); + return false; + case CBORTokenTag::DONE: + out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, + tokenizer->Status().pos}); + return false; + case CBORTokenTag::TRUE_VALUE: + out->HandleBool(true); + tokenizer->Next(); + return true; + case CBORTokenTag::FALSE_VALUE: + out->HandleBool(false); + tokenizer->Next(); + return true; + case CBORTokenTag::NULL_VALUE: + out->HandleNull(); + tokenizer->Next(); + return true; + case CBORTokenTag::INT32: + out->HandleInt32(tokenizer->GetInt32()); + tokenizer->Next(); + return true; + case CBORTokenTag::DOUBLE: + out->HandleDouble(tokenizer->GetDouble()); + tokenizer->Next(); + return true; + case CBORTokenTag::STRING8: + return ParseASCIIString(tokenizer, out); + case CBORTokenTag::STRING16: + ParseUTF16String(tokenizer, out); + return true; + case CBORTokenTag::BINARY: { + span binary = tokenizer->GetBinary(); + out->HandleBinary(std::vector(binary.begin(), binary.end())); + tokenizer->Next(); + return true; + } + case CBORTokenTag::MAP_START: + return ParseMap(stack_depth + 1, tokenizer, out); + case CBORTokenTag::ARRAY_START: + return ParseArray(stack_depth + 1, tokenizer, out); + default: + out->HandleError( + Status{Error::CBOR_UNSUPPORTED_VALUE, tokenizer->Status().pos}); + return false; + } +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +bool ParseArray(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START); + tokenizer->Next(); + out->HandleArrayBegin(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + out->HandleError( + Status{Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, tokenizer->Status().pos}); + return false; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer->Status()); + return false; + } + // Parse value. + if (!ParseValue(stack_depth, tokenizer, out)) return false; + } + out->HandleArrayEnd(); + tokenizer->Next(); + return true; +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +bool ParseMap(int32_t stack_depth, CBORTokenizer* tokenizer, + JSONParserHandler* out) { + assert(tokenizer->TokenTag() == CBORTokenTag::MAP_START); + out->HandleObjectBegin(); + tokenizer->Next(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + out->HandleError( + Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer->Status().pos}); + return false; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) { + out->HandleError(tokenizer->Status()); + return false; + } + // Parse key. + if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { + if (!ParseASCIIString(tokenizer, out)) return false; + } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { + ParseUTF16String(tokenizer, out); + } else { + out->HandleError( + Status{Error::CBOR_INVALID_MAP_KEY, tokenizer->Status().pos}); + return false; + } + // Parse value. + if (!ParseValue(stack_depth, tokenizer, out)) return false; + } + out->HandleObjectEnd(); + tokenizer->Next(); + return true; +} +} // namespace + +void ParseCBOR(span bytes, JSONParserHandler* json_out) { + if (bytes.empty()) { + json_out->HandleError(Status{Error::CBOR_NO_INPUT, 0}); + return; + } + if (bytes[0] != kInitialByteForEnvelope) { + json_out->HandleError(Status{Error::CBOR_INVALID_START_BYTE, 0}); + return; + } + CBORTokenizer tokenizer(bytes); + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + json_out->HandleError(tokenizer.Status()); + return; + } + // We checked for the envelope start byte above, so the tokenizer + // must agree here, since it's not an error. + assert(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE); + tokenizer.EnterEnvelope(); + if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) { + json_out->HandleError( + Status{Error::CBOR_MAP_START_EXPECTED, tokenizer.Status().pos}); + return; + } + if (!ParseMap(/*stack_depth=*/1, &tokenizer, json_out)) return; + if (tokenizer.TokenTag() == CBORTokenTag::DONE) return; + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) { + json_out->HandleError(tokenizer.Status()); + return; + } + json_out->HandleError( + Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos}); +} + +CBORTokenizer::CBORTokenizer(span bytes) : bytes_(bytes) { + ReadNextToken(/*enter_envelope=*/false); +} +CBORTokenizer::~CBORTokenizer() {} + +CBORTokenTag CBORTokenizer::TokenTag() const { return token_tag_; } + +void CBORTokenizer::Next() { + if (token_tag_ == CBORTokenTag::ERROR_VALUE || token_tag_ == CBORTokenTag::DONE) + return; + ReadNextToken(/*enter_envelope=*/false); +} + +void CBORTokenizer::EnterEnvelope() { + assert(token_tag_ == CBORTokenTag::ENVELOPE); + ReadNextToken(/*enter_envelope=*/true); +} + +Status CBORTokenizer::Status() const { return status_; } + +int32_t CBORTokenizer::GetInt32() const { + assert(token_tag_ == CBORTokenTag::INT32); + // The range checks happen in ::ReadNextToken(). + return static_cast( + token_start_type_ == MajorType::UNSIGNED + ? token_start_internal_value_ + : -static_cast(token_start_internal_value_) - 1); +} + +double CBORTokenizer::GetDouble() const { + assert(token_tag_ == CBORTokenTag::DOUBLE); + union { + uint64_t from_uint64; + double to_double; + } reinterpret; + reinterpret.from_uint64 = ReadBytesMostSignificantByteFirst( + bytes_.subspan(status_.pos + 1)); + return reinterpret.to_double; +} + +span CBORTokenizer::GetString8() const { + assert(token_tag_ == CBORTokenTag::STRING8); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +span CBORTokenizer::GetString16WireRep() const { + assert(token_tag_ == CBORTokenTag::STRING16); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +span CBORTokenizer::GetBinary() const { + assert(token_tag_ == CBORTokenTag::BINARY); + auto length = static_cast(token_start_internal_value_); + return bytes_.subspan(status_.pos + (token_byte_length_ - length), length); +} + +void CBORTokenizer::ReadNextToken(bool enter_envelope) { + if (enter_envelope) { + status_.pos += kEncodedEnvelopeHeaderSize; + } else { + status_.pos = + status_.pos == Status::npos() ? 0 : status_.pos + token_byte_length_; + } + status_.error = Error::OK; + if (status_.pos >= bytes_.size()) { + token_tag_ = CBORTokenTag::DONE; + return; + } + switch (bytes_[status_.pos]) { + case kStopByte: + SetToken(CBORTokenTag::STOP, 1); + return; + case kInitialByteIndefiniteLengthMap: + SetToken(CBORTokenTag::MAP_START, 1); + return; + case kInitialByteIndefiniteLengthArray: + SetToken(CBORTokenTag::ARRAY_START, 1); + return; + case kEncodedTrue: + SetToken(CBORTokenTag::TRUE_VALUE, 1); + return; + case kEncodedFalse: + SetToken(CBORTokenTag::FALSE_VALUE, 1); + return; + case kEncodedNull: + SetToken(CBORTokenTag::NULL_VALUE, 1); + return; + case kExpectedConversionToBase64Tag: { // BINARY + int8_t bytes_read = + ReadTokenStart(bytes_.subspan(status_.pos + 1), &token_start_type_, + &token_start_internal_value_); + int64_t token_byte_length = 1 + bytes_read + token_start_internal_value_; + if (-1 == bytes_read || token_start_type_ != MajorType::BYTE_STRING || + status_.pos + token_byte_length > bytes_.size()) { + SetError(Error::CBOR_INVALID_BINARY); + return; + } + SetToken(CBORTokenTag::BINARY, + static_cast(token_byte_length)); + return; + } + case kInitialByteForDouble: { // DOUBLE + if (status_.pos + kEncodedDoubleSize > bytes_.size()) { + SetError(Error::CBOR_INVALID_DOUBLE); + return; + } + SetToken(CBORTokenTag::DOUBLE, kEncodedDoubleSize); + return; + } + case kInitialByteForEnvelope: { // ENVELOPE + if (status_.pos + kEncodedEnvelopeHeaderSize > bytes_.size()) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + // The envelope must be a byte string with 32 bit length. + if (bytes_[status_.pos + 1] != kInitialByteFor32BitLengthByteString) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + // Read the length of the byte string. + token_start_internal_value_ = ReadBytesMostSignificantByteFirst( + bytes_.subspan(status_.pos + 2)); + // Make sure the payload is contained within the message. + if (token_start_internal_value_ + kEncodedEnvelopeHeaderSize + + status_.pos > + static_cast(bytes_.size())) { + SetError(Error::CBOR_INVALID_ENVELOPE); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::ENVELOPE, + kEncodedEnvelopeHeaderSize + length); + return; + } + default: { + span remainder = + bytes_.subspan(status_.pos, bytes_.size() - status_.pos); + assert(!remainder.empty()); + int8_t token_start_length = ReadTokenStart(remainder, &token_start_type_, + &token_start_internal_value_); + bool success = token_start_length != -1; + switch (token_start_type_) { + case MajorType::UNSIGNED: // INT32. + if (!success || std::numeric_limits::max() < + token_start_internal_value_) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, token_start_length); + return; + case MajorType::NEGATIVE: // INT32. + if (!success || + std::numeric_limits::min() > + -static_cast(token_start_internal_value_) - 1) { + SetError(Error::CBOR_INVALID_INT32); + return; + } + SetToken(CBORTokenTag::INT32, token_start_length); + return; + case MajorType::STRING: { // STRING8. + if (!success || remainder.size() < static_cast( + token_start_internal_value_)) { + SetError(Error::CBOR_INVALID_STRING8); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::STRING8, token_start_length + length); + return; + } + case MajorType::BYTE_STRING: { // STRING16. + if (!success || + remainder.size() < + static_cast(token_start_internal_value_) || + // Must be divisible by 2 since UTF16 is 2 bytes per character. + token_start_internal_value_ & 1) { + SetError(Error::CBOR_INVALID_STRING16); + return; + } + auto length = static_cast(token_start_internal_value_); + SetToken(CBORTokenTag::STRING16, token_start_length + length); + return; + } + case MajorType::ARRAY: + case MajorType::MAP: + case MajorType::TAG: + case MajorType::SIMPLE_VALUE: + SetError(Error::CBOR_UNSUPPORTED_VALUE); + return; + } + } + } +} + +void CBORTokenizer::SetToken(CBORTokenTag token_tag, + std::ptrdiff_t token_byte_length) { + token_tag_ = token_tag; + token_byte_length_ = token_byte_length; +} + +void CBORTokenizer::SetError(Error error) { + token_tag_ = CBORTokenTag::ERROR_VALUE; + status_.error = error; +} + +#if 0 +void DumpCBOR(span cbor) { + std::string indent; + CBORTokenizer tokenizer(cbor); + while (true) { + fprintf(stderr, "%s", indent.c_str()); + switch (tokenizer.TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + fprintf(stderr, "ERROR {status.error=%d, status.pos=%ld}\n", + tokenizer.Status().error, tokenizer.Status().pos); + return; + case CBORTokenTag::DONE: + fprintf(stderr, "DONE\n"); + return; + case CBORTokenTag::TRUE_VALUE: + fprintf(stderr, "TRUE_VALUE\n"); + break; + case CBORTokenTag::FALSE_VALUE: + fprintf(stderr, "FALSE_VALUE\n"); + break; + case CBORTokenTag::NULL_VALUE: + fprintf(stderr, "NULL_VALUE\n"); + break; + case CBORTokenTag::INT32: + fprintf(stderr, "INT32 [%d]\n", tokenizer.GetInt32()); + break; + case CBORTokenTag::DOUBLE: + fprintf(stderr, "DOUBLE [%lf]\n", tokenizer.GetDouble()); + break; + case CBORTokenTag::STRING8: { + span v = tokenizer.GetString8(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "STRING8 [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::STRING16: { + span v = tokenizer.GetString16WireRep(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "STRING16 [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::BINARY: { + span v = tokenizer.GetBinary(); + std::string t(v.begin(), v.end()); + fprintf(stderr, "BINARY [%s]\n", t.c_str()); + break; + } + case CBORTokenTag::MAP_START: + fprintf(stderr, "MAP_START\n"); + indent += " "; + break; + case CBORTokenTag::ARRAY_START: + fprintf(stderr, "ARRAY_START\n"); + indent += " "; + break; + case CBORTokenTag::STOP: + fprintf(stderr, "STOP\n"); + indent.erase(0, 2); + break; + case CBORTokenTag::ENVELOPE: + fprintf(stderr, "ENVELOPE\n"); + tokenizer.EnterEnvelope(); + continue; + } + tokenizer.Next(); + } +} +#endif + + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} + diff --git a/tools/inspector_protocol/lib/CBOR_h.template b/tools/inspector_protocol/lib/CBOR_h.template new file mode 100644 index 00000000000000..dd637f19e7d9d9 --- /dev/null +++ b/tools/inspector_protocol/lib/CBOR_h.template @@ -0,0 +1,416 @@ +{# This template is generated by gen_cbor_templates.py. #} +// Generated by lib/CBOR_h.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef {{"_".join(config.protocol.namespace)}}_CBOR_h +#define {{"_".join(config.protocol.namespace)}}_CBOR_h + +#include +#include +#include +#include + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +// ===== encoding/status.h ===== + +// Error codes. +enum class Error { + OK = 0, + // JSON parsing errors - json_parser.{h,cc}. + JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01, + JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02, + JSON_PARSER_NO_INPUT = 0x03, + JSON_PARSER_INVALID_TOKEN = 0x04, + JSON_PARSER_INVALID_NUMBER = 0x05, + JSON_PARSER_INVALID_STRING = 0x06, + JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07, + JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08, + JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09, + JSON_PARSER_COLON_EXPECTED = 0x0a, + JSON_PARSER_UNEXPECTED_OBJECT_END = 0x0b, + JSON_PARSER_COMMA_OR_OBJECT_END_EXPECTED = 0x0c, + JSON_PARSER_VALUE_EXPECTED = 0x0d, + + CBOR_INVALID_INT32 = 0x0e, + CBOR_INVALID_DOUBLE = 0x0f, + CBOR_INVALID_ENVELOPE = 0x10, + CBOR_INVALID_STRING8 = 0x11, + CBOR_INVALID_STRING16 = 0x12, + CBOR_INVALID_BINARY = 0x13, + CBOR_UNSUPPORTED_VALUE = 0x14, + CBOR_NO_INPUT = 0x15, + CBOR_INVALID_START_BYTE = 0x16, + CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x17, + CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x18, + CBOR_UNEXPECTED_EOF_IN_MAP = 0x19, + CBOR_INVALID_MAP_KEY = 0x1a, + CBOR_STACK_LIMIT_EXCEEDED = 0x1b, + CBOR_STRING8_MUST_BE_7BIT = 0x1c, + CBOR_TRAILING_JUNK = 0x1d, + CBOR_MAP_START_EXPECTED = 0x1e, +}; + +// A status value with position that can be copied. The default status +// is OK. Usually, error status values should come with a valid position. +struct Status { + static constexpr std::ptrdiff_t npos() { return -1; } + + bool ok() const { return error == Error::OK; } + + Error error = Error::OK; + std::ptrdiff_t pos = npos(); + Status(Error error, std::ptrdiff_t pos) : error(error), pos(pos) {} + Status() = default; +}; + +// ===== encoding/span.h ===== + +// This template is similar to std::span, which will be included in C++20. Like +// std::span it uses ptrdiff_t, which is signed (and thus a bit annoying +// sometimes when comparing with size_t), but other than this it's much simpler. +template +class span { + public: + using index_type = std::ptrdiff_t; + + span() : data_(nullptr), size_(0) {} + span(const T* data, index_type size) : data_(data), size_(size) {} + + const T* data() const { return data_; } + + const T* begin() const { return data_; } + const T* end() const { return data_ + size_; } + + const T& operator[](index_type idx) const { return data_[idx]; } + + span subspan(index_type offset, index_type count) const { + return span(data_ + offset, count); + } + + span subspan(index_type offset) const { + return span(data_ + offset, size_ - offset); + } + + bool empty() const { return size_ == 0; } + + index_type size() const { return size_; } + index_type size_bytes() const { return size_ * sizeof(T); } + + private: + const T* data_; + index_type size_; +}; + +// ===== encoding/json_parser_handler.h ===== + +// Handler interface for JSON parser events. See also json_parser.h. +class JSONParserHandler { + public: + virtual ~JSONParserHandler() = default; + virtual void HandleObjectBegin() = 0; + virtual void HandleObjectEnd() = 0; + virtual void HandleArrayBegin() = 0; + virtual void HandleArrayEnd() = 0; + // TODO(johannes): Support utf8 (requires utf16->utf8 conversion + // internally, including handling mismatched surrogate pairs). + virtual void HandleString16(std::vector chars) = 0; + virtual void HandleBinary(std::vector bytes) = 0; + virtual void HandleDouble(double value) = 0; + virtual void HandleInt32(int32_t value) = 0; + virtual void HandleBool(bool value) = 0; + virtual void HandleNull() = 0; + + // The parser may send one error even after other events have already + // been received. Client code is reponsible to then discard the + // already processed events. + // |error| must be an eror, as in, |error.is_ok()| can't be true. + virtual void HandleError(Status error) = 0; +}; + +// ===== encoding/cbor_internals.h ===== + +namespace cbor { +enum class MajorType; +} + +namespace cbor_internals { + +// Reads the start of a token with definitive size from |bytes|. +// |type| is the major type as specified in RFC 7049 Section 2.1. +// |value| is the payload (e.g. for MajorType::UNSIGNED) or is the size +// (e.g. for BYTE_STRING). +// If successful, returns the number of bytes read. Otherwise returns -1. +int8_t ReadTokenStart(span bytes, cbor::MajorType* type, + uint64_t* value); + +// Writes the start of a token with |type|. The |value| may indicate the size, +// or it may be the payload if the value is an unsigned integer. +void WriteTokenStart(cbor::MajorType type, uint64_t value, + std::vector* encoded); +} // namespace cbor_internals + +// ===== encoding/cbor.h ===== + + +namespace cbor { + +// The major types from RFC 7049 Section 2.1. +enum class MajorType { + UNSIGNED = 0, + NEGATIVE = 1, + BYTE_STRING = 2, + STRING = 3, + ARRAY = 4, + MAP = 5, + TAG = 6, + SIMPLE_VALUE = 7 +}; + +// Indicates the number of bits the "initial byte" needs to be shifted to the +// right after applying |kMajorTypeMask| to produce the major type in the +// lowermost bits. +static constexpr uint8_t kMajorTypeBitShift = 5u; +// Mask selecting the low-order 5 bits of the "initial byte", which is where +// the additional information is encoded. +static constexpr uint8_t kAdditionalInformationMask = 0x1f; +// Mask selecting the high-order 3 bits of the "initial byte", which indicates +// the major type of the encoded value. +static constexpr uint8_t kMajorTypeMask = 0xe0; +// Indicates the integer is in the following byte. +static constexpr uint8_t kAdditionalInformation1Byte = 24u; +// Indicates the integer is in the next 2 bytes. +static constexpr uint8_t kAdditionalInformation2Bytes = 25u; +// Indicates the integer is in the next 4 bytes. +static constexpr uint8_t kAdditionalInformation4Bytes = 26u; +// Indicates the integer is in the next 8 bytes. +static constexpr uint8_t kAdditionalInformation8Bytes = 27u; + +// Encodes the initial byte, consisting of the |type| in the first 3 bits +// followed by 5 bits of |additional_info|. +constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) { + return (static_cast(type) << kMajorTypeBitShift) | + (additional_info & kAdditionalInformationMask); +} + +// TAG 24 indicates that what follows is a byte string which is +// encoded in CBOR format. We use this as a wrapper for +// maps and arrays, allowing us to skip them, because the +// byte string carries its size (byte length). +// https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +static constexpr uint8_t kInitialByteForEnvelope = + EncodeInitialByte(MajorType::TAG, 24); +// The initial byte for a byte string with at most 2^32 bytes +// of payload. This is used for envelope encoding, even if +// the byte string is shorter. +static constexpr uint8_t kInitialByteFor32BitLengthByteString = + EncodeInitialByte(MajorType::BYTE_STRING, 26); + +// See RFC 7049 Section 2.2.1, indefinite length arrays / maps have additional +// info = 31. +static constexpr uint8_t kInitialByteIndefiniteLengthArray = + EncodeInitialByte(MajorType::ARRAY, 31); +static constexpr uint8_t kInitialByteIndefiniteLengthMap = + EncodeInitialByte(MajorType::MAP, 31); +// See RFC 7049 Section 2.3, Table 1; this is used for finishing indefinite +// length maps / arrays. +static constexpr uint8_t kStopByte = + EncodeInitialByte(MajorType::SIMPLE_VALUE, 31); + +} // namespace cbor + +// The binary encoding for the inspector protocol follows the CBOR specification +// (RFC 7049). Additional constraints: +// - Only indefinite length maps and arrays are supported. +// - Maps and arrays are wrapped with an envelope, that is, a +// CBOR tag with value 24 followed by a byte string specifying +// the byte length of the enclosed map / array. The byte string +// must use a 32 bit wide length. +// - At the top level, a message must be an indefinite length map +// wrapped by an envelope. +// - Maximal size for messages is 2^32 (4 GB). +// - For scalars, we support only the int32_t range, encoded as +// UNSIGNED/NEGATIVE (major types 0 / 1). +// - UTF16 strings, including with unbalanced surrogate pairs, are encoded +// as CBOR BYTE_STRING (major type 2). For such strings, the number of +// bytes encoded must be even. +// - UTF8 strings (major type 3) may only have ASCII characters +// (7 bit US-ASCII). +// - Arbitrary byte arrays, in the inspector protocol called 'binary', +// are encoded as BYTE_STRING (major type 2), prefixed with a byte +// indicating base64 when rendered as JSON. + +// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE| +// (major type 1) iff < 0. +void EncodeInt32(int32_t value, std::vector* out); + +// Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16 +// character in |in| is emitted with most significant byte first, +// appending to |out|. +void EncodeString16(span in, std::vector* out); + +// Encodes a UTF8 string |in| as STRING (major type 3). +void EncodeString8(span in, std::vector* out); + +// Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with +// definitive length, prefixed with tag 22 indicating expected conversion to +// base64 (see RFC 7049, Table 3 and Section 2.4.4.2). +void EncodeBinary(span in, std::vector* out); + +// Encodes / decodes a double as Major type 7 (SIMPLE_VALUE), +// with additional info = 27, followed by 8 bytes in big endian. +void EncodeDouble(double value, std::vector* out); + +// Some constants for CBOR tokens that only take a single byte on the wire. +uint8_t EncodeTrue(); +uint8_t EncodeFalse(); +uint8_t EncodeNull(); +uint8_t EncodeIndefiniteLengthArrayStart(); +uint8_t EncodeIndefiniteLengthMapStart(); +uint8_t EncodeStop(); + +// An envelope indicates the byte length of a wrapped item. +// We use this for maps and array, which allows the decoder +// to skip such (nested) values whole sale. +// It's implemented as a CBOR tag (major type 6) with additional +// info = 24, followed by a byte string with a 32 bit length value; +// so the maximal structure that we can wrap is 2^32 bits long. +// See also: https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +class EnvelopeEncoder { + public: + // Emits the envelope start bytes and records the position for the + // byte size in |byte_size_pos_|. Also emits empty bytes for the + // byte sisze so that encoding can continue. + void EncodeStart(std::vector* out); + // This records the current size in |out| at position byte_size_pos_. + // Returns true iff successful. + bool EncodeStop(std::vector* out); + + private: + std::size_t byte_size_pos_ = 0; +}; + +// This can be used to convert from JSON to CBOR, by passing the +// return value to the routines in json_parser.h. The handler will encode into +// |out|, and iff an error occurs it will set |status| to an error and clear +// |out|. Otherwise, |status.ok()| will be |true|. +std::unique_ptr NewJSONToCBOREncoder( + std::vector* out, Status* status); + +// Parses a CBOR encoded message from |bytes|, sending JSON events to +// |json_out|. If an error occurs, sends |out->HandleError|, and parsing stops. +// The client is responsible for discarding the already received information in +// that case. +void ParseCBOR(span bytes, JSONParserHandler* json_out); + +// Tags for the tokens within a CBOR message that CBORStream understands. +// Note that this is not the same terminology as the CBOR spec (RFC 7049), +// but rather, our adaptation. For instance, we lump unsigned and signed +// major type into INT32 here (and disallow values outside the int32_t range). +enum class CBORTokenTag { + // Encountered an error in the structure of the message. Consult + // status() for details. + ERROR_VALUE, + // Booleans and NULL. + TRUE_VALUE, + FALSE_VALUE, + NULL_VALUE, + // An int32_t (signed 32 bit integer). + INT32, + // A double (64 bit floating point). + DOUBLE, + // A UTF8 string. + STRING8, + // A UTF16 string. + STRING16, + // A binary string. + BINARY, + // Starts an indefinite length map; after the map start we expect + // alternating keys and values, followed by STOP. + MAP_START, + // Starts an indefinite length array; after the array start we + // expect values, followed by STOP. + ARRAY_START, + // Ends a map or an array. + STOP, + // An envelope indicator, wrapping a map or array. + // Internally this carries the byte length of the wrapped + // map or array. While CBORTokenizer::Next() will read / skip the entire + // envelope, CBORTokenizer::EnterEnvelope() reads the tokens + // inside of it. + ENVELOPE, + // We've reached the end there is nothing else to read. + DONE, +}; + +// CBORTokenizer segments a CBOR message, presenting the tokens therein as +// numbers, strings, etc. This is not a complete CBOR parser, but makes it much +// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse +// messages partially. +class CBORTokenizer { + public: + explicit CBORTokenizer(span bytes); + ~CBORTokenizer(); + + // Identifies the current token that we're looking at, + // or ERROR_VALUE (in which ase ::Status() has details) + // or DONE (if we're past the last token). + CBORTokenTag TokenTag() const; + + // Advances to the next token. + void Next(); + // Can only be called if TokenTag() == CBORTokenTag::ENVELOPE. + // While Next() would skip past the entire envelope / what it's + // wrapping, EnterEnvelope positions the cursor inside of the envelope, + // letting the client explore the nested structure. + void EnterEnvelope(); + + // If TokenTag() is CBORTokenTag::ERROR_VALUE, then Status().error describes + // the error more precisely; otherwise it'll be set to Error::OK. + // In either case, Status().pos is the current position. + struct Status Status() const; + + // The following methods retrieve the token values. They can only + // be called if TokenTag() matches. + + // To be called only if ::TokenTag() == CBORTokenTag::INT32. + int32_t GetInt32() const; + + // To be called only if ::TokenTag() == CBORTokenTag::DOUBLE. + double GetDouble() const; + + // To be called only if ::TokenTag() == CBORTokenTag::STRING8. + span GetString8() const; + + // Wire representation for STRING16 is low byte first (little endian). + // To be called only if ::TokenTag() == CBORTokenTag::STRING16. + span GetString16WireRep() const; + + // To be called only if ::TokenTag() == CBORTokenTag::BINARY. + span GetBinary() const; + + private: + void ReadNextToken(bool enter_envelope); + void SetToken(CBORTokenTag token, std::ptrdiff_t token_byte_length); + void SetError(Error error); + + span bytes_; + CBORTokenTag token_tag_; + struct Status status_; + std::ptrdiff_t token_byte_length_; + cbor::MajorType token_start_type_; + uint64_t token_start_internal_value_; +}; + +void DumpCBOR(span cbor); + + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} +#endif // !defined({{"_".join(config.protocol.namespace)}}_CBOR_h) diff --git a/tools/inspector_protocol/lib/Collections_h.template b/tools/inspector_protocol/lib/Collections_h.template deleted file mode 100644 index 7505a17bfa6e68..00000000000000 --- a/tools/inspector_protocol/lib/Collections_h.template +++ /dev/null @@ -1,43 +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. - -#ifndef {{"_".join(config.protocol.namespace)}}_Collections_h -#define {{"_".join(config.protocol.namespace)}}_Collections_h - -#include {{format_include(config.protocol.package, "Forward")}} -#include - -#if defined(__APPLE__) && !defined(_LIBCPP_VERSION) -#include -#include - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -template using HashMap = std::map; -template using HashSet = std::set; - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#else -#include -#include - -{% for namespace in config.protocol.namespace %} -namespace {{namespace}} { -{% endfor %} - -template using HashMap = std::unordered_map; -template using HashSet = std::unordered_set; - -{% for namespace in config.protocol.namespace %} -} // namespace {{namespace}} -{% endfor %} - -#endif // defined(__APPLE__) && !defined(_LIBCPP_VERSION) - -#endif // !defined({{"_".join(config.protocol.namespace)}}_Collections_h) diff --git a/tools/inspector_protocol/lib/DispatcherBase_cpp.template b/tools/inspector_protocol/lib/DispatcherBase_cpp.template index cecef743bffcc6..11843f433007fc 100644 --- a/tools/inspector_protocol/lib/DispatcherBase_cpp.template +++ b/tools/inspector_protocol/lib/DispatcherBase_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by DispatcherBase_cpp.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -68,10 +70,11 @@ DispatcherBase::WeakPtr::~WeakPtr() m_dispatcher->m_weakPtrs.erase(this); } -DispatcherBase::Callback::Callback(std::unique_ptr backendImpl, int callId, int callbackId) +DispatcherBase::Callback::Callback(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message) : m_backendImpl(std::move(backendImpl)) , m_callId(callId) - , m_callbackId(callbackId) { } + , m_method(method) + , m_message(message) { } DispatcherBase::Callback::~Callback() = default; @@ -92,32 +95,18 @@ void DispatcherBase::Callback::fallThroughIfActive() { if (!m_backendImpl || !m_backendImpl->get()) return; - m_backendImpl->get()->markFallThrough(m_callbackId); + m_backendImpl->get()->channel()->fallThrough(m_callId, m_method, m_message); m_backendImpl = nullptr; } DispatcherBase::DispatcherBase(FrontendChannel* frontendChannel) - : m_frontendChannel(frontendChannel) - , m_lastCallbackId(0) - , m_lastCallbackFallThrough(false) { } + : m_frontendChannel(frontendChannel) { } DispatcherBase::~DispatcherBase() { clearFrontend(); } -int DispatcherBase::nextCallbackId() -{ - m_lastCallbackFallThrough = false; - return ++m_lastCallbackId; -} - -void DispatcherBase::markFallThrough(int callbackId) -{ - DCHECK(callbackId == m_lastCallbackId); - m_lastCallbackFallThrough = true; -} - void DispatcherBase::sendResponse(int callId, const DispatchResponse& response, std::unique_ptr result) { if (!m_frontendChannel) @@ -153,18 +142,14 @@ public: return std::unique_ptr(new ProtocolError(code, errorMessage)); } - String serialize() override + String serializeToJSON() override { - std::unique_ptr error = DictionaryValue::create(); - error->setInteger("code", m_code); - error->setString("message", m_errorMessage); - if (m_data.length()) - error->setString("data", m_data); - std::unique_ptr message = DictionaryValue::create(); - message->setObject("error", std::move(error)); - if (m_hasCallId) - message->setInteger("id", m_callId); - return message->serialize(); + return serialize()->serializeToJSON(); + } + + std::vector serializeToBinary() override + { + return serialize()->serializeToBinary(); } ~ProtocolError() override {} @@ -176,6 +161,19 @@ private: { } + std::unique_ptr serialize() { + std::unique_ptr error = DictionaryValue::create(); + error->setInteger("code", m_code); + error->setString("message", m_errorMessage); + if (m_data.length()) + error->setString("data", m_data); + std::unique_ptr message = DictionaryValue::create(); + message->setObject("error", std::move(error)); + if (m_hasCallId) + message->setInteger("id", m_callId); + return message; + } + DispatchResponse::ErrorCode m_code; String m_errorMessage; String m_data; @@ -218,100 +216,87 @@ std::unique_ptr DispatcherBase::weakPtr() } UberDispatcher::UberDispatcher(FrontendChannel* frontendChannel) - : m_frontendChannel(frontendChannel) - , m_fallThroughForNotFound(false) { } - -void UberDispatcher::setFallThroughForNotFound(bool fallThroughForNotFound) -{ - m_fallThroughForNotFound = fallThroughForNotFound; -} + : m_frontendChannel(frontendChannel) { } void UberDispatcher::registerBackend(const String& name, std::unique_ptr dispatcher) { m_dispatchers[name] = std::move(dispatcher); } -void UberDispatcher::setupRedirects(const HashMap& redirects) +void UberDispatcher::setupRedirects(const std::unordered_map& redirects) { for (const auto& pair : redirects) m_redirects[pair.first] = pair.second; } -DispatchResponse::Status UberDispatcher::dispatch(std::unique_ptr parsedMessage, int* outCallId, String* outMethod) -{ +bool UberDispatcher::parseCommand(Value* parsedMessage, int* outCallId, String* outMethod) { if (!parsedMessage) { reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kParseError, "Message must be a valid JSON"); - return DispatchResponse::kError; + return false; } - std::unique_ptr messageObject = DictionaryValue::cast(std::move(parsedMessage)); + protocol::DictionaryValue* messageObject = DictionaryValue::cast(parsedMessage); if (!messageObject) { reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must be an object"); - return DispatchResponse::kError; + return false; } int callId = 0; protocol::Value* callIdValue = messageObject->get("id"); bool success = callIdValue && callIdValue->asInteger(&callId); - if (outCallId) - *outCallId = callId; if (!success) { reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must have integer 'id' property"); - return DispatchResponse::kError; + return false; } + if (outCallId) + *outCallId = callId; protocol::Value* methodValue = messageObject->get("method"); String method; success = methodValue && methodValue->asString(&method); - if (outMethod) - *outMethod = method; if (!success) { reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kInvalidRequest, "Message must have string 'method' property", nullptr); - return DispatchResponse::kError; + return false; } + if (outMethod) + *outMethod = method; + return true; +} - HashMap::iterator redirectIt = m_redirects.find(method); - if (redirectIt != m_redirects.end()) - method = redirectIt->second; - +protocol::DispatcherBase* UberDispatcher::findDispatcher(const String& method) { size_t dotIndex = StringUtil::find(method, "."); - if (dotIndex == StringUtil::kNotFound) { - if (m_fallThroughForNotFound) - return DispatchResponse::kFallThrough; - reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); - return DispatchResponse::kError; - } + if (dotIndex == StringUtil::kNotFound) + return nullptr; String domain = StringUtil::substring(method, 0, dotIndex); auto it = m_dispatchers.find(domain); - if (it == m_dispatchers.end()) { - if (m_fallThroughForNotFound) - return DispatchResponse::kFallThrough; - reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); - return DispatchResponse::kError; - } - return it->second->dispatch(callId, method, std::move(messageObject)); + if (it == m_dispatchers.end()) + return nullptr; + if (!it->second->canDispatch(method)) + return nullptr; + return it->second.get(); } -bool UberDispatcher::getCommandName(const String& message, String* method, std::unique_ptr* parsedMessage) +bool UberDispatcher::canDispatch(const String& in_method) { - std::unique_ptr value = StringUtil::parseJSON(message); - if (!value) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kParseError, "Message must be a valid JSON"); - return false; - } - - protocol::DictionaryValue* object = DictionaryValue::cast(value.get()); - if (!object) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must be an object"); - return false; - } + String method = in_method; + auto redirectIt = m_redirects.find(method); + if (redirectIt != m_redirects.end()) + method = redirectIt->second; + return !!findDispatcher(method); +} - if (!object->getString("method", method)) { - reportProtocolErrorTo(m_frontendChannel, DispatchResponse::kInvalidRequest, "Message must have string 'method' property"); - return false; +void UberDispatcher::dispatch(int callId, const String& in_method, std::unique_ptr parsedMessage, const ProtocolMessage& rawMessage) +{ + String method = in_method; + auto redirectIt = m_redirects.find(method); + if (redirectIt != m_redirects.end()) + method = redirectIt->second; + protocol::DispatcherBase* dispatcher = findDispatcher(method); + if (!dispatcher) { + reportProtocolErrorTo(m_frontendChannel, callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); + return; } - - parsedMessage->reset(DictionaryValue::cast(value.release())); - return true; + std::unique_ptr messageObject = DictionaryValue::cast(std::move(parsedMessage)); + dispatcher->dispatch(callId, method, rawMessage, std::move(messageObject)); } UberDispatcher::~UberDispatcher() = default; @@ -328,18 +313,32 @@ std::unique_ptr InternalResponse::createNotification(const Str return std::unique_ptr(new InternalResponse(0, notification, std::move(params))); } -String InternalResponse::serialize() +String InternalResponse::serializeToJSON() +{ + std::unique_ptr result = DictionaryValue::create(); + std::unique_ptr params(m_params ? std::move(m_params) : DictionaryValue::create()); + if (m_notification.length()) { + result->setString("method", m_notification); + result->setValue("params", SerializedValue::fromJSON(params->serializeToJSON())); + } else { + result->setInteger("id", m_callId); + result->setValue("result", SerializedValue::fromJSON(params->serializeToJSON())); + } + return result->serializeToJSON(); +} + +std::vector InternalResponse::serializeToBinary() { std::unique_ptr result = DictionaryValue::create(); std::unique_ptr params(m_params ? std::move(m_params) : DictionaryValue::create()); if (m_notification.length()) { result->setString("method", m_notification); - result->setValue("params", SerializedValue::create(params->serialize())); + result->setValue("params", SerializedValue::fromBinary(params->serializeToBinary())); } else { result->setInteger("id", m_callId); - result->setValue("result", SerializedValue::create(params->serialize())); + result->setValue("result", SerializedValue::fromBinary(params->serializeToBinary())); } - return result->serialize(); + return result->serializeToBinary(); } InternalResponse::InternalResponse(int callId, const String& notification, std::unique_ptr params) diff --git a/tools/inspector_protocol/lib/DispatcherBase_h.template b/tools/inspector_protocol/lib/DispatcherBase_h.template index d70a4afe71de2c..7d859c4f2753bb 100644 --- a/tools/inspector_protocol/lib/DispatcherBase_h.template +++ b/tools/inspector_protocol/lib/DispatcherBase_h.template @@ -1,3 +1,5 @@ +// This file is generated by DispatcherBase_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -5,9 +7,8 @@ #ifndef {{"_".join(config.protocol.namespace)}}_DispatcherBase_h #define {{"_".join(config.protocol.namespace)}}_DispatcherBase_h -//#include "Collections.h" -//#include "ErrorSupport.h" //#include "Forward.h" +//#include "ErrorSupport.h" //#include "Values.h" {% for namespace in config.protocol.namespace %} @@ -22,7 +23,6 @@ public: kSuccess = 0, kError = 1, kFallThrough = 2, - kAsync = 3 }; enum ErrorCode { @@ -68,7 +68,7 @@ public: class {{config.lib.export_macro}} Callback { public: - Callback(std::unique_ptr backendImpl, int callId, int callbackId); + Callback(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message); virtual ~Callback(); void dispose(); @@ -79,13 +79,16 @@ public: private: std::unique_ptr m_backendImpl; int m_callId; - int m_callbackId; + String m_method; + ProtocolMessage m_message; }; explicit DispatcherBase(FrontendChannel*); virtual ~DispatcherBase(); - virtual DispatchResponse::Status dispatch(int callId, const String& method, std::unique_ptr messageObject) = 0; + virtual bool canDispatch(const String& method) = 0; + virtual void dispatch(int callId, const String& method, const ProtocolMessage& rawMessage, std::unique_ptr messageObject) = 0; + FrontendChannel* channel() { return m_frontendChannel; } void sendResponse(int callId, const DispatchResponse&, std::unique_ptr result); void sendResponse(int callId, const DispatchResponse&); @@ -95,15 +98,9 @@ public: std::unique_ptr weakPtr(); - int nextCallbackId(); - void markFallThrough(int callbackId); - bool lastCallbackFallThrough() { return m_lastCallbackFallThrough; } - private: FrontendChannel* m_frontendChannel; - protocol::HashSet m_weakPtrs; - int m_lastCallbackId; - bool m_lastCallbackFallThrough; + std::unordered_set m_weakPtrs; }; class {{config.lib.export_macro}} UberDispatcher { @@ -111,19 +108,18 @@ class {{config.lib.export_macro}} UberDispatcher { public: explicit UberDispatcher(FrontendChannel*); void registerBackend(const String& name, std::unique_ptr); - void setupRedirects(const HashMap&); - DispatchResponse::Status dispatch(std::unique_ptr message, int* callId = nullptr, String* method = nullptr); + void setupRedirects(const std::unordered_map&); + bool parseCommand(Value* message, int* callId, String* method); + bool canDispatch(const String& method); + void dispatch(int callId, const String& method, std::unique_ptr message, const ProtocolMessage& rawMessage); FrontendChannel* channel() { return m_frontendChannel; } - bool fallThroughForNotFound() { return m_fallThroughForNotFound; } - void setFallThroughForNotFound(bool); - bool getCommandName(const String& message, String* method, std::unique_ptr* parsedMessage); virtual ~UberDispatcher(); private: + protocol::DispatcherBase* findDispatcher(const String& method); FrontendChannel* m_frontendChannel; - bool m_fallThroughForNotFound; - HashMap m_redirects; - protocol::HashMap> m_dispatchers; + std::unordered_map m_redirects; + std::unordered_map> m_dispatchers; }; class InternalResponse : public Serializable { @@ -132,7 +128,8 @@ public: static std::unique_ptr createResponse(int callId, std::unique_ptr params); static std::unique_ptr createNotification(const String& notification, std::unique_ptr params = nullptr); - String serialize() override; + String serializeToJSON() override; + std::vector serializeToBinary() override; ~InternalResponse() override {} @@ -146,24 +143,36 @@ private: class InternalRawNotification : public Serializable { public: - static std::unique_ptr create(const String& notification) + static std::unique_ptr fromJSON(String notification) + { + return std::unique_ptr(new InternalRawNotification(std::move(notification))); + } + + static std::unique_ptr fromBinary(std::vector notification) { - return std::unique_ptr(new InternalRawNotification(notification)); + return std::unique_ptr(new InternalRawNotification(std::move(notification))); } + ~InternalRawNotification() override {} - String serialize() override + String serializeToJSON() override + { + return std::move(m_jsonNotification); + } + + std::vector serializeToBinary() override { - return m_notification; + return std::move(m_binaryNotification); } private: - explicit InternalRawNotification(const String& notification) - : m_notification(notification) - { - } + explicit InternalRawNotification(String notification) + : m_jsonNotification(std::move(notification)) { } + explicit InternalRawNotification(std::vector notification) + : m_binaryNotification(std::move(notification)) { } - String m_notification; + String m_jsonNotification; + std::vector m_binaryNotification; }; {% for namespace in config.protocol.namespace %} diff --git a/tools/inspector_protocol/lib/ErrorSupport_cpp.template b/tools/inspector_protocol/lib/ErrorSupport_cpp.template index 7b858b8dc48f37..a5c2a79bbd25c4 100644 --- a/tools/inspector_protocol/lib/ErrorSupport_cpp.template +++ b/tools/inspector_protocol/lib/ErrorSupport_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by ErrorSupport_cpp.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/tools/inspector_protocol/lib/ErrorSupport_h.template b/tools/inspector_protocol/lib/ErrorSupport_h.template index 083f2a5eb0d4d3..f317a3cfb411b5 100644 --- a/tools/inspector_protocol/lib/ErrorSupport_h.template +++ b/tools/inspector_protocol/lib/ErrorSupport_h.template @@ -1,3 +1,5 @@ +// This file is generated by ErrorSupport_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -5,7 +7,7 @@ #ifndef {{"_".join(config.protocol.namespace)}}_ErrorSupport_h #define {{"_".join(config.protocol.namespace)}}_ErrorSupport_h -//#include "Forward.h" +#include {{format_include(config.protocol.package, "Forward")}} {% for namespace in config.protocol.namespace %} namespace {{namespace}} { diff --git a/tools/inspector_protocol/lib/Forward_h.template b/tools/inspector_protocol/lib/Forward_h.template index 34d1c0d3e946cd..ff5e685863395b 100644 --- a/tools/inspector_protocol/lib/Forward_h.template +++ b/tools/inspector_protocol/lib/Forward_h.template @@ -1,3 +1,5 @@ +// This file is generated by Forward_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,7 +12,11 @@ {% endif %} #include {{format_include(config.lib.string_header)}} +#include +#include #include +#include +#include {% for namespace in config.protocol.namespace %} namespace {{namespace}} { diff --git a/tools/inspector_protocol/lib/FrontendChannel_h.template b/tools/inspector_protocol/lib/FrontendChannel_h.template index 0454978b0c8e88..df104debadbe85 100644 --- a/tools/inspector_protocol/lib/FrontendChannel_h.template +++ b/tools/inspector_protocol/lib/FrontendChannel_h.template @@ -1,3 +1,5 @@ +// This file is generated by FrontendChannel_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,7 +13,14 @@ namespace {{namespace}} { class {{config.lib.export_macro}} Serializable { public: - virtual String serialize() = 0; + ProtocolMessage serialize(bool binary) { + if (binary) + return StringUtil::binaryToMessage(serializeToBinary()); + else + return StringUtil::jsonToMessage(serializeToJSON()); + } + virtual String serializeToJSON() = 0; + virtual std::vector serializeToBinary() = 0; virtual ~Serializable() = default; }; @@ -20,6 +29,7 @@ public: virtual ~FrontendChannel() { } virtual void sendProtocolResponse(int callId, std::unique_ptr message) = 0; virtual void sendProtocolNotification(std::unique_ptr message) = 0; + virtual void fallThrough(int callId, const String& method, const ProtocolMessage& message) = 0; virtual void flushProtocolNotifications() = 0; }; diff --git a/tools/inspector_protocol/lib/Maybe_h.template b/tools/inspector_protocol/lib/Maybe_h.template index 71593acd0e553d..22cfac6b240bef 100644 --- a/tools/inspector_protocol/lib/Maybe_h.template +++ b/tools/inspector_protocol/lib/Maybe_h.template @@ -1,3 +1,5 @@ +// This file is generated by Maybe_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -5,6 +7,41 @@ #ifndef {{"_".join(config.protocol.namespace)}}_Maybe_h #define {{"_".join(config.protocol.namespace)}}_Maybe_h +// This macro allows to test for the version of the GNU C++ compiler. +// Note that this also applies to compilers that masquerade as GCC, +// for example clang and the Intel C++ compiler for Linux. +// Use like: +// #if IP_GNUC_PREREQ(4, 3, 1) +// ... +// #endif +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) +#define IP_GNUC_PREREQ(major, minor, patchlevel) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= \ + ((major)*10000 + (minor)*100 + (patchlevel))) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) +#define IP_GNUC_PREREQ(major, minor, patchlevel) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= \ + ((major)*10000 + (minor)*100 + (patchlevel))) +#else +#define IP_GNUC_PREREQ(major, minor, patchlevel) 0 +#endif + +#if defined(__mips64) +#define IP_TARGET_ARCH_MIPS64 1 +#elif defined(__MIPSEB__) || defined(__MIPSEL__) +#define IP_TARGET_ARCH_MIPS 1 +#endif + +// Allowing the use of noexcept by removing the keyword on older compilers that +// do not support adding noexcept to default members. +#if ((IP_GNUC_PREREQ(4, 9, 0) && !defined(IP_TARGET_ARCH_MIPS) && \ + !defined(IP_TARGET_ARCH_MIPS64)) || \ + (defined(__clang__) && __cplusplus > 201300L)) +#define IP_NOEXCEPT noexcept +#else +#define IP_NOEXCEPT +#endif + //#include "Forward.h" {% for namespace in config.protocol.namespace %} @@ -16,7 +53,7 @@ class Maybe { public: Maybe() : m_value() { } Maybe(std::unique_ptr value) : m_value(std::move(value)) { } - Maybe(Maybe&& other) : m_value(std::move(other.m_value)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : m_value(std::move(other.m_value)) {} void operator=(std::unique_ptr value) { m_value = std::move(value); } T* fromJust() const { DCHECK(m_value); return m_value.get(); } T* fromMaybe(T* defaultValue) const { return m_value ? m_value.get() : defaultValue; } @@ -31,7 +68,9 @@ class MaybeBase { public: MaybeBase() : m_isJust(false) { } MaybeBase(T value) : m_isJust(true), m_value(value) { } - MaybeBase(MaybeBase&& other) : m_isJust(other.m_isJust), m_value(std::move(other.m_value)) { } + MaybeBase(MaybeBase&& other) IP_NOEXCEPT + : m_isJust(other.m_isJust), + m_value(std::move(other.m_value)) {} void operator=(T value) { m_value = value; m_isJust = true; } T fromJust() const { DCHECK(m_isJust); return m_value; } T fromMaybe(const T& defaultValue) const { return m_isJust ? m_value : defaultValue; } @@ -46,27 +85,27 @@ protected: template<> class Maybe : public MaybeBase { public: - Maybe() { } + Maybe() { m_value = false; } Maybe(bool value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; template<> class Maybe : public MaybeBase { public: - Maybe() { } + Maybe() { m_value = 0; } Maybe(int value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; template<> class Maybe : public MaybeBase { public: - Maybe() { } + Maybe() { m_value = 0; } Maybe(double value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; @@ -75,7 +114,16 @@ class Maybe : public MaybeBase { public: Maybe() { } Maybe(const String& value) : MaybeBase(value) { } - Maybe(Maybe&& other) : MaybeBase(std::move(other)) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} + using MaybeBase::operator=; +}; + +template<> +class Maybe : public MaybeBase { +public: + Maybe() { } + Maybe(Binary value) : MaybeBase(value) { } + Maybe(Maybe&& other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} using MaybeBase::operator=; }; @@ -83,4 +131,9 @@ public: } // namespace {{namespace}} {% endfor %} +#undef IP_GNUC_PREREQ +#undef IP_TARGET_ARCH_MIPS64 +#undef IP_TARGET_ARCH_MIPS +#undef IP_NOEXCEPT + #endif // !defined({{"_".join(config.protocol.namespace)}}_Maybe_h) diff --git a/tools/inspector_protocol/lib/Object_cpp.template b/tools/inspector_protocol/lib/Object_cpp.template index 91723a71e29ce4..1640a11127b442 100644 --- a/tools/inspector_protocol/lib/Object_cpp.template +++ b/tools/inspector_protocol/lib/Object_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by Object_cpp.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/tools/inspector_protocol/lib/Object_h.template b/tools/inspector_protocol/lib/Object_h.template index f6ffc57659e148..ec953d0d4836a4 100644 --- a/tools/inspector_protocol/lib/Object_h.template +++ b/tools/inspector_protocol/lib/Object_h.template @@ -1,3 +1,5 @@ +// This file is generated by Object_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -16,12 +18,12 @@ namespace {{namespace}} { class {{config.lib.export_macro}} Object { public: static std::unique_ptr fromValue(protocol::Value*, ErrorSupport*); + explicit Object(std::unique_ptr); ~Object(); std::unique_ptr toValue() const; std::unique_ptr clone() const; private: - explicit Object(std::unique_ptr); std::unique_ptr m_object; }; diff --git a/tools/inspector_protocol/lib/Parser_cpp.template b/tools/inspector_protocol/lib/Parser_cpp.template index f3dde5ac218e6f..ea7ecc5a1a4756 100644 --- a/tools/inspector_protocol/lib/Parser_cpp.template +++ b/tools/inspector_protocol/lib/Parser_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by Parser_cpp.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -425,9 +427,8 @@ std::unique_ptr buildValue(const Char* start, const Char* end, const Char double value = charactersToDouble(tokenStart, tokenEnd - tokenStart, &ok); if (!ok) return nullptr; - int number = static_cast(value); - if (number == value) - result = FundamentalValue::create(number); + if (value >= INT_MIN && value <= INT_MAX && static_cast(value) == value) + result = FundamentalValue::create(static_cast(value)); else result = FundamentalValue::create(value); break; diff --git a/tools/inspector_protocol/lib/Parser_h.template b/tools/inspector_protocol/lib/Parser_h.template index 8397d3f5d6911c..1832c2e9724b4a 100644 --- a/tools/inspector_protocol/lib/Parser_h.template +++ b/tools/inspector_protocol/lib/Parser_h.template @@ -1,3 +1,5 @@ +// This file is generated by Parser_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/tools/inspector_protocol/lib/Protocol_cpp.template b/tools/inspector_protocol/lib/Protocol_cpp.template index 901656373a4f52..88303a27ab9e7e 100644 --- a/tools/inspector_protocol/lib/Protocol_cpp.template +++ b/tools/inspector_protocol/lib/Protocol_cpp.template @@ -1,4 +1,4 @@ -// This file is generated. +// This file is generated by Protocol_cpp.template. // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -7,6 +7,6 @@ #include {{format_include(config.protocol.package, "Protocol")}} #include +#include #include - #include diff --git a/tools/inspector_protocol/lib/ValueConversions_h.template b/tools/inspector_protocol/lib/ValueConversions_h.template index 4d64ec9091a6c2..2ee5b724545a33 100644 --- a/tools/inspector_protocol/lib/ValueConversions_h.template +++ b/tools/inspector_protocol/lib/ValueConversions_h.template @@ -1,3 +1,5 @@ +// This file is generated by ValueConversions_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -99,6 +101,33 @@ struct ValueConversions { } }; +template<> +struct ValueConversions { + static Binary fromValue(protocol::Value* value, ErrorSupport* errors) + { + if (!value || + (value->type() != Value::TypeBinary && value->type() != Value::TypeString)) { + errors->addError("Either string base64 or binary value expected"); + return Binary(); + } + Binary binary; + if (value->asBinary(&binary)) + return binary; + String result; + value->asString(&result); + bool success; + Binary out = Binary::fromBase64(result, &success); + if (!success) + errors->addError("base64 decoding error"); + return out; + } + + static std::unique_ptr toValue(const Binary& value) + { + return BinaryValue::create(value); + } +}; + template<> struct ValueConversions { static std::unique_ptr fromValue(protocol::Value* value, ErrorSupport* errors) diff --git a/tools/inspector_protocol/lib/Values_cpp.template b/tools/inspector_protocol/lib/Values_cpp.template index b9f061346bf66f..4b4ba994151db7 100644 --- a/tools/inspector_protocol/lib/Values_cpp.template +++ b/tools/inspector_protocol/lib/Values_cpp.template @@ -1,3 +1,5 @@ +// This file is generated by Values_cpp.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -58,8 +60,162 @@ void escapeStringForJSONInternal(const Char* str, unsigned len, } } +// When parsing CBOR, we limit recursion depth for objects and arrays +// to this constant. +static constexpr int kStackLimitValues = 1000; + +// Below are three parsing routines for CBOR, which cover enough +// to roundtrip JSON messages. +std::unique_ptr parseMap(int32_t stack_depth, CBORTokenizer* tokenizer); +std::unique_ptr parseArray(int32_t stack_depth, CBORTokenizer* tokenizer); +std::unique_ptr parseValue(int32_t stack_depth, CBORTokenizer* tokenizer); + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +std::unique_ptr parseArray(int32_t stack_depth, CBORTokenizer* tokenizer) { + DCHECK(tokenizer->TokenTag() == CBORTokenTag::ARRAY_START); + tokenizer->Next(); + auto list = ListValue::create(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + // Error::CBOR_UNEXPECTED_EOF_IN_ARRAY + if (tokenizer->TokenTag() == CBORTokenTag::DONE) return nullptr; + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + // Parse value. + auto value = parseValue(stack_depth, tokenizer); + if (!value) return nullptr; + list->pushValue(std::move(value)); + } + tokenizer->Next(); + return list; +} + +std::unique_ptr parseValue( + int32_t stack_depth, CBORTokenizer* tokenizer) { + // Error::CBOR_STACK_LIMIT_EXCEEDED + if (stack_depth > kStackLimitValues) return nullptr; + // Skip past the envelope to get to what's inside. + if (tokenizer->TokenTag() == CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + switch (tokenizer->TokenTag()) { + case CBORTokenTag::ERROR_VALUE: + return nullptr; + case CBORTokenTag::DONE: + // Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE + return nullptr; + case CBORTokenTag::TRUE_VALUE: { + std::unique_ptr value = FundamentalValue::create(true); + tokenizer->Next(); + return value; + } + case CBORTokenTag::FALSE_VALUE: { + std::unique_ptr value = FundamentalValue::create(false); + tokenizer->Next(); + return value; + } + case CBORTokenTag::NULL_VALUE: { + std::unique_ptr value = FundamentalValue::null(); + tokenizer->Next(); + return value; + } + case CBORTokenTag::INT32: { + std::unique_ptr value = FundamentalValue::create(tokenizer->GetInt32()); + tokenizer->Next(); + return value; + } + case CBORTokenTag::DOUBLE: { + std::unique_ptr value = FundamentalValue::create(tokenizer->GetDouble()); + tokenizer->Next(); + return value; + } + case CBORTokenTag::STRING8: { + span str = tokenizer->GetString8(); + std::unique_ptr value = StringValue::create(StringUtil::fromUTF8(str.data(), str.size())); + tokenizer->Next(); + return value; + } + case CBORTokenTag::STRING16: + // NOT SUPPORTED YET. + return nullptr; + case CBORTokenTag::BINARY: { + span payload = tokenizer->GetBinary(); + tokenizer->Next(); + return BinaryValue::create(Binary::fromSpan(payload.data(), payload.size())); + } + case CBORTokenTag::MAP_START: + return parseMap(stack_depth + 1, tokenizer); + case CBORTokenTag::ARRAY_START: + return parseArray(stack_depth + 1, tokenizer); + default: + // Error::CBOR_UNSUPPORTED_VALUE + return nullptr; + } +} + +// |bytes| must start with the indefinite length array byte, so basically, +// ParseArray may only be called after an indefinite length array has been +// detected. +std::unique_ptr parseMap( + int32_t stack_depth, CBORTokenizer* tokenizer) { + auto dict = DictionaryValue::create(); + tokenizer->Next(); + while (tokenizer->TokenTag() != CBORTokenTag::STOP) { + if (tokenizer->TokenTag() == CBORTokenTag::DONE) { + // Error::CBOR_UNEXPECTED_EOF_IN_MAP + return nullptr; + } + if (tokenizer->TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + // Parse key. + String key; + if (tokenizer->TokenTag() == CBORTokenTag::STRING8) { + span key_span = tokenizer->GetString8(); + key = StringUtil::fromUTF8(key_span.data(), key_span.size()); + tokenizer->Next(); + } else if (tokenizer->TokenTag() == CBORTokenTag::STRING16) { + return nullptr; // STRING16 not supported yet. + } else { + // Error::CBOR_INVALID_MAP_KEY + return nullptr; + } + // Parse value. + auto value = parseValue(stack_depth, tokenizer); + if (!value) return nullptr; + dict->setValue(key, std::move(value)); + } + tokenizer->Next(); + return dict; +} + } // anonymous namespace +// static +std::unique_ptr Value::parseBinary(const uint8_t* data, size_t size) { + span bytes(data, size); + + // Error::CBOR_NO_INPUT + if (bytes.empty()) return nullptr; + + // Error::CBOR_INVALID_START_BYTE + // TODO(johannes): EncodeInitialByteForEnvelope() method. + if (bytes[0] != 0xd8) return nullptr; + + CBORTokenizer tokenizer(bytes); + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + + // We checked for the envelope start byte above, so the tokenizer + // must agree here, since it's not an error. + DCHECK(tokenizer.TokenTag() == CBORTokenTag::ENVELOPE); + tokenizer.EnterEnvelope(); + // Error::MAP_START_EXPECTED + if (tokenizer.TokenTag() != CBORTokenTag::MAP_START) return nullptr; + std::unique_ptr result = parseMap(/*stack_depth=*/1, &tokenizer); + if (!result) return nullptr; + if (tokenizer.TokenTag() == CBORTokenTag::DONE) return result; + if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) return nullptr; + // Error::CBOR_TRAILING_JUNK + return nullptr; +} + bool Value::asBoolean(bool*) const { return false; @@ -80,7 +236,7 @@ bool Value::asString(String*) const return false; } -bool Value::asSerialized(String*) const +bool Value::asBinary(Binary*) const { return false; } @@ -91,12 +247,17 @@ void Value::writeJSON(StringBuilder* output) const StringUtil::builderAppend(*output, nullValueString, 4); } +void Value::writeBinary(std::vector* bytes) const { + DCHECK(m_type == TypeNull); + bytes->push_back(EncodeNull()); +} + std::unique_ptr Value::clone() const { return Value::null(); } -String Value::serialize() +String Value::toJSONString() const { StringBuilder result; StringUtil::builderReserve(result, 512); @@ -104,6 +265,16 @@ String Value::serialize() return StringUtil::builderToString(result); } +String Value::serializeToJSON() { + return toJSONString(); +} + +std::vector Value::serializeToBinary() { + std::vector bytes; + writeBinary(&bytes); + return bytes; +} + bool FundamentalValue::asBoolean(bool* output) const { if (type() != TypeBoolean) @@ -152,6 +323,22 @@ void FundamentalValue::writeJSON(StringBuilder* output) const } } +void FundamentalValue::writeBinary(std::vector* bytes) const { + switch (type()) { + case TypeDouble: + EncodeDouble(m_doubleValue, bytes); + return; + case TypeInteger: + EncodeInt32(m_integerValue, bytes); + return; + case TypeBoolean: + bytes->push_back(m_boolValue ? EncodeTrue() : EncodeFalse()); + return; + default: + DCHECK(false); + } +} + std::unique_ptr FundamentalValue::clone() const { switch (type()) { @@ -176,26 +363,53 @@ void StringValue::writeJSON(StringBuilder* output) const StringUtil::builderAppendQuotedString(*output, m_stringValue); } +void StringValue::writeBinary(std::vector* bytes) const { + StringUTF8Adapter utf8(m_stringValue); + EncodeString8(span(reinterpret_cast(utf8.Data()), + utf8.length()), bytes); +} + std::unique_ptr StringValue::clone() const { return StringValue::create(m_stringValue); } -bool SerializedValue::asSerialized(String* output) const +bool BinaryValue::asBinary(Binary* output) const { - *output = m_serializedValue; + *output = m_binaryValue; return true; } +void BinaryValue::writeJSON(StringBuilder* output) const +{ + DCHECK(type() == TypeBinary); + StringUtil::builderAppendQuotedString(*output, m_binaryValue.toBase64()); +} + +void BinaryValue::writeBinary(std::vector* bytes) const { + EncodeBinary(span(m_binaryValue.data(), m_binaryValue.size()), bytes); +} + +std::unique_ptr BinaryValue::clone() const +{ + return BinaryValue::create(m_binaryValue); +} + void SerializedValue::writeJSON(StringBuilder* output) const { DCHECK(type() == TypeSerialized); - StringUtil::builderAppend(*output, m_serializedValue); + StringUtil::builderAppend(*output, m_serializedJSON); +} + +void SerializedValue::writeBinary(std::vector* output) const +{ + DCHECK(type() == TypeSerialized); + output->insert(output->end(), m_serializedBinary.begin(), m_serializedBinary.end()); } std::unique_ptr SerializedValue::clone() const { - return SerializedValue::create(m_serializedValue); + return std::unique_ptr(new SerializedValue(m_serializedJSON, m_serializedBinary)); } DictionaryValue::~DictionaryValue() @@ -335,6 +549,23 @@ void DictionaryValue::writeJSON(StringBuilder* output) const StringUtil::builderAppend(*output, '}'); } +void DictionaryValue::writeBinary(std::vector* bytes) const { + EnvelopeEncoder encoder; + encoder.EncodeStart(bytes); + bytes->push_back(EncodeIndefiniteLengthMapStart()); + for (size_t i = 0; i < m_order.size(); ++i) { + const String& key = m_order[i]; + Dictionary::const_iterator value = m_data.find(key); + DCHECK(value != m_data.cend() && value->second); + StringUTF8Adapter utf8(key); + EncodeString8(span(reinterpret_cast(utf8.Data()), + utf8.length()), bytes); + value->second->writeBinary(bytes); + } + bytes->push_back(EncodeStop()); + encoder.EncodeStop(bytes); +} + std::unique_ptr DictionaryValue::clone() const { std::unique_ptr result = DictionaryValue::create(); @@ -369,6 +600,17 @@ void ListValue::writeJSON(StringBuilder* output) const StringUtil::builderAppend(*output, ']'); } +void ListValue::writeBinary(std::vector* bytes) const { + EnvelopeEncoder encoder; + encoder.EncodeStart(bytes); + bytes->push_back(EncodeIndefiniteLengthArrayStart()); + for (size_t i = 0; i < m_data.size(); ++i) { + m_data[i]->writeBinary(bytes); + } + bytes->push_back(EncodeStop()); + encoder.EncodeStop(bytes); +} + std::unique_ptr ListValue::clone() const { std::unique_ptr result = ListValue::create(); diff --git a/tools/inspector_protocol/lib/Values_h.template b/tools/inspector_protocol/lib/Values_h.template index 3638b34b4e7718..4a2e58f4cd6850 100644 --- a/tools/inspector_protocol/lib/Values_h.template +++ b/tools/inspector_protocol/lib/Values_h.template @@ -1,3 +1,5 @@ +// This file is generated by Values_h.template. + // Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,7 +8,6 @@ #define {{"_".join(config.protocol.namespace)}}_Values_h //#include "Allocator.h" -//#include "Collections.h" //#include "Forward.h" {% for namespace in config.protocol.namespace %} @@ -27,15 +28,19 @@ public: return std::unique_ptr(new Value()); } + static std::unique_ptr parseBinary(const uint8_t* data, size_t size); + enum ValueType { TypeNull = 0, TypeBoolean, TypeInteger, TypeDouble, TypeString, + TypeBinary, TypeObject, TypeArray, - TypeSerialized + TypeSerialized, + TypeImported }; ValueType type() const { return m_type; } @@ -46,11 +51,14 @@ public: virtual bool asDouble(double* output) const; virtual bool asInteger(int* output) const; virtual bool asString(String* output) const; - virtual bool asSerialized(String* output) const; + virtual bool asBinary(Binary* output) const; virtual void writeJSON(StringBuilder* output) const; + virtual void writeBinary(std::vector* bytes) const; virtual std::unique_ptr clone() const; - String serialize() override; + String toJSONString() const; + String serializeToJSON() override; + std::vector serializeToBinary() override; protected: Value() : m_type(TypeNull) { } @@ -84,6 +92,7 @@ public: bool asDouble(double* output) const override; bool asInteger(int* output) const override; void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; private: @@ -112,6 +121,7 @@ public: bool asString(String* output) const override; void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; private: @@ -121,21 +131,47 @@ private: String m_stringValue; }; +class {{config.lib.export_macro}} BinaryValue : public Value { +public: + static std::unique_ptr create(const Binary& value) + { + return std::unique_ptr(new BinaryValue(value)); + } + + bool asBinary(Binary* output) const override; + void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; + std::unique_ptr clone() const override; + +private: + explicit BinaryValue(const Binary& value) : Value(TypeBinary), m_binaryValue(value) { } + + Binary m_binaryValue; +}; + class {{config.lib.export_macro}} SerializedValue : public Value { public: - static std::unique_ptr create(const String& value) + static std::unique_ptr fromJSON(const String& value) { return std::unique_ptr(new SerializedValue(value)); } - bool asSerialized(String* output) const override; + static std::unique_ptr fromBinary(std::vector value) + { + return std::unique_ptr(new SerializedValue(std::move(value))); + } + void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; private: - explicit SerializedValue(const String& value) : Value(TypeSerialized), m_serializedValue(value) { } - - String m_serializedValue; + explicit SerializedValue(const String& json) : Value(TypeSerialized), m_serializedJSON(json) { } + explicit SerializedValue(std::vector binary) : Value(TypeSerialized), m_serializedBinary(std::move(binary)) { } + SerializedValue(const String& json, const std::vector& binary) + : Value(TypeSerialized), m_serializedJSON(json), m_serializedBinary(binary) { } + String m_serializedJSON; + std::vector m_serializedBinary; }; class {{config.lib.export_macro}} DictionaryValue : public Value { @@ -159,6 +195,7 @@ public: } void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; size_t size() const { return m_data.size(); } @@ -200,7 +237,7 @@ private: m_order.push_back(key); } - using Dictionary = protocol::HashMap>; + using Dictionary = std::unordered_map>; Dictionary m_data; std::vector m_order; }; @@ -227,6 +264,7 @@ public: ~ListValue() override; void writeJSON(StringBuilder* output) const override; + void writeBinary(std::vector* bytes) const override; std::unique_ptr clone() const override; void pushValue(std::unique_ptr); diff --git a/tools/inspector_protocol/lib/base_string_adapter_cc.template b/tools/inspector_protocol/lib/base_string_adapter_cc.template new file mode 100644 index 00000000000000..ed3316446f4a51 --- /dev/null +++ b/tools/inspector_protocol/lib/base_string_adapter_cc.template @@ -0,0 +1,304 @@ +// This file is generated by DispatcherBase_cpp.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include {{format_include(config.protocol.package, "base_string_adapter")}} +#include {{format_include(config.protocol.package, "Protocol")}} + +#include +#include "base/base64.h" +#include "base/json/json_reader.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string16.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +std::unique_ptr toProtocolValue( + const base::Value* value, int depth) { + if (!value || !depth) + return nullptr; + if (value->is_none()) + return protocol::Value::null(); + if (value->is_bool()) { + bool inner; + value->GetAsBoolean(&inner); + return protocol::FundamentalValue::create(inner); + } + if (value->is_int()) { + int inner; + value->GetAsInteger(&inner); + return protocol::FundamentalValue::create(inner); + } + if (value->is_double()) { + double inner; + value->GetAsDouble(&inner); + return protocol::FundamentalValue::create(inner); + } + if (value->is_string()) { + std::string inner; + value->GetAsString(&inner); + return protocol::StringValue::create(inner); + } + if (value->is_list()) { + const base::ListValue* list = nullptr; + value->GetAsList(&list); + std::unique_ptr result = protocol::ListValue::create(); + for (size_t i = 0; i < list->GetSize(); i++) { + const base::Value* item = nullptr; + list->Get(i, &item); + std::unique_ptr converted = + toProtocolValue(item, depth - 1); + if (converted) + result->pushValue(std::move(converted)); + } + return std::move(result); + } + if (value->is_dict()) { + const base::DictionaryValue* dictionary = nullptr; + value->GetAsDictionary(&dictionary); + std::unique_ptr result = + protocol::DictionaryValue::create(); + for (base::DictionaryValue::Iterator it(*dictionary); + !it.IsAtEnd(); it.Advance()) { + std::unique_ptr converted = + toProtocolValue(&it.value(), depth - 1); + if (converted) + result->setValue(it.key(), std::move(converted)); + } + return std::move(result); + } + return nullptr; +} + +std::unique_ptr toBaseValue(Value* value, int depth) { + if (!value || !depth) + return nullptr; + if (value->type() == Value::TypeNull) + return std::make_unique(); + if (value->type() == Value::TypeBoolean) { + bool inner; + value->asBoolean(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeInteger) { + int inner; + value->asInteger(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeDouble) { + double inner; + value->asDouble(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeString) { + std::string inner; + value->asString(&inner); + return base::WrapUnique(new base::Value(inner)); + } + if (value->type() == Value::TypeArray) { + ListValue* list = ListValue::cast(value); + std::unique_ptr result(new base::ListValue()); + for (size_t i = 0; i < list->size(); i++) { + std::unique_ptr converted = + toBaseValue(list->at(i), depth - 1); + if (converted) + result->Append(std::move(converted)); + } + return std::move(result); + } + if (value->type() == Value::TypeObject) { + DictionaryValue* dict = DictionaryValue::cast(value); + std::unique_ptr result(new base::DictionaryValue()); + for (size_t i = 0; i < dict->size(); i++) { + DictionaryValue::Entry entry = dict->at(i); + std::unique_ptr converted = + toBaseValue(entry.second, depth - 1); + if (converted) + result->SetWithoutPathExpansion(entry.first, std::move(converted)); + } + return std::move(result); + } + return nullptr; +} + +// static +std::unique_ptr StringUtil::parseMessage( + const std::string& message, bool binary) { + if (binary) { + return Value::parseBinary( + reinterpret_cast(message.data()), + message.length()); + } + std::unique_ptr value = base::JSONReader::Read(message); + return toProtocolValue(value.get(), 1000); +} + +// static +ProtocolMessage StringUtil::jsonToMessage(String message) { + return message; +} + +// static +ProtocolMessage StringUtil::binaryToMessage(std::vector message) { + // TODO(pfeldman): figure out what to do with this copy. + return std::string(reinterpret_cast(message.data()), message.size()); +} + +StringBuilder::StringBuilder() {} + +StringBuilder::~StringBuilder() {} + +void StringBuilder::append(const std::string& s) { + string_ += s; +} + +void StringBuilder::append(char c) { + string_ += c; +} + +void StringBuilder::append(const char* characters, size_t length) { + string_.append(characters, length); +} + +// static +void StringUtil::builderAppendQuotedString(StringBuilder& builder, + const String& str) { + builder.append('"'); + base::string16 str16 = base::UTF8ToUTF16(str); + escapeWideStringForJSON(reinterpret_cast(&str16[0]), + str16.length(), &builder); + builder.append('"'); +} + +std::string StringBuilder::toString() { + return string_; +} + +void StringBuilder::reserveCapacity(size_t capacity) { + string_.reserve(capacity); +} + +Binary::Binary() : bytes_(new base::RefCountedBytes) {} +Binary::Binary(const Binary& binary) : bytes_(binary.bytes_) {} +Binary::Binary(scoped_refptr bytes) : bytes_(bytes) {} +Binary::~Binary() {} + +String Binary::toBase64() const { + std::string encoded; + base::Base64Encode( + base::StringPiece(reinterpret_cast(bytes_->front()), + bytes_->size()), + &encoded); + return encoded; +} + +// static +Binary Binary::fromBase64(const String& base64, bool* success) { + std::string decoded; + *success = base::Base64Decode(base::StringPiece(base64), &decoded); + if (*success) { + return Binary::fromString(std::move(decoded)); + } + return Binary(); +} + +// static +Binary Binary::fromRefCounted(scoped_refptr memory) { + return Binary(memory); +} + +// static +Binary Binary::fromVector(std::vector data) { + return Binary(base::RefCountedBytes::TakeVector(&data)); +} + +// static +Binary Binary::fromString(std::string data) { + return Binary(base::RefCountedString::TakeString(&data)); +} + +// static +Binary Binary::fromSpan(const uint8_t* data, size_t size) { + return Binary(scoped_refptr( + new base::RefCountedBytes(data, size))); +} + +namespace { +int32_t ReadEnvelopeSize(const uint8_t* in) { + return (in[0] << 24) + (in[1] << 16) + (in[2] << 8) + in[3]; +} + +void WriteEnvelopeSize(uint32_t value, uint8_t* out) { + *(out++) = (value >> 24) & 0xFF; + *(out++) = (value >> 16) & 0xFF; + *(out++) = (value >> 8) & 0xFF; + *(out++) = (value) & 0xFF; +} + +} + +bool AppendStringValueToMapBinary(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out) { + if (in.size() < 1 + 1 + 4 + 1 + 1) + return false; + const uint8_t* envelope = reinterpret_cast(in.data()); + if (cbor::kInitialByteForEnvelope != envelope[0]) + return false; + if (cbor::kInitialByteFor32BitLengthByteString != envelope[1]) + return false; + if (cbor::kInitialByteIndefiniteLengthMap != envelope[6]) + return false; + + uint32_t envelope_size = ReadEnvelopeSize(envelope + 2); + if (envelope_size + 2 + 4 != in.size()) + return false; + if (cbor::kStopByte != static_cast(*in.rbegin())) + return false; + + std::vector encoded_entry; + encoded_entry.reserve(1 + 4 + key.size() + 1 + 4 + value.size()); + span key_span( + reinterpret_cast(key.data()), key.size()); + EncodeString8(key_span, &encoded_entry); + span value_span( + reinterpret_cast(value.data()), value.size()); + EncodeString8(value_span, &encoded_entry); + + out->clear(); + out->reserve(in.size() + encoded_entry.size()); + out->append(in.begin(), in.end() - 1); + out->append(reinterpret_cast(encoded_entry.data()), + encoded_entry.size()); + out->append(1, static_cast(cbor::kStopByte)); + std::size_t new_size = envelope_size + out->size() - in.size(); + if (new_size > static_cast( + std::numeric_limits::max())) { + return false; + } + WriteEnvelopeSize(new_size, reinterpret_cast(&*out->begin() + 2)); + return true; +} + +bool AppendStringValueToMapJSON(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out) { + if (!in.length() || *in.rbegin() != '}') + return false; + std::string suffix = + base::StringPrintf(", \"%s\": \"%s\"}", key.begin(), value.begin()); + out->clear(); + out->reserve(in.length() + suffix.length() - 1); + out->append(in.data(), in.length() - 1); + out->append(suffix); + return true; +} + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} diff --git a/tools/inspector_protocol/lib/base_string_adapter_h.template b/tools/inspector_protocol/lib/base_string_adapter_h.template new file mode 100644 index 00000000000000..b0215e0745017a --- /dev/null +++ b/tools/inspector_protocol/lib/base_string_adapter_h.template @@ -0,0 +1,150 @@ +// This file is generated by Parser_h.template. + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef {{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H +#define {{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H + +#include +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted_memory.h" +#include "base/strings/string_number_conversions.h" +{% if config.lib.export_header %} +#include "{{config.lib.export_header}}" +{% endif %} + +namespace base { +class Value; +} + +{% for namespace in config.protocol.namespace %} +namespace {{namespace}} { +{% endfor %} + +class Value; + +using String = std::string; +using ProtocolMessage = std::string; + +class {{config.lib.export_macro}} StringUTF8Adapter { + public: + StringUTF8Adapter(const std::string& string) : string_(string) { } + const char* Data() const { return string_.data(); } + size_t length() const { return string_.length(); } + + private: + const std::string& string_; +}; + +class {{config.lib.export_macro}} StringBuilder { + public: + StringBuilder(); + ~StringBuilder(); + void append(const String&); + void append(char); + void append(const char*, size_t); + String toString(); + void reserveCapacity(size_t); + + private: + std::string string_; +}; + +class {{config.lib.export_macro}} StringUtil { + public: + static String substring(const String& s, unsigned pos, unsigned len) { + return s.substr(pos, len); + } + static String fromInteger(int number) { return base::NumberToString(number); } + static String fromDouble(double number) { + String s = base::NumberToString(number); + if (!s.empty()) { // .123 -> 0.123; -.123 -> -0.123 for valid JSON. + if (s[0] == '.') + s.insert(/*index=*/ 0, /*count=*/ 1, /*ch=*/ '0'); + else if (s[0] == '-' && s.size() >= 2 && s[1] == '.') + s.insert(/*index=*/ 1, /*count=*/ 1, /*ch=*/ '0'); + } + return s; + } + static double toDouble(const char* s, size_t len, bool* ok) { + double v = 0.0; + *ok = base::StringToDouble(std::string(s, len), &v); + return *ok ? v : 0.0; + } + static size_t find(const String& s, const char* needle) { + return s.find(needle); + } + static size_t find(const String& s, const String& needle) { + return s.find(needle); + } + static const size_t kNotFound = static_cast(-1); + static void builderAppend(StringBuilder& builder, const String& s) { + builder.append(s); + } + static void builderAppend(StringBuilder& builder, char c) { + builder.append(c); + } + static void builderAppend(StringBuilder& builder, const char* s, size_t len) { + builder.append(s, len); + } + static void builderAppendQuotedString(StringBuilder& builder, + const String& str); + static void builderReserve(StringBuilder& builder, unsigned capacity) { + builder.reserveCapacity(capacity); + } + static String builderToString(StringBuilder& builder) { + return builder.toString(); + } + + static std::unique_ptr parseMessage(const std::string& message, bool binary); + static ProtocolMessage jsonToMessage(String message); + static ProtocolMessage binaryToMessage(std::vector message); + + static String fromUTF8(const uint8_t* data, size_t length) { + return std::string(reinterpret_cast(data), length); + } +}; + +// A read-only sequence of uninterpreted bytes with reference-counted storage. +class {{config.lib.export_macro}} Binary { + public: + Binary(const Binary&); + Binary(); + ~Binary(); + + const uint8_t* data() const { return bytes_->front(); } + size_t size() const { return bytes_->size(); } + scoped_refptr bytes() const { return bytes_; } + + String toBase64() const; + + static Binary fromBase64(const String& base64, bool* success); + static Binary fromRefCounted(scoped_refptr memory); + static Binary fromVector(std::vector data); + static Binary fromString(std::string data); + static Binary fromSpan(const uint8_t* data, size_t size); + + private: + explicit Binary(scoped_refptr bytes); + scoped_refptr bytes_; +}; + +std::unique_ptr toProtocolValue(const base::Value* value, int depth); +std::unique_ptr toBaseValue(Value* value, int depth); + +bool AppendStringValueToMapBinary(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out); +bool AppendStringValueToMapJSON(base::StringPiece in, + base::StringPiece key, base::StringPiece value, std::string* out); + +{% for namespace in config.protocol.namespace %} +} // namespace {{namespace}} +{% endfor %} + +#endif // !defined({{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H) diff --git a/tools/inspector_protocol/ConvertProtocolToJSON.py b/tools/inspector_protocol/pdl.py similarity index 80% rename from tools/inspector_protocol/ConvertProtocolToJSON.py rename to tools/inspector_protocol/pdl.py index 56fc09d78cb18f..43111e944b4f5c 100644 --- a/tools/inspector_protocol/ConvertProtocolToJSON.py +++ b/tools/inspector_protocol/pdl.py @@ -1,28 +1,31 @@ -# Copyright 2017 The Chromium Authors. All rights reserved. +# Copyright 2018 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +from __future__ import print_function import collections import json import os.path import re import sys -file_name = None description = '' -primitiveTypes = ['integer', 'number', 'boolean', 'string', 'object', 'any', 'array'] +primitiveTypes = ['integer', 'number', 'boolean', 'string', 'object', 'any', 'array', 'binary'] -def assignType(item, type, isArray=False): - if isArray: + +def assignType(item, type, is_array=False, map_binary_to_string=False): + if is_array: item['type'] = 'array' item['items'] = collections.OrderedDict() - assignType(item['items'], type) + assignType(item['items'], type, False, map_binary_to_string) return if type == 'enum': type = 'string' + if map_binary_to_string and type == 'binary': + type = 'string' if type in primitiveTypes: item['type'] = type else: @@ -43,7 +46,7 @@ def createItem(d, experimental, deprecated, name=None): return result -def parse(data): +def parse(data, file_name, map_binary_to_string=False): protocol = collections.OrderedDict() protocol['version'] = collections.OrderedDict() protocol['domains'] = [] @@ -89,7 +92,7 @@ def parse(data): if 'types' not in domain: domain['types'] = [] item = createItem({'id': match.group(3)}, match.group(1), match.group(2)) - assignType(item, match.group(5), match.group(4)) + assignType(item, match.group(5), match.group(4), map_binary_to_string) domain['types'].append(item) continue @@ -116,7 +119,7 @@ def parse(data): param = createItem({}, match.group(1), match.group(2), match.group(6)) if match.group(3): param['optional'] = True - assignType(param, match.group(5), match.group(4)) + assignType(param, match.group(5), match.group(4), map_binary_to_string) if match.group(5) == 'enum': enumliterals = param['enum'] = [] subitems.append(param) @@ -157,27 +160,12 @@ def parse(data): enumliterals.append(trimLine) continue - print 'Error in %s:%s, illegal token: \t%s' % (file_name, i, line) + print('Error in %s:%s, illegal token: \t%s' % (file_name, i, line)) sys.exit(1) return protocol -def main(argv): - if len(argv) < 2: - sys.stderr.write("Usage: %s \n" % sys.argv[0]) - return 1 - global file_name - file_name = os.path.normpath(argv[0]) - input_file = open(file_name, "r") - pdl_string = input_file.read() - protocol = parse(pdl_string) - output_file = open(argv[0].replace('.pdl', '.json'), 'wb') - json.dump(protocol, output_file, indent=4, separators=(',', ': ')) - output_file.close() - - output_file = open(os.path.normpath(argv[1]), 'wb') - json.dump(protocol, output_file, indent=4, separators=(',', ': ')) - output_file.close() - - -if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) + +def loads(data, file_name, map_binary_to_string=False): + if file_name.endswith(".pdl"): + return parse(data, file_name, map_binary_to_string) + return json.loads(data) diff --git a/tools/inspector_protocol/sample_config.json b/tools/inspector_protocol/sample_config.json new file mode 100644 index 00000000000000..1a93135480a4ae --- /dev/null +++ b/tools/inspector_protocol/sample_config.json @@ -0,0 +1,55 @@ +{ + "protocol": { + "path": "./relative/path/protocol/sample_protocol.json", + "package": "include/generated/files/like/this", + "output": "place/generated/files/here", + "namespace": ["sample_project", "protocol"], + "export_macro": "LIB_EXPORT", + "export_header": "lib/lib_export.h", + "options": [ + { + "domain": "Domain1", + "include": ["command1", "command2"], + "async": ["command1"] + }, + { + "domain": "Domain2", + "exclude": ["command3", "command4"], + "exclude_events": ["event1"], + "async": ["command10"] + }, + { + "domain": "Domain3" + } + ], + }, + + "exported": { + "package": "include/exported/files/like/this", + "output": "place/exported/files/here", + "string_header": "include/exported/string.h", + "string_in": "String", + "string_out": "String", + "to_string_out": "toString(%s)", + "export_macro": "LIB_EXPORT", + "export_header": "lib/lib_export.h" + }, + + "imported": { + "path": "../relative/path/imported_protocol.json", + "package": "either/include/imported/files/from/here", + "header": "or/include/them/all/together/like/this/imported_protocol.h", + "to_imported_string": "toImportedString(%s)", + "from_imported_string": "fromImportedString(%s)", + "namespace": ["imported_project", "protocol"] + }, + + "lib": { + "package": "include/lib/files/like/this", + "output": "place/generated/lib/files/here", + "string_header": "string/implementation.h", + "platform_header": "platform/implementation.h", + "export_macro": "LIB_EXPORT", + "export_header": "lib/lib_export.h" + } +} diff --git a/tools/inspector_protocol/sample_expected_errors.json b/tools/inspector_protocol/sample_expected_errors.json new file mode 100644 index 00000000000000..85fa2641db6029 --- /dev/null +++ b/tools/inspector_protocol/sample_expected_errors.json @@ -0,0 +1,7 @@ +{ + "errors": [ + "Domain.event: event has been removed", + "Domain.command: command has been removed", + "Domain.command.param parameter->Domain.TypeName.property: required property has been removed" + ] +} \ No newline at end of file diff --git a/tools/inspector_protocol/templates/Exported_h.template b/tools/inspector_protocol/templates/Exported_h.template index 3d36ecffae3ca3..765f6c2135b9c3 100644 --- a/tools/inspector_protocol/templates/Exported_h.template +++ b/tools/inspector_protocol/templates/Exported_h.template @@ -1,4 +1,4 @@ -// This file is generated +// This file is generated by Exported_h.template. // Copyright (c) 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -15,6 +15,17 @@ {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} + +#ifndef {{"_".join(config.protocol.namespace)}}_exported_api_h +#define {{"_".join(config.protocol.namespace)}}_exported_api_h +class {{config.exported.export_macro}} Exported { +public: + virtual {{config.exported.string_out}} toJSONString() const = 0; + virtual void writeBinary(std::vector* out) const = 0; + virtual ~Exported() { } +}; +#endif // !defined({{"_".join(config.protocol.namespace)}}_exported_api_h) + namespace {{domain.domain}} { namespace API { @@ -48,11 +59,10 @@ namespace {{param.name | to_title_case}}Enum { {% for type in domain.types %} {% if not (type.type == "object") or not ("properties" in type) or not protocol.is_exported(domain.domain, type.id) %}{% continue %}{% endif %} -class {{config.exported.export_macro}} {{type.id}} { +class {{config.exported.export_macro}} {{type.id}} : public Exported { public: - virtual {{config.exported.string_out}} toJSONString() const = 0; - virtual ~{{type.id}}() { } static std::unique_ptr fromJSONString(const {{config.exported.string_in}}& json); + static std::unique_ptr fromBinary(const uint8_t* data, size_t length); }; {% endfor %} diff --git a/tools/inspector_protocol/templates/Imported_h.template b/tools/inspector_protocol/templates/Imported_h.template index 4c9d24bd5fccf7..f2e576a9c470ae 100644 --- a/tools/inspector_protocol/templates/Imported_h.template +++ b/tools/inspector_protocol/templates/Imported_h.template @@ -1,4 +1,4 @@ -// This file is generated +// This file is generated by Imported_h.template. // Copyright (c) 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -17,6 +17,37 @@ {% for namespace in config.protocol.namespace %} namespace {{namespace}} { {% endfor %} + +using Exported = {{"::".join(config.imported.namespace)}}::Exported; + +#ifndef {{"_".join(config.protocol.namespace)}}_imported_imported_h +#define {{"_".join(config.protocol.namespace)}}_imported_imported_h + +class {{config.lib.export_macro}} ImportedValue : public Value { +public: + static std::unique_ptr fromExported(const Exported* value) { + return std::unique_ptr(new ImportedValue(value)); + } + + void writeJSON(StringBuilder* output) const override { + auto json = m_exported->toJSONString(); + String local_json = ({{config.imported.from_imported_string % "std::move(json)"}}); + StringUtil::builderAppend(*output, local_json); + } + void writeBinary(std::vector* output) const override { + m_exported->writeBinary(output); + } + std::unique_ptr clone() const override { + return std::unique_ptr(new ImportedValue(m_exported)); + } + +private: + explicit ImportedValue(const Exported* exported) : Value(TypeImported), m_exported(exported) { } + const Exported* m_exported; +}; + +#endif // !defined({{"_".join(config.protocol.namespace)}}_imported_imported_h) + {% for type in domain.types %} {% if not (type.type == "object") or not ("properties" in type) or not protocol.is_imported(domain.domain, type.id) %}{% continue %}{% endif %} @@ -28,17 +59,18 @@ struct ValueConversions<{{"::".join(config.imported.namespace)}}::{{domain.domai errors->addError("value expected"); return nullptr; } - String json = value->serialize(); - auto result = {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}::fromJSONString({{config.imported.to_imported_string % "json"}}); + + std::vector binary; + value->writeBinary(&binary); + auto result = {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}::fromBinary(binary.data(), binary.size()); if (!result) errors->addError("cannot parse"); return result; } - static std::unique_ptr toValue(const {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}* value) + static std::unique_ptr toValue(const {{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}* exported) { - auto json = value->toJSONString(); - return SerializedValue::create({{config.imported.from_imported_string % "std::move(json)"}}); + return ImportedValue::fromExported(exported); } static std::unique_ptr toValue(const std::unique_ptr<{{"::".join(config.imported.namespace)}}::{{domain.domain}}::API::{{type.id}}>& value) @@ -46,6 +78,7 @@ struct ValueConversions<{{"::".join(config.imported.namespace)}}::{{domain.domai return toValue(value.get()); } }; + {% endfor %} {% for namespace in config.protocol.namespace %} diff --git a/tools/inspector_protocol/templates/TypeBuilder_cpp.template b/tools/inspector_protocol/templates/TypeBuilder_cpp.template index 026c1cdb8da9e1..4ef60a6ea2cdef 100644 --- a/tools/inspector_protocol/templates/TypeBuilder_cpp.template +++ b/tools/inspector_protocol/templates/TypeBuilder_cpp.template @@ -1,10 +1,10 @@ -// This file is generated +// This file is generated by TypeBuilder_cpp.template. // Copyright (c) 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include {{format_include(config.protocol.package, domain.domain)}} +#include {{format_domain_include(config.protocol.package, domain.domain)}} #include {{format_include(config.protocol.package, "Protocol")}} @@ -24,7 +24,7 @@ const char Metainfo::version[] = "{{domain.version}}"; namespace {{type.id}}Enum { {% for literal in type.enum %} -const char* {{ literal | dash_to_camelcase}} = "{{literal}}"; +const char {{ literal | dash_to_camelcase}}[] = "{{literal}}"; {% endfor %} } // namespace {{type.id}}Enum {% if protocol.is_exported(domain.domain, type.id) %} @@ -101,10 +101,15 @@ std::unique_ptr<{{type.id}}> {{type.id}}::clone() const {{config.exported.string_out}} {{type.id}}::toJSONString() const { - String json = toValue()->serialize(); + String json = toValue()->serializeToJSON(); return {{config.exported.to_string_out % "json"}}; } +void {{type.id}}::writeBinary(std::vector* out) const +{ + toValue()->writeBinary(out); +} + // static std::unique_ptr API::{{type.id}}::fromJSONString(const {{config.exported.string_in}}& json) { @@ -114,6 +119,17 @@ std::unique_ptr API::{{type.id}}::fromJSONString(const {{confi return nullptr; return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors); } + +// static +std::unique_ptr API::{{type.id}}::fromBinary(const uint8_t* data, size_t length) +{ + ErrorSupport errors; + std::unique_ptr value = Value::parseBinary(data, length); + if (!value) + return nullptr; + return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors); +} + {% endif %} {% endfor %} @@ -187,19 +203,23 @@ void Frontend::flush() m_frontendChannel->flushProtocolNotifications(); } -void Frontend::sendRawNotification(const String& notification) +void Frontend::sendRawNotification(String notification) +{ + m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromJSON(std::move(notification))); +} + +void Frontend::sendRawNotification(std::vector notification) { - m_frontendChannel->sendProtocolNotification(InternalRawNotification::create(notification)); + m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromBinary(std::move(notification))); } // --------------------- Dispatcher. class DispatcherImpl : public protocol::DispatcherBase { public: - DispatcherImpl(FrontendChannel* frontendChannel, Backend* backend, bool fallThroughForNotFound) + DispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) : DispatcherBase(frontendChannel) - , m_backend(backend) - , m_fallThroughForNotFound(fallThroughForNotFound) { + , m_backend(backend) { {% for command in domain.commands %} {% if "redirect" in command %} m_redirects["{{domain.domain}}.{{command.name}}"] = "{{command.redirect}}.{{command.name}}"; @@ -210,37 +230,35 @@ public: {% endfor %} } ~DispatcherImpl() override { } - DispatchResponse::Status dispatch(int callId, const String& method, std::unique_ptr messageObject) override; - HashMap& redirects() { return m_redirects; } + bool canDispatch(const String& method) override; + void dispatch(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr messageObject) override; + std::unordered_map& redirects() { return m_redirects; } protected: - using CallHandler = DispatchResponse::Status (DispatcherImpl::*)(int callId, std::unique_ptr messageObject, ErrorSupport* errors); - using DispatchMap = protocol::HashMap; + using CallHandler = void (DispatcherImpl::*)(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr messageObject, ErrorSupport* errors); + using DispatchMap = std::unordered_map; DispatchMap m_dispatchMap; - HashMap m_redirects; + std::unordered_map m_redirects; {% for command in domain.commands %} {% if "redirect" in command %}{% continue %}{% endif %} {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %} - DispatchResponse::Status {{command.name}}(int callId, std::unique_ptr requestMessageObject, ErrorSupport*); + void {{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr requestMessageObject, ErrorSupport*); {% endfor %} Backend* m_backend; - bool m_fallThroughForNotFound; }; -DispatchResponse::Status DispatcherImpl::dispatch(int callId, const String& method, std::unique_ptr messageObject) -{ - protocol::HashMap::iterator it = m_dispatchMap.find(method); - if (it == m_dispatchMap.end()) { - if (m_fallThroughForNotFound) - return DispatchResponse::kFallThrough; - reportProtocolError(callId, DispatchResponse::kMethodNotFound, "'" + method + "' wasn't found", nullptr); - return DispatchResponse::kError; - } +bool DispatcherImpl::canDispatch(const String& method) { + return m_dispatchMap.find(method) != m_dispatchMap.end(); +} +void DispatcherImpl::dispatch(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr messageObject) +{ + std::unordered_map::iterator it = m_dispatchMap.find(method); + DCHECK(it != m_dispatchMap.end()); protocol::ErrorSupport errors; - return (this->*(it->second))(callId, std::move(messageObject), &errors); + (this->*(it->second))(callId, method, message, std::move(messageObject), &errors); } {% for command in domain.commands %} @@ -251,8 +269,8 @@ DispatchResponse::Status DispatcherImpl::dispatch(int callId, const String& meth class {{command_name_title}}CallbackImpl : public Backend::{{command_name_title}}Callback, public DispatcherBase::Callback { public: - {{command_name_title}}CallbackImpl(std::unique_ptr backendImpl, int callId, int callbackId) - : DispatcherBase::Callback(std::move(backendImpl), callId, callbackId) { } + {{command_name_title}}CallbackImpl(std::unique_ptr backendImpl, int callId, const String& method, const ProtocolMessage& message) + : DispatcherBase::Callback(std::move(backendImpl), callId, method, message) { } void sendSuccess( {%- for parameter in command.returns -%} @@ -289,7 +307,7 @@ public: }; {% endif %} -DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::unique_ptr requestMessageObject, ErrorSupport* errors) +void DispatcherImpl::{{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr requestMessageObject, ErrorSupport* errors) { {% if "parameters" in command %} // Prepare input parameters. @@ -312,7 +330,7 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu errors->pop(); if (errors->hasErrors()) { reportProtocolError(callId, DispatchResponse::kInvalidParams, kInvalidParamsString, errors); - return DispatchResponse::kError; + return; } {% endif %} {% if "returns" in command and not protocol.is_async_command(domain.domain, command.name) %} @@ -343,8 +361,10 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu &out_{{parameter.name}} {%- endfor %} {% endif %}); - if (response.status() == DispatchResponse::kFallThrough) - return response.status(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, message); + return; + } {% if "returns" in command %} std::unique_ptr result = DictionaryValue::create(); if (response.status() == DispatchResponse::kSuccess) { @@ -363,10 +383,10 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu if (weak->get()) weak->get()->sendResponse(callId, response); {% endif %} - return response.status(); + return; {% else %} std::unique_ptr weak = weakPtr(); - std::unique_ptr<{{command_name_title}}CallbackImpl> callback(new {{command.name | to_title_case}}CallbackImpl(weakPtr(), callId, nextCallbackId())); + std::unique_ptr<{{command_name_title}}CallbackImpl> callback(new {{command.name | to_title_case}}CallbackImpl(weakPtr(), callId, method, message)); m_backend->{{command.name | to_method_case}}( {%- for property in command.parameters -%} {%- if not loop.first -%}, {% endif -%} @@ -378,7 +398,7 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu {%- endfor -%} {%- if command.parameters -%}, {% endif -%} std::move(callback)); - return (weak->get() && weak->get()->lastCallbackFallThrough()) ? DispatchResponse::kFallThrough : DispatchResponse::kAsync; + return; {% endif %} } {% endfor %} @@ -386,7 +406,7 @@ DispatchResponse::Status DispatcherImpl::{{command.name}}(int callId, std::uniqu // static void Dispatcher::wire(UberDispatcher* uber, Backend* backend) { - std::unique_ptr dispatcher(new DispatcherImpl(uber->channel(), backend, uber->fallThroughForNotFound())); + std::unique_ptr dispatcher(new DispatcherImpl(uber->channel(), backend)); uber->setupRedirects(dispatcher->redirects()); uber->registerBackend("{{domain.domain}}", std::move(dispatcher)); } diff --git a/tools/inspector_protocol/templates/TypeBuilder_h.template b/tools/inspector_protocol/templates/TypeBuilder_h.template index 744d496026a279..c670d65c46f20d 100644 --- a/tools/inspector_protocol/templates/TypeBuilder_h.template +++ b/tools/inspector_protocol/templates/TypeBuilder_h.template @@ -1,4 +1,4 @@ -// This file is generated +// This file is generated by TypeBuilder_h.template. // Copyright (c) 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -15,7 +15,7 @@ // and include Domain::API version from there. {% for name in domain.dependencies %} {% if protocol.is_imported_dependency(name) %} -#include {{format_include(config.protocol.package, name)}} +#include {{format_domain_include(config.protocol.package, name)}} {% endif %} {% endfor %} {% if protocol.is_exported_domain(domain.domain) %} @@ -46,7 +46,7 @@ using {{type.id}} = {{protocol.resolve_type(type).type}}; namespace {{type.id}}Enum { {% for literal in type.enum %} -{{config.protocol.export_macro}} extern const char* {{ literal | dash_to_camelcase}}; +{{config.protocol.export_macro}} extern const char {{ literal | dash_to_camelcase}}[]; {% endfor %} } // namespace {{type.id}}Enum {% endif %} @@ -100,10 +100,13 @@ public: {% endfor %} std::unique_ptr toValue() const; - String serialize() override { return toValue()->serialize(); } + String serializeToJSON() override { return toValue()->serializeToJSON(); } + std::vector serializeToBinary() override { return toValue()->serializeToBinary(); } + String toJSON() const { return toValue()->toJSONString(); } std::unique_ptr<{{type.id}}> clone() const; {% if protocol.is_exported(domain.domain, type.id) %} {{config.exported.string_out}} toJSONString() const override; + void writeBinary(std::vector* out) const override; {% endif %} template @@ -266,7 +269,8 @@ public: {% endfor %} void flush(); - void sendRawNotification(const String&); + void sendRawNotification(String); + void sendRawNotification(std::vector); private: FrontendChannel* m_frontendChannel; }; diff --git a/tools/inspector_protocol/testing/gmock/BUILD.gn b/tools/inspector_protocol/testing/gmock/BUILD.gn new file mode 100644 index 00000000000000..50aaef9628fee0 --- /dev/null +++ b/tools/inspector_protocol/testing/gmock/BUILD.gn @@ -0,0 +1,12 @@ +# 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 library simply delegates to the third party library; we're doing this +# to be compatible with the Chromium tree. +static_library("gmock") { + testonly = true + public_deps = [ + "//third_party/gtest:gmock", + ] +} diff --git a/tools/inspector_protocol/testing/gtest/BUILD.gn b/tools/inspector_protocol/testing/gtest/BUILD.gn new file mode 100644 index 00000000000000..7a105acd7582f9 --- /dev/null +++ b/tools/inspector_protocol/testing/gtest/BUILD.gn @@ -0,0 +1,25 @@ +# 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. + +# This provides the gtest / gmock main function which runs all gtest +# based tests in the binary. +static_library("gtest_main") { + testonly = true + sources = [ + "gtest_main.cc", + ] + deps = [ + "//third_party/gtest:gmock", + "//third_party/gtest:gtest", + ] +} + +# This library simply delegates to the third party library; we're doing this +# to be compatible with the Chromium tree. +static_library("gtest") { + testonly = true + public_deps = [ + "//third_party/gtest:gtest", + ] +} diff --git a/tools/inspector_protocol/testing/gtest/gtest_main.cc b/tools/inspector_protocol/testing/gtest/gtest_main.cc new file mode 100644 index 00000000000000..4f39cd4762dc1b --- /dev/null +++ b/tools/inspector_protocol/testing/gtest/gtest_main.cc @@ -0,0 +1,11 @@ +// 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 "gmock/gmock.h" +#include "gtest/gtest.h" + +GTEST_API_ int main(int argc, char** argv) { + testing::InitGoogleMock(&argc, argv); // Also inits GoogleTest. + return RUN_ALL_TESTS(); +} diff --git a/tools/inspector_protocol/testing/test.gni b/tools/inspector_protocol/testing/test.gni new file mode 100644 index 00000000000000..9fc4c2849664f8 --- /dev/null +++ b/tools/inspector_protocol/testing/test.gni @@ -0,0 +1,19 @@ +# 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. + +set_defaults("test") { + # These configs are duplicated from BUILDCONFIG.gn. + configs = [ + "//third_party/mini_chromium/mini_chromium/build:default", + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + "//third_party/mini_chromium/mini_chromium/build:executable", + ] +} + +template("test") { + executable(target_name) { + testonly = true + forward_variables_from(invoker, "*") + } +} diff --git a/tools/inspector_protocol/third_party/gtest/BUILD.gn b/tools/inspector_protocol/third_party/gtest/BUILD.gn new file mode 100644 index 00000000000000..d11439f7eeec1c --- /dev/null +++ b/tools/inspector_protocol/third_party/gtest/BUILD.gn @@ -0,0 +1,349 @@ +# 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. + +# This configuration was adapted from crashpad's gtest configuration. +# The purpose is to configure gtest and gmock so that it can be +# used to compile/run tests standalone, that is, outside the Chromium +# tree. This file also declares tests of the setup, which are useful +# when upgrading gtest / gmock to new versions (see the DEPS file). + +import("//testing/test.gni") + +config("gtest_private_config") { + visibility = [ ":*" ] + include_dirs = [ "gtest/googletest" ] + ldflags = [ "-pthread" ] + defines = [ "GUNIT_NO_GOOGLE3=1" ] +} + +config("gtest_public_config") { + include_dirs = [ "gtest/googletest/include" ] + ldflags = [ "-pthread" ] +} + +static_library("gtest") { + testonly = true + sources = [ + "gtest/googletest/include/gtest/gtest-death-test.h", + "gtest/googletest/include/gtest/gtest-message.h", + "gtest/googletest/include/gtest/gtest-param-test.h", + "gtest/googletest/include/gtest/gtest-printers.h", + "gtest/googletest/include/gtest/gtest-spi.h", + "gtest/googletest/include/gtest/gtest-test-part.h", + "gtest/googletest/include/gtest/gtest-typed-test.h", + "gtest/googletest/include/gtest/gtest.h", + "gtest/googletest/include/gtest/gtest_pred_impl.h", + "gtest/googletest/include/gtest/gtest_prod.h", + "gtest/googletest/include/gtest/internal/custom/gtest-port.h", + "gtest/googletest/include/gtest/internal/custom/gtest-printers.h", + "gtest/googletest/include/gtest/internal/custom/gtest.h", + "gtest/googletest/include/gtest/internal/gtest-death-test-internal.h", + "gtest/googletest/include/gtest/internal/gtest-filepath.h", + "gtest/googletest/include/gtest/internal/gtest-internal.h", + "gtest/googletest/include/gtest/internal/gtest-linked_ptr.h", + "gtest/googletest/include/gtest/internal/gtest-param-util-generated.h", + "gtest/googletest/include/gtest/internal/gtest-param-util.h", + "gtest/googletest/include/gtest/internal/gtest-port-arch.h", + "gtest/googletest/include/gtest/internal/gtest-port.h", + "gtest/googletest/include/gtest/internal/gtest-string.h", + "gtest/googletest/include/gtest/internal/gtest-tuple.h", + "gtest/googletest/include/gtest/internal/gtest-type-util.h", + "gtest/googletest/src/gtest-all.cc", + "gtest/googletest/src/gtest-death-test.cc", + "gtest/googletest/src/gtest-filepath.cc", + "gtest/googletest/src/gtest-internal-inl.h", + "gtest/googletest/src/gtest-port.cc", + "gtest/googletest/src/gtest-printers.cc", + "gtest/googletest/src/gtest-test-part.cc", + "gtest/googletest/src/gtest-typed-test.cc", + "gtest/googletest/src/gtest.cc", + ] + sources -= [ "gtest/googletest/src/gtest-all.cc" ] + public_configs = [ ":gtest_public_config" ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gtest_private_config" ] +} + +static_library("gtest_main") { + # Tests outside of this file should use ../../test:gtest_main instead. + visibility = [ ":*" ] + + testonly = true + sources = [ + "gtest/googletest/src/gtest_main.cc", + ] + deps = [ + ":gtest", + ] +} + +test("gtest_all_test") { + sources = [ + "gtest/googletest/test/gtest-death-test_test.cc", + "gtest/googletest/test/gtest-filepath_test.cc", + "gtest/googletest/test/gtest-linked_ptr_test.cc", + "gtest/googletest/test/gtest-message_test.cc", + "gtest/googletest/test/gtest-options_test.cc", + "gtest/googletest/test/gtest-port_test.cc", + "gtest/googletest/test/gtest-printers_test.cc", + "gtest/googletest/test/gtest-test-part_test.cc", + "gtest/googletest/test/gtest-typed-test2_test.cc", + "gtest/googletest/test/gtest-typed-test_test.cc", + "gtest/googletest/test/gtest-typed-test_test.h", + "gtest/googletest/test/gtest_main_unittest.cc", + "gtest/googletest/test/gtest_pred_impl_unittest.cc", + "gtest/googletest/test/gtest_prod_test.cc", + "gtest/googletest/test/gtest_unittest.cc", + "gtest/googletest/test/production.cc", + "gtest/googletest/test/production.h", + ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ":gtest_main", + ] +} + +test("gtest_environment_test") { + sources = [ + "gtest/googletest/test/gtest_environment_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +test("gtest_listener_test") { + sources = [ + "gtest/googletest/test/gtest-listener_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +test("gtest_no_test") { + sources = [ + "gtest/googletest/test/gtest_no_test_unittest.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +test("gtest_param_test") { + sources = [ + "gtest/googletest/test/gtest-param-test2_test.cc", + "gtest/googletest/test/gtest-param-test_test.cc", + "gtest/googletest/test/gtest-param-test_test.h", + ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +test("gtest_premature_exit_test") { + sources = [ + "gtest/googletest/test/gtest_premature_exit_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +test("gtest_repeat_test") { + sources = [ + "gtest/googletest/test/gtest_repeat_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +test("gtest_sole_header_test") { + sources = [ + "gtest/googletest/test/gtest_sole_header_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ":gtest_main", + ] +} + +test("gtest_stress_test") { + sources = [ + "gtest/googletest/test/gtest_stress_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +test("gtest_unittest_api_test") { + sources = [ + "gtest/googletest/test/gtest-unittest-api_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] +} + +group("gtest_all_tests") { + testonly = true + deps = [ + ":gtest_all_test", + ":gtest_environment_test", + ":gtest_listener_test", + ":gtest_no_test", + ":gtest_param_test", + ":gtest_premature_exit_test", + ":gtest_repeat_test", + ":gtest_sole_header_test", + ":gtest_stress_test", + ":gtest_unittest_api_test", + ] +} + +config("gmock_private_config") { + visibility = [ ":*" ] + include_dirs = [ "gtest/googlemock" ] + ldflags = [ "-pthread" ] +} + +config("gmock_public_config") { + include_dirs = [ "gtest/googlemock/include" ] + ldflags = [ "-pthread" ] +} + +static_library("gmock") { + testonly = true + sources = [ + "gtest/googlemock/include/gmock/gmock-actions.h", + "gtest/googlemock/include/gmock/gmock-cardinalities.h", + "gtest/googlemock/include/gmock/gmock-generated-actions.h", + "gtest/googlemock/include/gmock/gmock-generated-function-mockers.h", + "gtest/googlemock/include/gmock/gmock-generated-matchers.h", + "gtest/googlemock/include/gmock/gmock-generated-nice-strict.h", + "gtest/googlemock/include/gmock/gmock-matchers.h", + "gtest/googlemock/include/gmock/gmock-more-actions.h", + "gtest/googlemock/include/gmock/gmock-more-matchers.h", + "gtest/googlemock/include/gmock/gmock-spec-builders.h", + "gtest/googlemock/include/gmock/gmock.h", + "gtest/googlemock/include/gmock/internal/custom/gmock-generated-actions.h", + "gtest/googlemock/include/gmock/internal/custom/gmock-matchers.h", + "gtest/googlemock/include/gmock/internal/custom/gmock-port.h", + "gtest/googlemock/include/gmock/internal/gmock-generated-internal-utils.h", + "gtest/googlemock/include/gmock/internal/gmock-internal-utils.h", + "gtest/googlemock/include/gmock/internal/gmock-port.h", + "gtest/googlemock/src/gmock-all.cc", + "gtest/googlemock/src/gmock-cardinalities.cc", + "gtest/googlemock/src/gmock-internal-utils.cc", + "gtest/googlemock/src/gmock-matchers.cc", + "gtest/googlemock/src/gmock-spec-builders.cc", + "gtest/googlemock/src/gmock.cc", + ] + sources -= [ "gtest/googlemock/src/gmock-all.cc" ] + public_configs = [ ":gmock_public_config" ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gmock_private_config" ] + deps = [ + ":gtest", + ] +} + +static_library("gmock_main") { + # Tests outside of this file should use ../../test:gmock_main instead. + visibility = [ ":*" ] + testonly = true + sources = [ + "gtest/googlemock/src/gmock_main.cc", + ] + deps = [ + ":gmock", + ":gtest", + ] +} + +test("gmock_all_test") { + sources = [ + "gtest/googlemock/test/gmock-actions_test.cc", + "gtest/googlemock/test/gmock-cardinalities_test.cc", + "gtest/googlemock/test/gmock-generated-actions_test.cc", + "gtest/googlemock/test/gmock-generated-function-mockers_test.cc", + "gtest/googlemock/test/gmock-generated-internal-utils_test.cc", + "gtest/googlemock/test/gmock-generated-matchers_test.cc", + "gtest/googlemock/test/gmock-internal-utils_test.cc", + "gtest/googlemock/test/gmock-matchers_test.cc", + "gtest/googlemock/test/gmock-more-actions_test.cc", + "gtest/googlemock/test/gmock-nice-strict_test.cc", + "gtest/googlemock/test/gmock-port_test.cc", + "gtest/googlemock/test/gmock-spec-builders_test.cc", + "gtest/googlemock/test/gmock_test.cc", + ] + configs += [ + ":gmock_private_config", + ":gtest_private_config", + ] + deps = [ + ":gmock", + ":gmock_main", + ":gtest", + ] +} + +test("gmock_link_test") { + sources = [ + "gtest/googlemock/test/gmock_link2_test.cc", + "gtest/googlemock/test/gmock_link_test.cc", + "gtest/googlemock/test/gmock_link_test.h", + ] + configs += [ ":gmock_private_config" ] + deps = [ + ":gmock", + ":gmock_main", + ":gtest", + ] +} + +test("gmock_stress_test") { + sources = [ + "gtest/googlemock/test/gmock_stress_test.cc", + ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gmock_private_config" ] + deps = [ + ":gmock", + ":gtest", + ] +} + +group("gmock_all_tests") { + testonly = true + deps = [ + ":gmock_all_test", + ":gmock_link_test", + ":gmock_stress_test", + ] +}