diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ed0e4e1e7d7e00011575b21e687615165cedf90c..f120888c6b0d7c2df1deae90199bbec47fdc42b3 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -579,6 +579,9 @@ TYPE: LicenseType.bsd FILE: ../../../flutter/fml/file.h FILE: ../../../flutter/fml/macros.h FILE: ../../../flutter/fml/mapping.cc +FILE: ../../../flutter/fml/message.cc +FILE: ../../../flutter/fml/message.h +FILE: ../../../flutter/fml/message_unittests.cc FILE: ../../../flutter/fml/native_library.h FILE: ../../../flutter/fml/paths.cc FILE: ../../../flutter/fml/platform/fuchsia/paths_fuchsia.cc diff --git a/fml/BUILD.gn b/fml/BUILD.gn index aac3b3b3c34b806cc561922837fb87515b64b1b2..d49558bdad965750e071a828cc2e51679e613a17 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -32,6 +32,8 @@ source_set("fml") { "memory/weak_ptr.h", "memory/weak_ptr_internal.cc", "memory/weak_ptr_internal.h", + "message.cc", + "message.h", "message_loop.cc", "message_loop.h", "message_loop_impl.cc", @@ -163,6 +165,7 @@ executable("fml_unittests") { "memory/ref_counted_unittest.cc", "memory/weak_ptr_unittest.cc", "message_loop_unittests.cc", + "message_unittests.cc", "string_view_unittest.cc", "synchronization/thread_annotations_unittest.cc", "synchronization/thread_checker_unittest.cc", diff --git a/fml/message.cc b/fml/message.cc new file mode 100644 index 0000000000000000000000000000000000000000..7ee778fc1ffa8dcceb3ee57d30e09d0730daa3ac --- /dev/null +++ b/fml/message.cc @@ -0,0 +1,108 @@ +// Copyright 2018 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/fml/message.h" + +#include "flutter/fml/logging.h" + +namespace fml { + +Message::Message() = default; + +Message::~Message() = default; + +static uint32_t NextPowerOfTwoSize(uint32_t x) { + if (x == 0) { + return 1; + } + + --x; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + return x + 1; +} + +const uint8_t* Message::GetBuffer() const { + return buffer_; +} + +size_t Message::GetBufferSize() const { + return buffer_length_; +} + +size_t Message::GetDataLength() const { + return data_length_; +} + +size_t Message::GetSizeRead() const { + return size_read_; +} + +bool Message::Reserve(size_t size) { + if (buffer_length_ >= size) { + return true; + } + return Resize(NextPowerOfTwoSize(size)); +} + +bool Message::Resize(size_t size) { + if (buffer_ == nullptr) { + // This is the initial resize where we have no previous buffer. + FML_DCHECK(buffer_length_ == 0); + + void* buffer = ::malloc(size); + const bool success = buffer != nullptr; + + if (success) { + buffer_ = static_cast(buffer); + buffer_length_ = size; + } + + return success; + } + + FML_DCHECK(size > buffer_length_); + + void* resized = ::realloc(buffer_, size); + + const bool success = resized != nullptr; + + // In case of failure, the input buffer to realloc is still valid. + if (success) { + buffer_ = static_cast(resized); + buffer_length_ = size; + } + + return success; +} + +uint8_t* Message::PrepareEncode(size_t size) { + if (!Reserve(data_length_ + size)) { + return nullptr; + } + + auto old_length = data_length_; + data_length_ += size; + return buffer_ + old_length; +} + +uint8_t* Message::PrepareDecode(size_t size) { + if ((size + size_read_) > buffer_length_) { + return nullptr; + } + auto buffer = buffer_ + size_read_; + size_read_ += size; + return buffer; +} + +void Message::ResetRead() { + size_read_ = 0; +} + +} // namespace fml diff --git a/fml/message.h b/fml/message.h new file mode 100644 index 0000000000000000000000000000000000000000..607faee048fa6b0e2f64ba09cd01edd8e2f653b3 --- /dev/null +++ b/fml/message.h @@ -0,0 +1,204 @@ +// Copyright 2018 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_FML_MESSAGE_H_ +#define FLUTTER_FML_MESSAGE_H_ + +#include +#include +#include +#include +#include +#include + +#include "flutter/fml/compiler_specific.h" +#include "flutter/fml/macros.h" + +namespace fml { + +#define FML_SERIALIZE(message, value) \ + if (!message.Encode(value)) { \ + return false; \ + } + +#define FML_SERIALIZE_TRAITS(message, value, traits) \ + if (!message.Encode(value)) { \ + return false; \ + } + +#define FML_DESERIALIZE(message, value) \ + if (!message.Decode(value)) { \ + return false; \ + } + +#define FML_DESERIALIZE_TRAITS(message, value, traits) \ + if (!message.Decode(value)) { \ + return false; \ + } + +class Message; + +class MessageSerializable { + public: + virtual ~MessageSerializable() = default; + + virtual bool Serialize(Message& message) const = 0; + + virtual bool Deserialize(Message& message) = 0; + + virtual size_t GetSerializableTag() const { return 0; }; +}; + +// The traits passed to the encode/decode calls that accept traits should be +// something like the following. +// +// class MessageSerializableTraits { +// static size_t GetSerializableTag(const T&); +// static std::unique_ptr CreateForSerializableTag(size_t tag); +// }; + +template +struct Serializable : public std::integral_constant< + bool, + std::is_trivially_copyable::value || + std::is_base_of::value> { +}; + +// Utility class to encode and decode |Serializable| types to and from a buffer. +// Elements have to be read back into the same order they were written. +class Message { + public: + Message(); + + ~Message(); + + const uint8_t* GetBuffer() const; + + size_t GetBufferSize() const; + + size_t GetDataLength() const; + + size_t GetSizeRead() const; + + void ResetRead(); + + // Encoders. + + template ::value>> + FML_WARN_UNUSED_RESULT bool Encode(const T& value) { + if (auto buffer = PrepareEncode(sizeof(T))) { + ::memcpy(buffer, &value, sizeof(T)); + return true; + } + return false; + } + + FML_WARN_UNUSED_RESULT bool Encode(const MessageSerializable& value) { + return value.Serialize(*this); + } + + template ::value>> + FML_WARN_UNUSED_RESULT bool Encode(const std::unique_ptr& value) { + // Encode if null. + if (!Encode(static_cast(value))) { + return false; + } + + if (!value) { + return true; + } + + // Encode the type. + if (!Encode(Traits::GetSerializableTag(*value.get()))) { + return false; + } + + // Encode the value. + if (!Encode(*value.get())) { + return false; + } + + return true; + } + + // Decoders. + + template ::value>> + FML_WARN_UNUSED_RESULT bool Decode(T& value) { + if (auto buffer = PrepareDecode(sizeof(T))) { + ::memcpy(&value, buffer, sizeof(T)); + return true; + } + return false; + } + + FML_WARN_UNUSED_RESULT bool Decode(MessageSerializable& value) { + return value.Deserialize(*this); + } + + template ::value>> + FML_WARN_UNUSED_RESULT bool Decode(std::unique_ptr& value) { + // Decode if null. + bool is_null = false; + if (!Decode(is_null)) { + return false; + } + + if (is_null) { + return true; + } + + // Decode type. + size_t tag = 0; + if (!Decode(tag)) { + return false; + } + + std::unique_ptr new_value = Traits::CreateForSerializableTag(tag); + if (!new_value) { + return false; + } + + // Decode value. + if (!Decode(*new_value.get())) { + return false; + } + + std::swap(value, new_value); + + return true; + } + + private: + uint8_t* buffer_ = nullptr; + size_t buffer_length_ = 0; + size_t data_length_ = 0; + size_t size_read_ = 0; + + FML_WARN_UNUSED_RESULT + bool Reserve(size_t size); + + FML_WARN_UNUSED_RESULT + bool Resize(size_t size); + + FML_WARN_UNUSED_RESULT + uint8_t* PrepareEncode(size_t size); + + FML_WARN_UNUSED_RESULT + uint8_t* PrepareDecode(size_t size); + + FML_DISALLOW_COPY_AND_ASSIGN(Message); +}; + +} // namespace fml + +#endif // FLUTTER_FML_MESSAGE_H_ diff --git a/fml/message_unittests.cc b/fml/message_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..284c90512239cecbc906030a75f19ac8b9609c98 --- /dev/null +++ b/fml/message_unittests.cc @@ -0,0 +1,67 @@ +// Copyright 2018 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/fml/message.h" +#include "gtest/gtest.h" + +namespace fml { + +struct TestStruct { + int a = 12; + char b = 'x'; + float c = 99.0f; +}; + +TEST(MessageTest, CanEncodeTriviallyCopyableTypes) { + Message message; + ASSERT_TRUE(message.Encode(12)); + ASSERT_TRUE(message.Encode(11.0f)); + ASSERT_TRUE(message.Encode('a')); + + TestStruct s; + ASSERT_TRUE(message.Encode(s)); + ASSERT_GE(message.GetDataLength(), 0u); + ASSERT_GE(message.GetBufferSize(), 0u); + ASSERT_EQ(message.GetSizeRead(), 0u); +} + +TEST(MessageTest, CanDecodeTriviallyCopyableTypes) { + Message message; + ASSERT_TRUE(message.Encode(12)); + ASSERT_TRUE(message.Encode(11.0f)); + ASSERT_TRUE(message.Encode('a')); + TestStruct s; + s.a = 10; + s.b = 'y'; + s.c = 11.1f; + + ASSERT_TRUE(message.Encode(s)); + + ASSERT_GE(message.GetDataLength(), 0u); + ASSERT_GE(message.GetBufferSize(), 0u); + ASSERT_EQ(message.GetSizeRead(), 0u); + + int int1 = 0; + ASSERT_TRUE(message.Decode(int1)); + ASSERT_EQ(12, int1); + + float float1 = 0.0f; + ASSERT_TRUE(message.Decode(float1)); + ASSERT_EQ(float1, 11.0f); + + char char1 = 'x'; + ASSERT_TRUE(message.Decode(char1)); + ASSERT_EQ(char1, 'a'); + + TestStruct s1; + ASSERT_TRUE(message.Decode(s1)); + ASSERT_EQ(s1.a, 10); + ASSERT_EQ(s1.b, 'y'); + ASSERT_EQ(s1.c, 11.1f); + + ASSERT_NE(message.GetSizeRead(), 0u); + ASSERT_EQ(message.GetDataLength(), message.GetSizeRead()); +} + +} // namespace fml