Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions fml/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
108 changes: 108 additions & 0 deletions fml/message.cc
Original file line number Diff line number Diff line change
@@ -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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can buffer_ be changed to a std::vector<uint8_t> instead of implementing resize logic here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not figure out how to deal with allocation errors on malloc and realloc when using the std::vector container. This implementation handles those edge cases and the FML_WARN_UNUSED_RESULT makes it hard to ignore those errors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vector::resize will throw on an allocation failure. Also, many allocators will always report success, followed by a segfault when the caller tries to use unavailable memory.

Either way is fine though

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<uint8_t*>(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<uint8_t*>(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
204 changes: 204 additions & 0 deletions fml/message.h
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include <type_traits>
#include <utility>

#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<traits>(value)) { \
return false; \
}

#define FML_DESERIALIZE(message, value) \
if (!message.Decode(value)) { \
return false; \
}

#define FML_DESERIALIZE_TRAITS(message, value, traits) \
if (!message.Decode<traits>(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<T> CreateForSerializableTag(size_t tag);
// };

template <class T>
struct Serializable : public std::integral_constant<
bool,
std::is_trivially_copyable<T>::value ||
std::is_base_of<MessageSerializable, T>::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 <typename T,
typename = std::enable_if_t<std::is_trivially_copyable<T>::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 <typename Traits,
typename T,
typename = std::enable_if_t<
std::is_base_of<MessageSerializable, T>::value>>
FML_WARN_UNUSED_RESULT bool Encode(const std::unique_ptr<T>& value) {
// Encode if null.
if (!Encode(static_cast<bool>(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 <typename T,
typename = std::enable_if_t<std::is_trivially_copyable<T>::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 <typename Traits,
typename T,
typename = std::enable_if_t<
std::is_base_of<MessageSerializable, T>::value>>
FML_WARN_UNUSED_RESULT bool Decode(std::unique_ptr<T>& 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<T> 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_
67 changes: 67 additions & 0 deletions fml/message_unittests.cc
Original file line number Diff line number Diff line change
@@ -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