From 81206aefb5822034699c013b3cc332b8b81b3801 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 4 Aug 2020 15:07:50 -0700 Subject: [PATCH 1/7] Allow extending the C++ standard codec Standard*Codec allows for extensions to support arbitrary types; this had not previously been implemented for the C++ version. Fixes https://github.com/flutter/flutter/issues/31174 --- ci/licenses_golden/licenses_flutter | 5 +- ...tream_wrappers.h => byte_buffer_streams.h} | 47 ++- .../cpp/client_wrapper/core_wrapper_files.gni | 5 +- .../include/flutter/byte_streams.h | 79 +++++ .../include/flutter/encodable_value.h | 57 +++- .../flutter/standard_codec_serializer.h | 76 +++++ .../include/flutter/standard_message_codec.h | 25 +- .../include/flutter/standard_method_codec.h | 27 +- .../cpp/client_wrapper/standard_codec.cc | 287 ++++++++++-------- .../standard_codec_serializer.h | 54 ---- .../standard_message_codec_unittests.cc | 150 ++++++++- .../standard_method_codec_unittests.cc | 79 +++++ 12 files changed, 655 insertions(+), 236 deletions(-) rename shell/platform/common/cpp/client_wrapper/{byte_stream_wrappers.h => byte_buffer_streams.h} (58%) create mode 100644 shell/platform/common/cpp/client_wrapper/include/flutter/byte_streams.h create mode 100644 shell/platform/common/cpp/client_wrapper/include/flutter/standard_codec_serializer.h delete mode 100644 shell/platform/common/cpp/client_wrapper/standard_codec_serializer.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2ec58e6f5ae90..37b8c4a55a914 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -834,12 +834,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 @@ -854,6 +855,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 @@ -862,7 +864,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 diff --git a/shell/platform/common/cpp/client_wrapper/byte_stream_wrappers.h b/shell/platform/common/cpp/client_wrapper/byte_buffer_streams.h similarity index 58% rename from shell/platform/common/cpp/client_wrapper/byte_stream_wrappers.h rename to shell/platform/common/cpp/client_wrapper/byte_buffer_streams.h index 96ab0b0d06210..86cdfc4551209 100644 --- a/shell/platform/common/cpp/client_wrapper/byte_stream_wrappers.h +++ b/shell/platform/common/cpp/client_wrapper/byte_buffer_streams.h @@ -2,30 +2,29 @@ // 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 #include #include #include #include +#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; @@ -33,9 +32,8 @@ class ByteBufferStreamReader { 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; @@ -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; @@ -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* 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) { @@ -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_ diff --git a/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni b/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni index 18b0897e2a987..f86d87e024313 100644 --- a/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni +++ b/shell/platform/common/cpp/client_wrapper/core_wrapper_files.gni @@ -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", @@ -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", ], @@ -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") diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/byte_streams.h b/shell/platform/common/cpp/client_wrapper/include/flutter/byte_streams.h new file mode 100644 index 0000000000000..11f9928cd027a --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/byte_streams.h @@ -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(&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(&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(&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(&value), 4); + } + + // Writes the given 64-bit int to the stream. + void WriteInt64(int64_t value) { + WriteBytes(reinterpret_cast(&value), 8); + } + + // Writes the given 36-bit double to the stream. + void WriteDouble(double value) { + WriteBytes(reinterpret_cast(&value), 8); + } +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_BYTE_STREAMS_H_ diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h index bb0265e0c49c9..628e5bc77d7cb 100644 --- a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h @@ -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 - +#include +#include #include #include #include @@ -25,6 +25,47 @@ 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(std::get(value)); +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_; } + + // Passthrough to std::any's type(). + const std::type_info& type() const noexcept { return value_.type(); } + + // 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. @@ -48,7 +89,8 @@ using EncodableValueVariant = std::variant, std::vector, EncodableList, - EncodableMap>; + EncodableMap, + CustomEncodableValue>; } // namespace internal // An object that can contain any value or collection type supported by @@ -76,7 +118,7 @@ using EncodableValueVariant = std::variant(value)) { +// if (std::holds_alternative(value)) { // std::string some_string = std::get(value); // } // @@ -99,6 +141,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. // diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/standard_codec_serializer.h b/shell/platform/common/cpp/client_wrapper/include/flutter/standard_codec_serializer.h new file mode 100644 index 0000000000000..f571b3256e360 --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/standard_codec_serializer.h @@ -0,0 +1,76 @@ +// 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_STANDARD_CODEC_SERIALIZER_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_CODEC_SERIALIZER_H_ + +#include "byte_streams.h" +#include "encodable_value.h" + +namespace flutter { + +// Encapsulates the logic for encoding/decoding EncodableValues to/from the +// standard codec binary representation. +// +// This can be subclassed to extend the standard codec with support for new +// types. +class StandardCodecSerializer { + public: + virtual ~StandardCodecSerializer(); + + // Returns the shared serializer instance. + static const StandardCodecSerializer& GetInstance(); + + // Prevent copying. + StandardCodecSerializer(StandardCodecSerializer const&) = delete; + StandardCodecSerializer& operator=(StandardCodecSerializer const&) = delete; + + // Reads and returns the next value from |stream|. + EncodableValue ReadValue(ByteStreamReader* stream) const; + + // Writes the encoding of |value| to |stream|, including the initial type + // discrimination byte. + // + // Can be overridden by a subclass to extend the codec. + virtual void WriteValue(const EncodableValue& value, + ByteStreamWriter* stream) const; + + protected: + // Codecs require long-lived serializers, so clients should always use + // GetInstance(). + StandardCodecSerializer(); + + // Reads and returns the next value from |stream|, whose discrimination byte + // was |type|. + // + // The discrimination byte will already have been read from the stream when + // this is called. + // + // Can be overridden by a subclass to extend the codec. + virtual EncodableValue ReadValueOfType(uint8_t type, + ByteStreamReader* stream) const; + + // Reads the variable-length size from the current position in |stream|. + size_t ReadSize(ByteStreamReader* stream) const; + + // Writes the variable-length size encoding to |stream|. + void WriteSize(size_t size, ByteStreamWriter* stream) const; + + private: + // Reads a fixed-type list whose values are of type T from the current + // position in |stream|, and returns it as the corresponding EncodableValue. + // |T| must correspond to one of the supported list value types of + // EncodableValue. + template + EncodableValue ReadVector(ByteStreamReader* stream) const; + + // Writes |vector| to |stream| as a fixed-type list. |T| must correspond to + // one of the supported list value types of EncodableValue. + template + void WriteVector(const std::vector vector, ByteStreamWriter* stream) const; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_CODEC_SERIALIZER_H_ diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h b/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h index 75644c7a85f31..735dcda580c7e 100644 --- a/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h @@ -5,8 +5,11 @@ #ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_MESSAGE_CODEC_H_ #define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_MESSAGE_CODEC_H_ +#include + #include "encodable_value.h" #include "message_codec.h" +#include "standard_codec_serializer.h" namespace flutter { @@ -14,8 +17,17 @@ namespace flutter { // Flutter engine via message channels. class StandardMessageCodec : public MessageCodec { public: - // Returns the shared instance of the codec. - static const StandardMessageCodec& GetInstance(); + // Returns an instance of the codec, optionally using a custom serializer to + // add support for more types. + // + // If provided, |serializer| must be long-lived. If no serializer is provided, + // the default will be used. + // + // The instance returned for a given |serializer| will be shared, and + // any instance returned from this will be long-lived, and can be safely + // passed to, e.g., channel constructors. + static const StandardMessageCodec& GetInstance( + const StandardCodecSerializer* serializer = nullptr); ~StandardMessageCodec(); @@ -24,9 +36,6 @@ class StandardMessageCodec : public MessageCodec { StandardMessageCodec& operator=(StandardMessageCodec const&) = delete; protected: - // Instances should be obtained via GetInstance. - StandardMessageCodec(); - // |flutter::MessageCodec| std::unique_ptr DecodeMessageInternal( const uint8_t* binary_message, @@ -35,6 +44,12 @@ class StandardMessageCodec : public MessageCodec { // |flutter::MessageCodec| std::unique_ptr> EncodeMessageInternal( const EncodableValue& message) const override; + + private: + // Instances should be obtained via GetInstance. + explicit StandardMessageCodec(const StandardCodecSerializer* serializer); + + const StandardCodecSerializer* serializer_; }; } // namespace flutter diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h b/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h index ef40897893183..729babc2b62b0 100644 --- a/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h @@ -5,28 +5,37 @@ #ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_METHOD_CODEC_H_ #define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_STANDARD_METHOD_CODEC_H_ +#include + #include "encodable_value.h" #include "method_call.h" #include "method_codec.h" +#include "standard_codec_serializer.h" namespace flutter { // An implementation of MethodCodec that uses a binary serialization. class StandardMethodCodec : public MethodCodec { public: - // Returns the shared instance of the codec. - static const StandardMethodCodec& GetInstance(); + // Returns an instance of the codec, optionally using a custom serializer to + // add support for more types. + // + // If provided, |serializer| must be long-lived. If no serializer is provided, + // the default will be used. + // + // The instance returned for a given |extension| will be shared, and + // any instance returned from this will be long-lived, and can be safely + // passed to, e.g., channel constructors. + static const StandardMethodCodec& GetInstance( + const StandardCodecSerializer* serializer = nullptr); - ~StandardMethodCodec() = default; + ~StandardMethodCodec(); // Prevent copying. StandardMethodCodec(StandardMethodCodec const&) = delete; StandardMethodCodec& operator=(StandardMethodCodec const&) = delete; protected: - // Instances should be obtained via GetInstance. - StandardMethodCodec() = default; - // |flutter::MethodCodec| std::unique_ptr> DecodeMethodCallInternal( const uint8_t* message, @@ -51,6 +60,12 @@ class StandardMethodCodec : public MethodCodec { const uint8_t* response, size_t response_size, MethodResult* result) const override; + + private: + // Instances should be obtained via GetInstance. + explicit StandardMethodCodec(const StandardCodecSerializer* serializer); + + const StandardCodecSerializer* serializer_; }; } // namespace flutter diff --git a/shell/platform/common/cpp/client_wrapper/standard_codec.cc b/shell/platform/common/cpp/client_wrapper/standard_codec.cc index 1f8e5375567ca..6ba13b48a67e1 100644 --- a/shell/platform/common/cpp/client_wrapper/standard_codec.cc +++ b/shell/platform/common/cpp/client_wrapper/standard_codec.cc @@ -8,17 +8,17 @@ // together to simplify use of the client wrapper, since the common case is // that any client that needs one of these files needs all three. -#include - +#include #include #include #include #include #include +#include "byte_buffer_streams.h" +#include "include/flutter/standard_codec_serializer.h" #include "include/flutter/standard_message_codec.h" #include "include/flutter/standard_method_codec.h" -#include "standard_codec_serializer.h" namespace flutter { @@ -111,75 +111,19 @@ StandardCodecSerializer::StandardCodecSerializer() = default; StandardCodecSerializer::~StandardCodecSerializer() = default; +const StandardCodecSerializer& StandardCodecSerializer::GetInstance() { + static StandardCodecSerializer sInstance; + return sInstance; +}; + EncodableValue StandardCodecSerializer::ReadValue( - ByteBufferStreamReader* stream) const { - EncodedType type = static_cast(stream->ReadByte()); - switch (type) { - case EncodedType::kNull: - return EncodableValue(); - case EncodedType::kTrue: - return EncodableValue(true); - case EncodedType::kFalse: - return EncodableValue(false); - case EncodedType::kInt32: { - int32_t int_value = 0; - stream->ReadBytes(reinterpret_cast(&int_value), 4); - return EncodableValue(int_value); - } - case EncodedType::kInt64: { - int64_t long_value = 0; - stream->ReadBytes(reinterpret_cast(&long_value), 8); - return EncodableValue(long_value); - } - case EncodedType::kFloat64: { - double double_value = 0; - stream->ReadAlignment(8); - stream->ReadBytes(reinterpret_cast(&double_value), 8); - return EncodableValue(double_value); - } - case EncodedType::kLargeInt: - case EncodedType::kString: { - size_t size = ReadSize(stream); - std::string string_value; - string_value.resize(size); - stream->ReadBytes(reinterpret_cast(&string_value[0]), size); - return EncodableValue(string_value); - } - case EncodedType::kUInt8List: - return ReadVector(stream); - case EncodedType::kInt32List: - return ReadVector(stream); - case EncodedType::kInt64List: - return ReadVector(stream); - case EncodedType::kFloat64List: - return ReadVector(stream); - case EncodedType::kList: { - size_t length = ReadSize(stream); - EncodableList list_value; - list_value.reserve(length); - for (size_t i = 0; i < length; ++i) { - list_value.push_back(ReadValue(stream)); - } - return EncodableValue(list_value); - } - case EncodedType::kMap: { - size_t length = ReadSize(stream); - EncodableMap map_value; - for (size_t i = 0; i < length; ++i) { - EncodableValue key = ReadValue(stream); - EncodableValue value = ReadValue(stream); - map_value.emplace(std::move(key), std::move(value)); - } - return EncodableValue(map_value); - } - } - std::cerr << "Unknown type in StandardCodecSerializer::ReadValue: " - << static_cast(type) << std::endl; - return EncodableValue(); + ByteStreamReader* stream) const { + uint8_t type = stream->ReadByte(); + return ReadValueOfType(type, stream); } void StandardCodecSerializer::WriteValue(const EncodableValue& value, - ByteBufferStreamWriter* stream) const { + ByteStreamWriter* stream) const { stream->WriteByte(static_cast(EncodedTypeForValue(value))); #ifdef USE_LEGACY_ENCODABLE_VALUE switch (value.type()) { @@ -187,22 +131,16 @@ void StandardCodecSerializer::WriteValue(const EncodableValue& value, case EncodableValue::Type::kBool: // Null and bool are encoded directly in the type. break; - case EncodableValue::Type::kInt: { - int32_t int_value = value.IntValue(); - stream->WriteBytes(reinterpret_cast(&int_value), 4); + case EncodableValue::Type::kInt: + stream->WriteInt32(std::get(value)); break; - } - case EncodableValue::Type::kLong: { - int64_t long_value = value.LongValue(); - stream->WriteBytes(reinterpret_cast(&long_value), 8); + case case EncodableValue::Type::kLong: + stream->WriteInt64(std::get(value)); break; - } - case EncodableValue::Type::kDouble: { + case EncodableValue::Type::kDouble: stream->WriteAlignment(8); - double double_value = value.DoubleValue(); - stream->WriteBytes(reinterpret_cast(&double_value), 8); + stream->WriteDouble(std::get(value)); break; - } case EncodableValue::Type::kString: { const auto& string_value = value.StringValue(); size_t size = string_value.size(); @@ -246,22 +184,16 @@ void StandardCodecSerializer::WriteValue(const EncodableValue& value, case 1: // Null and bool are encoded directly in the type. break; - case 2: { - int32_t int_value = std::get(value); - stream->WriteBytes(reinterpret_cast(&int_value), 4); + case 2: + stream->WriteInt32(std::get(value)); break; - } - case 3: { - int64_t long_value = std::get(value); - stream->WriteBytes(reinterpret_cast(&long_value), 8); + case 3: + stream->WriteInt64(std::get(value)); break; - } - case 4: { + case 4: stream->WriteAlignment(8); - double double_value = std::get(value); - stream->WriteBytes(reinterpret_cast(&double_value), 8); + stream->WriteDouble(std::get(value)); break; - } case 5: { const auto& string_value = std::get(value); size_t size = string_value.size(); @@ -301,11 +233,74 @@ void StandardCodecSerializer::WriteValue(const EncodableValue& value, } break; } + case 12: + std::cerr + << "Unhandled custom type in StandardCodecSerializer::WriteValue. " + << "Custom types require codec extensions." << std::endl; + break; } #endif } -size_t StandardCodecSerializer::ReadSize(ByteBufferStreamReader* stream) const { +EncodableValue StandardCodecSerializer::ReadValueOfType( + uint8_t type, + ByteStreamReader* stream) const { + switch (static_cast(type)) { + case EncodedType::kNull: + return EncodableValue(); + case EncodedType::kTrue: + return EncodableValue(true); + case EncodedType::kFalse: + return EncodableValue(false); + case EncodedType::kInt32: + return EncodableValue(stream->ReadInt32()); + case EncodedType::kInt64: + return EncodableValue(stream->ReadInt64()); + case EncodedType::kFloat64: + stream->ReadAlignment(8); + return EncodableValue(stream->ReadDouble()); + case EncodedType::kLargeInt: + case EncodedType::kString: { + size_t size = ReadSize(stream); + std::string string_value; + string_value.resize(size); + stream->ReadBytes(reinterpret_cast(&string_value[0]), size); + return EncodableValue(string_value); + } + case EncodedType::kUInt8List: + return ReadVector(stream); + case EncodedType::kInt32List: + return ReadVector(stream); + case EncodedType::kInt64List: + return ReadVector(stream); + case EncodedType::kFloat64List: + return ReadVector(stream); + case EncodedType::kList: { + size_t length = ReadSize(stream); + EncodableList list_value; + list_value.reserve(length); + for (size_t i = 0; i < length; ++i) { + list_value.push_back(ReadValue(stream)); + } + return EncodableValue(list_value); + } + case EncodedType::kMap: { + size_t length = ReadSize(stream); + EncodableMap map_value; + for (size_t i = 0; i < length; ++i) { + EncodableValue key = ReadValue(stream); + EncodableValue value = ReadValue(stream); + map_value.emplace(std::move(key), std::move(value)); + } + return EncodableValue(map_value); + } + } + std::cerr << "Unknown type in StandardCodecSerializer::ReadValueOfType: " + << static_cast(type) << std::endl; + return EncodableValue(); +} + +size_t StandardCodecSerializer::ReadSize(ByteStreamReader* stream) const { uint8_t byte = stream->ReadByte(); if (byte < 254) { return byte; @@ -321,7 +316,7 @@ size_t StandardCodecSerializer::ReadSize(ByteBufferStreamReader* stream) const { } void StandardCodecSerializer::WriteSize(size_t size, - ByteBufferStreamWriter* stream) const { + ByteStreamWriter* stream) const { if (size < 254) { stream->WriteByte(static_cast(size)); } else if (size <= 0xffff) { @@ -337,7 +332,7 @@ void StandardCodecSerializer::WriteSize(size_t size, template EncodableValue StandardCodecSerializer::ReadVector( - ByteBufferStreamReader* stream) const { + ByteStreamReader* stream) const { size_t count = ReadSize(stream); std::vector vector; vector.resize(count); @@ -351,9 +346,8 @@ EncodableValue StandardCodecSerializer::ReadVector( } template -void StandardCodecSerializer::WriteVector( - const std::vector vector, - ByteBufferStreamWriter* stream) const { +void StandardCodecSerializer::WriteVector(const std::vector vector, + ByteStreamWriter* stream) const { size_t count = vector.size(); WriteSize(count, stream); if (count == 0) { @@ -370,59 +364,92 @@ void StandardCodecSerializer::WriteVector( // ===== standard_message_codec.h ===== // static -const StandardMessageCodec& StandardMessageCodec::GetInstance() { - static StandardMessageCodec sInstance; - return sInstance; +const StandardMessageCodec& StandardMessageCodec::GetInstance( + const StandardCodecSerializer* serializer) { + if (!serializer) { + serializer = &StandardCodecSerializer::GetInstance(); + } + auto* sInstances = new std::map>; + auto it = sInstances->find(serializer); + if (it == sInstances->end()) { + // Uses new due to private constructor (to prevent API clients from + // accidentally passing temporary codec instances to channels). + auto emplace_result = sInstances->emplace( + serializer, std::unique_ptr( + new StandardMessageCodec(serializer))); + it = emplace_result.first; + } + return *(it->second); } -StandardMessageCodec::StandardMessageCodec() = default; +StandardMessageCodec::StandardMessageCodec( + const StandardCodecSerializer* serializer) + : serializer_(serializer) {} StandardMessageCodec::~StandardMessageCodec() = default; std::unique_ptr StandardMessageCodec::DecodeMessageInternal( const uint8_t* binary_message, size_t message_size) const { - StandardCodecSerializer serializer; ByteBufferStreamReader stream(binary_message, message_size); - return std::make_unique(serializer.ReadValue(&stream)); + return std::make_unique(serializer_->ReadValue(&stream)); } std::unique_ptr> StandardMessageCodec::EncodeMessageInternal( const EncodableValue& message) const { - StandardCodecSerializer serializer; auto encoded = std::make_unique>(); ByteBufferStreamWriter stream(encoded.get()); - serializer.WriteValue(message, &stream); + serializer_->WriteValue(message, &stream); return encoded; } // ===== standard_method_codec.h ===== // static -const StandardMethodCodec& StandardMethodCodec::GetInstance() { - static StandardMethodCodec sInstance; - return sInstance; +const StandardMethodCodec& StandardMethodCodec::GetInstance( + const StandardCodecSerializer* serializer) { + if (!serializer) { + serializer = &StandardCodecSerializer::GetInstance(); + } + auto* sInstances = new std::map>; + auto it = sInstances->find(serializer); + if (it == sInstances->end()) { + // Uses new due to private constructor (to prevent API clients from + // accidentally passing temporary codec instances to channels). + auto emplace_result = sInstances->emplace( + serializer, std::unique_ptr( + new StandardMethodCodec(serializer))); + it = emplace_result.first; + } + return *(it->second); } +StandardMethodCodec::StandardMethodCodec( + const StandardCodecSerializer* serializer) + : serializer_(serializer) {} + +StandardMethodCodec::~StandardMethodCodec() = default; + std::unique_ptr> StandardMethodCodec::DecodeMethodCallInternal(const uint8_t* message, size_t message_size) const { - StandardCodecSerializer serializer; ByteBufferStreamReader stream(message, message_size); #ifdef USE_LEGACY_ENCODABLE_VALUE - EncodableValue method_name = serializer.ReadValue(&stream); + EncodableValue method_name = serializer_->ReadValue(&stream); if (!method_name.IsString()) { std::cerr << "Invalid method call; method name is not a string." << std::endl; return nullptr; } auto arguments = - std::make_unique(serializer.ReadValue(&stream)); + std::make_unique(serializer_->ReadValue(&stream)); return std::make_unique>(method_name.StringValue(), std::move(arguments)); #else - EncodableValue method_name_value = serializer.ReadValue(&stream); + EncodableValue method_name_value = serializer_->ReadValue(&stream); const auto* method_name = std::get_if(&method_name_value); if (!method_name) { std::cerr << "Invalid method call; method name is not a string." @@ -430,7 +457,7 @@ StandardMethodCodec::DecodeMethodCallInternal(const uint8_t* message, return nullptr; } auto arguments = - std::make_unique(serializer.ReadValue(&stream)); + std::make_unique(serializer_->ReadValue(&stream)); return std::make_unique>(*method_name, std::move(arguments)); #endif @@ -439,14 +466,13 @@ StandardMethodCodec::DecodeMethodCallInternal(const uint8_t* message, std::unique_ptr> StandardMethodCodec::EncodeMethodCallInternal( const MethodCall& method_call) const { - StandardCodecSerializer serializer; auto encoded = std::make_unique>(); ByteBufferStreamWriter stream(encoded.get()); - serializer.WriteValue(EncodableValue(method_call.method_name()), &stream); + serializer_->WriteValue(EncodableValue(method_call.method_name()), &stream); if (method_call.arguments()) { - serializer.WriteValue(*method_call.arguments(), &stream); + serializer_->WriteValue(*method_call.arguments(), &stream); } else { - serializer.WriteValue(EncodableValue(), &stream); + serializer_->WriteValue(EncodableValue(), &stream); } return encoded; } @@ -454,14 +480,13 @@ StandardMethodCodec::EncodeMethodCallInternal( std::unique_ptr> StandardMethodCodec::EncodeSuccessEnvelopeInternal( const EncodableValue* result) const { - StandardCodecSerializer serializer; auto encoded = std::make_unique>(); ByteBufferStreamWriter stream(encoded.get()); stream.WriteByte(0); if (result) { - serializer.WriteValue(*result, &stream); + serializer_->WriteValue(*result, &stream); } else { - serializer.WriteValue(EncodableValue(), &stream); + serializer_->WriteValue(EncodableValue(), &stream); } return encoded; } @@ -471,20 +496,19 @@ StandardMethodCodec::EncodeErrorEnvelopeInternal( const std::string& error_code, const std::string& error_message, const EncodableValue* error_details) const { - StandardCodecSerializer serializer; auto encoded = std::make_unique>(); ByteBufferStreamWriter stream(encoded.get()); stream.WriteByte(1); - serializer.WriteValue(EncodableValue(error_code), &stream); + serializer_->WriteValue(EncodableValue(error_code), &stream); if (error_message.empty()) { - serializer.WriteValue(EncodableValue(), &stream); + serializer_->WriteValue(EncodableValue(), &stream); } else { - serializer.WriteValue(EncodableValue(error_message), &stream); + serializer_->WriteValue(EncodableValue(error_message), &stream); } if (error_details) { - serializer.WriteValue(*error_details, &stream); + serializer_->WriteValue(*error_details, &stream); } else { - serializer.WriteValue(EncodableValue(), &stream); + serializer_->WriteValue(EncodableValue(), &stream); } return encoded; } @@ -493,19 +517,18 @@ bool StandardMethodCodec::DecodeAndProcessResponseEnvelopeInternal( const uint8_t* response, size_t response_size, MethodResult* result) const { - StandardCodecSerializer serializer; ByteBufferStreamReader stream(response, response_size); uint8_t flag = stream.ReadByte(); switch (flag) { case 0: { - EncodableValue value = serializer.ReadValue(&stream); + EncodableValue value = serializer_->ReadValue(&stream); result->Success(value.IsNull() ? nullptr : &value); return true; } case 1: { - EncodableValue code = serializer.ReadValue(&stream); - EncodableValue message = serializer.ReadValue(&stream); - EncodableValue details = serializer.ReadValue(&stream); + EncodableValue code = serializer_->ReadValue(&stream); + EncodableValue message = serializer_->ReadValue(&stream); + EncodableValue details = serializer_->ReadValue(&stream); #ifdef USE_LEGACY_ENCODABLE_VALUE result->Error(code.StringValue(), message.IsNull() ? "" : message.StringValue(), diff --git a/shell/platform/common/cpp/client_wrapper/standard_codec_serializer.h b/shell/platform/common/cpp/client_wrapper/standard_codec_serializer.h deleted file mode 100644 index 89aab3b9988f8..0000000000000 --- a/shell/platform/common/cpp/client_wrapper/standard_codec_serializer.h +++ /dev/null @@ -1,54 +0,0 @@ -// 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_ENCODABLE_VALUE_SERIALIZER_H_ -#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_ENCODABLE_VALUE_SERIALIZER_H_ - -#include "byte_stream_wrappers.h" -#include "include/flutter/encodable_value.h" - -namespace flutter { - -// Encapsulates the logic for encoding/decoding EncodableValues to/from the -// standard codec binary representation. -class StandardCodecSerializer { - public: - StandardCodecSerializer(); - ~StandardCodecSerializer(); - - // Prevent copying. - StandardCodecSerializer(StandardCodecSerializer const&) = delete; - StandardCodecSerializer& operator=(StandardCodecSerializer const&) = delete; - - // Reads and returns the next value from |stream|. - EncodableValue ReadValue(ByteBufferStreamReader* stream) const; - - // Writes the encoding of |value| to |stream|. - void WriteValue(const EncodableValue& value, - ByteBufferStreamWriter* stream) const; - - protected: - // Reads the variable-length size from the current position in |stream|. - size_t ReadSize(ByteBufferStreamReader* stream) const; - - // Writes the variable-length size encoding to |stream|. - void WriteSize(size_t size, ByteBufferStreamWriter* stream) const; - - // Reads a fixed-type list whose values are of type T from the current - // position in |stream|, and returns it as the corresponding EncodableValue. - // |T| must correspond to one of the support list value types of - // EncodableValue. - template - EncodableValue ReadVector(ByteBufferStreamReader* stream) const; - - // Writes |vector| to |stream| as a fixed-type list. |T| must correspond to - // one of the support list value types of EncodableValue. - template - void WriteVector(const std::vector vector, - ByteBufferStreamWriter* stream) const; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_ENCODABLE_VALUE_SERIALIZER_H_ diff --git a/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc b/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc index 16228057298cd..1d1b8b8a73c9f 100644 --- a/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc +++ b/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc @@ -12,15 +12,28 @@ namespace flutter { // Validates round-trip encoding and decoding of |value|, and checks that the // encoded value matches |expected_encoding|. -static void CheckEncodeDecode(const EncodableValue& value, - const std::vector& expected_encoding) { - const StandardMessageCodec& codec = StandardMessageCodec::GetInstance(); +// +// If testing with CustomEncodableValues, |serializer| must be provided to +// handle the encoding/decoding, and |custom_comparator| must be provided to +// validate equality since CustomEncodableValue doesn't define a useful ==. +static void CheckEncodeDecode( + const EncodableValue& value, + const std::vector& expected_encoding, + const StandardCodecSerializer* serializer = nullptr, + std::function + custom_comparator = nullptr) { + const StandardMessageCodec& codec = + StandardMessageCodec::GetInstance(serializer); auto encoded = codec.EncodeMessage(value); ASSERT_TRUE(encoded); EXPECT_EQ(*encoded, expected_encoding); auto decoded = codec.DecodeMessage(*encoded); - EXPECT_EQ(value, *decoded); + if (custom_comparator) { + EXPECT_TRUE(custom_comparator(value, *decoded)); + } else { + EXPECT_EQ(value, *decoded); + } } // Validates round-trip encoding and decoding of |value|, and checks that the @@ -168,4 +181,133 @@ TEST(StandardMessageCodec, CanEncodeAndDecodeFloat64Array) { CheckEncodeDecode(value, bytes); } +namespace { + +// A representation of a point, for custom type testing. +class Point { + public: + Point(int x, int y) : x_(x), y_(y) {} + ~Point() = default; + + int x() const { return x_; } + int y() const { return y_; } + + bool operator==(const Point& other) const { + return x_ == other.x_ && y_ == other.y_; + } + + private: + int x_; + int y_; +}; + +// A typed binary data object with extra fields, for custom type testing. +class SomeData { + public: + SomeData(const std::string label, const std::vector& data) + : label_(label), data_(data) {} + ~SomeData() = default; + + const std::string& label() const { return label_; } + const std::vector& data() const { return data_; } + + private: + std::string label_; + std::vector data_; +}; + +// Codec extension for EncodablePoint. +class TestCodecSerializer : public StandardCodecSerializer { + public: + TestCodecSerializer() = default; + virtual ~TestCodecSerializer() = default; + + static const TestCodecSerializer& GetInstance() { + static TestCodecSerializer sInstance; + return sInstance; + } + + // |TestCodecSerializer| + EncodableValue ReadValueOfType(uint8_t type, + ByteStreamReader* stream) const override { + if (type == kPointType) { + int32_t x = stream->ReadInt32(); + int32_t y = stream->ReadInt32(); + return CustomEncodableValue(Point(x, y)); + } else if (type == kSomeDataType) { + size_t size = ReadSize(stream); + std::vector data; + data.resize(size); + stream->ReadBytes(data.data(), size); + EncodableValue label = ReadValue(stream); + return CustomEncodableValue(SomeData(std::get(label), data)); + } + return StandardCodecSerializer::ReadValueOfType(type, stream); + } + + // |TestCodecSerializer| + void WriteValue(const EncodableValue& value, + ByteStreamWriter* stream) const override { + auto custom_value = std::get_if(&value); + if (!custom_value) { + StandardCodecSerializer::WriteValue(value, stream); + return; + } + if (custom_value->type() == typeid(Point)) { + stream->WriteByte(kPointType); + const Point& point = std::any_cast(*custom_value); + stream->WriteInt32(point.x()); + stream->WriteInt32(point.y()); + } else if (custom_value->type() == typeid(SomeData)) { + stream->WriteByte(kSomeDataType); + const SomeData& some_data = std::any_cast(*custom_value); + size_t data_size = some_data.data().size(); + WriteSize(data_size, stream); + stream->WriteBytes(some_data.data().data(), data_size); + WriteValue(EncodableValue(some_data.label()), stream); + } + } + + private: + static constexpr uint8_t kPointType = 128; + static constexpr uint8_t kSomeDataType = 129; +}; + +} // namespace + +TEST(StandardMessageCodec, CanEncodeAndDecodeSimpleCustomType) { + std::vector bytes = {0x80, 0x09, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00}; + auto point_comparator = [](const EncodableValue& a, const EncodableValue& b) { + const Point& a_point = + std::any_cast(std::get(a)); + const Point& b_point = + std::any_cast(std::get(b)); + return a_point == b_point; + }; + CheckEncodeDecode(CustomEncodableValue(Point(9, 16)), bytes, + &TestCodecSerializer::GetInstance(), point_comparator); +} + +TEST(StandardMessageCodec, CanEncodeAndDecodeVariableLengthCustomType) { + std::vector bytes = { + 0x81, // custom type + 0x06, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, // data + 0x07, 0x04, // string type and length + 0x74, 0x65, 0x73, 0x74 // string characters + }; + auto some_data_comparator = [](const EncodableValue& a, + const EncodableValue& b) { + const SomeData& data_a = + std::any_cast(std::get(a)); + const SomeData& data_b = + std::any_cast(std::get(b)); + return data_a.data() == data_b.data() && data_a.label() == data_b.label(); + }; + CheckEncodeDecode(CustomEncodableValue( + SomeData("test", {0x00, 0x01, 0x02, 0x03, 0x04, 0x05})), + bytes, &TestCodecSerializer::GetInstance(), + some_data_comparator); +} + } // namespace flutter diff --git a/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc b/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc index 6d0978772ca84..c7d880d735c92 100644 --- a/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc +++ b/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc @@ -158,4 +158,83 @@ TEST(StandardMethodCodec, HandlesErrorEnvelopesWithDetails) { EXPECT_TRUE(decoded_successfully); } +namespace { + +// A representation of a point, for custom type testing. +class Point { + public: + Point(int x, int y) : x_(x), y_(y) {} + ~Point() = default; + + int x() const { return x_; } + int y() const { return y_; } + + bool operator==(const Point& other) const { + return x_ == other.x_ && y_ == other.y_; + } + + private: + int x_; + int y_; +}; + +// Codec extension for Point. +class TestCodecSerializer : public StandardCodecSerializer { + public: + TestCodecSerializer() = default; + virtual ~TestCodecSerializer() = default; + + static const TestCodecSerializer& GetInstance() { + static TestCodecSerializer sInstance; + return sInstance; + } + + // |StandardCodecSerializer| + EncodableValue ReadValueOfType(uint8_t type, + ByteStreamReader* stream) const override { + if (type == kPointType) { + int32_t x = stream->ReadInt32(); + int32_t y = stream->ReadInt32(); + return CustomEncodableValue(Point(x, y)); + } + return StandardCodecSerializer::ReadValueOfType(type, stream); + } + + // |StandardCodecSerializer| + void WriteValue(const EncodableValue& value, + ByteStreamWriter* stream) const override { + auto custom_value = std::get_if(&value); + if (!custom_value) { + StandardCodecSerializer::WriteValue(value, stream); + return; + } + stream->WriteByte(kPointType); + const Point& point = std::any_cast(*custom_value); + stream->WriteInt32(point.x()); + stream->WriteInt32(point.y()); + } + + private: + static constexpr uint8_t kPointType = 128; +}; + +} // namespace + +TEST(StandardMethodCodec, HandlesCustomTypeArguments) { + const StandardMethodCodec& codec = + StandardMethodCodec::GetInstance(&TestCodecSerializer::GetInstance()); + Point point(7, 9); + MethodCall call( + "hello", std::make_unique(CustomEncodableValue(point))); + auto encoded = codec.EncodeMethodCall(call); + ASSERT_NE(encoded.get(), nullptr); + std::unique_ptr> decoded = + codec.DecodeMethodCall(*encoded); + ASSERT_NE(decoded.get(), nullptr); + + const Point& decoded_point = std::any_cast( + std::get(*decoded->arguments())); + EXPECT_EQ(point, decoded_point); +}; + } // namespace flutter From 2d78fb1a583e08b4cbc375ab169be0e05572239d Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sat, 8 Aug 2020 14:56:38 -0700 Subject: [PATCH 2/7] Update to satisfy bot clang-format --- .../cpp/client_wrapper/include/flutter/encodable_value.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h index 628e5bc77d7cb..384edd062df53 100644 --- a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h @@ -45,8 +45,8 @@ class CustomEncodableValue { ~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_; } + operator std::any &() { return value_; } + operator const std::any &() const { return value_; } // Passthrough to std::any's type(). const std::type_info& type() const noexcept { return value_.type(); } From d2dfd33b1af946e4d87499df68fcbcb41ac583cd Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 10 Aug 2020 10:01:53 -0700 Subject: [PATCH 3/7] Conditionalize type() passthrough on RTTI, and enable RTTI for unit tests. --- .../common/cpp/client_wrapper/BUILD.gn | 32 ++++++++++++++++--- .../include/flutter/encodable_value.h | 2 ++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/shell/platform/common/cpp/client_wrapper/BUILD.gn b/shell/platform/common/cpp/client_wrapper/BUILD.gn index ade14b76f713e..b4e7c37502f5b 100644 --- a/shell/platform/common/cpp/client_wrapper/BUILD.gn +++ b/shell/platform/common/cpp/client_wrapper/BUILD.gn @@ -10,11 +10,31 @@ source_set("client_wrapper") { sources = core_cpp_client_wrapper_sources public = core_cpp_client_wrapper_includes - deps = [ "//flutter/shell/platform/common/cpp:common_cpp_library_headers" ] + deps = [ + "//flutter/shell/platform/common/cpp:common_cpp_library_headers", + ] + + configs += + [ "//flutter/shell/platform/common/cpp:desktop_library_implementation" ] + + public_configs = + [ "//flutter/shell/platform/common/cpp:relative_flutter_library_headers" ] +} + +# Client library with RTTI enabled for unit tests of CustomEncodableValue. +source_set("client_wrapper_with_rtti") { + sources = core_cpp_client_wrapper_sources + public = core_cpp_client_wrapper_includes + + deps = [ + "//flutter/shell/platform/common/cpp:common_cpp_library_headers", + ] configs += [ "//flutter/shell/platform/common/cpp:desktop_library_implementation" ] + configs -= [ "//build/config/compiler:no_rtti" ] + public_configs = [ "//flutter/shell/platform/common/cpp:relative_flutter_library_headers" ] } @@ -27,8 +47,9 @@ source_set("client_wrapper_library_stubs") { defines = [ "FLUTTER_DESKTOP_LIBRARY" ] - public_deps = - [ "//flutter/shell/platform/common/cpp:common_cpp_library_headers" ] + public_deps = [ + "//flutter/shell/platform/common/cpp:common_cpp_library_headers", + ] } test_fixtures("client_wrapper_fixtures") { @@ -51,9 +72,9 @@ executable("client_wrapper_unittests") { ] deps = [ - ":client_wrapper", ":client_wrapper_fixtures", ":client_wrapper_library_stubs", + ":client_wrapper_with_rtti", "//flutter/testing", # TODO(chunhtai): Consider refactoring flutter_root/testing so that there's a testing @@ -62,5 +83,8 @@ executable("client_wrapper_unittests") { "//third_party/dart/runtime:libdart_jit", ] + # Allow testing the RTTI-enabled features of CustomEncodableValue. + configs -= [ "//build/config/compiler:no_rtti" ] + defines = [ "FLUTTER_DESKTOP_LIBRARY" ] } diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h index 384edd062df53..b010c606ab219 100644 --- a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h @@ -48,8 +48,10 @@ class CustomEncodableValue { 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. From 1279a8ddf9bf43cc13097bc8613968c2e3568a46 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 10 Aug 2020 10:19:02 -0700 Subject: [PATCH 4/7] Reformat with correct gn binary --- shell/platform/common/cpp/client_wrapper/BUILD.gn | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/shell/platform/common/cpp/client_wrapper/BUILD.gn b/shell/platform/common/cpp/client_wrapper/BUILD.gn index b4e7c37502f5b..39cb497193081 100644 --- a/shell/platform/common/cpp/client_wrapper/BUILD.gn +++ b/shell/platform/common/cpp/client_wrapper/BUILD.gn @@ -10,9 +10,7 @@ source_set("client_wrapper") { sources = core_cpp_client_wrapper_sources public = core_cpp_client_wrapper_includes - deps = [ - "//flutter/shell/platform/common/cpp:common_cpp_library_headers", - ] + deps = [ "//flutter/shell/platform/common/cpp:common_cpp_library_headers" ] configs += [ "//flutter/shell/platform/common/cpp:desktop_library_implementation" ] @@ -26,9 +24,7 @@ source_set("client_wrapper_with_rtti") { sources = core_cpp_client_wrapper_sources public = core_cpp_client_wrapper_includes - deps = [ - "//flutter/shell/platform/common/cpp:common_cpp_library_headers", - ] + deps = [ "//flutter/shell/platform/common/cpp:common_cpp_library_headers" ] configs += [ "//flutter/shell/platform/common/cpp:desktop_library_implementation" ] @@ -47,9 +43,8 @@ source_set("client_wrapper_library_stubs") { defines = [ "FLUTTER_DESKTOP_LIBRARY" ] - public_deps = [ - "//flutter/shell/platform/common/cpp:common_cpp_library_headers", - ] + public_deps = + [ "//flutter/shell/platform/common/cpp:common_cpp_library_headers" ] } test_fixtures("client_wrapper_fixtures") { From ff55c8e6f3981088ec22b3f0f89a7e23c3b7736b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 10 Aug 2020 11:12:27 -0700 Subject: [PATCH 5/7] Undo the partial enabling of RTTI; mixing with non-RTTI test superclasses doesn't work. --- .../common/cpp/client_wrapper/BUILD.gn | 23 +---- .../include/flutter/encodable_value.h | 8 ++ .../standard_message_codec_unittests.cc | 99 +------------------ .../standard_method_codec_unittests.cc | 67 +------------ 4 files changed, 17 insertions(+), 180 deletions(-) diff --git a/shell/platform/common/cpp/client_wrapper/BUILD.gn b/shell/platform/common/cpp/client_wrapper/BUILD.gn index 39cb497193081..268424b18e4a2 100644 --- a/shell/platform/common/cpp/client_wrapper/BUILD.gn +++ b/shell/platform/common/cpp/client_wrapper/BUILD.gn @@ -19,22 +19,6 @@ source_set("client_wrapper") { [ "//flutter/shell/platform/common/cpp:relative_flutter_library_headers" ] } -# Client library with RTTI enabled for unit tests of CustomEncodableValue. -source_set("client_wrapper_with_rtti") { - sources = core_cpp_client_wrapper_sources - public = core_cpp_client_wrapper_includes - - deps = [ "//flutter/shell/platform/common/cpp:common_cpp_library_headers" ] - - configs += - [ "//flutter/shell/platform/common/cpp:desktop_library_implementation" ] - - configs -= [ "//build/config/compiler:no_rtti" ] - - public_configs = - [ "//flutter/shell/platform/common/cpp:relative_flutter_library_headers" ] -} - source_set("client_wrapper_library_stubs") { sources = [ "testing/stub_flutter_api.cc", @@ -64,12 +48,14 @@ 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 = [ ":client_wrapper_fixtures", ":client_wrapper_library_stubs", - ":client_wrapper_with_rtti", + ":client_wrapper", "//flutter/testing", # TODO(chunhtai): Consider refactoring flutter_root/testing so that there's a testing @@ -78,8 +64,5 @@ executable("client_wrapper_unittests") { "//third_party/dart/runtime:libdart_jit", ] - # Allow testing the RTTI-enabled features of CustomEncodableValue. - configs -= [ "//build/config/compiler:no_rtti" ] - defines = [ "FLUTTER_DESKTOP_LIBRARY" ] } diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h index b010c606ab219..91ac03d54781c 100644 --- a/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h @@ -39,6 +39,14 @@ static_assert(sizeof(double) == 8, "EncodableValue requires a 64-bit double"); // std::any. For example: // const MyType& my_type_value = // std::any_cast(std::get(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) {} diff --git a/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc b/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc index 1d1b8b8a73c9f..3d8c077133eab 100644 --- a/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc +++ b/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc @@ -6,6 +6,7 @@ #include #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.h" #include "gtest/gtest.h" namespace flutter { @@ -181,100 +182,6 @@ TEST(StandardMessageCodec, CanEncodeAndDecodeFloat64Array) { CheckEncodeDecode(value, bytes); } -namespace { - -// A representation of a point, for custom type testing. -class Point { - public: - Point(int x, int y) : x_(x), y_(y) {} - ~Point() = default; - - int x() const { return x_; } - int y() const { return y_; } - - bool operator==(const Point& other) const { - return x_ == other.x_ && y_ == other.y_; - } - - private: - int x_; - int y_; -}; - -// A typed binary data object with extra fields, for custom type testing. -class SomeData { - public: - SomeData(const std::string label, const std::vector& data) - : label_(label), data_(data) {} - ~SomeData() = default; - - const std::string& label() const { return label_; } - const std::vector& data() const { return data_; } - - private: - std::string label_; - std::vector data_; -}; - -// Codec extension for EncodablePoint. -class TestCodecSerializer : public StandardCodecSerializer { - public: - TestCodecSerializer() = default; - virtual ~TestCodecSerializer() = default; - - static const TestCodecSerializer& GetInstance() { - static TestCodecSerializer sInstance; - return sInstance; - } - - // |TestCodecSerializer| - EncodableValue ReadValueOfType(uint8_t type, - ByteStreamReader* stream) const override { - if (type == kPointType) { - int32_t x = stream->ReadInt32(); - int32_t y = stream->ReadInt32(); - return CustomEncodableValue(Point(x, y)); - } else if (type == kSomeDataType) { - size_t size = ReadSize(stream); - std::vector data; - data.resize(size); - stream->ReadBytes(data.data(), size); - EncodableValue label = ReadValue(stream); - return CustomEncodableValue(SomeData(std::get(label), data)); - } - return StandardCodecSerializer::ReadValueOfType(type, stream); - } - - // |TestCodecSerializer| - void WriteValue(const EncodableValue& value, - ByteStreamWriter* stream) const override { - auto custom_value = std::get_if(&value); - if (!custom_value) { - StandardCodecSerializer::WriteValue(value, stream); - return; - } - if (custom_value->type() == typeid(Point)) { - stream->WriteByte(kPointType); - const Point& point = std::any_cast(*custom_value); - stream->WriteInt32(point.x()); - stream->WriteInt32(point.y()); - } else if (custom_value->type() == typeid(SomeData)) { - stream->WriteByte(kSomeDataType); - const SomeData& some_data = std::any_cast(*custom_value); - size_t data_size = some_data.data().size(); - WriteSize(data_size, stream); - stream->WriteBytes(some_data.data().data(), data_size); - WriteValue(EncodableValue(some_data.label()), stream); - } - } - - private: - static constexpr uint8_t kPointType = 128; - static constexpr uint8_t kSomeDataType = 129; -}; - -} // namespace - TEST(StandardMessageCodec, CanEncodeAndDecodeSimpleCustomType) { std::vector bytes = {0x80, 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00}; @@ -286,7 +193,7 @@ TEST(StandardMessageCodec, CanEncodeAndDecodeSimpleCustomType) { return a_point == b_point; }; CheckEncodeDecode(CustomEncodableValue(Point(9, 16)), bytes, - &TestCodecSerializer::GetInstance(), point_comparator); + &PointExtensionSerializer::GetInstance(), point_comparator); } TEST(StandardMessageCodec, CanEncodeAndDecodeVariableLengthCustomType) { @@ -306,7 +213,7 @@ TEST(StandardMessageCodec, CanEncodeAndDecodeVariableLengthCustomType) { }; CheckEncodeDecode(CustomEncodableValue( SomeData("test", {0x00, 0x01, 0x02, 0x03, 0x04, 0x05})), - bytes, &TestCodecSerializer::GetInstance(), + bytes, &SomeDataExtensionSerializer::GetInstance(), some_data_comparator); } diff --git a/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc b/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc index c7d880d735c92..bb7a6f930f788 100644 --- a/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc +++ b/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_result_functions.h" #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.h" #include "gtest/gtest.h" namespace flutter { @@ -158,71 +159,9 @@ TEST(StandardMethodCodec, HandlesErrorEnvelopesWithDetails) { EXPECT_TRUE(decoded_successfully); } -namespace { - -// A representation of a point, for custom type testing. -class Point { - public: - Point(int x, int y) : x_(x), y_(y) {} - ~Point() = default; - - int x() const { return x_; } - int y() const { return y_; } - - bool operator==(const Point& other) const { - return x_ == other.x_ && y_ == other.y_; - } - - private: - int x_; - int y_; -}; - -// Codec extension for Point. -class TestCodecSerializer : public StandardCodecSerializer { - public: - TestCodecSerializer() = default; - virtual ~TestCodecSerializer() = default; - - static const TestCodecSerializer& GetInstance() { - static TestCodecSerializer sInstance; - return sInstance; - } - - // |StandardCodecSerializer| - EncodableValue ReadValueOfType(uint8_t type, - ByteStreamReader* stream) const override { - if (type == kPointType) { - int32_t x = stream->ReadInt32(); - int32_t y = stream->ReadInt32(); - return CustomEncodableValue(Point(x, y)); - } - return StandardCodecSerializer::ReadValueOfType(type, stream); - } - - // |StandardCodecSerializer| - void WriteValue(const EncodableValue& value, - ByteStreamWriter* stream) const override { - auto custom_value = std::get_if(&value); - if (!custom_value) { - StandardCodecSerializer::WriteValue(value, stream); - return; - } - stream->WriteByte(kPointType); - const Point& point = std::any_cast(*custom_value); - stream->WriteInt32(point.x()); - stream->WriteInt32(point.y()); - } - - private: - static constexpr uint8_t kPointType = 128; -}; - -} // namespace - TEST(StandardMethodCodec, HandlesCustomTypeArguments) { - const StandardMethodCodec& codec = - StandardMethodCodec::GetInstance(&TestCodecSerializer::GetInstance()); + const StandardMethodCodec& codec = StandardMethodCodec::GetInstance( + &PointExtensionSerializer::GetInstance()); Point point(7, 9); MethodCall call( "hello", std::make_unique(CustomEncodableValue(point))); From d3be70292fb0b7fd733a90e27a2a6c84c83a01aa Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 10 Aug 2020 11:15:52 -0700 Subject: [PATCH 6/7] GN format fix --- shell/platform/common/cpp/client_wrapper/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/common/cpp/client_wrapper/BUILD.gn b/shell/platform/common/cpp/client_wrapper/BUILD.gn index 268424b18e4a2..331a0c97d3509 100644 --- a/shell/platform/common/cpp/client_wrapper/BUILD.gn +++ b/shell/platform/common/cpp/client_wrapper/BUILD.gn @@ -53,9 +53,9 @@ executable("client_wrapper_unittests") { ] deps = [ + ":client_wrapper", ":client_wrapper_fixtures", ":client_wrapper_library_stubs", - ":client_wrapper", "//flutter/testing", # TODO(chunhtai): Consider refactoring flutter_root/testing so that there's a testing From 2ae01b7e6772adcc8b405bf9b7ef997ae3de5a29 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 10 Aug 2020 11:20:20 -0700 Subject: [PATCH 7/7] Add missing files --- .../testing/test_codec_extensions.cc | 80 +++++++++++++++++ .../testing/test_codec_extensions.h | 89 +++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.cc create mode 100644 shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.h diff --git a/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.cc b/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.cc new file mode 100644 index 0000000000000..53bdfe93a54e8 --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.cc @@ -0,0 +1,80 @@ +// 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. + +#include "flutter/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.h" + +namespace flutter { + +PointExtensionSerializer::PointExtensionSerializer() = default; +PointExtensionSerializer::~PointExtensionSerializer() = default; + +// static +const PointExtensionSerializer& PointExtensionSerializer::GetInstance() { + static PointExtensionSerializer sInstance; + return sInstance; +} + +EncodableValue PointExtensionSerializer::ReadValueOfType( + uint8_t type, + ByteStreamReader* stream) const { + if (type == kPointType) { + int32_t x = stream->ReadInt32(); + int32_t y = stream->ReadInt32(); + return CustomEncodableValue(Point(x, y)); + } + return StandardCodecSerializer::ReadValueOfType(type, stream); +} + +void PointExtensionSerializer::WriteValue(const EncodableValue& value, + ByteStreamWriter* stream) const { + auto custom_value = std::get_if(&value); + if (!custom_value) { + StandardCodecSerializer::WriteValue(value, stream); + return; + } + stream->WriteByte(kPointType); + const Point& point = std::any_cast(*custom_value); + stream->WriteInt32(point.x()); + stream->WriteInt32(point.y()); +} + +SomeDataExtensionSerializer::SomeDataExtensionSerializer() = default; +SomeDataExtensionSerializer::~SomeDataExtensionSerializer() = default; + +// static +const SomeDataExtensionSerializer& SomeDataExtensionSerializer::GetInstance() { + static SomeDataExtensionSerializer sInstance; + return sInstance; +} + +EncodableValue SomeDataExtensionSerializer::ReadValueOfType( + uint8_t type, + ByteStreamReader* stream) const { + if (type == kSomeDataType) { + size_t size = ReadSize(stream); + std::vector data; + data.resize(size); + stream->ReadBytes(data.data(), size); + EncodableValue label = ReadValue(stream); + return CustomEncodableValue(SomeData(std::get(label), data)); + } + return StandardCodecSerializer::ReadValueOfType(type, stream); +} + +void SomeDataExtensionSerializer::WriteValue(const EncodableValue& value, + ByteStreamWriter* stream) const { + auto custom_value = std::get_if(&value); + if (!custom_value) { + StandardCodecSerializer::WriteValue(value, stream); + return; + } + stream->WriteByte(kSomeDataType); + const SomeData& some_data = std::any_cast(*custom_value); + size_t data_size = some_data.data().size(); + WriteSize(data_size, stream); + stream->WriteBytes(some_data.data().data(), data_size); + WriteValue(EncodableValue(some_data.label()), stream); +} + +} // namespace flutter diff --git a/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.h b/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.h new file mode 100644 index 0000000000000..cbe01c8a886ff --- /dev/null +++ b/shell/platform/common/cpp/client_wrapper/testing/test_codec_extensions.h @@ -0,0 +1,89 @@ +// 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_TESTING_TEST_CODEC_EXTENSIONS_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_TESTING_TEST_CODEC_EXTENSIONS_H_ + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_codec_serializer.h" + +namespace flutter { + +// A representation of a point, for custom type testing of a simple type. +class Point { + public: + Point(int x, int y) : x_(x), y_(y) {} + ~Point() = default; + + int x() const { return x_; } + int y() const { return y_; } + + bool operator==(const Point& other) const { + return x_ == other.x_ && y_ == other.y_; + } + + private: + int x_; + int y_; +}; + +// A typed binary data object with extra fields, for custom type testing of a +// variable-length type that includes types handled by the core standard codec. +class SomeData { + public: + SomeData(const std::string label, const std::vector& data) + : label_(label), data_(data) {} + ~SomeData() = default; + + const std::string& label() const { return label_; } + const std::vector& data() const { return data_; } + + private: + std::string label_; + std::vector data_; +}; + +// Codec extension for Point. +class PointExtensionSerializer : public StandardCodecSerializer { + public: + PointExtensionSerializer(); + virtual ~PointExtensionSerializer(); + + static const PointExtensionSerializer& GetInstance(); + + // |TestCodecSerializer| + EncodableValue ReadValueOfType(uint8_t type, + ByteStreamReader* stream) const override; + + // |TestCodecSerializer| + void WriteValue(const EncodableValue& value, + ByteStreamWriter* stream) const override; + + private: + static constexpr uint8_t kPointType = 128; +}; + +// Codec extension for SomeData. +class SomeDataExtensionSerializer : public StandardCodecSerializer { + public: + SomeDataExtensionSerializer(); + virtual ~SomeDataExtensionSerializer(); + + static const SomeDataExtensionSerializer& GetInstance(); + + // |TestCodecSerializer| + EncodableValue ReadValueOfType(uint8_t type, + ByteStreamReader* stream) const override; + + // |TestCodecSerializer| + void WriteValue(const EncodableValue& value, + ByteStreamWriter* stream) const override; + + private: + static constexpr uint8_t kSomeDataType = 129; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_TESTING_TEST_CODEC_EXTENSIONS_H_