Roll inspector_protocol library to inculude unified (de)serialization support

Note that changes in test expectation come from a more verbose
error diagnostics for expected errors around input parameter
validation.

Original change: https://chromium-review.googlesource.com/c/deps/inspector_protocol/+/2270757

Bug: chromium:1099809

Change-Id: I4fc2efc9c89d0af645dad937d719fa36e1d33489
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2277142
Reviewed-by: Yang Guo <yangguo@chromium.org>
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68657}
This commit is contained in:
Andrey Kosyakov 2020-07-01 08:01:18 -07:00 committed by Commit Bot
parent 4769e1586b
commit 3573d5e0fa
41 changed files with 1997 additions and 240 deletions

View File

@ -247,8 +247,7 @@ class InjectedScript::ProtocolPromiseHandler {
// we try to capture a fresh stack trace.
if (maybeMessage.ToLocal(&message)) {
v8::Local<v8::Value> exception = result;
protocol::detail::PtrMaybe<protocol::Runtime::ExceptionDetails>
exceptionDetails;
protocol::PtrMaybe<protocol::Runtime::ExceptionDetails> exceptionDetails;
response = scope.injectedScript()->createExceptionDetails(
message, exception, m_objectGroup, &exceptionDetails);
if (!response.IsSuccess()) {

View File

@ -246,7 +246,66 @@ String16 stackTraceIdToString(uintptr_t id) {
} // namespace v8_inspector
namespace v8_crdtp {
void SerializerTraits<v8_inspector::protocol::Binary>::Serialize(
using v8_inspector::String16;
using v8_inspector::protocol::Binary;
using v8_inspector::protocol::StringUtil;
// static
bool ProtocolTypeTraits<String16>::Deserialize(DeserializerState* state,
String16* value) {
auto* tokenizer = state->tokenizer();
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) {
const auto str = tokenizer->GetString8();
*value = StringUtil::fromUTF8(str.data(), str.size());
return true;
}
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) {
const auto str = tokenizer->GetString16WireRep();
*value = StringUtil::fromUTF16LE(
reinterpret_cast<const uint16_t*>(str.data()), str.size() / 2);
return true;
}
state->RegisterError(Error::BINDINGS_STRING_VALUE_EXPECTED);
return false;
}
// static
void ProtocolTypeTraits<String16>::Serialize(const String16& value,
std::vector<uint8_t>* bytes) {
cbor::EncodeFromUTF16(
span<uint16_t>(reinterpret_cast<const uint16_t*>(value.characters16()),
value.length()),
bytes);
}
// static
bool ProtocolTypeTraits<Binary>::Deserialize(DeserializerState* state,
Binary* value) {
auto* tokenizer = state->tokenizer();
if (tokenizer->TokenTag() == cbor::CBORTokenTag::BINARY) {
const span<uint8_t> bin = tokenizer->GetBinary();
*value = Binary::fromSpan(bin.data(), bin.size());
return true;
}
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) {
const auto str_span = tokenizer->GetString8();
auto str = StringUtil::fromUTF8(str_span.data(), str_span.size());
bool success = false;
*value = Binary::fromBase64(str, &success);
return success;
}
state->RegisterError(Error::BINDINGS_BINARY_VALUE_EXPECTED);
return false;
}
// static
void ProtocolTypeTraits<Binary>::Serialize(const Binary& value,
std::vector<uint8_t>* bytes) {
cbor::EncodeBinary(span<uint8_t>(value.data(), value.size()), bytes);
}
void SerializerTraits<Binary>::Serialize(
const v8_inspector::protocol::Binary& binary, std::vector<uint8_t>* out) {
cbor::EncodeBinary(span<uint8_t>(binary.data(), binary.size()), out);
}

View File

@ -6,14 +6,15 @@
#define V8_INSPECTOR_STRING_UTIL_H_
#include <stdint.h>
#include <memory>
#include "../../third_party/inspector_protocol/crdtp/protocol_core.h"
#include "include/v8-inspector.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/inspector/string-16.h"
#include "include/v8-inspector.h"
namespace v8_inspector {
namespace protocol {
@ -86,11 +87,42 @@ String16 stackTraceIdToString(uintptr_t id);
// See third_party/inspector_protocol/crdtp/serializer_traits.h.
namespace v8_crdtp {
template <>
struct ProtocolTypeTraits<v8_inspector::String16> {
static bool Deserialize(DeserializerState* state,
v8_inspector::String16* value);
static void Serialize(const v8_inspector::String16& value,
std::vector<uint8_t>* bytes);
};
template <>
struct ProtocolTypeTraits<v8_inspector::protocol::Binary> {
static bool Deserialize(DeserializerState* state,
v8_inspector::protocol::Binary* value);
static void Serialize(const v8_inspector::protocol::Binary& value,
std::vector<uint8_t>* bytes);
};
template <>
struct SerializerTraits<v8_inspector::protocol::Binary> {
static void Serialize(const v8_inspector::protocol::Binary& binary,
std::vector<uint8_t>* out);
};
namespace detail {
template <>
struct MaybeTypedef<v8_inspector::String16> {
typedef ValueMaybe<v8_inspector::String16> type;
};
template <>
struct MaybeTypedef<v8_inspector::protocol::Binary> {
typedef ValueMaybe<v8_inspector::protocol::Binary> type;
};
} // namespace detail
} // namespace v8_crdtp
#endif // V8_INSPECTOR_STRING_UTIL_H_

View File

@ -1552,10 +1552,9 @@ void V8DebuggerAgentImpl::didParseSource(
String16 scriptId = script->scriptId();
String16 scriptURL = script->sourceURL();
String16 scriptLanguage = getScriptLanguage(*script);
Maybe<int> codeOffset =
script->getLanguage() == V8DebuggerScript::Language::JavaScript
? Maybe<int>()
: script->codeOffset();
Maybe<int> codeOffset;
if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly)
codeOffset = script->codeOffset();
std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols =
getDebugSymbols(*script);
@ -1727,10 +1726,11 @@ void V8DebuggerAgentImpl::didPause(
WrapMode::kNoPreview, &obj);
std::unique_ptr<protocol::DictionaryValue> breakAuxData;
if (obj) {
breakAuxData = obj->toValue();
std::vector<uint8_t> serialized;
obj->AppendSerialized(&serialized);
breakAuxData = protocol::DictionaryValue::cast(
protocol::Value::parseBinary(serialized.data(), serialized.size()));
breakAuxData->setBoolean("uncaught", isUncaught);
} else {
breakAuxData = nullptr;
}
hitReasons.push_back(
std::make_pair(breakReason, std::move(breakAuxData)));

View File

@ -108,14 +108,14 @@ Running test: testConsoleLog
functionName : eval
lineNumber : 0
scriptId : <scriptId>
url :
url :
}
[1] : {
columnNumber : 0
functionName :
functionName :
lineNumber : 0
scriptId : <scriptId>
url :
url :
}
]
}
@ -232,7 +232,7 @@ ReleaseObject with invalid params.
{
error : {
code : -32602
data : objectId: string value expected
data : Failed to deserialize params.objectId - BINDINGS: mandatory field missing at <some position>
message : Invalid parameters
}
id : <messageId>
@ -299,7 +299,7 @@ ReleaseObjectGroup with invalid params
{
error : {
code : -32602
data : objectGroup: string value expected
data : Failed to deserialize params.objectGroup - BINDINGS: mandatory field missing at <some position>
message : Invalid parameters
}
id : <messageId>
@ -327,7 +327,7 @@ Running test: testCallFrameIdTypeError
{
error : {
code : -32602
data : callFrameId: string value expected
data : Failed to deserialize params.callFrameId - BINDINGS: string value expected at <some position>
message : Invalid parameters
}
id : <messageId>
@ -347,7 +347,7 @@ Running test: testNullExpression
{
error : {
code : -32602
data : expression: string value expected
data : Failed to deserialize params.expression - BINDINGS: string value expected at <some position>
message : Invalid parameters
}
id : <messageId>

View File

@ -66,7 +66,7 @@ const {Protocol} = InspectorTest.start(
async function testReleaseObjectInvalid() {
const releaseObjectResult = await Protocol.Runtime.releaseObject({});
InspectorTest.log('ReleaseObject with invalid params.');
InspectorTest.logMessage(releaseObjectResult);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectResult));
},
async function testObjectGroups() {
await Protocol.Runtime.evaluate({ expression: 'var a = {x:3};', callFrameId });
@ -89,7 +89,7 @@ const {Protocol} = InspectorTest.start(
async function testReleaseObjectGroupInvalid() {
const releaseObjectGroupResult = await Protocol.Runtime.releaseObjectGroup({});
InspectorTest.log('ReleaseObjectGroup with invalid params');
InspectorTest.logMessage(releaseObjectGroupResult);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectGroupResult));
},
async function testEvaluateSyntaxError() {
const result = await Protocol.Debugger.evaluateOnCallFrame({ expression: `[]]`, callFrameId });
@ -101,7 +101,7 @@ const {Protocol} = InspectorTest.start(
},
async function testCallFrameIdTypeError() {
const result = await Protocol.Debugger.evaluateOnCallFrame({ expression: `console.log(42)`, callFrameId: {} });
InspectorTest.logMessage(result);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(result));
},
async function testCallFrameIdInvalidInput() {
InspectorTest.log('Testing evaluateOnCallFrame with non-existent callFrameId');
@ -115,7 +115,7 @@ const {Protocol} = InspectorTest.start(
async function evalAndLog(expression, callFrameId, returnByValue) {
const result = await Protocol.Debugger.evaluateOnCallFrame({ expression, callFrameId, returnByValue });
InspectorTest.logMessage(result);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(result));
}
// Helper function that calls a function on all objects with ids in objectIds, then returns

View File

@ -3,5 +3,5 @@ setBreakpointByUrl error: undefined
setBreakpoint error: {
"code": -32602,
"message": "Invalid parameters",
"data": "location: object expected"
"data": "Failed to deserialize params.location - BINDINGS: mandatory field missing at <some position>"
}

View File

@ -8,12 +8,14 @@ Protocol.Debugger.setBreakpointByUrl({ url: "http://example.com", lineNumber: 10
function didSetBreakpointByUrlBeforeEnable(message)
{
InspectorTest.log("setBreakpointByUrl error: " + JSON.stringify(message.error, null, 2));
InspectorTest.log("setBreakpointByUrl error: " + JSON.stringify(
InspectorTest.trimErrorMessage(message).error, null, 2));
Protocol.Debugger.setBreakpoint().then(didSetBreakpointBeforeEnable);
}
function didSetBreakpointBeforeEnable(message)
{
InspectorTest.log("setBreakpoint error: " + JSON.stringify(message.error, null, 2));
InspectorTest.log("setBreakpoint error: " + JSON.stringify(
InspectorTest.trimErrorMessage(message).error, null, 2));
InspectorTest.completeTest();
}

View File

@ -218,7 +218,7 @@ setVariableValue with invalid scopeNumber
{
error : {
code : -32602
data : scopeNumber: integer value expected
data : Failed to deserialize params.scopeNumber - BINDINGS: int32 value expected at <some position>
message : Invalid parameters
}
id : <messageId>
@ -253,8 +253,8 @@ setVariableValue with invalid objectId
{
error : {
code : -32602
data : newValue.objectId: string value expected
data : Failed to deserialize params.newValue.objectId - BINDINGS: string value expected at <some position>
message : Invalid parameters
}
id : <messageId>
}
}

View File

@ -66,24 +66,24 @@ const { contextGroup, Protocol } = InspectorTest.start(
async function testInvalidFrame() {
let result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'num', newValue: { unserializableValue: 'NaN' }, callFrameId: 'fakeCallFrame' });
InspectorTest.log('setVariableValue with invalid callFrameId');
InspectorTest.logMessage(result);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(result));
result = await Protocol.Debugger.setVariableValue({ scopeNumber: 'invalidScopeType', variableName: 'num', newValue: { unserializableValue: 'NaN' }, callFrameId });
InspectorTest.log('setVariableValue with invalid scopeNumber')
InspectorTest.logMessage(result);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(result));
result = await Protocol.Debugger.setVariableValue({ scopeNumber: 1000, variableName: 'num', newValue: { unserializableValue: 'NaN' }, callFrameId });
InspectorTest.log('setVariableValue with invalid scopeNumber');
InspectorTest.logMessage(result);
result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'FakeObjectName', newValue: { unserializableValue: 'NaN' }, callFrameId });
InspectorTest.log('setVariableValue with invalid variableName');
InspectorTest.logMessage(result);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(result));
},
async function testNewValueErrors() {
let result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'num', newValue: { unserializableValue: 'not unserializable value' }, callFrameId });
InspectorTest.log('setVariableValue with invalid unserializableValue');
InspectorTest.logMessage(result);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(result));
result = await Protocol.Debugger.setVariableValue({ scopeNumber: 0, variableName: 'num', newValue: { objectId: 2000 }, callFrameId });
InspectorTest.log('setVariableValue with invalid objectId');
InspectorTest.logMessage(result);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(result));
}
]);

View File

@ -129,6 +129,14 @@ InspectorTest.decodeBase64 = function(base64) {
return bytes;
}
InspectorTest.trimErrorMessage = function(message) {
if (!message.error || !message.error.data)
return message;
message.error.data = message.error.data.replace(/at position \d+/,
'at <some position>');
return message;
}
InspectorTest.ContextGroup = class {
constructor() {
this.id = utils.createContextGroup();

View File

@ -71,7 +71,7 @@ ReleaseObject with invalid params.
{
error : {
code : -32602
data : objectId: string value expected
data : Failed to deserialize params.objectId - BINDINGS: mandatory field missing at <some position>
message : Invalid parameters
}
id : <messageId>
@ -150,8 +150,8 @@ ReleaseObjectGroup with invalid params
{
error : {
code : -32602
data : objectGroup: string value expected
data : Failed to deserialize params.objectGroup - BINDINGS: mandatory field missing at <some position>
message : Invalid parameters
}
id : <messageId>
}
}

View File

@ -31,7 +31,7 @@ const {Protocol} = InspectorTest.start(
async function testReleaseObjectInvalid() {
const releaseObjectResult = await Protocol.Runtime.releaseObject({});
InspectorTest.log('ReleaseObject with invalid params.');
InspectorTest.logMessage(releaseObjectResult);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectResult));
},
async function testObjectGroups() {
await logAndEvaluate('var a = {x:3};');
@ -58,7 +58,7 @@ const {Protocol} = InspectorTest.start(
async function testReleaseObjectGroupInvalid() {
const releaseObjectGroupResult = await Protocol.Runtime.releaseObjectGroup({});
InspectorTest.log('ReleaseObjectGroup with invalid params');
InspectorTest.logMessage(releaseObjectGroupResult);
InspectorTest.logMessage(InspectorTest.trimErrorMessage(releaseObjectGroupResult));
}
]);

View File

@ -20,10 +20,12 @@ v8_source_set("crdtp") {
"crdtp/error_support.h",
"crdtp/export.h",
"crdtp/find_by_first.h",
"crdtp/glue.h",
"crdtp/json.cc",
"crdtp/json.h",
"crdtp/maybe.h",
"crdtp/parser_handler.h",
"crdtp/protocol_core.cc",
"crdtp/protocol_core.h",
"crdtp/serializable.cc",
"crdtp/serializable.h",
"crdtp/serializer_traits.h",

View File

@ -2,7 +2,7 @@ Name: inspector protocol
Short Name: inspector_protocol
URL: https://chromium.googlesource.com/deps/inspector_protocol/
Version: 0
Revision: b7cda08cd6e522df2159413ba5f29d2a953cc1c4
Revision: 22455bf37cd7a1913918e626a1997cbe77f32d1a
License: BSD
License File: LICENSE
Security Critical: no

View File

@ -371,7 +371,6 @@ class Protocol(object):
for rule in config.imported.options]
self.patch_full_qualified_refs()
self.create_notification_types()
self.create_type_definitions()
self.generate_used_types()
@ -434,8 +433,6 @@ class Protocol(object):
for event in domain["events"]:
if self.generate_event(domain_name, event["name"]):
all_refs |= self.all_references(event)
all_refs.add('%s.%sNotification' % (domain_name,
to_title_case(event["name"])))
dependencies = self.generate_type_dependencies()
queue = set(all_refs)
@ -457,20 +454,6 @@ class Protocol(object):
dependencies[domain_name + "." + type["id"]] = related_types
return dependencies
def create_notification_types(self):
for domain in self.json_api["domains"]:
if "events" in domain:
for event in domain["events"]:
event_type = dict()
event_type["description"] = "Wrapper for notification params"
event_type["type"] = "object"
event_type["id"] = to_title_case(event["name"]) + "Notification"
if "parameters" in event:
event_type["properties"] = copy.deepcopy(event["parameters"])
if "types" not in domain:
domain["types"] = list()
domain["types"].append(event_type)
def create_type_definitions(self):
imported_namespace = ""
if self.config.imported:
@ -664,6 +647,7 @@ def main():
"Protocol_cpp.template",
"Values_cpp.template",
"Object_cpp.template",
"ValueConversions_cpp.template",
]
forward_h_templates = [

View File

@ -9,6 +9,7 @@
#include "error_support.h"
#include "find_by_first.h"
#include "frontend_channel.h"
#include "protocol_core.h"
namespace v8_crdtp {
// =============================================================================
@ -321,6 +322,18 @@ std::unique_ptr<Serializable> CreateErrorResponse(
return protocol_error;
}
std::unique_ptr<Serializable> CreateErrorResponse(
int call_id,
DispatchResponse dispatch_response,
const DeserializerState& state) {
auto protocol_error =
std::make_unique<ProtocolError>(std::move(dispatch_response));
protocol_error->SetCallId(call_id);
// TODO(caseq): should we plumb the call name here?
protocol_error->SetData(state.ErrorMessage(MakeSpan("params")));
return protocol_error;
}
std::unique_ptr<Serializable> CreateErrorNotification(
DispatchResponse dispatch_response) {
return std::make_unique<ProtocolError>(std::move(dispatch_response));
@ -475,6 +488,21 @@ bool DomainDispatcher::MaybeReportInvalidParams(
return true;
}
bool DomainDispatcher::MaybeReportInvalidParams(
const Dispatchable& dispatchable,
const DeserializerState& state) {
if (state.status().ok())
return false;
if (frontend_channel_) {
frontend_channel_->SendProtocolResponse(
dispatchable.CallId(),
CreateErrorResponse(
dispatchable.CallId(),
DispatchResponse::InvalidParams("Invalid parameters"), state));
}
return true;
}
void DomainDispatcher::clearFrontend() {
frontend_channel_ = nullptr;
for (auto& weak : weak_ptrs_)

View File

@ -16,8 +16,9 @@
#include "status.h"
namespace v8_crdtp {
class FrontendChannel;
class DeserializerState;
class ErrorSupport;
class FrontendChannel;
namespace cbor {
class CBORTokenizer;
} // namespace cbor
@ -234,6 +235,8 @@ class DomainDispatcher {
// optimized for code size of the callee.
bool MaybeReportInvalidParams(const Dispatchable& dispatchable,
const ErrorSupport& errors);
bool MaybeReportInvalidParams(const Dispatchable& dispatchable,
const DeserializerState& state);
FrontendChannel* channel() { return frontend_channel_; }

View File

@ -0,0 +1,104 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_CRDTP_MAYBE_H_
#define V8_CRDTP_MAYBE_H_
#include <cassert>
#include <memory>
namespace v8_crdtp {
// =============================================================================
// detail::PtrMaybe, detail::ValueMaybe, templates for optional
// pointers / values which are used in ../lib/Forward_h.template.
// =============================================================================
namespace detail {
template <typename T>
class PtrMaybe {
public:
PtrMaybe() = default;
PtrMaybe(std::unique_ptr<T> value) : value_(std::move(value)) {}
PtrMaybe(PtrMaybe&& other) noexcept : value_(std::move(other.value_)) {}
void operator=(std::unique_ptr<T> value) { value_ = std::move(value); }
T* fromJust() const {
assert(value_);
return value_.get();
}
T* fromMaybe(T* default_value) const {
return value_ ? value_.get() : default_value;
}
bool isJust() const { return value_ != nullptr; }
std::unique_ptr<T> takeJust() {
assert(value_);
return std::move(value_);
}
private:
std::unique_ptr<T> value_;
};
template <typename T>
class ValueMaybe {
public:
ValueMaybe() : is_just_(false), value_() {}
ValueMaybe(T value) : is_just_(true), value_(std::move(value)) {}
ValueMaybe(ValueMaybe&& other) noexcept
: is_just_(other.is_just_), value_(std::move(other.value_)) {}
void operator=(T value) {
value_ = value;
is_just_ = true;
}
const T& fromJust() const {
assert(is_just_);
return value_;
}
const T& fromMaybe(const T& default_value) const {
return is_just_ ? value_ : default_value;
}
bool isJust() const { return is_just_; }
T takeJust() {
assert(is_just_);
is_just_ = false;
return std::move(value_);
}
private:
bool is_just_;
T value_;
};
template <typename T>
struct MaybeTypedef {
typedef PtrMaybe<T> type;
};
template <>
struct MaybeTypedef<bool> {
typedef ValueMaybe<bool> type;
};
template <>
struct MaybeTypedef<int> {
typedef ValueMaybe<int> type;
};
template <>
struct MaybeTypedef<double> {
typedef ValueMaybe<double> type;
};
template <>
struct MaybeTypedef<std::string> {
typedef ValueMaybe<std::string> type;
};
} // namespace detail
template <typename T>
using Maybe = typename detail::MaybeTypedef<T>::type;
} // namespace v8_crdtp
#endif // V8_CRDTP_MAYBE_H_

View File

@ -0,0 +1,44 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "maybe.h"
#include <string>
#include <vector>
#include "test_platform.h"
namespace v8_crdtp {
// =============================================================================
// detail::PtrMaybe, detail::ValueMaybe, templates for optional
// pointers / values which are used in ../lib/Forward_h.template.
// =============================================================================
TEST(PtrMaybeTest, SmokeTest) {
detail::PtrMaybe<std::vector<uint32_t>> example;
EXPECT_FALSE(example.isJust());
EXPECT_TRUE(nullptr == example.fromMaybe(nullptr));
std::unique_ptr<std::vector<uint32_t>> v(new std::vector<uint32_t>);
v->push_back(42);
v->push_back(21);
example = std::move(v);
EXPECT_TRUE(example.isJust());
EXPECT_THAT(*example.fromJust(), testing::ElementsAre(42, 21));
std::unique_ptr<std::vector<uint32_t>> out = example.takeJust();
EXPECT_FALSE(example.isJust());
EXPECT_THAT(*out, testing::ElementsAre(42, 21));
}
TEST(PtrValueTest, SmokeTest) {
detail::ValueMaybe<int32_t> example;
EXPECT_FALSE(example.isJust());
EXPECT_EQ(-1, example.fromMaybe(-1));
example = 42;
EXPECT_TRUE(example.isJust());
EXPECT_EQ(42, example.fromJust());
int32_t out = example.takeJust();
EXPECT_EQ(out, 42);
}
} // namespace v8_crdtp

View File

@ -0,0 +1,288 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "protocol_core.h"
#include <cassert>
#include <string>
namespace v8_crdtp {
DeserializerState::DeserializerState(std::vector<uint8_t> bytes)
: storage_(new std::vector<uint8_t>(std::move(bytes))),
tokenizer_(span<uint8_t>(storage_->data(), storage_->size())) {}
DeserializerState::DeserializerState(Storage storage, span<uint8_t> span)
: storage_(std::move(storage)), tokenizer_(span) {}
void DeserializerState::RegisterError(Error error) {
assert(Error::OK != error);
if (tokenizer_.Status().ok())
status_ = Status{error, tokenizer_.Status().pos};
}
void DeserializerState::RegisterFieldPath(span<char> name) {
field_path_.push_back(name);
}
std::string DeserializerState::ErrorMessage(span<char> message_name) const {
std::string msg = "Failed to deserialize ";
msg.append(message_name.begin(), message_name.end());
for (int field = static_cast<int>(field_path_.size()) - 1; field >= 0;
--field) {
msg.append(".");
msg.append(field_path_[field].begin(), field_path_[field].end());
}
Status s = status();
if (!s.ok())
msg += " - " + s.ToASCIIString();
return msg;
}
Status DeserializerState::status() const {
if (!tokenizer_.Status().ok())
return tokenizer_.Status();
return status_;
}
namespace {
constexpr int32_t GetMandatoryFieldMask(
const DeserializerDescriptor::Field* fields,
size_t count) {
int32_t mask = 0;
for (size_t i = 0; i < count; ++i) {
if (!fields[i].is_optional)
mask |= (1 << i);
}
return mask;
}
} // namespace
DeserializerDescriptor::DeserializerDescriptor(const Field* fields,
size_t field_count)
: fields_(fields),
field_count_(field_count),
mandatory_field_mask_(GetMandatoryFieldMask(fields, field_count)) {}
bool DeserializerDescriptor::Deserialize(DeserializerState* state,
void* obj) const {
auto* tokenizer = state->tokenizer();
// As a special compatibility quirk, allow empty objects if
// no mandatory fields are required.
if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE &&
!mandatory_field_mask_) {
return true;
}
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
if (tokenizer->TokenTag() != cbor::CBORTokenTag::MAP_START) {
state->RegisterError(Error::CBOR_MAP_START_EXPECTED);
return false;
}
tokenizer->Next();
int32_t seen_mandatory_fields = 0;
for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP; tokenizer->Next()) {
if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) {
state->RegisterError(Error::CBOR_INVALID_MAP_KEY);
return false;
}
span<uint8_t> u_key = tokenizer->GetString8();
span<char> key(reinterpret_cast<const char*>(u_key.data()), u_key.size());
tokenizer->Next();
if (!DeserializeField(state, key, &seen_mandatory_fields, obj))
return false;
}
// Only compute mandatory fields once per type.
int32_t missing_fields = seen_mandatory_fields ^ mandatory_field_mask_;
if (missing_fields) {
int32_t idx = 0;
while ((missing_fields & 1) == 0) {
missing_fields >>= 1;
++idx;
}
state->RegisterError(Error::BINDINGS_MANDATORY_FIELD_MISSING);
state->RegisterFieldPath(fields_[idx].name);
return false;
}
return true;
}
bool DeserializerDescriptor::DeserializeField(DeserializerState* state,
span<char> name,
int* seen_mandatory_fields,
void* obj) const {
// TODO(caseq): consider checking if the sought field is the one
// after the last deserialized.
const auto* begin = fields_;
const auto* end = fields_ + field_count_;
auto entry = std::lower_bound(
begin, end, name, [](const Field& field_desc, span<char> field_name) {
return SpanLessThan(field_desc.name, field_name);
});
// Unknown field is not an error -- we may be working against an
// implementation of a later version of the protocol.
// TODO(caseq): support unknown arrays and maps not enclosed by an envelope.
if (entry == end || !SpanEquals(entry->name, name))
return true;
if (!entry->deserializer(state, obj)) {
state->RegisterFieldPath(name);
return false;
}
if (!entry->is_optional)
*seen_mandatory_fields |= 1 << (entry - begin);
return true;
}
bool ProtocolTypeTraits<bool>::Deserialize(DeserializerState* state,
bool* value) {
const auto tag = state->tokenizer()->TokenTag();
if (tag == cbor::CBORTokenTag::TRUE_VALUE) {
*value = true;
return true;
}
if (tag == cbor::CBORTokenTag::FALSE_VALUE) {
*value = false;
return true;
}
state->RegisterError(Error::BINDINGS_BOOL_VALUE_EXPECTED);
return false;
}
void ProtocolTypeTraits<bool>::Serialize(bool value,
std::vector<uint8_t>* bytes) {
bytes->push_back(value ? cbor::EncodeTrue() : cbor::EncodeFalse());
}
bool ProtocolTypeTraits<int32_t>::Deserialize(DeserializerState* state,
int32_t* value) {
if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::INT32) {
state->RegisterError(Error::BINDINGS_INT32_VALUE_EXPECTED);
return false;
}
*value = state->tokenizer()->GetInt32();
return true;
}
void ProtocolTypeTraits<int32_t>::Serialize(int32_t value,
std::vector<uint8_t>* bytes) {
cbor::EncodeInt32(value, bytes);
}
ContainerSerializer::ContainerSerializer(std::vector<uint8_t>* bytes,
uint8_t tag)
: bytes_(bytes) {
envelope_.EncodeStart(bytes_);
bytes_->push_back(tag);
}
void ContainerSerializer::EncodeStop() {
bytes_->push_back(cbor::EncodeStop());
envelope_.EncodeStop(bytes_);
}
ObjectSerializer::ObjectSerializer()
: serializer_(&owned_bytes_, cbor::EncodeIndefiniteLengthMapStart()) {}
ObjectSerializer::~ObjectSerializer() = default;
std::unique_ptr<Serializable> ObjectSerializer::Finish() {
serializer_.EncodeStop();
return Serializable::From(std::move(owned_bytes_));
}
bool ProtocolTypeTraits<double>::Deserialize(DeserializerState* state,
double* value) {
// Double values that round-trip through JSON may end up getting represented
// as an int32 (SIGNED, UNSIGNED) on the wire in CBOR. Therefore, we also
// accept an INT32 here.
if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::INT32) {
*value = state->tokenizer()->GetInt32();
return true;
}
if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::DOUBLE) {
state->RegisterError(Error::BINDINGS_DOUBLE_VALUE_EXPECTED);
return false;
}
*value = state->tokenizer()->GetDouble();
return true;
}
void ProtocolTypeTraits<double>::Serialize(double value,
std::vector<uint8_t>* bytes) {
cbor::EncodeDouble(value, bytes);
}
class IncomingDeferredMessage : public DeferredMessage {
public:
// Creates the state from the part of another message.
// Note storage is opaque and is mostly to retain ownership.
// It may be null in case caller owns the memory and will dispose
// of the message synchronously.
IncomingDeferredMessage(DeserializerState::Storage storage,
span<uint8_t> span)
: storage_(storage), span_(span) {}
private:
DeserializerState MakeDeserializer() const override {
return DeserializerState(storage_, span_);
}
void AppendSerialized(std::vector<uint8_t>* out) const override {
out->insert(out->end(), span_.begin(), span_.end());
}
DeserializerState::Storage storage_;
span<uint8_t> span_;
};
class OutgoingDeferredMessage : public DeferredMessage {
public:
OutgoingDeferredMessage() = default;
explicit OutgoingDeferredMessage(std::unique_ptr<Serializable> serializable)
: serializable_(std::move(serializable)) {
assert(!!serializable_);
}
private:
DeserializerState MakeDeserializer() const override {
return DeserializerState(serializable_->Serialize());
}
void AppendSerialized(std::vector<uint8_t>* out) const override {
serializable_->AppendSerialized(out);
}
std::unique_ptr<Serializable> serializable_;
};
// static
std::unique_ptr<DeferredMessage> DeferredMessage::FromSerializable(
std::unique_ptr<Serializable> serializeable) {
return std::make_unique<OutgoingDeferredMessage>(std::move(serializeable));
}
// static
std::unique_ptr<DeferredMessage> DeferredMessage::FromSpan(
span<uint8_t> bytes) {
return std::make_unique<IncomingDeferredMessage>(nullptr, bytes);
}
bool ProtocolTypeTraits<std::unique_ptr<DeferredMessage>>::Deserialize(
DeserializerState* state,
std::unique_ptr<DeferredMessage>* value) {
if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::ENVELOPE) {
state->RegisterError(Error::CBOR_INVALID_ENVELOPE);
return false;
}
*value = std::make_unique<IncomingDeferredMessage>(
state->storage(), state->tokenizer()->GetEnvelope());
return true;
}
void ProtocolTypeTraits<std::unique_ptr<DeferredMessage>>::Serialize(
const std::unique_ptr<DeferredMessage>& value,
std::vector<uint8_t>* bytes) {
value->AppendSerialized(bytes);
}
} // namespace v8_crdtp

View File

@ -0,0 +1,406 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_CRDTP_PROTOCOL_CORE_H_
#define V8_CRDTP_PROTOCOL_CORE_H_
#include <sys/types.h>
#include <memory>
#include <string>
#include <vector>
#include "cbor.h"
#include "maybe.h"
#include "serializable.h"
#include "span.h"
#include "status.h"
namespace v8_crdtp {
class DeserializerState {
public:
using Storage = std::shared_ptr<const std::vector<uint8_t>>;
// Creates a state from the raw bytes received from the peer.
explicit DeserializerState(std::vector<uint8_t> bytes);
// Creates the state from the part of another message.
DeserializerState(Storage storage, span<uint8_t> span);
DeserializerState(const DeserializerState& r) = delete;
DeserializerState(DeserializerState&& r) = default;
// Registers |error|, unless the tokenizer's status is already an error.
void RegisterError(Error error);
// Registers |name| as a segment of the field path.
void RegisterFieldPath(span<char> name);
// Produces an error message considering |tokenizer.Status()|,
// status_, and field_path_.
std::string ErrorMessage(span<char> message_name) const;
Status status() const;
const Storage& storage() const { return storage_; }
cbor::CBORTokenizer* tokenizer() { return &tokenizer_; }
private:
const Storage storage_;
cbor::CBORTokenizer tokenizer_;
Status status_;
std::vector<span<char>> field_path_;
};
template <typename T, typename = void>
struct ProtocolTypeTraits {};
template <>
struct ProtocolTypeTraits<bool> {
static bool Deserialize(DeserializerState* state, bool* value);
static void Serialize(bool value, std::vector<uint8_t>* bytes);
};
template <>
struct ProtocolTypeTraits<int32_t> {
static bool Deserialize(DeserializerState* state, int* value);
static void Serialize(int value, std::vector<uint8_t>* bytes);
};
template <>
struct ProtocolTypeTraits<double> {
static bool Deserialize(DeserializerState* state, double* value);
static void Serialize(double value, std::vector<uint8_t>* bytes);
};
class ContainerSerializer {
public:
ContainerSerializer(std::vector<uint8_t>* bytes, uint8_t tag);
template <typename T>
void AddField(span<char> field_name, const T& value) {
cbor::EncodeString8(
span<uint8_t>(reinterpret_cast<const uint8_t*>(field_name.data()),
field_name.size()),
bytes_);
ProtocolTypeTraits<T>::Serialize(value, bytes_);
}
template <typename T>
void AddField(span<char> field_name, const detail::ValueMaybe<T>& value) {
if (!value.isJust())
return;
AddField(field_name, value.fromJust());
}
template <typename T>
void AddField(span<char> field_name, const detail::PtrMaybe<T>& value) {
if (!value.isJust())
return;
AddField(field_name, *value.fromJust());
}
void EncodeStop();
private:
std::vector<uint8_t>* const bytes_;
cbor::EnvelopeEncoder envelope_;
};
class ObjectSerializer {
public:
ObjectSerializer();
~ObjectSerializer();
template <typename T>
void AddField(span<char> name, const T& field) {
serializer_.AddField(name, field);
}
std::unique_ptr<Serializable> Finish();
private:
std::vector<uint8_t> owned_bytes_;
ContainerSerializer serializer_;
};
class DeserializerDescriptor {
public:
struct Field {
span<char> name;
bool is_optional;
bool (*deserializer)(DeserializerState* state, void* obj);
};
DeserializerDescriptor(const Field* fields, size_t field_count);
bool Deserialize(DeserializerState* state, void* obj) const;
private:
bool DeserializeField(DeserializerState* state,
span<char> name,
int* seen_mandatory_fields,
void* obj) const;
const Field* const fields_;
const size_t field_count_;
const int mandatory_field_mask_;
};
template <typename T>
struct ProtocolTypeTraits<std::vector<T>> {
static bool Deserialize(DeserializerState* state, std::vector<T>* value) {
auto* tokenizer = state->tokenizer();
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
if (tokenizer->TokenTag() != cbor::CBORTokenTag::ARRAY_START) {
state->RegisterError(Error::CBOR_ARRAY_START_EXPECTED);
return false;
}
assert(value->empty());
tokenizer->Next();
for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP;
tokenizer->Next()) {
value->emplace_back();
if (!ProtocolTypeTraits<T>::Deserialize(state, &value->back()))
return false;
}
return true;
}
static void Serialize(const std::vector<T>& value,
std::vector<uint8_t>* bytes) {
ContainerSerializer container_serializer(
bytes, cbor::EncodeIndefiniteLengthArrayStart());
for (const auto& item : value)
ProtocolTypeTraits<T>::Serialize(item, bytes);
container_serializer.EncodeStop();
}
};
template <typename T>
struct ProtocolTypeTraits<std::unique_ptr<std::vector<T>>> {
static bool Deserialize(DeserializerState* state,
std::unique_ptr<std::vector<T>>* value) {
auto res = std::make_unique<std::vector<T>>();
if (!ProtocolTypeTraits<std::vector<T>>::Deserialize(state, res.get()))
return false;
*value = std::move(res);
return true;
}
static void Serialize(const std::unique_ptr<std::vector<T>>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<std::vector<T>>::Serialize(*value, bytes);
}
};
class DeferredMessage : public Serializable {
public:
static std::unique_ptr<DeferredMessage> FromSerializable(
std::unique_ptr<Serializable> serializeable);
static std::unique_ptr<DeferredMessage> FromSpan(span<uint8_t> bytes);
~DeferredMessage() override = default;
virtual DeserializerState MakeDeserializer() const = 0;
protected:
DeferredMessage() = default;
};
template <>
struct ProtocolTypeTraits<std::unique_ptr<DeferredMessage>> {
static bool Deserialize(DeserializerState* state,
std::unique_ptr<DeferredMessage>* value);
static void Serialize(const std::unique_ptr<DeferredMessage>& value,
std::vector<uint8_t>* bytes);
};
template <typename T>
struct ProtocolTypeTraits<detail::ValueMaybe<T>> {
static bool Deserialize(DeserializerState* state,
detail::ValueMaybe<T>* value) {
T res;
if (!ProtocolTypeTraits<T>::Deserialize(state, &res))
return false;
*value = std::move(res);
return true;
}
static void Serialize(const detail::ValueMaybe<T>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<T>::Serialize(value.fromJust(), bytes);
}
};
template <typename T>
struct ProtocolTypeTraits<detail::PtrMaybe<T>> {
static bool Deserialize(DeserializerState* state,
detail::PtrMaybe<T>* value) {
std::unique_ptr<T> res;
if (!ProtocolTypeTraits<std::unique_ptr<T>>::Deserialize(state, &res))
return false;
*value = std::move(res);
return true;
}
static void Serialize(const detail::PtrMaybe<T>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<T>::Serialize(*value.fromJust(), bytes);
}
};
template <typename T>
class DeserializableProtocolObject {
public:
static StatusOr<std::unique_ptr<T>> ReadFrom(
const DeferredMessage& deferred_message) {
auto state = deferred_message.MakeDeserializer();
if (auto res = Deserialize(&state))
return StatusOr<std::unique_ptr<T>>(std::move(res));
return StatusOr<std::unique_ptr<T>>(state.status());
}
static StatusOr<std::unique_ptr<T>> ReadFrom(std::vector<uint8_t> bytes) {
auto state = DeserializerState(std::move(bytes));
if (auto res = Deserialize(&state))
return StatusOr<std::unique_ptr<T>>(std::move(res));
return StatusOr<std::unique_ptr<T>>(state.status());
}
// Short-hand for legacy clients. This would swallow any errors, consider
// using ReadFrom.
static std::unique_ptr<T> FromBinary(const uint8_t* bytes, size_t size) {
std::unique_ptr<T> value(new T());
auto deserializer = DeferredMessage::FromSpan(span<uint8_t>(bytes, size))
->MakeDeserializer();
Deserialize(&deserializer, value.get());
return value;
}
static bool Deserialize(DeserializerState* state, T* value) {
return T::deserializer_descriptor().Deserialize(state, value);
}
protected:
// This is for the sake of the macros used by derived classes thay may be in
// a different namespace;
using ProtocolType = T;
using DeserializerDescriptorType = DeserializerDescriptor;
template <typename U>
using DeserializableBase = DeserializableProtocolObject<U>;
DeserializableProtocolObject() = default;
~DeserializableProtocolObject() = default;
private:
friend struct ProtocolTypeTraits<std::unique_ptr<T>>;
static std::unique_ptr<T> Deserialize(DeserializerState* state) {
std::unique_ptr<T> value(new T());
if (Deserialize(state, value.get()))
return value;
return nullptr;
}
};
template <typename T>
class ProtocolObject : public Serializable,
public DeserializableProtocolObject<T> {
public:
std::unique_ptr<T> Clone() const {
std::vector<uint8_t> serialized;
AppendSerialized(&serialized);
return T::ReadFrom(std::move(serialized)).value();
}
// TODO(caseq): compatibility only, remove.
std::unique_ptr<T> clone() const { return Clone(); }
protected:
using ProtocolType = T;
ProtocolObject() = default;
};
template <typename T>
struct ProtocolTypeTraits<
T,
typename std::enable_if<
std::is_base_of<ProtocolObject<T>, T>::value>::type> {
static bool Deserialize(DeserializerState* state, T* value) {
return T::Deserialize(state, value);
}
static void Serialize(const T& value, std::vector<uint8_t>* bytes) {
value.AppendSerialized(bytes);
}
};
template <typename T>
struct ProtocolTypeTraits<
std::unique_ptr<T>,
typename std::enable_if<
std::is_base_of<ProtocolObject<T>, T>::value>::type> {
static bool Deserialize(DeserializerState* state, std::unique_ptr<T>* value) {
std::unique_ptr<T> res = T::Deserialize(state);
if (!res)
return false;
*value = std::move(res);
return true;
}
static void Serialize(const std::unique_ptr<T>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<T>::Serialize(*value, bytes);
}
};
#define DECLARE_DESERIALIZATION_SUPPORT() \
friend DeserializableBase<ProtocolType>; \
static const DeserializerDescriptorType& deserializer_descriptor()
#define DECLARE_SERIALIZATION_SUPPORT() \
public: \
void AppendSerialized(std::vector<uint8_t>* bytes) const override; \
\
private: \
friend DeserializableBase<ProtocolType>; \
static const DeserializerDescriptorType& deserializer_descriptor()
#define V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, is_optional) \
{ \
MakeSpan(name), is_optional, \
[](DeserializerState* __state, void* __obj) -> bool { \
return ProtocolTypeTraits<decltype(field)>::Deserialize( \
__state, &static_cast<ProtocolType*>(__obj)->field); \
} \
}
// clang-format off
#define V8_CRDTP_BEGIN_DESERIALIZER(type) \
const type::DeserializerDescriptorType& type::deserializer_descriptor() { \
using namespace v8_crdtp; \
static const DeserializerDescriptorType::Field fields[] = {
#define V8_CRDTP_END_DESERIALIZER() \
}; \
static const DeserializerDescriptorType s_desc( \
fields, sizeof fields / sizeof fields[0]); \
return s_desc; \
}
#define V8_CRDTP_DESERIALIZE_FIELD(name, field) \
V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, false)
#define V8_CRDTP_DESERIALIZE_FIELD_OPT(name, field) \
V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, true)
#define V8_CRDTP_BEGIN_SERIALIZER(type) \
void type::AppendSerialized(std::vector<uint8_t>* bytes) const { \
using namespace v8_crdtp; \
ContainerSerializer __serializer(bytes, \
cbor::EncodeIndefiniteLengthMapStart());
#define V8_CRDTP_SERIALIZE_FIELD(name, field) \
__serializer.AddField(MakeSpan(name), field)
#define V8_CRDTP_END_SERIALIZER() \
__serializer.EncodeStop(); \
} class __cddtp_dummy_name
// clang-format on
} // namespace v8_crdtp
#endif // V8_CRDTP_PROTOCOL_CORE_H_

View File

@ -0,0 +1,497 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "protocol_core.h"
#include <memory>
#include "cbor.h"
#include "maybe.h"
#include "status_test_support.h"
#include "test_platform.h"
namespace v8_crdtp {
// Test-only. Real-life bindings use UTF8/16 conversions as needed.
template <>
struct ProtocolTypeTraits<std::string> {
static bool Deserialize(DeserializerState* state, std::string* value) {
if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::STRING8) {
auto cbor_span = state->tokenizer()->GetString8();
value->assign(reinterpret_cast<const char*>(cbor_span.data()),
cbor_span.size());
return true;
}
state->RegisterError(Error::BINDINGS_STRING8_VALUE_EXPECTED);
return false;
}
static void Serialize(const std::string& value, std::vector<uint8_t>* bytes) {
cbor::EncodeString8(
span<uint8_t>(reinterpret_cast<const uint8_t*>(value.data()),
value.size()),
bytes);
}
};
namespace {
using ::testing::Eq;
template <typename TResult, typename TArg>
std::unique_ptr<TResult> RoundtripToType(const TArg& obj) {
std::vector<uint8_t> bytes;
obj.AppendSerialized(&bytes);
StatusOr<std::unique_ptr<TResult>> result =
TResult::ReadFrom(std::move(bytes));
return std::move(result).value();
}
template <typename T>
std::unique_ptr<T> Roundtrip(const T& obj) {
return RoundtripToType<T, T>(obj);
}
// These TestTypeFOO classes below would normally be generated
// by the protocol generator.
class TestTypeBasic : public ProtocolObject<TestTypeBasic> {
public:
TestTypeBasic() = default;
const std::string& GetValue() const { return value_; }
void SetValue(std::string value) { value_ = std::move(value); }
private:
DECLARE_SERIALIZATION_SUPPORT();
std::string value_;
};
// clang-format off
V8_CRDTP_BEGIN_DESERIALIZER(TestTypeBasic)
V8_CRDTP_DESERIALIZE_FIELD("value", value_)
V8_CRDTP_END_DESERIALIZER()
V8_CRDTP_BEGIN_SERIALIZER(TestTypeBasic)
V8_CRDTP_SERIALIZE_FIELD("value", value_);
V8_CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, Basic) {
TestTypeBasic obj1;
obj1.SetValue("foo");
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->GetValue(), Eq("foo"));
}
TEST(ProtocolCoreTest, FailedToDeserializeTestTypeBasic) {
std::vector<uint8_t> garbage = {'g', 'a', 'r', 'b', 'a', 'g', 'e'};
StatusOr<std::unique_ptr<TestTypeBasic>> result =
TestTypeBasic::ReadFrom(std::move(garbage));
EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, 0));
}
class TestTypeBasicDouble : public ProtocolObject<TestTypeBasicDouble> {
public:
TestTypeBasicDouble() = default;
double GetValue() const { return value_; }
void SetValue(double value) { value_ = value; }
private:
DECLARE_SERIALIZATION_SUPPORT();
double value_;
};
// clang-format off
V8_CRDTP_BEGIN_DESERIALIZER(TestTypeBasicDouble)
V8_CRDTP_DESERIALIZE_FIELD("value", value_)
V8_CRDTP_END_DESERIALIZER()
V8_CRDTP_BEGIN_SERIALIZER(TestTypeBasicDouble)
V8_CRDTP_SERIALIZE_FIELD("value", value_);
V8_CRDTP_END_SERIALIZER();
// clang-format on
TEST(TestBasicDouble, ParserAllowsAllowsDoubleEncodedAsInt) {
// We allow double's encoded as INT32, because this is what a roundtrip via
// JSON would produce.
std::vector<uint8_t> encoded;
crdtp::cbor::EnvelopeEncoder envelope;
envelope.EncodeStart(&encoded);
encoded.push_back(crdtp::cbor::EncodeIndefiniteLengthMapStart());
crdtp::cbor::EncodeString8(crdtp::SpanFrom("value"), &encoded);
crdtp::cbor::EncodeInt32(
42, &encoded); // It's a double field, but we encode an int.
encoded.push_back(crdtp::cbor::EncodeStop());
envelope.EncodeStop(&encoded);
auto obj = TestTypeBasicDouble::ReadFrom(encoded).value();
ASSERT_THAT(obj, Not(testing::IsNull()));
EXPECT_THAT(obj->GetValue(), Eq(42));
}
class TestTypeComposite : public ProtocolObject<TestTypeComposite> {
public:
bool GetBoolField() const { return bool_field_; }
void SetBoolField(bool value) { bool_field_ = value; }
int GetIntField() const { return int_field_; }
void SetIntField(int value) { int_field_ = value; }
double GetDoubleField() const { return double_field_; }
void SetDoubleField(double value) { double_field_ = value; }
const std::string& GetStrField() const { return str_field_; }
void SetStrField(std::string value) { str_field_ = std::move(value); }
const TestTypeBasic* GetTestTypeBasicField() {
return test_type1_field_.get();
}
void SetTestTypeBasicField(std::unique_ptr<TestTypeBasic> value) {
test_type1_field_ = std::move(value);
}
private:
DECLARE_SERIALIZATION_SUPPORT();
bool bool_field_ = false;
int int_field_ = 0;
double double_field_ = 0.0;
std::string str_field_;
std::unique_ptr<TestTypeBasic> test_type1_field_;
};
// clang-format off
V8_CRDTP_BEGIN_DESERIALIZER(TestTypeComposite)
V8_CRDTP_DESERIALIZE_FIELD("bool_field", bool_field_),
V8_CRDTP_DESERIALIZE_FIELD("double_field", double_field_),
V8_CRDTP_DESERIALIZE_FIELD("int_field", int_field_),
V8_CRDTP_DESERIALIZE_FIELD("str_field", str_field_),
V8_CRDTP_DESERIALIZE_FIELD("test_type1_field", test_type1_field_),
V8_CRDTP_END_DESERIALIZER()
V8_CRDTP_BEGIN_SERIALIZER(TestTypeComposite)
V8_CRDTP_SERIALIZE_FIELD("bool_field", bool_field_),
V8_CRDTP_SERIALIZE_FIELD("double_field", double_field_),
V8_CRDTP_SERIALIZE_FIELD("int_field", int_field_),
V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_),
V8_CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_),
V8_CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, Composite) {
TestTypeComposite obj1;
obj1.SetBoolField(true);
obj1.SetIntField(42);
obj1.SetDoubleField(2.718281828);
obj1.SetStrField("bar");
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bazzzz");
obj1.SetTestTypeBasicField(std::move(val1));
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->GetBoolField(), Eq(true));
EXPECT_THAT(obj2->GetIntField(), Eq(42));
EXPECT_THAT(obj2->GetDoubleField(), Eq(2.718281828));
EXPECT_THAT(obj2->GetStrField(), Eq("bar"));
EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bazzzz"));
}
class CompositeParsingTest : public testing::Test {
public:
CompositeParsingTest() {
TestTypeComposite top;
top.SetIntField(42);
top.SetBoolField(true);
top.SetIntField(42);
top.SetDoubleField(2.718281828);
top.SetStrField("junk");
auto child = std::make_unique<TestTypeBasic>();
child->SetValue("child_value");
top.SetTestTypeBasicField(std::move(child));
// Let's establish that |serialized_| is a properly serialized
// representation of |top|, by checking that it deserializes ok.
top.AppendSerialized(&serialized_);
TestTypeComposite::ReadFrom(serialized_).value();
}
protected:
std::vector<uint8_t> serialized_;
};
TEST_F(CompositeParsingTest, DecodingFailure_CBORTokenizer) {
// Mutates |serialized_| so that it won't parse correctly. In this case,
// we're changing a string value so that it's invalid, making CBORTokenizer
// unhappy.
size_t position =
std::string(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size())
.find("child_value");
EXPECT_GT(position, 0ul);
// We override the byte just before so that it's still a string
// (3 << 5), but the length is encoded in the bytes that follows.
// So, we override that with 0xff (255), which exceeds the length
// of the message and thereby makes the string8 invalid.
--position;
serialized_[position] = 3 << 5 | // major type: STRING
25; // length in encoded in byte that follows.
serialized_[position + 1] = 0xff; // length
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, position));
}
TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingShallow) {
// We're changing the string key "int_field" to something else ("lnt_field"),
// so that the mandatory field value won't be found. Unknown fields are
// ignored for compatibility, so that's why this simple technique works here.
size_t position =
std::string(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size())
.find("int_field");
serialized_[position] = 'l'; // Change 'i' to 'l'.
// serialized_.size() - 1 is the STOP character for the entire message,
size_t expected_error_pos = serialized_.size() - 1;
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING,
expected_error_pos));
}
TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingNested) {
// We're changing the string key "value" to something else ("falue"), so that
// the mandatory field value in TestTypeBasic in the child won't be found.
size_t position =
std::string(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size())
.find("value");
serialized_[position] = 'f'; // Change 'v' to 'f'.
// serialized_.size() - 1 is the STOP character for the enclosing message,
// and serialized_.size() - 2 is the STOP character for TestTypeBasic.
size_t expected_error_pos = serialized_.size() - 2;
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING,
expected_error_pos));
}
TEST_F(CompositeParsingTest, DecodingFailure_BoolValueExpected) {
// We're changing the bool value (true) to null; we do this by looking
// for bool_field, and searching from there for TRUE; both TRUE and null
// are just one byte in the serialized buffer, so this swap is convenient.
std::string serialized_view(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size());
size_t position = serialized_view.find("bool_field");
for (; position < serialized_.size(); ++position) {
if (serialized_[position] == crdtp::cbor::EncodeTrue()) {
serialized_[position] = crdtp::cbor::EncodeNull();
break;
}
}
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(),
StatusIs(Error::BINDINGS_BOOL_VALUE_EXPECTED, position));
}
class TestTypeArrays : public ProtocolObject<TestTypeArrays> {
public:
const std::vector<int>* GetIntArray() const { return &int_array_; }
void SetIntArray(std::vector<int> value) { int_array_ = std::move(value); }
const std::vector<double>* GetDoubleArray() const { return &double_array_; }
void SetDoubleArray(std::vector<double> value) {
double_array_ = std::move(value);
}
const std::vector<std::string>* GetStrArray() const { return &str_array_; }
void SetStrArray(std::vector<std::string> value) {
str_array_ = std::move(value);
}
const std::vector<std::unique_ptr<TestTypeBasic>>* GetTestTypeBasicArray()
const {
return &test_type_basic_array_;
}
void SetTestTypeBasicArray(
std::vector<std::unique_ptr<TestTypeBasic>> value) {
test_type_basic_array_ = std::move(value);
}
private:
DECLARE_SERIALIZATION_SUPPORT();
std::vector<int> int_array_;
std::vector<double> double_array_;
std::vector<std::string> str_array_;
std::vector<std::unique_ptr<TestTypeBasic>> test_type_basic_array_;
};
// clang-format off
V8_CRDTP_BEGIN_DESERIALIZER(TestTypeArrays)
V8_CRDTP_DESERIALIZE_FIELD("int_array", int_array_),
V8_CRDTP_DESERIALIZE_FIELD("str_array", str_array_),
V8_CRDTP_DESERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_),
V8_CRDTP_END_DESERIALIZER()
V8_CRDTP_BEGIN_SERIALIZER(TestTypeArrays)
V8_CRDTP_SERIALIZE_FIELD("int_array", int_array_),
V8_CRDTP_SERIALIZE_FIELD("str_array", str_array_),
V8_CRDTP_SERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_),
V8_CRDTP_END_SERIALIZER();
// clang-format on
TEST_F(CompositeParsingTest, Arrays) {
TestTypeArrays obj1;
obj1.SetIntArray(std::vector<int>{1, 3, 5, 7});
std::vector<std::string> strs;
strs.emplace_back("foo");
strs.emplace_back(std::string("bar"));
obj1.SetStrArray(std::move(strs));
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bazzzz");
std::vector<std::unique_ptr<TestTypeBasic>> vec1;
vec1.emplace_back(std::move(val1));
obj1.SetTestTypeBasicArray(std::move(vec1));
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(*obj2->GetIntArray(), testing::ElementsAre(1, 3, 5, 7));
EXPECT_THAT(*obj2->GetStrArray(), testing::ElementsAre("foo", "bar"));
EXPECT_THAT(obj2->GetDoubleArray()->size(), Eq(0ul));
EXPECT_THAT(obj2->GetTestTypeBasicArray()->size(), Eq(1ul));
EXPECT_THAT(obj2->GetTestTypeBasicArray()->front()->GetValue(), Eq("bazzzz"));
}
class TestTypeOptional : public ProtocolObject<TestTypeOptional> {
public:
TestTypeOptional() = default;
bool HasIntField() const { return int_field_.isJust(); }
int GetIntField() const { return int_field_.fromJust(); }
void SetIntField(int value) { int_field_ = value; }
bool HasStrField() { return str_field_.isJust(); }
const std::string& GetStrField() const { return str_field_.fromJust(); }
void SetStrField(std::string value) { str_field_ = std::move(value); }
bool HasTestTypeBasicField() { return test_type_basic_field_.isJust(); }
const TestTypeBasic* GetTestTypeBasicField() const {
return test_type_basic_field_.isJust() ? test_type_basic_field_.fromJust()
: nullptr;
}
void SetTestTypeBasicField(std::unique_ptr<TestTypeBasic> value) {
test_type_basic_field_ = std::move(value);
}
private:
DECLARE_SERIALIZATION_SUPPORT();
Maybe<int> int_field_;
Maybe<std::string> str_field_;
Maybe<TestTypeBasic> test_type_basic_field_;
};
// clang-format off
V8_CRDTP_BEGIN_DESERIALIZER(TestTypeOptional)
V8_CRDTP_DESERIALIZE_FIELD_OPT("int_field", int_field_),
V8_CRDTP_DESERIALIZE_FIELD_OPT("str_field", str_field_),
V8_CRDTP_DESERIALIZE_FIELD_OPT("test_type_basic_field", test_type_basic_field_),
V8_CRDTP_END_DESERIALIZER()
V8_CRDTP_BEGIN_SERIALIZER(TestTypeOptional)
V8_CRDTP_SERIALIZE_FIELD("int_field", int_field_),
V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_),
V8_CRDTP_SERIALIZE_FIELD("test_type_basic_field", test_type_basic_field_),
V8_CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, OptionalAbsent) {
TestTypeOptional obj1;
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->HasIntField(), Eq(false));
EXPECT_THAT(obj2->HasStrField(), Eq(false));
EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(false));
}
TEST(ProtocolCoreTest, OptionalPresent) {
TestTypeOptional obj1;
obj1.SetIntField(42);
obj1.SetStrField("foo");
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bar");
obj1.SetTestTypeBasicField(std::move(val1));
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->HasIntField(), Eq(true));
EXPECT_THAT(obj2->GetIntField(), Eq(42));
EXPECT_THAT(obj2->HasStrField(), Eq(true));
EXPECT_THAT(obj2->GetStrField(), Eq("foo"));
EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(true));
EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bar"));
}
class TestTypeLazy : public ProtocolObject<TestTypeLazy> {
public:
TestTypeLazy() = default;
const std::string& GetStrField() const { return str_field_; }
void SetStrField(std::string value) { str_field_ = std::move(value); }
const DeferredMessage* deferred_test_type1_field() const {
return test_type1_field_.get();
}
private:
DECLARE_SERIALIZATION_SUPPORT();
std::string str_field_;
std::unique_ptr<DeferredMessage> test_type1_field_;
};
// clang-format off
V8_CRDTP_BEGIN_DESERIALIZER(TestTypeLazy)
V8_CRDTP_DESERIALIZE_FIELD("str_field", str_field_),
V8_CRDTP_DESERIALIZE_FIELD_OPT("test_type1_field", test_type1_field_),
V8_CRDTP_END_DESERIALIZER()
V8_CRDTP_BEGIN_SERIALIZER(TestTypeLazy)
V8_CRDTP_SERIALIZE_FIELD("str_field", str_field_),
V8_CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_),
V8_CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, TestDeferredMessage) {
TestTypeComposite obj1;
obj1.SetStrField("bar");
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bazzzz");
obj1.SetTestTypeBasicField(std::move(val1));
auto obj2 = RoundtripToType<TestTypeLazy>(obj1);
EXPECT_THAT(obj2->GetStrField(), Eq("bar"));
TestTypeBasic basic_val;
auto deserializer = obj2->deferred_test_type1_field()->MakeDeserializer();
EXPECT_THAT(TestTypeBasic::Deserialize(&deserializer, &basic_val), Eq(true));
EXPECT_THAT(basic_val.GetValue(), Eq("bazzzz"));
StatusOr<std::unique_ptr<TestTypeBasic>> maybe_parsed =
TestTypeBasic::ReadFrom(*obj2->deferred_test_type1_field());
ASSERT_THAT(maybe_parsed.status(), StatusIsOk());
ASSERT_THAT((*maybe_parsed), Not(testing::IsNull()));
ASSERT_EQ((*maybe_parsed)->GetValue(), "bazzzz");
}
} // namespace
} // namespace v8_crdtp

View File

@ -9,7 +9,7 @@
#include <string>
#include <vector>
#include "cbor.h"
#include "glue.h"
#include "maybe.h"
#include "span.h"
namespace v8_crdtp {
@ -124,9 +124,9 @@ struct FieldSerializerTraits {
};
template <typename T>
struct FieldSerializerTraits<glue::detail::PtrMaybe<T>> {
struct FieldSerializerTraits<detail::PtrMaybe<T>> {
static void Serialize(span<uint8_t> field_name,
const glue::detail::PtrMaybe<T>& field_value,
const detail::PtrMaybe<T>& field_value,
std::vector<uint8_t>* out) {
if (!field_value.isJust())
return;
@ -136,9 +136,9 @@ struct FieldSerializerTraits<glue::detail::PtrMaybe<T>> {
};
template <typename T>
struct FieldSerializerTraits<glue::detail::ValueMaybe<T>> {
struct FieldSerializerTraits<detail::ValueMaybe<T>> {
static void Serialize(span<uint8_t> field_name,
const glue::detail::ValueMaybe<T>& field_value,
const detail::ValueMaybe<T>& field_value,
std::vector<uint8_t>* out) {
if (!field_value.isJust())
return;

View File

@ -172,12 +172,12 @@ TEST(FieldSerializerTraits, RequiredField) {
template <typename T>
class FieldSerializerTraits_MaybeTest : public ::testing::Test {};
using MaybeTypes =
::testing::Types<glue::detail::ValueMaybe<bool>,
glue::detail::ValueMaybe<double>,
glue::detail::ValueMaybe<int32_t>,
glue::detail::ValueMaybe<std::string>,
glue::detail::PtrMaybe<Foo>,
glue::detail::PtrMaybe<std::vector<std::unique_ptr<Foo>>>>;
::testing::Types<detail::ValueMaybe<bool>,
detail::ValueMaybe<double>,
detail::ValueMaybe<int32_t>,
detail::ValueMaybe<std::string>,
detail::PtrMaybe<Foo>,
detail::PtrMaybe<std::vector<std::unique_ptr<Foo>>>>;
TYPED_TEST_SUITE(FieldSerializerTraits_MaybeTest, MaybeTypes);
TYPED_TEST(FieldSerializerTraits_MaybeTest, NoOutputForFieldsIfNotJust) {
@ -188,9 +188,8 @@ TYPED_TEST(FieldSerializerTraits_MaybeTest, NoOutputForFieldsIfNotJust) {
TEST(FieldSerializerTraits, MaybeBool) {
std::vector<uint8_t> out;
SerializeField(SpanFrom("true"), glue::detail::ValueMaybe<bool>(true), &out);
SerializeField(SpanFrom("false"), glue::detail::ValueMaybe<bool>(false),
&out);
SerializeField(SpanFrom("true"), detail::ValueMaybe<bool>(true), &out);
SerializeField(SpanFrom("false"), detail::ValueMaybe<bool>(false), &out);
std::vector<uint8_t> expected;
cbor::EncodeString8(SpanFrom("true"), &expected);
@ -203,7 +202,7 @@ TEST(FieldSerializerTraits, MaybeBool) {
TEST(FieldSerializerTraits, MaybeDouble) {
std::vector<uint8_t> out;
SerializeField(SpanFrom("dbl"), glue::detail::ValueMaybe<double>(3.14), &out);
SerializeField(SpanFrom("dbl"), detail::ValueMaybe<double>(3.14), &out);
std::vector<uint8_t> expected;
cbor::EncodeString8(SpanFrom("dbl"), &expected);
@ -215,7 +214,7 @@ TEST(FieldSerializerTraits, MaybeDouble) {
TEST(FieldSerializerTraits, MaybePtrFoo) {
std::vector<uint8_t> out;
SerializeField(SpanFrom("foo"),
glue::detail::PtrMaybe<Foo>(std::make_unique<Foo>(42)), &out);
detail::PtrMaybe<Foo>(std::make_unique<Foo>(42)), &out);
std::vector<uint8_t> expected;
cbor::EncodeString8(SpanFrom("foo"), &expected);

View File

@ -21,4 +21,19 @@ bool SpanEquals(span<uint8_t> x, span<uint8_t> y) noexcept {
return x.data() == y.data() || len == 0 ||
std::memcmp(x.data(), y.data(), len) == 0;
}
bool SpanLessThan(span<char> x, span<char> y) noexcept {
auto min_size = std::min(x.size(), y.size());
const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size);
return (r < 0) || (r == 0 && x.size() < y.size());
}
bool SpanEquals(span<char> x, span<char> y) noexcept {
auto len = x.size();
if (len != y.size())
return false;
return x.data() == y.data() || len == 0 ||
std::memcmp(x.data(), y.data(), len) == 0;
}
} // namespace v8_crdtp

View File

@ -50,6 +50,11 @@ class span {
index_type size_;
};
template <size_t N>
constexpr span<char> MakeSpan(const char (&str)[N]) {
return span<char>(str, N - 1);
}
template <size_t N>
constexpr span<uint8_t> SpanFrom(const char (&str)[N]) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(str), N - 1);
@ -75,11 +80,15 @@ inline span<typename C::value_type> SpanFrom(const C& v) {
}
// Less than / equality comparison functions for sorting / searching for byte
// spans. These are similar to absl::string_view's < and == operators.
// spans.
bool SpanLessThan(span<uint8_t> x, span<uint8_t> y) noexcept;
bool SpanEquals(span<uint8_t> x, span<uint8_t> y) noexcept;
// Less than / equality comparison functions for sorting / searching for byte
// spans.
bool SpanLessThan(span<char> x, span<char> y) noexcept;
bool SpanEquals(span<char> x, span<char> y) noexcept;
struct SpanLt {
bool operator()(span<uint8_t> l, span<uint8_t> r) const {
return SpanLessThan(l, r);

View File

@ -5,6 +5,7 @@
#ifndef V8_CRDTP_STATUS_H_
#define V8_CRDTP_STATUS_H_
#include <cassert>
#include <cstddef>
#include <limits>
#include <string>
@ -103,6 +104,35 @@ struct Status {
// includes the position.
std::string ToASCIIString() const;
};
template <typename T>
class StatusOr {
public:
explicit StatusOr(const T& value) : value_(value) {}
explicit StatusOr(T&& value) : value_(std::move(value)) {}
explicit StatusOr(const Status& status) : status_(status) {}
bool ok() const { return status_.ok(); }
T& operator*() & {
assert(ok());
return value_;
}
const T& operator*() const& { return value(); }
T&& operator*() && { return value(); }
const Status& status() const { return status_; }
T& value() & { return *this; }
T&& value() && {
assert(ok());
return std::move(value_);
}
const T& value() const& { return *this; }
private:
Status status_;
T value_;
};
} // namespace v8_crdtp
#endif // V8_CRDTP_STATUS_H_

View File

@ -37,6 +37,7 @@ template("inspector_protocol_generate") {
"$inspector_protocol_dir/lib/Object_cpp.template",
"$inspector_protocol_dir/lib/Object_h.template",
"$inspector_protocol_dir/lib/Protocol_cpp.template",
"$inspector_protocol_dir/lib/ValueConversions_cpp.template",
"$inspector_protocol_dir/lib/ValueConversions_h.template",
"$inspector_protocol_dir/lib/Values_cpp.template",
"$inspector_protocol_dir/lib/Values_h.template",

View File

@ -21,7 +21,7 @@
#include "{{config.crdtp.dir}}/error_support.h"
#include "{{config.crdtp.dir}}/dispatch.h"
#include "{{config.crdtp.dir}}/frontend_channel.h"
#include "{{config.crdtp.dir}}/glue.h"
#include "{{config.crdtp.dir}}/protocol_core.h"
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
@ -42,7 +42,14 @@ class SerializedValue;
class StringValue;
class Value;
using {{config.crdtp.namespace}}::detail::PtrMaybe;
using {{config.crdtp.namespace}}::detail::ValueMaybe;
template<typename T>
using Maybe = {{config.crdtp.namespace}}::Maybe<T>;
namespace detail {
template <typename T>
struct ArrayTypedef { typedef std::vector<std::unique_ptr<T>> type; };
@ -62,32 +69,6 @@ struct ArrayTypedef<bool> { typedef std::vector<bool> type; };
template <typename T>
using Array = typename detail::ArrayTypedef<T>::type;
namespace detail {
using {{config.crdtp.namespace}}::glue::detail::PtrMaybe;
using {{config.crdtp.namespace}}::glue::detail::ValueMaybe;
template <typename T>
struct MaybeTypedef { typedef PtrMaybe<T> type; };
template <>
struct MaybeTypedef<bool> { typedef ValueMaybe<bool> type; };
template <>
struct MaybeTypedef<int> { typedef ValueMaybe<int> type; };
template <>
struct MaybeTypedef<double> { typedef ValueMaybe<double> type; };
template <>
struct MaybeTypedef<String> { typedef ValueMaybe<String> type; };
template <>
struct MaybeTypedef<Binary> { typedef ValueMaybe<Binary> type; };
} // namespace detail
template <typename T>
using Maybe = typename detail::MaybeTypedef<T>::type;
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}

View File

@ -28,7 +28,11 @@ public:
std::unique_ptr<protocol::DictionaryValue> toValue() const;
std::unique_ptr<Object> clone() const;
private:
Object() = default;
friend struct {{config.crdtp.namespace}}::ProtocolTypeTraits<std::unique_ptr<Object>, void>;
std::unique_ptr<protocol::DictionaryValue> m_object;
};

View File

@ -0,0 +1,123 @@
// This file is generated by ValueConversions_cpp.template.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include {{format_include(config.protocol.package, "Protocol")}}
#include <algorithm>
#include <climits>
#include <string>
//#include "ValueConversions.h"
//#include "Values.h"
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}
{% for namespace in config.protocol.namespace %}
} // namespce
{% endfor %}
namespace {{config.crdtp.namespace}} {
namespace {
using {{"::".join(config.protocol.namespace)}}::Binary;
using {{"::".join(config.protocol.namespace)}}::Object;
using {{"::".join(config.protocol.namespace)}}::Value;
using {{"::".join(config.protocol.namespace)}}::String;
using {{"::".join(config.protocol.namespace)}}::DictionaryValue;
using {{"::".join(config.protocol.namespace)}}::FundamentalValue;
using {{"::".join(config.protocol.namespace)}}::StringValue;
using {{"::".join(config.protocol.namespace)}}::StringUtil;
//using {{"::".join(config.protocol.namespace)}}::EncodeString;
std::unique_ptr<Value> ReadValue(DeserializerState* state) {
cbor::CBORTokenizer* tokenizer = state->tokenizer();
switch (tokenizer->TokenTag()) {
case cbor::CBORTokenTag::TRUE_VALUE:
return FundamentalValue::create(true);
case cbor::CBORTokenTag::FALSE_VALUE:
return FundamentalValue::create(false);
case cbor::CBORTokenTag::NULL_VALUE:
return Value::null();
case cbor::CBORTokenTag::INT32:
return FundamentalValue::create(tokenizer->GetInt32());
case cbor::CBORTokenTag::DOUBLE:
return FundamentalValue::create(tokenizer->GetDouble());
case cbor::CBORTokenTag::STRING8: {
const auto str = tokenizer->GetString8();
return StringValue::create(StringUtil::fromUTF8(str.data(), str.size()));
}
case cbor::CBORTokenTag::STRING16: {
const auto str = tokenizer->GetString16WireRep();
return StringValue::create(StringUtil::fromUTF16LE(reinterpret_cast<const uint16_t*>(str.data()), str.size() / 2));
}
case cbor::CBORTokenTag::ENVELOPE: {
const auto env = tokenizer->GetEnvelope();
return Value::parseBinary(env.data(), env.size());
}
// Intentionally not supported.
case cbor::CBORTokenTag::BINARY:
// Should not be encountered outside of envelope.
case cbor::CBORTokenTag::MAP_START:
case cbor::CBORTokenTag::ARRAY_START:
default:
state->RegisterError(Error::CBOR_UNSUPPORTED_VALUE);
return nullptr;
}
}
} // namespace
// static
bool ProtocolTypeTraits<std::unique_ptr<Value>>::Deserialize(
DeserializerState* state, std::unique_ptr<Value>* value) {
auto result = ReadValue(state);
if (!result)
return false;
*value = std::move(result);
return true;
}
// static
void ProtocolTypeTraits<std::unique_ptr<Value>>::Serialize(
const std::unique_ptr<Value>& value, std::vector<uint8_t>* bytes) {
value->AppendSerialized(bytes);
}
// static
bool ProtocolTypeTraits<std::unique_ptr<DictionaryValue>>::Deserialize(
DeserializerState* state, std::unique_ptr<DictionaryValue>* value) {
std::unique_ptr<Value> res;
if (!ProtocolTypeTraits<std::unique_ptr<Value>>::Deserialize(state, &res))
return false;
*value = DictionaryValue::cast(std::move(res));
return true;
}
// static
void ProtocolTypeTraits<std::unique_ptr<DictionaryValue>>::Serialize(
const std::unique_ptr<DictionaryValue>& value, std::vector<uint8_t>* bytes) {
value->AppendSerialized(bytes);
}
// static
bool ProtocolTypeTraits<std::unique_ptr<Object>>::Deserialize(DeserializerState* state, std::unique_ptr<Object>* value) {
auto res = DictionaryValue::create();
if (ProtocolTypeTraits<std::unique_ptr<DictionaryValue>>::Deserialize(state, &res)) {
*value = std::make_unique<Object>(std::move(res));
return true;
}
return false;
}
void ProtocolTypeTraits<std::unique_ptr<Object>>::Serialize(const std::unique_ptr<Object>& value, std::vector<uint8_t>* bytes) {
value->AppendSerialized(bytes);
}
} // namespace {{config.crdtp.namespace}}

View File

@ -259,8 +259,61 @@ struct ValueConversions<ListValue> {
}
};
template<typename T> struct ValueTypeConverter {
static std::unique_ptr<T> FromValue(const protocol::Value& value) {
std::vector<uint8_t> bytes;
value.AppendSerialized(&bytes);
return T::FromBinary(bytes.data(), bytes.size());
}
static std::unique_ptr<protocol::DictionaryValue> ToValue(const T& obj) {
std::vector<uint8_t> bytes;
obj.AppendSerialized(&bytes);
auto result = Value::parseBinary(bytes.data(), bytes.size());
return DictionaryValue::cast(std::move(result));
}
};
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
namespace {{config.crdtp.namespace}} {
template<typename T>
struct ProtocolTypeTraits<T,
typename std::enable_if<std::is_base_of<{{"::".join(config.protocol.namespace)}}::Value, T>::value>::type> {
static void Serialize(const {{"::".join(config.protocol.namespace)}}::Value& value, std::vector<uint8_t>* bytes) {
value.AppendSerialized(bytes);
}
};
template <>
struct ProtocolTypeTraits<std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Value>> {
static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Value>* value);
static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Value>& value, std::vector<uint8_t>* bytes);
};
template <>
struct ProtocolTypeTraits<std::unique_ptr<{{"::".join(config.protocol.namespace)}}::DictionaryValue>> {
static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::DictionaryValue>* value);
static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::DictionaryValue>& value, std::vector<uint8_t>* bytes);
};
// TODO(caseq): get rid of it, it's just a DictionaryValue really.
template <>
struct ProtocolTypeTraits<std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Object>> {
static bool Deserialize(DeserializerState* state, std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Object>* value);
static void Serialize(const std::unique_ptr<{{"::".join(config.protocol.namespace)}}::Object>& value, std::vector<uint8_t>* bytes);
};
template<>
struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::Object> {
static void Serialize(const {{"::".join(config.protocol.namespace)}}::Object& value, std::vector<uint8_t>* bytes) {
value.AppendSerialized(bytes);
}
};
} // namespace {{config.crdtp.namespace}}
#endif // !defined({{"_".join(config.protocol.namespace)}}_ValueConversions_h)

View File

@ -62,7 +62,7 @@ class ValueParserHandler : public ParserHandler {
DCHECK(stack_.back().is_dict);
stack_.pop_back();
}
void HandleArrayBegin() override {
if (!status_.ok()) return;
std::unique_ptr<ListValue> list = ListValue::create();
@ -91,19 +91,19 @@ class ValueParserHandler : public ParserHandler {
AddValueToParent(
BinaryValue::create(Binary::fromSpan(bytes.data(), bytes.size())));
}
void HandleDouble(double value) override {
AddValueToParent(FundamentalValue::create(value));
}
void HandleInt32(int32_t value) override {
AddValueToParent(FundamentalValue::create(value));
}
void HandleBool(bool value) override {
AddValueToParent(FundamentalValue::create(value));
}
void HandleNull() override {
AddValueToParent(Value::null());
}
@ -318,7 +318,6 @@ void EncodeString(const String& s, std::vector<uint8_t>* out) {
}
}
} // namespace
void StringValue::AppendSerialized(std::vector<uint8_t>* bytes) const {
EncodeString(m_stringValue, bytes);
}

View File

@ -20,6 +20,11 @@ class ListValue;
class DictionaryValue;
class Value;
#define PROTOCOL_DISALLOW_COPY(ClassName) \
private: \
ClassName(const ClassName&) = delete; \
ClassName& operator=(const ClassName&) = delete
class {{config.lib.export_macro}} Value : public Serializable {
PROTOCOL_DISALLOW_COPY(Value);
public:

View File

@ -16,6 +16,13 @@
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "{{config.crdtp.dir}}/cbor.h"
#include "{{config.crdtp.dir}}/protocol_core.h"
using namespace {{config.crdtp.namespace}};
using {{"::".join(config.protocol.namespace)}}::Binary;
using {{"::".join(config.protocol.namespace)}}::String;
using {{"::".join(config.protocol.namespace)}}::StringUtil;
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
@ -138,6 +145,28 @@ String StringUtil::fromUTF16LE(const uint16_t* data, size_t length) {
return utf8;
}
bool StringUtil::ReadString(DeserializerState* state, String* value) {
auto* tokenizer = state->tokenizer();
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) {
const auto str = tokenizer->GetString8();
*value = StringUtil::fromUTF8(str.data(), str.size());
return true;
}
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING16) {
const auto str = tokenizer->GetString16WireRep();
*value = StringUtil::fromUTF16LE(reinterpret_cast<const uint16_t*>(str.data()), str.size() / 2);
return true;
}
state->RegisterError(Error::BINDINGS_STRING_VALUE_EXPECTED);
return false;
}
void StringUtil::WriteString(const String& str, std::vector<uint8_t>* bytes) {
cbor::EncodeString8(span<uint8_t>(StringUtil::CharactersUTF8(str),
StringUtil::CharacterCount(str)),
bytes);
}
Binary::Binary() : bytes_(new base::RefCountedBytes) {}
Binary::Binary(const Binary& binary) : bytes_(binary.bytes_) {}
Binary::Binary(scoped_refptr<base::RefCountedMemory> bytes) : bytes_(bytes) {}
@ -190,3 +219,31 @@ Binary Binary::fromSpan(const uint8_t* data, size_t size) {
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
namespace {{config.crdtp.namespace}} {
// static
bool ProtocolTypeTraits<Binary>::Deserialize(DeserializerState* state, Binary* value) {
auto* tokenizer = state->tokenizer();
if (tokenizer->TokenTag() == cbor::CBORTokenTag::BINARY) {
const span<uint8_t> bin = tokenizer->GetBinary();
*value = Binary::fromSpan(bin.data(), bin.size());
return true;
}
if (tokenizer->TokenTag() == cbor::CBORTokenTag::STRING8) {
const auto str_span = tokenizer->GetString8();
String str = StringUtil::fromUTF8(str_span.data(), str_span.size());
bool success = false;
*value = Binary::fromBase64(str, &success);
return success;
}
state->RegisterError(Error::BINDINGS_BINARY_VALUE_EXPECTED);
return false;
}
// static
void ProtocolTypeTraits<Binary>::Serialize(const Binary& value, std::vector<uint8_t>* bytes) {
value.AppendSerialized(bytes);
}
} // namespace {{config.crdtp.namespace}}

View File

@ -15,6 +15,7 @@
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "{{config.crdtp.dir}}/serializable.h"
#include "{{config.crdtp.dir}}/protocol_core.h"
{% if config.lib.export_header %}
#include "{{config.lib.export_header}}"
@ -24,6 +25,10 @@ namespace base {
class Value;
}
namespace {{config.crdtp.namespace}} {
class DeserializerState;
}
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}
@ -46,6 +51,9 @@ class {{config.lib.export_macro}} StringUtil {
}
static const uint16_t* CharactersUTF16(const String& s) { return nullptr; }
static size_t CharacterCount(const String& s) { return s.size(); }
static bool ReadString({{config.crdtp.namespace}}::DeserializerState* state, String* str);
static void WriteString(const String& str, std::vector<uint8_t>* bytes);
};
// A read-only sequence of uninterpreted bytes with reference-counted storage.
@ -81,4 +89,24 @@ std::unique_ptr<base::Value> toBaseValue(Value* value, int depth);
} // namespace {{namespace}}
{% endfor %}
namespace {{config.crdtp.namespace}} {
template <>
struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::String> {
static bool Deserialize(DeserializerState* state, {{"::".join(config.protocol.namespace)}}::String* value) {
return {{"::".join(config.protocol.namespace)}}::StringUtil::ReadString(state, value);
}
static void Serialize(const {{"::".join(config.protocol.namespace)}}::String& value, std::vector<uint8_t>* bytes) {
{{"::".join(config.protocol.namespace)}}::StringUtil::WriteString(value, bytes);
}
};
template <>
struct ProtocolTypeTraits<{{"::".join(config.protocol.namespace)}}::Binary> {
static bool Deserialize(DeserializerState* state, {{"::".join(config.protocol.namespace)}}::Binary* value);
static void Serialize(const {{"::".join(config.protocol.namespace)}}::Binary& value, std::vector<uint8_t>* bytes);
};
} // {{config.crdtp.namespace}}
#endif // !defined({{"_".join(config.protocol.namespace)}}_BASE_STRING_ADAPTER_H)

View File

@ -31,13 +31,16 @@ FILES_TO_SYNC = [
'crdtp/find_by_first.h',
'crdtp/find_by_first_test.cc',
'crdtp/frontend_channel.h',
'crdtp/glue.h',
'crdtp/glue_test.cc',
'crdtp/maybe.h',
'crdtp/maybe_test.cc',
'crdtp/json.cc',
'crdtp/json.h',
'crdtp/json_platform.h',
'crdtp/json_test.cc',
'crdtp/parser_handler.h',
'crdtp/protocol_core_test.cc',
'crdtp/protocol_core.cc',
'crdtp/protocol_core.h',
'crdtp/serializable.h',
'crdtp/serializable.cc',
'crdtp/serializable_test.cc',

View File

@ -14,6 +14,53 @@
#include {{format_include(config.imported.package, domain.domain)}}
{% endif %}
#ifndef {{"_".join(config.protocol.namespace)}}_imported_imported_h
namespace {{config.crdtp.namespace}} {
template <typename T>
struct ProtocolTypeTraits<
std::unique_ptr<T>,
typename std::enable_if<
std::is_base_of<{{"::".join(config.imported.namespace)}}::Exported, T>::value>::type> {
static bool Deserialize(DeserializerState* state, std::unique_ptr<T>* value) {
if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::ENVELOPE) {
state->RegisterError(Error::CBOR_INVALID_ENVELOPE);
return false;
}
span<uint8_t> env = state->tokenizer()->GetEnvelope();
auto res = T::fromBinary(env.data(), env.size());
if (!res) {
// TODO(caseq): properly plumb an error rather than returning a bogus code.
state->RegisterError(Error::MESSAGE_MUST_BE_AN_OBJECT);
return false;
}
*value = std::move(res);
return true;
}
static void Serialize(const std::unique_ptr<T>& value, std::vector<uint8_t>* bytes) {
// Use virtual method, so that outgoing protocol objects could be retained
// by a pointer to ProtocolObject.
value->AppendSerialized(bytes);
}
};
template <typename T>
struct ProtocolTypeTraits<
T,
typename std::enable_if<
std::is_base_of<{{"::".join(config.imported.namespace)}}::Exported, T>::value>::type> {
static void Serialize(const T& value, std::vector<uint8_t>* bytes) {
// Use virtual method, so that outgoing protocol objects could be retained
// by a pointer to ProtocolObject.
value.AppendSerialized(bytes);
}
};
} // namespace {{config.crdtp.namespace}}
#endif // {{"_".join(config.protocol.namespace)}}_imported_imported_h
{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}

View File

@ -10,7 +10,6 @@
#include "{{config.crdtp.dir}}/cbor.h"
#include "{{config.crdtp.dir}}/find_by_first.h"
#include "{{config.crdtp.dir}}/serializer_traits.h"
#include "{{config.crdtp.dir}}/span.h"
{% for namespace in config.protocol.namespace %}
@ -18,6 +17,9 @@ namespace {{namespace}} {
{% endfor %}
namespace {{domain.domain}} {
using {{config.crdtp.namespace}}::DeserializerState;
using {{config.crdtp.namespace}}::ProtocolTypeTraits;
// ------------- Enum values from types.
const char Metainfo::domainName[] = "{{domain.domain}}";
@ -43,6 +45,7 @@ const char* {{ literal | dash_to_camelcase}} = "{{literal}}";
} // namespace API
{% endif %}
{% endif %}
{% for property in type.properties %}
{% if "enum" in property %}
@ -52,80 +55,28 @@ const char* {{type.id}}::{{property.name | to_title_case}}Enum::{{literal | dash
{% endif %}
{% endfor %}
{% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %}
std::unique_ptr<{{type.id}}> {{type.id}}::fromValue(protocol::Value* value, ErrorSupport* errors)
{
if (!value || value->type() != protocol::Value::TypeObject) {
errors->AddError("object expected");
return nullptr;
}
std::unique_ptr<{{type.id}}> result(new {{type.id}}());
protocol::DictionaryValue* object = DictionaryValue::cast(value);
errors->Push();
{% for property in type.properties %}
protocol::Value* {{property.name}}Value = object->get("{{property.name}}");
V8_CRDTP_BEGIN_DESERIALIZER({{type.id}})
{% for property in type.properties | sort(attribute = 'name', case_sensitive=True) %}
{% if property.optional %}
if ({{property.name}}Value) {
errors->SetName("{{property.name}}");
result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors);
}
V8_CRDTP_DESERIALIZE_FIELD_OPT("{{property.name}}", m_{{property.name}}),
{% else %}
errors->SetName("{{property.name}}");
result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors);
V8_CRDTP_DESERIALIZE_FIELD("{{property.name}}", m_{{property.name}}),
{% endif %}
{% endfor %}
errors->Pop();
if (!errors->Errors().empty())
return nullptr;
return result;
}
{% endfor %}
V8_CRDTP_END_DESERIALIZER()
std::unique_ptr<protocol::DictionaryValue> {{type.id}}::toValue() const
{
std::unique_ptr<protocol::DictionaryValue> result = DictionaryValue::create();
{% for property in type.properties %}
{% set property_type = protocol.resolve_type(property) %}
{% set property_field = "m_" + property.name %}
{% if property.optional %}
if ({{property_field}}.isJust())
result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_field}}.fromJust()));
{% else %}
result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_type.to_raw_type % property_field}}));
{% endif %}
{% endfor %}
return result;
}
V8_CRDTP_BEGIN_SERIALIZER({{type.id}})
{% for property in type.properties %}
V8_CRDTP_SERIALIZE_FIELD("{{property.name}}", m_{{property.name}});
{% endfor %}
V8_CRDTP_END_SERIALIZER();
void {{type.id}}::AppendSerialized(std::vector<uint8_t>* out) const {
{{config.crdtp.namespace}}::cbor::EnvelopeEncoder envelope_encoder;
envelope_encoder.EncodeStart(out);
out->push_back({{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart());
{% for property in type.properties %}
{% set property_field = "m_" + property.name %}
{{config.crdtp.namespace}}::SerializeField({{config.crdtp.namespace}}::SpanFrom("{{property.name}}"), {{property_field}}, out);
{% endfor %}
out->push_back({{config.crdtp.namespace}}::cbor::EncodeStop());
envelope_encoder.EncodeStop(out);
}
std::unique_ptr<{{type.id}}> {{type.id}}::clone() const
{
ErrorSupport errors;
return fromValue(toValue().get(), &errors);
}
{% if protocol.is_exported(domain.domain, type.id) %}
// static
std::unique_ptr<API::{{type.id}}> API::{{type.id}}::fromBinary(const uint8_t* data, size_t length)
{
ErrorSupport errors;
std::unique_ptr<Value> value = Value::parseBinary(data, length);
if (!value)
return nullptr;
return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors);
return protocol::{{domain.domain}}::{{type.id}}::FromBinary(data, length);
}
{% endif %}
{% endfor %}
@ -174,20 +125,11 @@ void Frontend::{{event.name | to_method_case}}(
if (!frontend_channel_)
return;
{% if event.parameters %}
std::unique_ptr<{{event.name | to_title_case}}Notification> messageData = {{event.name | to_title_case}}Notification::{{"create" | to_method_case}}()
{{config.crdtp.namespace}}::ObjectSerializer serializer;
{% for parameter in event.parameters %}
{% if not "optional" in parameter %}
.{{"set" | to_method_case}}{{parameter.name | to_title_case}}({{protocol.resolve_type(parameter).to_pass_type % parameter.name}})
{% endif %}
serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), {{parameter.name}});
{% endfor %}
.{{ "build" | to_method_case }}();
{% for parameter in event.parameters %}
{% if "optional" in parameter %}
if ({{parameter.name}}.isJust())
messageData->{{"set" | to_method_case}}{{parameter.name | to_title_case}}(std::move({{parameter.name}}).takeJust());
{% endif %}
{% endfor %}
frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}", std::move(messageData)));
frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}", serializer.Finish()));
{% else %}
frontend_channel_->SendProtocolNotification({{config.crdtp.namespace}}::CreateNotification("{{domain.domain}}.{{event.name}}"));
{% endif %}
@ -213,14 +155,14 @@ public:
, m_backend(backend) {}
~DomainDispatcherImpl() override { }
using CallHandler = void (DomainDispatcherImpl::*)(const {{config.crdtp.namespace}}::Dispatchable& dispatchable, DictionaryValue* params, ErrorSupport* errors);
using CallHandler = void (DomainDispatcherImpl::*)(const {{config.crdtp.namespace}}::Dispatchable& dispatchable);
std::function<void(const {{config.crdtp.namespace}}::Dispatchable&)> Dispatch({{config.crdtp.namespace}}::span<uint8_t> command_name) override;
{% for command in domain.commands %}
{% if "redirect" in command %}{% continue %}{% endif %}
{% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %}
void {{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable, DictionaryValue* params, ErrorSupport* errors);
void {{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable);
{% endfor %}
protected:
Backend* m_backend;
@ -252,13 +194,9 @@ DomainDispatcherImpl::CallHandler CommandByName({{config.crdtp.namespace}}::span
std::function<void(const {{config.crdtp.namespace}}::Dispatchable&)> DomainDispatcherImpl::Dispatch({{config.crdtp.namespace}}::span<uint8_t> command_name) {
CallHandler handler = CommandByName(command_name);
if (!handler) return nullptr;
return [this, handler](const {{config.crdtp.namespace}}::Dispatchable& dispatchable){
std::unique_ptr<DictionaryValue> params =
DictionaryValue::cast(protocol::Value::parseBinary(dispatchable.Params().data(),
dispatchable.Params().size()));
ErrorSupport errors;
errors.Push();
(this->*handler)(dispatchable, params.get(), &errors);
return [this, handler](const {{config.crdtp.namespace}}::Dispatchable& dispatchable) {
(this->*handler)(dispatchable);
};
}
@ -284,16 +222,11 @@ public:
{%- if not loop.last -%}, {% endif -%}
{%- endfor -%}) override
{
std::vector<uint8_t> result_buffer;
{{config.crdtp.namespace}}::cbor::EnvelopeEncoder envelope_encoder;
envelope_encoder.EncodeStart(&result_buffer);
result_buffer.push_back({{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart());
{{config.crdtp.namespace}}::ObjectSerializer serializer;
{% for parameter in command.returns %}
{{config.crdtp.namespace}}::SerializeField({{config.crdtp.namespace}}::SpanFrom("{{parameter.name}}"), {{parameter.name}}, &result_buffer);
serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), {{parameter.name}});
{% endfor %}
result_buffer.push_back({{config.crdtp.namespace}}::cbor::EncodeStop());
envelope_encoder.EncodeStop(&result_buffer);
sendIfActive({{config.crdtp.namespace}}::Serializable::From(std::move(result_buffer)), DispatchResponse::Success());
sendIfActive(serializer.Finish(), DispatchResponse::Success());
}
void fallThrough() override
@ -309,26 +242,45 @@ public:
};
{% endif %}
void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable, DictionaryValue* params, ErrorSupport* errors)
{
namespace {
{% if "parameters" in command %}
// Prepare input parameters.
struct {{command.name}}Params : public {{config.crdtp.namespace}}::DeserializableProtocolObject<{{command.name}}Params> {
{% for parameter in command.parameters %}
{% set parameter_type = protocol.resolve_type(parameter) %}
protocol::Value* {{parameter.name}}Value = params ? params->get("{{parameter.name}}") : nullptr;
{% if parameter.optional %}
Maybe<{{parameter_type.raw_type}}> in_{{parameter.name}};
if ({{parameter.name}}Value) {
errors->SetName("{{parameter.name}}");
in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors);
}
Maybe<{{parameter_type.raw_type}}> {{parameter.name}};
{% else %}
errors->SetName("{{parameter.name}}");
{{parameter_type.type}} in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors);
{{parameter_type.type}} {{parameter.name}};
{% endif %}
{% endfor %}
if (MaybeReportInvalidParams(dispatchable, *errors)) return;
DECLARE_DESERIALIZATION_SUPPORT();
};
V8_CRDTP_BEGIN_DESERIALIZER({{command.name}}Params)
{% for parameter in command.parameters | sort(attribute = 'name', case_sensitive=True) %}
{% if parameter.optional %}
V8_CRDTP_DESERIALIZE_FIELD_OPT("{{parameter.name}}", {{parameter.name}}),
{% else %}
V8_CRDTP_DESERIALIZE_FIELD("{{parameter.name}}", {{parameter.name}}),
{% endif %}
{% endfor %}
V8_CRDTP_END_DESERIALIZER()
{% endif %}
} // namespace
void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Dispatchable& dispatchable)
{
// Prepare input parameters.
{% if "parameters" in command %}
auto deserializer = {{config.crdtp.namespace}}::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer();
{{command.name}}Params params;
{{command.name}}Params::Deserialize(&deserializer, &params);
if (MaybeReportInvalidParams(dispatchable, deserializer))
return;
{% endif %}
{% if "returns" in command and not protocol.is_async_command(domain.domain, command.name) %}
// Declare output parameters.
{% for parameter in command.returns %}
@ -346,9 +298,9 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di
{%- for parameter in command.parameters -%}
{%- if not loop.first -%}, {% endif -%}
{%- if "optional" in parameter -%}
std::move(in_{{parameter.name}})
std::move(params.{{parameter.name}})
{%- else -%}
{{protocol.resolve_type(parameter).to_pass_type % ("in_" + parameter.name)}}
{{protocol.resolve_type(parameter).to_pass_type % ("params." + parameter.name)}}
{%- endif -%}
{%- endfor %}
{%- if "returns" in command %}
@ -363,18 +315,17 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di
}
{% if "returns" in command %}
if (weak->get()) {
std::vector<uint8_t> result;
std::unique_ptr<{{config.crdtp.namespace}}::Serializable> result;
if (response.IsSuccess()) {
{{config.crdtp.namespace}}::cbor::EnvelopeEncoder envelope_encoder;
envelope_encoder.EncodeStart(&result);
result.push_back({{config.crdtp.namespace}}::cbor::EncodeIndefiniteLengthMapStart());
{{config.crdtp.namespace}}::ObjectSerializer serializer;
{% for parameter in command.returns %}
{{config.crdtp.namespace}}::SerializeField({{config.crdtp.namespace}}::SpanFrom("{{parameter.name}}"), out_{{parameter.name}}, &result);
serializer.AddField({{config.crdtp.namespace}}::MakeSpan("{{parameter.name}}"), out_{{parameter.name}});
{% endfor %}
result.push_back({{config.crdtp.namespace}}::cbor::EncodeStop());
envelope_encoder.EncodeStop(&result);
result = serializer.Finish();
} else {
result = Serializable::From({});
}
weak->get()->sendResponse(dispatchable.CallId(), response, {{config.crdtp.namespace}}::Serializable::From(std::move(result)));
weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result));
}
{% else %}
if (weak->get())
@ -386,9 +337,9 @@ void DomainDispatcherImpl::{{command.name}}(const {{config.crdtp.namespace}}::Di
{%- for property in command.parameters -%}
{%- if not loop.first -%}, {% endif -%}
{%- if "optional" in property -%}
std::move(in_{{property.name}})
std::move(params.{{property.name}})
{%- else -%}
{{protocol.resolve_type(property).to_pass_type % ("in_" + property.name)}}
{{protocol.resolve_type(property).to_pass_type % ("params." + property.name)}}
{%- endif -%}
{%- endfor -%}
{%- if command.parameters -%}, {% endif -%}

View File

@ -26,8 +26,6 @@
namespace {{namespace}} {
{% endfor %}
namespace {{domain.domain}} {
// ------------- Forward and enum declarations.
{% for type in domain.types %}
{% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %}
{% if type.type == "object" %}
@ -40,6 +38,8 @@ using {{type.id}} = Object;
using {{type.id}} = {{protocol.resolve_type(type).type}};
{% endif %}
{% endfor %}
// ------------- Forward and enum declarations.
{% for type in domain.types %}
{% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %}
{% if "enum" in type %}
@ -71,11 +71,9 @@ namespace {{param.name | to_title_case}}Enum {
{% if not protocol.generate_type(domain.domain, type.id) %}{% continue %}{% endif %}
{% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %}
class {{config.protocol.export_macro}} {{type.id}} : public Serializable{% if protocol.is_exported(domain.domain, type.id) %}, public API::{{type.id}}{% endif %}{
PROTOCOL_DISALLOW_COPY({{type.id}});
class {{config.protocol.export_macro}} {{type.id}} : public ::{{config.crdtp.namespace}}::ProtocolObject<{{type.id}}>{% if protocol.is_exported(domain.domain, type.id) %},
public API::{{type.id}}{% endif %} {
public:
static std::unique_ptr<{{type.id}}> fromValue(protocol::Value* value, ErrorSupport* errors);
~{{type.id}}() override { }
{% for property in type.properties %}
{% set property_type = protocol.resolve_type(property) %}
@ -99,10 +97,6 @@ public:
void {{"set" | to_method_case}}{{property_name}}({{property_type.pass_type}} value) { {{property_field}} = {{property_type.to_rvalue % "value"}}; }
{% endfor %}
std::unique_ptr<protocol::DictionaryValue> toValue() const;
void AppendSerialized(std::vector<uint8_t>* out) const override;
std::unique_ptr<{{type.id}}> clone() const;
template<int STATE>
class {{type.id}}Builder {
public:
@ -160,6 +154,8 @@ public:
}
private:
DECLARE_SERIALIZATION_SUPPORT();
{{type.id}}()
{
{% for property in type.properties %}