Skip to content

Commit

Permalink
Allow extending the C++ standard codec (flutter#20317)
Browse files Browse the repository at this point in the history
Standard*Codec allows for extensions to support arbitrary types; this
had not previously been implemented for the C++ version.

Overview of changes:
- EncodableValue's std::variant type now allows for a new CustomEncodableValue, which is a thin wrapper around std::any, to store arbitrary extension types.
- ByteBufferStream* has been split into an interface class and the buffer-based implementation, with the former now part of the public API surface to be used in standard codec extensions.
  - They also gained utility methods for some common data types to simplify writing extensions.
- StandardCodecSerializer is now part of the public API surface, and is subclassable.
- StandardCodecSerializer's ReadValue has been split into ReadValue and ReadValueOfType to match the structure used when subclassing on the Dart side, for easier porting of custom extensions across languages.
- Standard*Codec now optionally accepts a non-default serializer in GetInstance, providing a shared instance using that serializer.

Fixes flutter/flutter#31174
  • Loading branch information
stuartmorgan authored Aug 10, 2020
1 parent 8ed1964 commit b28b1df
Show file tree
Hide file tree
Showing 15 changed files with 682 additions and 236 deletions.
5 changes: 3 additions & 2 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -835,12 +835,13 @@ FILE: ../../../flutter/shell/platform/android/surface/android_surface_mock.h
FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.cc
FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/basic_message_channel_unittests.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/byte_stream_wrappers.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/byte_buffer_streams.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/encodable_value_unittests.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/engine_method_result.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/event_channel_unittests.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/basic_message_channel.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/byte_streams.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/engine_method_result.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/event_channel.h
Expand All @@ -855,6 +856,7 @@ FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_result_functions.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registry.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_codec_serializer.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/method_call_unittests.cc
Expand All @@ -863,7 +865,6 @@ FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/method_result_fu
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/plugin_registrar.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/plugin_registrar_unittests.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/standard_codec.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/standard_codec_serializer.h
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc
FILE: ../../../flutter/shell/platform/common/cpp/incoming_message_dispatcher.cc
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/common/cpp/client_wrapper/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ executable("client_wrapper_unittests") {
"plugin_registrar_unittests.cc",
"standard_message_codec_unittests.cc",
"standard_method_codec_unittests.cc",
"testing/test_codec_extensions.cc",
"testing/test_codec_extensions.h",
]

deps = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,38 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_STREAM_WRAPPERS_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_STREAM_WRAPPERS_H_

// Utility classes for interacting with a buffer of bytes as a stream, for use
// in message channel codecs.
#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_BUFFER_STREAMS_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_BUFFER_STREAMS_H_

#include <cassert>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>

#include "include/flutter/byte_streams.h"

namespace flutter {

// Wraps an array of bytes with utility methods for treating it as a readable
// stream.
class ByteBufferStreamReader {
// Implementation of ByteStreamReader base on a byte array.
class ByteBufferStreamReader : public ByteStreamReader {
public:
// Createa a reader reading from |bytes|, which must have a length of |size|.
// |bytes| must remain valid for the lifetime of this object.
explicit ByteBufferStreamReader(const uint8_t* bytes, size_t size)
: bytes_(bytes), size_(size) {}

// Reads and returns the next byte from the stream.
uint8_t ReadByte() {
// |ByteStreamReader|
uint8_t ReadByte() override {
if (location_ >= size_) {
std::cerr << "Invalid read in StandardCodecByteStreamReader" << std::endl;
return 0;
}
return bytes_[location_++];
}

// Reads the next |length| bytes from the stream into |buffer|. The caller
// is responsible for ensuring that |buffer| is large enough.
void ReadBytes(uint8_t* buffer, size_t length) {
// |ByteStreamReader|
void ReadBytes(uint8_t* buffer, size_t length) override {
if (location_ + length > size_) {
std::cerr << "Invalid read in StandardCodecByteStreamReader" << std::endl;
return;
Expand All @@ -44,9 +42,8 @@ class ByteBufferStreamReader {
location_ += length;
}

// Advances the read cursor to the next multiple of |alignment| relative to
// the start of the wrapped byte buffer, unless it is already aligned.
void ReadAlignment(uint8_t alignment) {
// |ByteStreamReader|
void ReadAlignment(uint8_t alignment) override {
uint8_t mod = location_ % alignment;
if (mod) {
location_ += alignment - mod;
Expand All @@ -62,30 +59,26 @@ class ByteBufferStreamReader {
size_t location_ = 0;
};

// Wraps an array of bytes with utility methods for treating it as a writable
// stream.
class ByteBufferStreamWriter {
// Implementation of ByteStreamWriter based on a byte array.
class ByteBufferStreamWriter : public ByteStreamWriter {
public:
// Createa a writter that writes into |buffer|.
// Creates a writer that writes into |buffer|.
// |buffer| must remain valid for the lifetime of this object.
explicit ByteBufferStreamWriter(std::vector<uint8_t>* buffer)
: bytes_(buffer) {
assert(buffer);
}

// Writes |byte| to the wrapped buffer.
// |ByteStreamWriter|
void WriteByte(uint8_t byte) { bytes_->push_back(byte); }

// Writes the next |length| bytes from |bytes| into the wrapped buffer.
// The caller is responsible for ensuring that |buffer| is large enough.
// |ByteStreamWriter|
void WriteBytes(const uint8_t* bytes, size_t length) {
assert(length > 0);
bytes_->insert(bytes_->end(), bytes, bytes + length);
}

// Writes 0s until the next multiple of |alignment| relative to
// the start of the wrapped byte buffer, unless the write positition is
// already aligned.
// |ByteStreamWriter|
void WriteAlignment(uint8_t alignment) {
uint8_t mod = bytes_->size() % alignment;
if (mod) {
Expand All @@ -102,4 +95,4 @@ class ByteBufferStreamWriter {

} // namespace flutter

#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_STREAM_WRAPPERS_H_
#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_BYTE_BUFFER_STREAMS_H_
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ core_cpp_client_wrapper_includes =
get_path_info([
"include/flutter/basic_message_channel.h",
"include/flutter/binary_messenger.h",
"include/flutter/byte_streams.h",
"include/flutter/encodable_value.h",
"include/flutter/engine_method_result.h",
"include/flutter/event_channel.h",
Expand All @@ -20,6 +21,7 @@ core_cpp_client_wrapper_includes =
"include/flutter/method_result.h",
"include/flutter/plugin_registrar.h",
"include/flutter/plugin_registry.h",
"include/flutter/standard_codec_serializer.h",
"include/flutter/standard_message_codec.h",
"include/flutter/standard_method_codec.h",
],
Expand All @@ -29,10 +31,9 @@ core_cpp_client_wrapper_includes =
# reasonable (without forcing different kinds of clients to take unnecessary
# code) to simplify use.
core_cpp_client_wrapper_sources = get_path_info([
"byte_stream_wrappers.h",
"byte_buffer_streams.h",
"engine_method_result.cc",
"plugin_registrar.cc",
"standard_codec_serializer.h",
"standard_codec.cc",
],
"abspath")
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_BYTE_STREAMS_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_BYTE_STREAMS_H_

// Interfaces for interacting with a stream of bytes, for use in codecs.

namespace flutter {

// An interface for a class that reads from a byte stream.
class ByteStreamReader {
public:
// Reads and returns the next byte from the stream.
virtual uint8_t ReadByte() = 0;

// Reads the next |length| bytes from the stream into |buffer|. The caller
// is responsible for ensuring that |buffer| is large enough.
virtual void ReadBytes(uint8_t* buffer, size_t length) = 0;

// Advances the read cursor to the next multiple of |alignment| relative to
// the start of the stream, unless it is already aligned.
virtual void ReadAlignment(uint8_t alignment) = 0;

// Reads and returns the next 32-bit integer from the stream.
int32_t ReadInt32() {
int32_t value = 0;
ReadBytes(reinterpret_cast<uint8_t*>(&value), 4);
return value;
}

// Reads and returns the next 64-bit integer from the stream.
int64_t ReadInt64() {
int64_t value = 0;
ReadBytes(reinterpret_cast<uint8_t*>(&value), 8);
return value;
}

// Reads and returns the next 64-bit floating point number from the stream.
double ReadDouble() {
double value = 0;
ReadBytes(reinterpret_cast<uint8_t*>(&value), 8);
return value;
}
};

// An interface for a class that writes to a byte stream.
class ByteStreamWriter {
public:
// Writes |byte| to the stream.
virtual void WriteByte(uint8_t byte) = 0;

// Writes the next |length| bytes from |bytes| to the stream
virtual void WriteBytes(const uint8_t* bytes, size_t length) = 0;

// Writes 0s until the next multiple of |alignment| relative to the start
// of the stream, unless the write positition is already aligned.
virtual void WriteAlignment(uint8_t alignment) = 0;

// Writes the given 32-bit int to the stream.
void WriteInt32(int32_t value) {
WriteBytes(reinterpret_cast<const uint8_t*>(&value), 4);
}

// Writes the given 64-bit int to the stream.
void WriteInt64(int64_t value) {
WriteBytes(reinterpret_cast<const uint8_t*>(&value), 8);
}

// Writes the given 36-bit double to the stream.
void WriteDouble(double value) {
WriteBytes(reinterpret_cast<const uint8_t*>(&value), 8);
}
};

} // namespace flutter

#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_BYTE_STREAMS_H_
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_

#include <assert.h>

#include <any>
#include <cassert>
#include <cstdint>
#include <map>
#include <string>
Expand All @@ -25,6 +25,57 @@ static_assert(sizeof(double) == 8, "EncodableValue requires a 64-bit double");
// version, or it will break when the legacy version is removed.
#ifndef USE_LEGACY_ENCODABLE_VALUE

// A container for arbitrary types in EncodableValue.
//
// This is used in conjunction with StandardCodecExtension to allow using other
// types with a StandardMethodCodec/StandardMessageCodec. It is implicitly
// convertible to EncodableValue, so constructing an EncodableValue from a
// custom type can generally be written as:
// CustomEncodableValue(MyType(...))
// rather than:
// EncodableValue(CustomEncodableValue(MyType(...)))
//
// For extracting recieved custom types, it is implicitly convertible to
// std::any. For example:
// const MyType& my_type_value =
// std::any_cast<MyType>(std::get<CustomEncodableValue>(value));
//
// If RTTI is enabled, different extension types can be checked with type():
// if (custom_value->type() == typeid(SomeData)) { ... }
// Clients that wish to disable RTTI would need to decide on another approach
// for distinguishing types (e.g., in StandardCodecExtension::WriteValueOfType)
// if multiple custom types are needed. For instance, wrapping all of the
// extension types in an EncodableValue-style variant, and only ever storing
// that variant in CustomEncodableValue.
class CustomEncodableValue {
public:
explicit CustomEncodableValue(const std::any& value) : value_(value) {}
~CustomEncodableValue() = default;

// Allow implict conversion to std::any to allow direct use of any_cast.
operator std::any &() { return value_; }
operator const std::any &() const { return value_; }

#if __has_feature(cxx_rtti)
// Passthrough to std::any's type().
const std::type_info& type() const noexcept { return value_.type(); }
#endif

// This operator exists only to provide a stable ordering for use as a
// std::map key, to satisfy the compiler requirements for EncodableValue.
// It does not attempt to provide useful ordering semantics, and using a
// custom value as a map key is not recommended.
bool operator<(const CustomEncodableValue& other) const {
return this < &other;
}
bool operator==(const CustomEncodableValue& other) const {
return this == &other;
}

private:
std::any value_;
};

class EncodableValue;

// Convenience type aliases.
Expand All @@ -48,7 +99,8 @@ using EncodableValueVariant = std::variant<std::monostate,
std::vector<int64_t>,
std::vector<double>,
EncodableList,
EncodableMap>;
EncodableMap,
CustomEncodableValue>;
} // namespace internal

// An object that can contain any value or collection type supported by
Expand Down Expand Up @@ -76,7 +128,7 @@ using EncodableValueVariant = std::variant<std::monostate,
//
// The primary API surface for this object is std::variant. For instance,
// getting a string value from an EncodableValue, with type checking:
// if (std::get<std::string>(value)) {
// if (std::holds_alternative<std::string>(value)) {
// std::string some_string = std::get<std::string>(value);
// }
//
Expand All @@ -99,6 +151,13 @@ class EncodableValue : public internal::EncodableValueVariant {
return *this;
}

// Allow implicit conversion from CustomEncodableValue; the only reason to
// make a CustomEncodableValue (which can only be constructed explicitly) is
// to use it with EncodableValue, so the risk of unintended conversions is
// minimal, and it avoids the need for the verbose:
// EncodableValue(CustomEncodableValue(...)).
EncodableValue(const CustomEncodableValue& v) : super(v) {}

// Override the conversion constructors from std::variant to make them
// explicit, to avoid implicit conversion.
//
Expand Down
Loading

0 comments on commit b28b1df

Please sign in to comment.