diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f79af121f3781..1fc47f7d694cb 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1173,18 +1173,35 @@ FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.cc FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.h FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger.cc FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger_private.h +FILE: ../../../flutter/shell/platform/linux/fl_codec.cc +FILE: ../../../flutter/shell/platform/linux/fl_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_dart_project.cc FILE: ../../../flutter/shell/platform/linux/fl_dart_project_test.cc FILE: ../../../flutter/shell/platform/linux/fl_engine.cc FILE: ../../../flutter/shell/platform/linux/fl_engine_private.h +FILE: ../../../flutter/shell/platform/linux/fl_method_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_method_codec.cc +FILE: ../../../flutter/shell/platform/linux/fl_method_codec_private.h +FILE: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_renderer.cc FILE: ../../../flutter/shell/platform/linux/fl_renderer.h FILE: ../../../flutter/shell/platform/linux/fl_renderer_x11.cc FILE: ../../../flutter/shell/platform/linux/fl_renderer_x11.h +FILE: ../../../flutter/shell/platform/linux/fl_standard_codec.cc +FILE: ../../../flutter/shell/platform/linux/fl_standard_codec_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_standard_method_codec.cc +FILE: ../../../flutter/shell/platform/linux/fl_standard_method_codec_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_value.cc FILE: ../../../flutter/shell/platform/linux/fl_view.cc FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_engine.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_standard_codec.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_value.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/flutter_linux.h FILE: ../../../flutter/shell/platform/windows/angle_surface_manager.cc diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 213374bdc15ac..7ebc62a3f4e87 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -46,7 +46,13 @@ if (build_glfw_shell) { _public_headers = [ "public/flutter_linux/fl_binary_messenger.h", "public/flutter_linux/fl_dart_project.h", + "public/flutter_linux/fl_codec.h", "public/flutter_linux/fl_engine.h", + "public/flutter_linux/fl_method_channel.h", + "public/flutter_linux/fl_method_codec.h", + "public/flutter_linux/fl_standard_codec.h", + "public/flutter_linux/fl_standard_method_codec.h", + "public/flutter_linux/fl_value.h", "public/flutter_linux/fl_view.h", "public/flutter_linux/flutter_linux.h", ] @@ -60,10 +66,16 @@ source_set("flutter_linux") { sources = [ "fl_binary_messenger.cc", + "fl_codec.cc", "fl_dart_project.cc", "fl_engine.cc", + "fl_method_channel.cc", + "fl_method_codec.cc", "fl_renderer.cc", "fl_renderer_x11.cc", + "fl_standard_codec.cc", + "fl_standard_method_codec.cc", + "fl_value.cc", "fl_view.cc", ] @@ -89,7 +101,11 @@ executable("flutter_linux_unittests") { testonly = true sources = [ + "fl_codec_test.cc", "fl_dart_project_test.cc", + "fl_method_codec_test.cc", + "fl_standard_codec_test.cc", + "fl_standard_method_codec_test.cc", ] public_configs = [ "//flutter:config" ] diff --git a/shell/platform/linux/fl_binary_messenger.cc b/shell/platform/linux/fl_binary_messenger.cc index 40fd541b9d37c..7cebc45fc8754 100644 --- a/shell/platform/linux/fl_binary_messenger.cc +++ b/shell/platform/linux/fl_binary_messenger.cc @@ -45,11 +45,6 @@ struct _FlBinaryMessengerResponseHandle { const FlutterPlatformMessageResponseHandle* response_handle; }; -static void engine_weak_notify_cb(gpointer user_data, GObject* object) { - FlBinaryMessenger* self = FL_BINARY_MESSENGER(user_data); - self->engine = nullptr; -} - static FlBinaryMessengerResponseHandle* response_handle_new( const FlutterPlatformMessageResponseHandle* response_handle) { FlBinaryMessengerResponseHandle* handle = @@ -64,6 +59,14 @@ static void response_handle_free(FlBinaryMessengerResponseHandle* handle) { g_free(handle); } +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlBinaryMessengerResponseHandle, + response_handle_free); + +static void engine_weak_notify_cb(gpointer user_data, GObject* object) { + FlBinaryMessenger* self = FL_BINARY_MESSENGER(user_data); + self->engine = nullptr; +} + static gboolean fl_binary_messenger_platform_message_callback( FlEngine* engine, const gchar* channel, @@ -139,18 +142,20 @@ G_MODULE_EXPORT void fl_binary_messenger_set_message_handler_on_channel( G_MODULE_EXPORT gboolean fl_binary_messenger_send_response( FlBinaryMessenger* self, - FlBinaryMessengerResponseHandle* response_handle, + FlBinaryMessengerResponseHandle* response_handle_, GBytes* response, GError** error) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(self), FALSE); - g_return_val_if_fail(response_handle != nullptr, FALSE); + g_return_val_if_fail(response_handle_ != nullptr, FALSE); + + // Take reference to ensure it is freed + g_autoptr(FlBinaryMessengerResponseHandle) response_handle = response_handle_; if (self->engine == nullptr) return TRUE; gboolean result = fl_engine_send_platform_message_response( self->engine, response_handle->response_handle, response, error); - response_handle_free(response_handle); return result; } diff --git a/shell/platform/linux/fl_codec.cc b/shell/platform/linux/fl_codec.cc new file mode 100644 index 0000000000000..b3c82fd4f44f5 --- /dev/null +++ b/shell/platform/linux/fl_codec.cc @@ -0,0 +1,53 @@ +// 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/linux/public/flutter_linux/fl_codec.h" + +#include + +G_DEFINE_QUARK(fl_codec_error_quark, fl_codec_error) + +// Added here to stop the compiler from optimising this function away +G_MODULE_EXPORT GType fl_codec_get_type(); + +G_DEFINE_TYPE(FlCodec, fl_codec, G_TYPE_OBJECT) + +static void fl_codec_class_init(FlCodecClass* klass) {} + +static void fl_codec_init(FlCodec* self) {} + +G_MODULE_EXPORT gboolean fl_codec_write_value(FlCodec* self, + GByteArray* buffer, + FlValue* value, + GError** error) { + g_return_val_if_fail(FL_IS_CODEC(self), FALSE); + g_return_val_if_fail(buffer != nullptr, FALSE); + + g_autoptr(FlValue) null_value = NULL; + if (value == nullptr) + value = null_value = fl_value_null_new(); + + return FL_CODEC_GET_CLASS(self)->write_value(self, buffer, value, error); +} + +G_MODULE_EXPORT FlValue* fl_codec_read_value(FlCodec* self, + GBytes* buffer, + size_t* offset, + GError** error) { + g_return_val_if_fail(FL_IS_CODEC(self), nullptr); + g_return_val_if_fail(buffer != nullptr, nullptr); + + size_t o = offset != nullptr ? *offset : 0; + if (o >= g_bytes_get_size(buffer)) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA, + "Out of data"); + return NULL; + } + + FlValue* value = + FL_CODEC_GET_CLASS(self)->read_value(self, buffer, &o, error); + if (value != nullptr && offset != nullptr) + *offset = o; + return value; +} diff --git a/shell/platform/linux/fl_codec_test.cc b/shell/platform/linux/fl_codec_test.cc new file mode 100644 index 0000000000000..2b9e8a944a89d --- /dev/null +++ b/shell/platform/linux/fl_codec_test.cc @@ -0,0 +1,186 @@ +// 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/linux/public/flutter_linux/fl_codec.h" +#include "gtest/gtest.h" + +G_DECLARE_FINAL_TYPE(FlTestCodec, fl_test_codec, FL, TEST_CODEC, FlCodec) + +struct _FlTestCodec { + FlCodec parent_instance; +}; + +G_DEFINE_TYPE(FlTestCodec, fl_test_codec, fl_codec_get_type()) + +static gboolean fl_test_codec_write_value(FlCodec* codec, + GByteArray* buffer, + FlValue* value, + GError** error) { + EXPECT_TRUE(FL_IS_TEST_CODEC(codec)); + + if (fl_value_get_type(value) == FL_VALUE_TYPE_INT) { + char c = '0' + fl_value_get_int(value); + g_byte_array_append(buffer, reinterpret_cast(&c), 1); + return TRUE; + } else { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, "ERROR"); + return FALSE; + } +} + +static FlValue* fl_test_codec_read_value(FlCodec* codec, + GBytes* message, + size_t* offset, + GError** error) { + EXPECT_TRUE(FL_IS_TEST_CODEC(codec)); + EXPECT_TRUE(*offset < g_bytes_get_size(message)); + + size_t data_length; + const uint8_t* data = + static_cast(g_bytes_get_data(message, &data_length)); + if (data_length == 0) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, "ERROR"); + return FALSE; + } + + g_autoptr(FlValue) value = fl_value_int_new(data[*offset] - '0'); + (*offset)++; + return fl_value_ref(value); +} + +static void fl_test_codec_class_init(FlTestCodecClass* klass) { + FL_CODEC_CLASS(klass)->write_value = fl_test_codec_write_value; + FL_CODEC_CLASS(klass)->read_value = fl_test_codec_read_value; +} + +static void fl_test_codec_init(FlTestCodec* self) {} + +static FlTestCodec* fl_test_codec_new() { + return FL_TEST_CODEC(g_object_new(fl_test_codec_get_type(), nullptr)); +} + +TEST(FlCodecTest, WriteValue) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + g_autoptr(GByteArray) buffer = g_byte_array_new(); + + g_autoptr(FlValue) value = fl_value_int_new(1); + g_autoptr(GError) error = nullptr; + gboolean result = + fl_codec_write_value(FL_CODEC(codec), buffer, value, &error); + EXPECT_TRUE(result); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(buffer->len, static_cast(1)); + EXPECT_EQ(buffer->data[0], '1'); +} + +TEST(FlCodecTest, WriteValues) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + g_autoptr(GByteArray) buffer = g_byte_array_new(); + + for (int i = 1; i <= 5; i++) { + g_autoptr(FlValue) value = fl_value_int_new(i); + g_autoptr(GError) error = nullptr; + gboolean result = + fl_codec_write_value(FL_CODEC(codec), buffer, value, &error); + EXPECT_TRUE(result); + EXPECT_EQ(error, nullptr); + } + + EXPECT_EQ(buffer->len, static_cast(5)); + for (int i = 1; i <= 5; i++) + EXPECT_EQ(buffer->data[i - 1], '0' + i); +} + +TEST(FlCodecTest, WriteValueError) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + g_autoptr(GByteArray) buffer = g_byte_array_new(); + + g_autoptr(FlValue) value = fl_value_null_new(); + g_autoptr(GError) error = nullptr; + gboolean result = + fl_codec_write_value(FL_CODEC(codec), buffer, value, &error); + EXPECT_FALSE(result); + EXPECT_TRUE(g_error_matches(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED)); + EXPECT_EQ(buffer->len, static_cast(0)); +} + +TEST(FlCodecTest, ReadValueEmpty) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + g_autoptr(GBytes) message = g_bytes_new(nullptr, 0); + + size_t offset = 0; + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) value = + fl_codec_read_value(FL_CODEC(codec), message, &offset, &error); + EXPECT_EQ(value, nullptr); + EXPECT_TRUE( + g_error_matches(error, FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA)); + EXPECT_EQ(offset, static_cast(0)); +} + +TEST(FlCodecTest, ReadValue) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + uint8_t data[] = {'1'}; + g_autoptr(GBytes) message = g_bytes_new(data, 1); + + size_t offset = 0; + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) value = + fl_codec_read_value(FL_CODEC(codec), message, &offset, &error); + EXPECT_NE(value, nullptr); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(offset, static_cast(1)); + + ASSERT_TRUE(fl_value_get_type(value) == FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 1); +} + +TEST(FlCodecTest, ReadValues) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + uint8_t data[] = {'1', '2', '3', '4', '5'}; + g_autoptr(GBytes) message = g_bytes_new(data, 5); + + size_t offset = 0; + for (int i = 1; i <= 5; i++) { + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) value = + fl_codec_read_value(FL_CODEC(codec), message, &offset, &error); + EXPECT_NE(value, nullptr); + EXPECT_EQ(error, nullptr); + ASSERT_TRUE(fl_value_get_type(value) == FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), i); + } + + EXPECT_EQ(offset, static_cast(5)); +} + +TEST(FlCodecTest, ReadValueNullOffset) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + uint8_t data[] = {'1'}; + g_autoptr(GBytes) message = g_bytes_new(data, 1); + + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) value = + fl_codec_read_value(FL_CODEC(codec), message, NULL, &error); + EXPECT_NE(value, nullptr); + EXPECT_EQ(error, nullptr); + + ASSERT_TRUE(fl_value_get_type(value) == FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 1); +} + +TEST(FlCodecTest, ReadValueInvalidOffset) { + g_autoptr(FlTestCodec) codec = fl_test_codec_new(); + uint8_t data[] = {'1', '2', '3', '4', '5'}; + g_autoptr(GBytes) message = g_bytes_new(data, 5); + + size_t offset = 9999; + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) value = + fl_codec_read_value(FL_CODEC(codec), message, &offset, &error); + EXPECT_EQ(value, nullptr); + EXPECT_TRUE( + g_error_matches(error, FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA)); + EXPECT_EQ(offset, static_cast(9999)); +} diff --git a/shell/platform/linux/fl_method_channel.cc b/shell/platform/linux/fl_method_channel.cc new file mode 100644 index 0000000000000..c4eefab683c49 --- /dev/null +++ b/shell/platform/linux/fl_method_channel.cc @@ -0,0 +1,220 @@ +// 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/linux/public/flutter_linux/fl_method_channel.h" + +#include "flutter/shell/platform/linux/fl_method_codec_private.h" + +#include + +struct _FlMethodChannel { + GObject parent_instance; + + FlBinaryMessenger* messenger; + FlMethodCodec* codec; + gchar* name; + + FlMethodChannelCallback callback; + gpointer callback_data; +}; + +// Added here to stop the compiler from optimising this function away +G_MODULE_EXPORT GType fl_method_channel_get_type(); + +G_DEFINE_TYPE(FlMethodChannel, fl_method_channel, G_TYPE_OBJECT) + +struct _FlMethodChannelResponseHandle { + FlBinaryMessengerResponseHandle* response_handle; +}; + +static FlMethodChannelResponseHandle* response_handle_new( + FlBinaryMessengerResponseHandle* response_handle) { + FlMethodChannelResponseHandle* handle = + static_cast( + g_malloc0(sizeof(FlMethodChannelResponseHandle))); + handle->response_handle = response_handle; + + return handle; +} + +static void response_handle_free(FlMethodChannelResponseHandle* handle) { + g_free(handle); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlMethodChannelResponseHandle, + response_handle_free); + +static void message_cb(FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + FlBinaryMessengerResponseHandle* response_handle, + gpointer user_data) { + FlMethodChannel* self = FL_METHOD_CHANNEL(user_data); + + if (self->callback == nullptr) { + fl_binary_messenger_send_response(messenger, response_handle, nullptr, + nullptr); + return; + } + + g_autofree gchar* method = nullptr; + g_autoptr(FlValue) args = nullptr; + g_autoptr(GError) error = nullptr; + if (!fl_method_codec_decode_method_call(self->codec, message, &method, &args, + &error)) { + g_warning("Failed to decode method call: %s", error->message); + fl_binary_messenger_send_response(messenger, response_handle, nullptr, + nullptr); + return; + } + + self->callback(self, method, args, response_handle_new(response_handle), + self->callback_data); +} + +static void send_on_channel_ready_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + GTask* task = static_cast(user_data); + g_task_return_pointer(task, result, g_object_unref); +} + +static void fl_method_channel_dispose(GObject* object) { + FlMethodChannel* self = FL_METHOD_CHANNEL(object); + + g_clear_pointer(&self->name, g_free); + + G_OBJECT_CLASS(fl_method_channel_parent_class)->dispose(object); +} + +static void fl_method_channel_class_init(FlMethodChannelClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_method_channel_dispose; +} + +static void fl_method_channel_init(FlMethodChannel* self) {} + +G_MODULE_EXPORT FlMethodChannel* fl_method_channel_new( + FlBinaryMessenger* messenger, + const gchar* name, + FlMethodCodec* codec) { + g_return_val_if_fail(name != nullptr, nullptr); + g_return_val_if_fail(FL_IS_METHOD_CODEC(codec), nullptr); + + FlMethodChannel* self = static_cast( + g_object_new(fl_method_channel_get_type(), nullptr)); + self->messenger = static_cast(g_object_ref(messenger)); + self->name = g_strdup(name); + self->codec = static_cast(g_object_ref(codec)); + + fl_binary_messenger_set_message_handler_on_channel(messenger, name, + message_cb, self); + + return self; +} + +G_MODULE_EXPORT void fl_method_channel_set_callback( + FlMethodChannel* self, + FlMethodChannelCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_METHOD_CHANNEL(self)); + self->callback = callback; + self->callback_data = user_data; +} + +G_MODULE_EXPORT void fl_method_channel_invoke(FlMethodChannel* self, + const gchar* method, + FlValue* args, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + g_return_if_fail(FL_IS_METHOD_CHANNEL(self)); + g_return_if_fail(method != nullptr); + + g_autoptr(GTask) task = + callback != nullptr ? g_task_new(self, cancellable, callback, user_data) + : nullptr; + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = + fl_method_codec_encode_method_call(self->codec, method, args, &error); + if (message == nullptr) { + if (task != nullptr) + g_task_return_error(task, error); + return; + } + + fl_binary_messenger_send_on_channel( + self->messenger, self->name, message, cancellable, + callback != nullptr ? send_on_channel_ready_cb : nullptr, + g_steal_pointer(&task)); +} + +G_MODULE_EXPORT gboolean fl_method_channel_invoke_finish(FlMethodChannel* self, + GAsyncResult* result, + gchar** error_code, + gchar** error_message, + FlValue** value, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CHANNEL(self), FALSE); + g_return_val_if_fail(g_task_is_valid(result, self), FALSE); + g_return_val_if_fail(error_code, FALSE); + + g_autoptr(GTask) task = reinterpret_cast(result); + GAsyncResult* r = + static_cast(g_task_propagate_pointer(task, nullptr)); + + g_autoptr(GBytes) response = + fl_binary_messenger_send_on_channel_finish(self->messenger, r, error); + if (response == nullptr) + return FALSE; + + return fl_method_codec_decode_response(self->codec, response, error_code, + error_message, value, error); +} + +G_MODULE_EXPORT gboolean +fl_method_channel_respond(FlMethodChannel* self, + FlMethodChannelResponseHandle* response_handle, + FlValue* result, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CHANNEL(self), FALSE); + g_return_val_if_fail(response_handle != nullptr, FALSE); + + // Take reference to ensure it is freed + g_autoptr(FlMethodChannelResponseHandle) handle = response_handle; + + g_autoptr(GBytes) response = + fl_method_codec_encode_success_envelope(self->codec, result, error); + if (response == nullptr) + return FALSE; + + return fl_binary_messenger_send_response( + self->messenger, handle->response_handle, response, error); +} + +G_MODULE_EXPORT gboolean +fl_method_channel_respond_error(FlMethodChannel* self, + FlMethodChannelResponseHandle* response_handle, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CHANNEL(self), FALSE); + g_return_val_if_fail(response_handle != nullptr, FALSE); + g_return_val_if_fail(code != nullptr, FALSE); + + // Take reference to ensure it is freed + g_autoptr(FlMethodChannelResponseHandle) owned_response_handle = + response_handle; + + g_autoptr(GBytes) response = fl_method_codec_encode_error_envelope( + self->codec, code, message, details, error); + if (response == nullptr) + return FALSE; + + gboolean result = fl_binary_messenger_send_response( + self->messenger, owned_response_handle->response_handle, response, error); + + return result; +} diff --git a/shell/platform/linux/fl_method_codec.cc b/shell/platform/linux/fl_method_codec.cc new file mode 100644 index 0000000000000..720b24914fa5c --- /dev/null +++ b/shell/platform/linux/fl_method_codec.cc @@ -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. + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" + +#include + +// Added here to stop the compiler from optimising this function away +G_MODULE_EXPORT GType fl_method_codec_get_type(); + +G_DEFINE_TYPE(FlMethodCodec, fl_method_codec, G_TYPE_OBJECT) + +static void fl_method_codec_class_init(FlMethodCodecClass* klass) {} + +static void fl_method_codec_init(FlMethodCodec* self) {} + +GBytes* fl_method_codec_encode_method_call(FlMethodCodec* self, + const gchar* name, + FlValue* args, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CODEC(self), nullptr); + g_return_val_if_fail(name != nullptr, nullptr); + + return FL_METHOD_CODEC_GET_CLASS(self)->encode_method_call(self, name, args, + error); +} + +gboolean fl_method_codec_decode_method_call(FlMethodCodec* self, + GBytes* message, + gchar** name, + FlValue** args, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CODEC(self), FALSE); + g_return_val_if_fail(message != nullptr, FALSE); + g_return_val_if_fail(name != nullptr, FALSE); + g_return_val_if_fail(args != nullptr, FALSE); + + return FL_METHOD_CODEC_GET_CLASS(self)->decode_method_call(self, message, + name, args, error); +} + +GBytes* fl_method_codec_encode_success_envelope(FlMethodCodec* self, + FlValue* result, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CODEC(self), nullptr); + + return FL_METHOD_CODEC_GET_CLASS(self)->encode_success_envelope(self, result, + error); +} + +GBytes* fl_method_codec_encode_error_envelope(FlMethodCodec* self, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CODEC(self), nullptr); + g_return_val_if_fail(code != nullptr, nullptr); + + return FL_METHOD_CODEC_GET_CLASS(self)->encode_error_envelope( + self, code, message, details, error); +} + +gboolean fl_method_codec_decode_response(FlMethodCodec* self, + GBytes* message, + gchar** error_code, + gchar** error_message, + FlValue** result, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CODEC(self), FALSE); + g_return_val_if_fail(message != nullptr, FALSE); + g_return_val_if_fail(error_code != nullptr, FALSE); + g_return_val_if_fail(error_message != nullptr, FALSE); + g_return_val_if_fail(result != nullptr, FALSE); + + return FL_METHOD_CODEC_GET_CLASS(self)->decode_response( + self, message, error_code, error_message, result, error); +} diff --git a/shell/platform/linux/fl_method_codec_private.h b/shell/platform/linux/fl_method_codec_private.h new file mode 100644 index 0000000000000..e34e9b8010538 --- /dev/null +++ b/shell/platform/linux/fl_method_codec_private.h @@ -0,0 +1,107 @@ +// 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_LINUX_FL_METHOD_CODEC_PRIVATE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_PRIVATE_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h" + +G_BEGIN_DECLS + +/** + * fl_method_codec_encode_method_call: + * @codec: a #FlCodec + * @name: method name + * @args: (allow-none): method arguments, or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Encode a method call. + * + * Return: (transfer full): a binary encoding of this method call or %NULL if + * not able to encode. + */ +GBytes* fl_method_codec_encode_method_call(FlMethodCodec* codec, + const gchar* name, + FlValue* args, + GError** error); + +/** + * fl_method_codec_decode_method_call: + * @codec: a #FlCodec + * @message: message to decode. + * @name: (transfer full): location to write method name or %NULL if not + * required + * @args: (transfer full): location to write method arguments, or %NULL if not + * required + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Encode a method call. + * + * Return: %TRUE if successfully decoded. + */ +gboolean fl_method_codec_decode_method_call(FlMethodCodec* codec, + GBytes* message, + gchar** name, + FlValue** args, + GError** error); + +/** + * fl_method_codec_encode_success_envelope: + * @codec: a #FlCodec + * @result: (allow-none): method result, or %NULL + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Encode a successful response to a method call. + * + * Return: (transfer full): a binary encoding of this response or %NULL if not + * able to encode. + */ +GBytes* fl_method_codec_encode_success_envelope(FlMethodCodec* codec, + FlValue* result, + GError** error); + +/** + * fl_method_codec_encode_error_envelope: + * @codec: a #FlCodec + * @code: an error code. + * @message: (allow-none): an error message or %NULL. + * @details: (allow-none): error details, or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Encode an error response to a method call. + * + * Return: (transfer full): a binary encoding of this response or %NULL if not + * able to encode. + */ +GBytes* fl_method_codec_encode_error_envelope(FlMethodCodec* codec, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error); + +/** + * fl_method_codec_decode_response: + * @codec: a #FlCodec + * @message: message to decode. + * @error_code: (transfer full): location to write error code. + * @error_message: (transfer full): location to write error message + * @result: (transfer full): location to write call response, or error details + * if an error. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Decode a response to a method call. If the call resulted in an error then + * @error_code is set, otherwise it is %NULL. + * + * Return: %TRUE if successfully decoded. + */ +gboolean fl_method_codec_decode_response(FlMethodCodec* codec, + GBytes* message, + gchar** error_code, + gchar** error_message, + FlValue** result, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_PRIVATE_H_ diff --git a/shell/platform/linux/fl_method_codec_test.cc b/shell/platform/linux/fl_method_codec_test.cc new file mode 100644 index 0000000000000..375d589390e87 --- /dev/null +++ b/shell/platform/linux/fl_method_codec_test.cc @@ -0,0 +1,385 @@ +// 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/linux/public/flutter_linux/fl_method_codec.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_codec.h" +#include "gtest/gtest.h" + +G_DECLARE_FINAL_TYPE(FlTestMethodCodec, + fl_test_method_codec, + FL, + TEST_METHOD_CODEC, + FlMethodCodec) + +struct _FlTestMethodCodec { + FlMethodCodec parent_instance; +}; + +G_DEFINE_TYPE(FlTestMethodCodec, + fl_test_method_codec, + fl_method_codec_get_type()) + +static gchar* message_to_text(GBytes* message) { + size_t data_length; + const gchar* data = + static_cast(g_bytes_get_data(message, &data_length)); + return g_strndup(data, data_length); +} + +static GBytes* text_to_message(const gchar* text) { + return g_bytes_new(text, strlen(text)); +} + +static GBytes* fl_test_codec_encode_method_call(FlMethodCodec* codec, + const gchar* name, + FlValue* args, + GError** error) { + EXPECT_TRUE(FL_IS_TEST_METHOD_CODEC(codec)); + + g_autofree gchar* text = nullptr; + if (args == nullptr || fl_value_get_type(args) == FL_VALUE_TYPE_NULL) + text = g_strdup_printf("%s()", name); + else if (fl_value_get_type(args) == FL_VALUE_TYPE_INT) + text = g_strdup_printf("%s(%" G_GINT64_FORMAT ")", name, + fl_value_get_int(args)); + else { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, "ERROR"); + return nullptr; + } + + return text_to_message(text); +} + +static gboolean fl_test_codec_decode_method_call(FlMethodCodec* codec, + GBytes* message, + gchar** name, + FlValue** args, + GError** error) { + EXPECT_TRUE(FL_IS_TEST_METHOD_CODEC(codec)); + + g_autofree gchar* m = message_to_text(message); + + if (strcmp(m, "error") == 0) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, "ERROR"); + return FALSE; + } else { + *name = g_strdup(m); + *args = fl_value_null_new(); + return TRUE; + } +} + +static GBytes* fl_test_codec_encode_success_envelope(FlMethodCodec* codec, + FlValue* result, + GError** error) { + EXPECT_TRUE(FL_IS_TEST_METHOD_CODEC(codec)); + + g_autofree gchar* text = nullptr; + if (result == nullptr || fl_value_get_type(result) == FL_VALUE_TYPE_NULL) + text = g_strdup("(null)"); + else if (fl_value_get_type(result) == FL_VALUE_TYPE_INT) + text = g_strdup_printf("%" G_GINT64_FORMAT, fl_value_get_int(result)); + else { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, "ERROR"); + return nullptr; + } + + return text_to_message(text); +} + +static GBytes* fl_test_codec_encode_error_envelope(FlMethodCodec* codec, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error) { + EXPECT_TRUE(FL_IS_TEST_METHOD_CODEC(codec)); + + if (details != nullptr && fl_value_get_type(details) != FL_VALUE_TYPE_INT) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, "ERROR"); + return nullptr; + } + + g_autofree gchar* text = nullptr; + if (message == nullptr) { + if (details == nullptr || fl_value_get_type(details) == FL_VALUE_TYPE_NULL) + text = g_strdup_printf("Error_%s()", code); + else + text = g_strdup_printf("Error_%s(%" G_GINT64_FORMAT ")", code, + fl_value_get_int(details)); + } else { + if (details == nullptr || fl_value_get_type(details) == FL_VALUE_TYPE_NULL) + text = g_strdup_printf("Error_%s(%s)", code, message); + else + text = g_strdup_printf("Error_%s(%s,%" G_GINT64_FORMAT ")", code, message, + fl_value_get_int(details)); + } + + return text_to_message(text); +} + +static gboolean fl_test_codec_decode_response(FlMethodCodec* codec, + GBytes* message, + gchar** error_code, + gchar** error_message, + FlValue** result, + GError** error) { + EXPECT_TRUE(FL_IS_TEST_METHOD_CODEC(codec)); + + g_autofree gchar* m = message_to_text(message); + if (strcmp(m, "codec-error") == 0) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, "ERROR"); + return FALSE; + } else if (strcmp(m, "error") == 0) { + *error_code = g_strdup("code"); + *error_message = g_strdup("message"); + *result = fl_value_int_new(42); + return TRUE; + } else { + *error_code = nullptr; + *error_message = nullptr; + *result = fl_value_string_new(m); + return TRUE; + } +} + +static void fl_test_method_codec_class_init(FlTestMethodCodecClass* klass) { + FL_METHOD_CODEC_CLASS(klass)->encode_method_call = + fl_test_codec_encode_method_call; + FL_METHOD_CODEC_CLASS(klass)->decode_method_call = + fl_test_codec_decode_method_call; + FL_METHOD_CODEC_CLASS(klass)->encode_success_envelope = + fl_test_codec_encode_success_envelope; + FL_METHOD_CODEC_CLASS(klass)->encode_error_envelope = + fl_test_codec_encode_error_envelope; + FL_METHOD_CODEC_CLASS(klass)->decode_response = fl_test_codec_decode_response; +} + +static void fl_test_method_codec_init(FlTestMethodCodec* self) {} + +static FlTestMethodCodec* fl_test_method_codec_new() { + return FL_TEST_METHOD_CODEC( + g_object_new(fl_test_method_codec_get_type(), nullptr)); +} + +TEST(FlMethodCodecTest, EncodeMethodCall) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), "foo", nullptr, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "foo()"); +} + +TEST(FlMethodCodecTest, EncodeMethodCallEmptyName) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), "", nullptr, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "()"); +} + +TEST(FlMethodCodecTest, EncodeMethodCallArgs) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(FlValue) args = fl_value_int_new(42); + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), "foo", args, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "foo(42)"); +} + +TEST(FlMethodCodecTest, EncodeMethodCallError) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(FlValue) args = fl_value_bool_new(FALSE); + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), "foo", args, &error); + EXPECT_EQ(message, nullptr); + EXPECT_TRUE(g_error_matches(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED)); +} + +TEST(FlMethodCodecTest, DecodeMethodCall) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GBytes) message = text_to_message("foo"); + + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + g_autoptr(GError) error = nullptr; + gboolean result = fl_method_codec_decode_method_call( + FL_METHOD_CODEC(codec), message, &name, &args, &error); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(result); + + EXPECT_STREQ(name, "foo"); + ASSERT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_NULL); +} + +TEST(FlMethodCodecTest, EncodeSuccessEnvelope) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(FlValue) result = fl_value_int_new(42); + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_success_envelope( + FL_METHOD_CODEC(codec), result, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "42"); +} + +TEST(FlMethodCodecTest, EncodeSuccessEnvelopeEmpty) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_success_envelope( + FL_METHOD_CODEC(codec), nullptr, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "(null)"); +} + +TEST(FlMethodCodecTest, EncodeSuccessEnvelopeError) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(FlValue) result = fl_value_string_new("X"); + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_success_envelope( + FL_METHOD_CODEC(codec), result, &error); + EXPECT_EQ(message, nullptr); + EXPECT_TRUE(g_error_matches(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED)); +} + +TEST(FlMethodCodecTest, EncodeErrorEnvelopeNoMessageOrDetails) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_error_envelope( + FL_METHOD_CODEC(codec), "code", nullptr, nullptr, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "Error_code()"); +} + +TEST(FlMethodCodecTest, EncodeErrorEnvelopeMessage) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_error_envelope( + FL_METHOD_CODEC(codec), "code", "message", nullptr, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "Error_code(message)"); +} + +TEST(FlMethodCodecTest, EncodeErrorEnvelopeDetails) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(FlValue) details = fl_value_int_new(42); + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_error_envelope( + FL_METHOD_CODEC(codec), "code", nullptr, details, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "Error_code(42)"); +} + +TEST(FlMethodCodecTest, EncodeErrorEnvelopeMessageAndDetails) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(FlValue) details = fl_value_int_new(42); + g_autoptr(GError) error = nullptr; + g_autoptr(GBytes) message = fl_method_codec_encode_error_envelope( + FL_METHOD_CODEC(codec), "code", "message", details, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(message, nullptr); + + g_autofree gchar* message_text = message_to_text(message); + EXPECT_STREQ(message_text, "Error_code(message,42)"); +} + +TEST(FlMethodCodecTest, DecodeResponseSuccess) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GBytes) message = text_to_message("echo"); + + g_autofree gchar* error_code = nullptr; + g_autofree gchar* error_message = nullptr; + g_autoptr(FlValue) result_or_details = nullptr; + g_autoptr(GError) error = nullptr; + gboolean result = fl_method_codec_decode_response( + FL_METHOD_CODEC(codec), message, &error_code, &error_message, + &result_or_details, &error); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(result); + EXPECT_EQ(error_code, nullptr); + EXPECT_EQ(error_message, nullptr); + + ASSERT_EQ(fl_value_get_type(result_or_details), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(result_or_details), "echo"); +} + +TEST(FlMethodCodecTest, DecodeResponseCodecError) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GBytes) message = text_to_message("codec-error"); + + g_autofree gchar* error_code = nullptr; + g_autofree gchar* error_message = nullptr; + g_autoptr(FlValue) result_or_details = nullptr; + g_autoptr(GError) error = nullptr; + gboolean result = fl_method_codec_decode_response( + FL_METHOD_CODEC(codec), message, &error_code, &error_message, + &result_or_details, &error); + EXPECT_EQ(error_code, nullptr); + EXPECT_EQ(error_message, nullptr); + EXPECT_EQ(result_or_details, nullptr); + EXPECT_TRUE(g_error_matches(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED)); + EXPECT_FALSE(result); +} + +TEST(FlMethodCodecTest, DecodeResponseError) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GBytes) message = text_to_message("error"); + + g_autofree gchar* error_code = nullptr; + g_autofree gchar* error_message = nullptr; + g_autoptr(FlValue) details = nullptr; + g_autoptr(GError) error = nullptr; + gboolean result = fl_method_codec_decode_response( + FL_METHOD_CODEC(codec), message, &error_code, &error_message, &details, + &error); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(result); + EXPECT_STREQ(error_code, "code"); + EXPECT_STREQ(error_message, "message"); + + ASSERT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(details), 42); +} diff --git a/shell/platform/linux/fl_standard_codec.cc b/shell/platform/linux/fl_standard_codec.cc new file mode 100644 index 0000000000000..7c1d1591c7d07 --- /dev/null +++ b/shell/platform/linux/fl_standard_codec.cc @@ -0,0 +1,481 @@ +// 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/linux/public/flutter_linux/fl_standard_codec.h" + +#include + +G_DEFINE_QUARK(fl_standard_codec_error_quark, fl_standard_codec_error) + +// See lib/src/services/message_codecs.dart in Flutter source for description of +// encoding + +// Type values +static constexpr int kValueNull = 0; +static constexpr int kValueTrue = 1; +static constexpr int kValueFalse = 2; +static constexpr int kValueInt32 = 3; +static constexpr int kValueInt64 = 4; +static constexpr int kValueFloat64 = 6; +static constexpr int kValueString = 7; +static constexpr int kValueUint8List = 8; +static constexpr int kValueInt32List = 9; +static constexpr int kValueInt64List = 10; +static constexpr int kValueFloat64List = 11; +static constexpr int kValueList = 12; +static constexpr int kValueMap = 13; + +// TODO(robert-ancell) Add support for large integer type (kValueLargeInt = 5) + +struct _FlStandardCodec { + FlCodec parent_instance; +}; + +G_DEFINE_TYPE(FlStandardCodec, fl_standard_codec, fl_codec_get_type()) + +static void write_uint8(GByteArray* buffer, uint8_t value) { + g_byte_array_append(buffer, &value, 1); +} + +static void write_uint16(GByteArray* buffer, uint16_t value) { + g_byte_array_append(buffer, reinterpret_cast(&value), + sizeof(uint16_t)); +} + +static void write_uint32(GByteArray* buffer, uint32_t value) { + g_byte_array_append(buffer, reinterpret_cast(&value), + sizeof(uint32_t)); +} + +static void write_int32(GByteArray* buffer, int32_t value) { + g_byte_array_append(buffer, reinterpret_cast(&value), + sizeof(int32_t)); +} + +static void write_int64(GByteArray* buffer, int64_t value) { + g_byte_array_append(buffer, reinterpret_cast(&value), + sizeof(int64_t)); +} + +static void write_float64(GByteArray* buffer, double value) { + g_byte_array_append(buffer, reinterpret_cast(&value), + sizeof(double)); +} + +static void write_size(GByteArray* buffer, uint32_t size) { + if (size < 254) + write_uint8(buffer, size); + else if (size <= 0xffff) { + write_uint8(buffer, 254); + write_uint16(buffer, size); + } else { + write_uint8(buffer, 255); + write_uint32(buffer, size); + } +} + +static void encode_value(GByteArray* buffer, FlValue* value) { + if (value == nullptr) { + write_uint8(buffer, kValueNull); + return; + } + + switch (fl_value_get_type(value)) { + case FL_VALUE_TYPE_NULL: + write_uint8(buffer, kValueNull); + break; + case FL_VALUE_TYPE_BOOL: + if (fl_value_get_bool(value)) + write_uint8(buffer, kValueTrue); + else + write_uint8(buffer, kValueFalse); + break; + case FL_VALUE_TYPE_INT: { + int64_t v = fl_value_get_int(value); + if (v >= INT32_MIN && v <= INT32_MAX) { + write_uint8(buffer, kValueInt32); + write_int32(buffer, v); + } else { + write_uint8(buffer, kValueInt64); + write_int64(buffer, v); + } + break; + } + case FL_VALUE_TYPE_FLOAT: + write_uint8(buffer, kValueFloat64); + write_float64(buffer, fl_value_get_float(value)); + break; + case FL_VALUE_TYPE_STRING: { + write_uint8(buffer, kValueString); + const char* text = fl_value_get_string(value); + size_t length = strlen(text); + write_size(buffer, length); + g_byte_array_append(buffer, reinterpret_cast(text), + length); + break; + } + case FL_VALUE_TYPE_UINT8_LIST: { + write_uint8(buffer, kValueUint8List); + size_t length = fl_value_get_length(value); + write_size(buffer, length); + g_byte_array_append(buffer, fl_value_get_uint8_list(value), + sizeof(uint8_t) * length); + break; + } + case FL_VALUE_TYPE_INT32_LIST: { + write_uint8(buffer, kValueInt32List); + size_t length = fl_value_get_length(value); + write_size(buffer, length); + g_byte_array_append( + buffer, + reinterpret_cast(fl_value_get_int32_list(value)), + sizeof(int32_t) * length); + break; + } + case FL_VALUE_TYPE_INT64_LIST: { + write_uint8(buffer, kValueInt64List); + size_t length = fl_value_get_length(value); + write_size(buffer, length); + g_byte_array_append( + buffer, + reinterpret_cast(fl_value_get_int64_list(value)), + sizeof(int64_t) * length); + break; + } + case FL_VALUE_TYPE_FLOAT_LIST: { + write_uint8(buffer, kValueFloat64List); + size_t length = fl_value_get_length(value); + write_size(buffer, length); + g_byte_array_append( + buffer, + reinterpret_cast(fl_value_get_float_list(value)), + sizeof(double) * length); + break; + } + case FL_VALUE_TYPE_LIST: + write_uint8(buffer, kValueList); + write_size(buffer, fl_value_get_length(value)); + for (size_t i = 0; i < fl_value_get_length(value); i++) + encode_value(buffer, fl_value_list_get_value(value, i)); + break; + case FL_VALUE_TYPE_MAP: + write_uint8(buffer, kValueMap); + write_size(buffer, fl_value_get_length(value)); + for (size_t i = 0; i < fl_value_get_length(value); i++) { + encode_value(buffer, fl_value_map_get_key(value, i)); + encode_value(buffer, fl_value_map_get_value(value, i)); + } + break; + } +} + +static gboolean check_size(size_t data_length, + size_t* offset, + size_t required, + GError** error) { + if (*offset + required > data_length) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA, + "Unexpected end of data"); + return FALSE; + } + return TRUE; +} + +static gboolean read_uint8(const uint8_t* data, + size_t data_length, + size_t* offset, + uint8_t* value, + GError** error) { + if (!check_size(data_length, offset, 1, error)) + return FALSE; + + *value = data[*offset]; + (*offset)++; + return TRUE; +} + +static gboolean read_uint16(const uint8_t* data, + size_t data_length, + size_t* offset, + uint16_t* value, + GError** error) { + if (!check_size(data_length, offset, 2, error)) + return FALSE; + + *value = *reinterpret_cast(data + *offset); + *offset += 2; + return TRUE; +} + +static gboolean read_uint32(const uint8_t* data, + size_t data_length, + size_t* offset, + uint32_t* value, + GError** error) { + if (!check_size(data_length, offset, 4, error)) + return FALSE; + + *value = *reinterpret_cast(data + *offset); + *offset += 4; + return TRUE; +} + +static gboolean read_size(const uint8_t* data, + size_t data_length, + size_t* offset, + uint32_t* value, + GError** error) { + uint8_t value8; + if (!read_uint8(data, data_length, offset, &value8, error)) + return FALSE; + + if (value8 == 255) { + if (!read_uint32(data, data_length, offset, value, error)) + return FALSE; + } else if (value8 == 254) { + uint16_t value16; + if (!read_uint16(data, data_length, offset, &value16, error)) + return FALSE; + *value = value16; + } else + *value = value8; + + return TRUE; +} + +static FlValue* decode_int32(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + if (!check_size(data_length, offset, 4, error)) + return nullptr; + + FlValue* value = + fl_value_int_new(*reinterpret_cast(data + *offset)); + *offset += 4; + return value; +} + +static FlValue* decode_int64(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + if (!check_size(data_length, offset, 8, error)) + return nullptr; + + FlValue* value = + fl_value_int_new(*reinterpret_cast(data + *offset)); + *offset += 8; + return value; +} + +static FlValue* decode_float64(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + if (!check_size(data_length, offset, 8, error)) + return nullptr; + + FlValue* value = + fl_value_float_new(*reinterpret_cast(data + *offset)); + *offset += 8; + return value; +} + +static FlValue* decode_string(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint32_t length; + if (!read_size(data, data_length, offset, &length, error)) + return nullptr; + if (!check_size(data_length, offset, length, error)) + return nullptr; + FlValue* value = fl_value_string_new_sized( + reinterpret_cast(data + *offset), length); + *offset += length; + return value; +} + +static FlValue* decode_uint8_list(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint32_t length; + if (!read_size(data, data_length, offset, &length, error)) + return nullptr; + if (!check_size(data_length, offset, sizeof(uint8_t) * length, error)) + return nullptr; + FlValue* value = fl_value_uint8_list_new(data + *offset, length); + *offset += length; + return value; +} + +static FlValue* decode_int32_list(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint32_t length; + if (!read_size(data, data_length, offset, &length, error)) + return nullptr; + if (!check_size(data_length, offset, sizeof(int32_t) * length, error)) + return nullptr; + FlValue* value = fl_value_int32_list_new( + reinterpret_cast(data + *offset), length); + *offset += sizeof(int32_t) * length; + return value; +} + +static FlValue* decode_int64_list(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint32_t length; + if (!read_size(data, data_length, offset, &length, error)) + return nullptr; + if (!check_size(data_length, offset, sizeof(int64_t) * length, error)) + return nullptr; + FlValue* value = fl_value_int64_list_new( + reinterpret_cast(data + *offset), length); + *offset += sizeof(int64_t) * length; + return value; +} + +static FlValue* decode_float64_list(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint32_t length; + if (!read_size(data, data_length, offset, &length, error)) + return nullptr; + if (!check_size(data_length, offset, sizeof(double) * length, error)) + return nullptr; + FlValue* value = fl_value_float_list_new( + reinterpret_cast(data + *offset), length); + *offset += sizeof(double) * length; + return value; +} + +static FlValue* decode_value(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error); + +static FlValue* decode_list(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint32_t length; + if (!read_size(data, data_length, offset, &length, error)) + return nullptr; + + g_autoptr(FlValue) list = fl_value_list_new(); + for (size_t i = 0; i < length; i++) { + g_autoptr(FlValue) child = decode_value(data, data_length, offset, error); + if (child == nullptr) + return nullptr; + fl_value_list_add(list, child); + } + + return fl_value_ref(list); +} + +static FlValue* decode_map(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint32_t length; + if (!read_size(data, data_length, offset, &length, error)) + return nullptr; + + g_autoptr(FlValue) map = fl_value_map_new(); + for (size_t i = 0; i < length; i++) { + g_autoptr(FlValue) key = decode_value(data, data_length, offset, error); + if (key == nullptr) + return nullptr; + g_autoptr(FlValue) value = decode_value(data, data_length, offset, error); + if (value == nullptr) + return nullptr; + fl_value_map_set(map, key, value); + } + + return fl_value_ref(map); +} + +static FlValue* decode_value(const uint8_t* data, + size_t data_length, + size_t* offset, + GError** error) { + uint8_t type; + if (!read_uint8(data, data_length, offset, &type, error)) + return nullptr; + + g_autoptr(FlValue) value = nullptr; + if (type == kValueNull) + return fl_value_null_new(); + else if (type == kValueTrue) + return fl_value_bool_new(TRUE); + else if (type == kValueFalse) + return fl_value_bool_new(FALSE); + else if (type == kValueInt32) + value = decode_int32(data, data_length, offset, error); + else if (type == kValueInt64) + value = decode_int64(data, data_length, offset, error); + else if (type == kValueFloat64) + value = decode_float64(data, data_length, offset, error); + else if (type == kValueString) + value = decode_string(data, data_length, offset, error); + else if (type == kValueUint8List) + value = decode_uint8_list(data, data_length, offset, error); + else if (type == kValueInt32List) + value = decode_int32_list(data, data_length, offset, error); + else if (type == kValueInt64List) + value = decode_int64_list(data, data_length, offset, error); + else if (type == kValueFloat64List) + value = decode_float64_list(data, data_length, offset, error); + else if (type == kValueList) + value = decode_list(data, data_length, offset, error); + else if (type == kValueMap) + value = decode_map(data, data_length, offset, error); + else { + g_set_error(error, FL_STANDARD_CODEC_ERROR, + FL_STANDARD_CODEC_ERROR_UNKNOWN_TYPE, + "Unexpected standard codec type %02x", type); + return nullptr; + } + + return value == nullptr ? nullptr : fl_value_ref(value); +} + +static gboolean fl_standard_codec_write_value(FlCodec* codec, + GByteArray* buffer, + FlValue* value, + GError** error) { + encode_value(buffer, value); + return TRUE; +} + +static FlValue* fl_standard_codec_read_value(FlCodec* codec, + GBytes* message, + size_t* offset, + GError** error) { + gsize data_length; + const uint8_t* data = + static_cast(g_bytes_get_data(message, &data_length)); + g_autoptr(FlValue) value = decode_value(data, data_length, offset, error); + + return value == nullptr ? nullptr : fl_value_ref(value); +} + +static void fl_standard_codec_class_init(FlStandardCodecClass* klass) { + FL_CODEC_CLASS(klass)->write_value = fl_standard_codec_write_value; + FL_CODEC_CLASS(klass)->read_value = fl_standard_codec_read_value; +} + +static void fl_standard_codec_init(FlStandardCodec* self) {} + +G_MODULE_EXPORT FlStandardCodec* fl_standard_codec_new() { + return static_cast( + g_object_new(fl_standard_codec_get_type(), nullptr)); +} diff --git a/shell/platform/linux/fl_standard_codec_test.cc b/shell/platform/linux/fl_standard_codec_test.cc new file mode 100644 index 0000000000000..bb804cc15ca2e --- /dev/null +++ b/shell/platform/linux/fl_standard_codec_test.cc @@ -0,0 +1,966 @@ +// 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/linux/public/flutter_linux/fl_standard_codec.h" +#include "gtest/gtest.h" + +// FIXME(robert-ancell) Have two cases where endianess matters +// FIXME(robert-ancell) Add tests for strings/lists/maps with more than 253 +// elements + +static gchar* encode_value(FlValue* value) { + g_autoptr(FlStandardCodec) codec = fl_standard_codec_new(); + g_autoptr(GByteArray) buffer = g_byte_array_new(); + g_autoptr(GError) error = NULL; + gboolean result = + fl_codec_write_value(FL_CODEC(codec), buffer, value, &error); + EXPECT_TRUE(result); + EXPECT_EQ(error, nullptr); + + GString* hex_string = g_string_new(""); + for (guint i = 0; i < buffer->len; i++) + g_string_append_printf(hex_string, "%02x", buffer->data[i]); + return g_string_free(hex_string, FALSE); +} + +static uint8_t hex_digit_to_int(char value) { + if (value >= '0' && value <= '9') + return value - '0'; + else if (value >= 'a' && value <= 'f') + return value - 'a' + 10; + else if (value >= 'F' && value <= 'F') + return value - 'A' + 10; + else + return 0; +} + +static uint8_t parse_hex8(const gchar* hex_string) { + if (hex_string[0] == '\0') + return 0x00; + return hex_digit_to_int(hex_string[0]) << 4 | hex_digit_to_int(hex_string[1]); +} + +static GBytes* hex_string_to_bytes(const gchar* hex_string) { + GByteArray* buffer = g_byte_array_new(); + for (int i = 0; hex_string[i] != '\0' && hex_string[i + 1] != '\0'; i += 2) { + uint8_t value = parse_hex8(hex_string + i); + g_byte_array_append(buffer, &value, 1); + } + return g_byte_array_free_to_bytes(buffer); +} + +static FlValue* decode_value(const char* hex_string) { + g_autoptr(FlStandardCodec) codec = fl_standard_codec_new(); + g_autoptr(GBytes) data = hex_string_to_bytes(hex_string); + size_t offset = 0; + g_autoptr(GError) error = NULL; + g_autoptr(FlValue) value = + fl_codec_read_value(FL_CODEC(codec), data, &offset, &error); + EXPECT_EQ(offset, g_bytes_get_size(data)); + EXPECT_EQ(error, nullptr); + EXPECT_NE(value, nullptr); + return fl_value_ref(value); +} + +static void decode_error_value(const char* hex_string, + GQuark domain, + gint code) { + g_autoptr(FlStandardCodec) codec = fl_standard_codec_new(); + g_autoptr(GBytes) data = hex_string_to_bytes(hex_string); + size_t offset = 0; + g_autoptr(GError) error = NULL; + g_autoptr(FlValue) value = + fl_codec_read_value(FL_CODEC(codec), data, &offset, &error); + EXPECT_TRUE(value == nullptr); + EXPECT_TRUE(g_error_matches(error, domain, code)); + EXPECT_EQ(offset, static_cast(0)); +} + +TEST(FlStandardCodecTest, EncodeNULL) { + g_autofree gchar* hex_string = encode_value(NULL); + EXPECT_STREQ(hex_string, "00"); +} + +TEST(FlStandardCodecTest, EncodeNull) { + g_autoptr(FlValue) value = fl_value_null_new(); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "00"); +} + +static gchar* encode_bool(gboolean value) { + g_autoptr(FlValue) v = fl_value_bool_new(value); + return encode_value(v); +} + +TEST(FlStandardCodecTest, EncodeBoolFalse) { + g_autofree gchar* hex_string = encode_bool(FALSE); + EXPECT_STREQ(hex_string, "02"); +} + +TEST(FlStandardCodecTest, EncodeBoolTrue) { + g_autofree gchar* hex_string = encode_bool(TRUE); + EXPECT_STREQ(hex_string, "01"); +} + +static gchar* encode_int(int64_t value) { + g_autoptr(FlValue) v = fl_value_int_new(value); + return encode_value(v); +} + +TEST(FlStandardCodecTest, EncodeIntZero) { + g_autofree gchar* hex_string = encode_int(0); + EXPECT_STREQ(hex_string, "0300000000"); +} + +TEST(FlStandardCodecTest, EncodeIntOne) { + g_autofree gchar* hex_string = encode_int(1); + EXPECT_STREQ(hex_string, "0301000000"); +} + +TEST(FlStandardCodecTest, EncodeInt32) { + g_autofree gchar* hex_string = encode_int(0x01234567); + EXPECT_STREQ(hex_string, "0367452301"); +} + +TEST(FlStandardCodecTest, EncodeInt32Min) { + g_autofree gchar* hex_string = encode_int(G_MININT32); + EXPECT_STREQ(hex_string, "0300000080"); +} + +TEST(FlStandardCodecTest, EncodeInt32Max) { + g_autofree gchar* hex_string = encode_int(G_MAXINT32); + EXPECT_STREQ(hex_string, "03ffffff7f"); +} + +TEST(FlStandardCodecTest, EncodeInt64) { + g_autofree gchar* hex_string = encode_int(0x0123456789abcdef); + EXPECT_STREQ(hex_string, "04efcdab8967452301"); +} + +TEST(FlStandardCodecTest, EncodeInt64Min) { + g_autofree gchar* hex_string = encode_int(G_MININT64); + EXPECT_STREQ(hex_string, "040000000000000080"); +} + +TEST(FlStandardCodecTest, EncodeInt64Max) { + g_autofree gchar* hex_string = encode_int(G_MAXINT64); + EXPECT_STREQ(hex_string, "04ffffffffffffff7f"); +} + +TEST(FlStandardCodecTest, DecodeIntZero) { + g_autoptr(FlValue) value = decode_value("0300000000"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 0); +} + +TEST(FlStandardCodecTest, DecodeIntOne) { + g_autoptr(FlValue) value = decode_value("0301000000"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 1); +} + +TEST(FlStandardCodecTest, DecodeInt32) { + g_autoptr(FlValue) value = decode_value("0367452301"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 0x01234567); +} + +TEST(FlStandardCodecTest, DecodeInt32Min) { + g_autoptr(FlValue) value = decode_value("0300000080"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), G_MININT32); +} + +TEST(FlStandardCodecTest, DecodeInt32Max) { + g_autoptr(FlValue) value = decode_value("03ffffff7f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), G_MAXINT32); +} + +TEST(FlStandardCodecTest, DecodeInt64) { + g_autoptr(FlValue) value = decode_value("04efcdab8967452301"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 0x0123456789abcdef); +} + +TEST(FlStandardCodecTest, DecodeInt64Min) { + g_autoptr(FlValue) value = decode_value("040000000000000080"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), G_MININT64); +} + +TEST(FlStandardCodecTest, DecodeInt64Max) { + g_autoptr(FlValue) value = decode_value("04ffffffffffffff7f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), G_MAXINT64); +} + +TEST(FlStandardCodecTest, DecodeInt32NoData) { + decode_error_value("03", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeIntShortData1) { + decode_error_value("0367", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeIntShortData2) { + decode_error_value("03674523", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt64NoData) { + decode_error_value("04", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt64ShortData1) { + decode_error_value("04ef", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt64ShortData2) { + decode_error_value("04efcdab89674523", FL_CODEC_ERROR, + FL_CODEC_ERROR_OUT_OF_DATA); +} + +static gchar* encode_float(double value) { + g_autoptr(FlValue) v = fl_value_float_new(value); + return encode_value(v); +} + +TEST(FlStandardCodecTest, EncodeFloatZero) { + g_autofree gchar* hex_string = encode_float(0); + EXPECT_STREQ(hex_string, "060000000000000000"); +} + +TEST(FlStandardCodecTest, EncodeFloatOne) { + g_autofree gchar* hex_string = encode_float(1); + EXPECT_STREQ(hex_string, "06000000000000f03f"); +} + +TEST(FlStandardCodecTest, EncodeFloatMinusOne) { + g_autofree gchar* hex_string = encode_float(-1); + EXPECT_STREQ(hex_string, "06000000000000f0bf"); +} + +TEST(FlStandardCodecTest, EncodeFloatHalf) { + g_autofree gchar* hex_string = encode_float(0.5); + EXPECT_STREQ(hex_string, "06000000000000e03f"); +} + +TEST(FlStandardCodecTest, EncodeFloatFraction) { + g_autofree gchar* hex_string = encode_float(M_PI); + EXPECT_STREQ(hex_string, "06182d4454fb210940"); +} + +TEST(FlStandardCodecTest, EncodeFloatMinusZero) { + g_autofree gchar* hex_string = encode_float(-0.0); + EXPECT_STREQ(hex_string, "060000000000000080"); +} + +TEST(FlStandardCodecTest, EncodeFloatNaN) { + g_autofree gchar* hex_string = encode_float(NAN); + EXPECT_STREQ(hex_string, "06000000000000f87f"); +} + +TEST(FlStandardCodecTest, EncodeFloatInfinity) { + g_autofree gchar* hex_string = encode_float(INFINITY); + EXPECT_STREQ(hex_string, "06000000000000f07f"); +} + +TEST(FlStandardCodecTest, DecodeFloatZero) { + g_autoptr(FlValue) value = decode_value("060000000000000000"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 0.0); +} + +TEST(FlStandardCodecTest, DecodeFloatOne) { + g_autoptr(FlValue) value = decode_value("06000000000000f03f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 1.0); +} + +TEST(FlStandardCodecTest, DecodeFloatMinusOne) { + g_autoptr(FlValue) value = decode_value("06000000000000f0bf"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), -1.0); +} + +TEST(FlStandardCodecTest, DecodeFloatHalf) { + g_autoptr(FlValue) value = decode_value("06000000000000e03f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 0.5); +} + +TEST(FlStandardCodecTest, DecodeFloatPi) { + g_autoptr(FlValue) value = decode_value("06182d4454fb210940"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), M_PI); +} + +TEST(FlStandardCodecTest, DecodeFloatMinusZero) { + g_autoptr(FlValue) value = decode_value("060000000000000080"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), -0.0); +} + +TEST(FlStandardCodecTest, DecodeFloatNaN) { + g_autoptr(FlValue) value = decode_value("06000000000000f87f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_TRUE(isnan(fl_value_get_float(value))); +} + +TEST(FlStandardCodecTest, DecodeFloatInfinity) { + g_autoptr(FlValue) value = decode_value("06000000000000f07f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_TRUE(isinf(fl_value_get_float(value))); +} + +TEST(FlStandardCodecTest, DecodeFloatNoData) { + decode_error_value("0600", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeFloatShortData1) { + decode_error_value("0600", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeFloatShortData2) { + decode_error_value("0600000000000000", FL_CODEC_ERROR, + FL_CODEC_ERROR_OUT_OF_DATA); +} + +static gchar* encode_string(const gchar* value) { + g_autoptr(FlValue) v = fl_value_string_new(value); + return encode_value(v); +} + +TEST(FlStandardCodecTest, EncodeStringEmpty) { + g_autofree gchar* hex_string = encode_string(""); + EXPECT_STREQ(hex_string, "0700"); +} + +TEST(FlStandardCodecTest, EncodeStringHello) { + g_autofree gchar* hex_string = encode_string("hello"); + EXPECT_STREQ(hex_string, "070568656c6c6f"); +} + +TEST(FlStandardCodecTest, EncodeStringEmptySized) { + g_autoptr(FlValue) value = fl_value_string_new_sized(NULL, 0); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "0700"); +} + +TEST(FlStandardCodecTest, EncodeStringHelloSized) { + g_autoptr(FlValue) value = fl_value_string_new_sized("Hello World", 5); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "070548656c6c6f"); +} + +TEST(FlStandardCodecTest, DecodeStringEmpty) { + g_autoptr(FlValue) value = decode_value("0700"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), ""); +} + +TEST(FlStandardCodecTest, DecodeStringHello) { + g_autoptr(FlValue) value = decode_value("070568656c6c6f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "hello"); +} + +TEST(FlStandardCodecTest, DecodeStringNoData) { + decode_error_value("07", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeStringLengthNoData) { + decode_error_value("0705", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeStringShortData1) { + decode_error_value("070568", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeStringShortData2) { + decode_error_value("070568656c6c", FL_CODEC_ERROR, + FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, EncodeUint8ListEmpty) { + g_autoptr(FlValue) value = fl_value_uint8_list_new(NULL, 0); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "0800"); +} + +TEST(FlStandardCodecTest, EncodeUint8List) { + uint8_t data[] = {0, 1, 2, 3, 4}; + g_autoptr(FlValue) value = fl_value_uint8_list_new(data, 5); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "08050001020304"); +} + +TEST(FlStandardCodecTest, DecodeUint8ListEmpty) { + g_autoptr(FlValue) value = decode_value("0800"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_UINT8_LIST); + EXPECT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeUint8List) { + g_autoptr(FlValue) value = decode_value("08050001020304"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_UINT8_LIST); + EXPECT_EQ(fl_value_get_length(value), static_cast(5)); + const uint8_t* data = fl_value_get_uint8_list(value); + EXPECT_EQ(data[0], 0); + EXPECT_EQ(data[1], 1); + EXPECT_EQ(data[2], 2); + EXPECT_EQ(data[3], 3); + EXPECT_EQ(data[4], 4); +} + +TEST(FlStandardCodecTest, DecodeUint8ListNoData) { + decode_error_value("08", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeUint8ListLengthNoData) { + decode_error_value("0805", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeUint8ListShortData1) { + decode_error_value("080500", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeUint8ListShortData2) { + decode_error_value("080500010203", FL_CODEC_ERROR, + FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, EncodeInt32ListEmpty) { + g_autoptr(FlValue) value = fl_value_int32_list_new(NULL, 0); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "0900"); +} + +TEST(FlStandardCodecTest, EncodeInt32List) { + int32_t data[] = {0, -1, 2, -3, 4}; + g_autoptr(FlValue) value = fl_value_int32_list_new(data, 5); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "090500000000ffffffff02000000fdffffff04000000"); +} + +TEST(FlStandardCodecTest, DecodeInt32ListEmpty) { + g_autoptr(FlValue) value = decode_value("0900"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT32_LIST); + EXPECT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeInt32List) { + g_autoptr(FlValue) value = + decode_value("090500000000ffffffff02000000fdffffff04000000"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT32_LIST); + const int32_t* data = fl_value_get_int32_list(value); + EXPECT_EQ(data[0], 0); + EXPECT_EQ(data[1], -1); + EXPECT_EQ(data[2], 2); + EXPECT_EQ(data[3], -3); + EXPECT_EQ(data[4], 4); +} + +TEST(FlStandardCodecTest, DecodeInt32ListNoData) { + decode_error_value("09", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt32ListLengthNoData) { + decode_error_value("0905", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt32ListShortData1) { + decode_error_value("090500", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt32ListShortData2) { + decode_error_value("090500000000ffffffff02000000fdffffff040000", + FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, EncodeInt64ListEmpty) { + g_autoptr(FlValue) value = fl_value_int64_list_new(NULL, 0); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "0a00"); +} + +TEST(FlStandardCodecTest, EncodeInt64List) { + int64_t data[] = {0, -1, 2, -3, 4}; + g_autoptr(FlValue) value = fl_value_int64_list_new(data, 5); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, + "0a050000000000000000ffffffffffffffff0200000000000000fdffffff" + "ffffffff0400000000000000"); +} + +TEST(FlStandardCodecTest, DecodeInt64ListEmpty) { + g_autoptr(FlValue) value = decode_value("0a00"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT64_LIST); + EXPECT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeInt64List) { + g_autoptr(FlValue) value = decode_value( + "0a050000000000000000ffffffffffffffff0200000000000000fdffffff" + "ffffffff0400000000000000"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT64_LIST); + const int64_t* data = fl_value_get_int64_list(value); + EXPECT_EQ(data[0], 0); + EXPECT_EQ(data[1], -1); + EXPECT_EQ(data[2], 2); + EXPECT_EQ(data[3], -3); + EXPECT_EQ(data[4], 4); +} + +TEST(FlStandardCodecTest, DecodeInt64ListNoData) { + decode_error_value("0a", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt64ListLengthNoData) { + decode_error_value("0a05", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt64ListShortData1) { + decode_error_value("0a0500", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeInt64ListShortData2) { + decode_error_value( + "0a050000000000000000ffffffffffffffff0200000000000000fdffffffffffffff0400" + "0000000000", + FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, EncodeFloatListEmpty) { + g_autoptr(FlValue) value = fl_value_float_list_new(NULL, 0); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "0b00"); +} + +TEST(FlStandardCodecTest, EncodeFloatList) { + double data[] = {0, -0.5, 0.25, -0.125, 0.00625}; + g_autoptr(FlValue) value = fl_value_float_list_new(data, 5); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, + "0b050000000000000000000000000000e0bf000000000000d03f00000000" + "0000c0bf9a9999999999793f"); +} + +TEST(FlStandardCodecTest, DecodeFloatListEmpty) { + g_autoptr(FlValue) value = decode_value("0b00"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT_LIST); + EXPECT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeFloatList) { + g_autoptr(FlValue) value = decode_value( + "0b050000000000000000000000000000e0bf000000000000d03f00000000" + "0000c0bf9a9999999999793f"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT_LIST); + const double* data = fl_value_get_float_list(value); + EXPECT_FLOAT_EQ(data[0], 0.0); + EXPECT_FLOAT_EQ(data[1], -0.5); + EXPECT_FLOAT_EQ(data[2], 0.25); + EXPECT_FLOAT_EQ(data[3], -0.125); + EXPECT_FLOAT_EQ(data[4], 0.00625); +} + +TEST(FlStandardCodecTest, DecodeFloatListNoData) { + decode_error_value("0b", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeFloatListLengthNoData) { + decode_error_value("0b05", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeFloatListShortData1) { + decode_error_value("0b0500", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeFloatListShortData2) { + decode_error_value( + "0b050000000000000000000000000000e0bf000000000000d03f000000000000c0bf9a99" + "9999999979", + FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, EncodeListEmpty) { + g_autoptr(FlValue) value = fl_value_list_new(); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "0c00"); +} + +TEST(FlStandardCodecTest, EncodeListTypes) { + g_autoptr(FlValue) value = fl_value_list_new(); + fl_value_list_add_take(value, fl_value_null_new()); + fl_value_list_add_take(value, fl_value_bool_new(TRUE)); + fl_value_list_add_take(value, fl_value_int_new(42)); + fl_value_list_add_take(value, fl_value_float_new(-1.5)); + fl_value_list_add_take(value, fl_value_string_new("hello")); + fl_value_list_add_take(value, fl_value_list_new()); + fl_value_list_add_take(value, fl_value_map_new()); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, + "0c070001032a00000006000000000000f8bf070568656c6c6f0c000d00"); +} + +TEST(FlStandardCodecTest, EncodeListNested) { + g_autoptr(FlValue) even_numbers = fl_value_list_new(); + g_autoptr(FlValue) odd_numbers = fl_value_list_new(); + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) + fl_value_list_add_take(even_numbers, fl_value_int_new(i)); + else + fl_value_list_add_take(odd_numbers, fl_value_int_new(i)); + } + g_autoptr(FlValue) value = fl_value_list_new(); + fl_value_list_add(value, even_numbers); + fl_value_list_add(value, odd_numbers); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, + "0c020c05030000000003020000000304000000030600000003080000000c" + "0503010000000303000000030500000003070000000309000000"); +} + +TEST(FlStandardCodecTest, DecodeListEmpty) { + g_autoptr(FlValue) value = decode_value("0c00"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_LIST); + EXPECT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeListTypes) { + g_autoptr(FlValue) value = decode_value( + "0c070001032a00000006000000000000f8bf070568656c6c6f0c000d00"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_LIST); + ASSERT_EQ(fl_value_get_length(value), static_cast(7)); + ASSERT_EQ(fl_value_get_type(fl_value_list_get_value(value, 0)), + FL_VALUE_TYPE_NULL); + ASSERT_EQ(fl_value_get_type(fl_value_list_get_value(value, 1)), + FL_VALUE_TYPE_BOOL); + EXPECT_TRUE(fl_value_get_bool(fl_value_list_get_value(value, 1))); + ASSERT_EQ(fl_value_get_type(fl_value_list_get_value(value, 2)), + FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(fl_value_list_get_value(value, 2)), 42); + ASSERT_EQ(fl_value_get_type(fl_value_list_get_value(value, 3)), + FL_VALUE_TYPE_FLOAT); + EXPECT_FLOAT_EQ(fl_value_get_float(fl_value_list_get_value(value, 3)), -1.5); + ASSERT_EQ(fl_value_get_type(fl_value_list_get_value(value, 4)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_list_get_value(value, 4)), "hello"); + ASSERT_EQ(fl_value_get_type(fl_value_list_get_value(value, 5)), + FL_VALUE_TYPE_LIST); + ASSERT_EQ(fl_value_get_length(fl_value_list_get_value(value, 5)), + static_cast(0)); + ASSERT_EQ(fl_value_get_type(fl_value_list_get_value(value, 6)), + FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(fl_value_list_get_value(value, 6)), + static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeListNested) { + g_autoptr(FlValue) value = decode_value( + "0c020c05030000000003020000000304000000030600000003080000000c" + "0503010000000303000000030500000003070000000309000000"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_LIST); + ASSERT_EQ(fl_value_get_length(value), static_cast(2)); + FlValue* even_list = fl_value_list_get_value(value, 0); + ASSERT_EQ(fl_value_get_type(even_list), FL_VALUE_TYPE_LIST); + ASSERT_EQ(fl_value_get_length(even_list), static_cast(5)); + FlValue* odd_list = fl_value_list_get_value(value, 1); + ASSERT_EQ(fl_value_get_type(odd_list), FL_VALUE_TYPE_LIST); + ASSERT_EQ(fl_value_get_length(odd_list), static_cast(5)); + for (int i = 0; i < 5; i++) { + FlValue* v = fl_value_list_get_value(even_list, i); + ASSERT_EQ(fl_value_get_type(v), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(v), i * 2); + + v = fl_value_list_get_value(odd_list, i); + ASSERT_EQ(fl_value_get_type(v), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(v), i * 2 + 1); + } +} + +TEST(FlStandardCodecTest, DecodeListNoData) { + decode_error_value("0c", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeListLengthNoData) { + decode_error_value("0c07", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeListShortData1) { + decode_error_value("0c0700", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeListShortData2) { + decode_error_value("0c070001032a00000006000000000000f8bf070568656c6c6f0c000d", + FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, EncodeMapEmpty) { + g_autoptr(FlValue) value = fl_value_map_new(); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, "0d00"); +} + +TEST(FlStandardCodecTest, EncodeMapKeyTypes) { + g_autoptr(FlValue) value = fl_value_map_new(); + fl_value_map_set_take(value, fl_value_null_new(), + fl_value_string_new("null")); + fl_value_map_set_take(value, fl_value_bool_new(TRUE), + fl_value_string_new("bool")); + fl_value_map_set_take(value, fl_value_int_new(42), + fl_value_string_new("int")); + fl_value_map_set_take(value, fl_value_float_new(-1.5), + fl_value_string_new("float")); + fl_value_map_set_take(value, fl_value_string_new("hello"), + fl_value_string_new("string")); + fl_value_map_set_take(value, fl_value_list_new(), + fl_value_string_new("list")); + fl_value_map_set_take(value, fl_value_map_new(), fl_value_string_new("map")); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, + "0d070007046e756c6c010704626f6f6c032a0000000703696e7406000000" + "000000f8bf0705666c6f6174070568656c6c6f0706737472696e670c0007" + "046c6973740d0007036d6170"); +} + +TEST(FlStandardCodecTest, EncodeMapValueTypes) { + g_autoptr(FlValue) value = fl_value_map_new(); + fl_value_map_set_take(value, fl_value_string_new("null"), + fl_value_null_new()); + fl_value_map_set_take(value, fl_value_string_new("bool"), + fl_value_bool_new(TRUE)); + fl_value_map_set_take(value, fl_value_string_new("int"), + fl_value_int_new(42)); + fl_value_map_set_take(value, fl_value_string_new("float"), + fl_value_float_new(-1.5)); + fl_value_map_set_take(value, fl_value_string_new("string"), + fl_value_string_new("hello")); + fl_value_map_set_take(value, fl_value_string_new("list"), + fl_value_list_new()); + fl_value_map_set_take(value, fl_value_string_new("map"), fl_value_map_new()); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, + "0d0707046e756c6c000704626f6f6c010703696e74032a0000000705666c" + "6f617406000000000000f8bf0706737472696e67070568656c6c6f07046c" + "6973740c0007036d61700d00"); +} + +TEST(FlStandardCodecTest, EncodeMapNested) { + g_autoptr(FlValue) str_to_int = fl_value_map_new(); + g_autoptr(FlValue) int_to_str = fl_value_map_new(); + const char* numbers[] = {"zero", "one", "two", "three", NULL}; + for (int i = 0; numbers[i] != NULL; i++) { + fl_value_map_set_take(str_to_int, fl_value_string_new(numbers[i]), + fl_value_int_new(i)); + fl_value_map_set_take(int_to_str, fl_value_int_new(i), + fl_value_string_new(numbers[i])); + } + g_autoptr(FlValue) value = fl_value_map_new(); + fl_value_map_set_string(value, "str-to-int", str_to_int); + fl_value_map_set_string(value, "int-to-str", int_to_str); + g_autofree gchar* hex_string = encode_value(value); + EXPECT_STREQ(hex_string, + "0d02070a7374722d746f2d696e740d0407047a65726f030000000007036f" + "6e650301000000070374776f030200000007057468726565030300000007" + "0a696e742d746f2d7374720d04030000000007047a65726f030100000007" + "036f6e650302000000070374776f030300000007057468726565"); +} + +TEST(FlStandardCodecTest, DecodeMapEmpty) { + g_autoptr(FlValue) value = decode_value("0d00"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeMapKeyTypes) { + g_autoptr(FlValue) value = decode_value( + "0d070007046e756c6c010704626f6f6c032a0000000703696e7406000000" + "000000f8bf0705666c6f6174070568656c6c6f0706737472696e670c0007" + "046c6973740d0007036d6170"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(value), static_cast(7)); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 0)), + FL_VALUE_TYPE_NULL); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 0)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 0)), "null"); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 1)), + FL_VALUE_TYPE_BOOL); + EXPECT_TRUE(fl_value_get_bool(fl_value_map_get_key(value, 1))); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 1)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 1)), "bool"); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 2)), + FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(fl_value_map_get_key(value, 2)), 42); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 2)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 2)), "int"); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 3)), + FL_VALUE_TYPE_FLOAT); + EXPECT_FLOAT_EQ(fl_value_get_float(fl_value_map_get_key(value, 3)), -1.5); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 3)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 3)), "float"); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 4)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 4)), "hello"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 4)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 4)), "string"); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 5)), + FL_VALUE_TYPE_LIST); + ASSERT_EQ(fl_value_get_length(fl_value_map_get_key(value, 5)), + static_cast(0)); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 5)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 5)), "list"); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 6)), + FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(fl_value_map_get_key(value, 6)), + static_cast(0)); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 6)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 6)), "map"); +} + +TEST(FlStandardCodecTest, DecodeMapValueTypes) { + g_autoptr(FlValue) value = decode_value( + "0d0707046e756c6c000704626f6f6c010703696e74032a0000000705666c" + "6f617406000000000000f8bf0706737472696e67070568656c6c6f07046c" + "6973740c0007036d61700d00"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(value), static_cast(7)); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 0)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 0)), "null"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 0)), + FL_VALUE_TYPE_NULL); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 1)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 1)), "bool"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 1)), + FL_VALUE_TYPE_BOOL); + EXPECT_TRUE(fl_value_get_bool(fl_value_map_get_value(value, 1))); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 2)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 2)), "int"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 2)), + FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(fl_value_map_get_value(value, 2)), 42); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 3)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 3)), "float"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 3)), + FL_VALUE_TYPE_FLOAT); + EXPECT_FLOAT_EQ(fl_value_get_float(fl_value_map_get_value(value, 3)), -1.5); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 4)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 4)), "string"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 4)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(value, 4)), "hello"); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 5)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 5)), "list"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 5)), + FL_VALUE_TYPE_LIST); + ASSERT_EQ(fl_value_get_length(fl_value_map_get_value(value, 5)), + static_cast(0)); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 6)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 6)), "map"); + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(value, 6)), + FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(fl_value_map_get_value(value, 6)), + static_cast(0)); +} + +TEST(FlStandardCodecTest, DecodeMapNested) { + g_autoptr(FlValue) value = decode_value( + "0d02070a7374722d746f2d696e740d0407047a65726f030000000007036f" + "6e650301000000070374776f030200000007057468726565030300000007" + "0a696e742d746f2d7374720d04030000000007047a65726f030100000007" + "036f6e650302000000070374776f030300000007057468726565"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(value), static_cast(2)); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 0)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 0)), + "str-to-int"); + FlValue* str_to_int = fl_value_map_get_value(value, 0); + ASSERT_EQ(fl_value_get_type(str_to_int), FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(str_to_int), static_cast(4)); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(value, 1)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(value, 1)), + "int-to-str"); + FlValue* int_to_str = fl_value_map_get_value(value, 1); + ASSERT_EQ(fl_value_get_type(int_to_str), FL_VALUE_TYPE_MAP); + ASSERT_EQ(fl_value_get_length(int_to_str), static_cast(4)); + + const char* numbers[] = {"zero", "one", "two", "three", NULL}; + for (int i = 0; numbers[i] != NULL; i++) { + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(str_to_int, i)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_key(str_to_int, i)), + numbers[i]); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(str_to_int, i)), + FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(fl_value_map_get_value(str_to_int, i)), i); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_key(int_to_str, i)), + FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(fl_value_map_get_key(int_to_str, i)), i); + + ASSERT_EQ(fl_value_get_type(fl_value_map_get_value(int_to_str, i)), + FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(fl_value_map_get_value(int_to_str, i)), + numbers[i]); + } +} + +TEST(FlStandardCodecTest, DecodeMapNoData) { + decode_error_value("0d", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeMapLengthNoData) { + decode_error_value("0d07", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeMapShortData1) { + decode_error_value("0d0707", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeMapShortData2) { + decode_error_value( + "0d0707046e756c6c000704626f6f6c010703696e74032a0000000705666c" + "6f617406000000000000f8bf0706737472696e67070568656c6c6f07046c" + "6973740c0007036d61700d", + FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardCodecTest, DecodeUnknownType) { + decode_error_value("0e", FL_STANDARD_CODEC_ERROR, + FL_STANDARD_CODEC_ERROR_UNKNOWN_TYPE); +} diff --git a/shell/platform/linux/fl_standard_method_codec.cc b/shell/platform/linux/fl_standard_method_codec.cc new file mode 100644 index 0000000000000..28c1c5e093573 --- /dev/null +++ b/shell/platform/linux/fl_standard_method_codec.cc @@ -0,0 +1,222 @@ +// 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/linux/public/flutter_linux/fl_standard_method_codec.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_codec.h" + +#include + +static constexpr guint8 kEnvelopeTypeSuccess = 0; +static constexpr guint8 kEnvelopeTypeError = 1; + +struct _FlStandardMethodCodec { + FlMethodCodec parent_instance; + + FlStandardCodec* codec; +}; + +G_DEFINE_TYPE(FlStandardMethodCodec, + fl_standard_method_codec, + fl_method_codec_get_type()) + +static void fl_standard_method_codec_dispose(GObject* object) { + FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(object); + + g_clear_object(&self->codec); + + G_OBJECT_CLASS(fl_standard_method_codec_parent_class)->dispose(object); +} + +static GBytes* fl_standard_method_codec_encode_method_call(FlMethodCodec* codec, + const gchar* name, + FlValue* args, + GError** error) { + FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec); + + g_autoptr(GByteArray) buffer = g_byte_array_new(); + g_autoptr(FlValue) name_value = fl_value_string_new(name); + if (!fl_codec_write_value(FL_CODEC(self->codec), buffer, name_value, error)) + return nullptr; + if (!fl_codec_write_value(FL_CODEC(self->codec), buffer, args, error)) + return nullptr; + + return g_byte_array_free_to_bytes( + static_cast(g_steal_pointer(&buffer))); +} + +static gboolean fl_standard_method_codec_decode_method_call( + FlMethodCodec* codec, + GBytes* message, + gchar** name, + FlValue** args, + GError** error) { + FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec); + + size_t offset = 0; + g_autoptr(FlValue) name_value = + fl_codec_read_value(FL_CODEC(self->codec), message, &offset, error); + if (name_value == nullptr) + return FALSE; + if (fl_value_get_type(name_value) != FL_VALUE_TYPE_STRING) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, + "Method call name wrong type"); + return FALSE; + } + + g_autoptr(FlValue) args_value = + fl_codec_read_value(FL_CODEC(self->codec), message, &offset, error); + if (args_value == nullptr) + return FALSE; + + if (offset != g_bytes_get_size(message)) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, + "Unexpected extra data"); + return FALSE; + } + + *name = g_strdup(fl_value_get_string(name_value)); + *args = fl_value_ref(args_value); + + return TRUE; +} + +static GBytes* fl_standard_method_codec_encode_success_envelope( + FlMethodCodec* codec, + FlValue* result, + GError** error) { + FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec); + + g_autoptr(GByteArray) buffer = g_byte_array_new(); + guint8 type = kEnvelopeTypeSuccess; + g_byte_array_append(buffer, &type, 1); + if (!fl_codec_write_value(FL_CODEC(self->codec), buffer, result, error)) + return nullptr; + + return g_byte_array_free_to_bytes( + static_cast(g_steal_pointer(&buffer))); +} + +static GBytes* fl_standard_method_codec_encode_error_envelope( + FlMethodCodec* codec, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error) { + FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec); + + g_autoptr(GByteArray) buffer = g_byte_array_new(); + guint8 type = kEnvelopeTypeError; + g_byte_array_append(buffer, &type, 1); + g_autoptr(FlValue) code_value = fl_value_string_new(code); + if (!fl_codec_write_value(FL_CODEC(self->codec), buffer, code_value, error)) + return nullptr; + g_autoptr(FlValue) message_value = + message != nullptr ? fl_value_string_new(message) : nullptr; + if (!fl_codec_write_value(FL_CODEC(self->codec), buffer, message_value, + error)) + return nullptr; + if (!fl_codec_write_value(FL_CODEC(self->codec), buffer, details, error)) + return nullptr; + + return g_byte_array_free_to_bytes( + static_cast(g_steal_pointer(&buffer))); +} + +static gboolean fl_standard_method_codec_decode_response(FlMethodCodec* codec, + GBytes* message, + gchar** error_code, + gchar** error_message, + FlValue** result, + GError** error) { + FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec); + + if (g_bytes_get_size(message) == 0) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA, + "Empty response"); + return FALSE; + } + + const guint8* data = + static_cast(g_bytes_get_data(message, nullptr)); + guint8 type = data[0]; + size_t offset = 1; + + g_autoptr(FlValue) code_value = nullptr; + g_autoptr(FlValue) message_value = nullptr; + if (type == kEnvelopeTypeError) { + code_value = + fl_codec_read_value(FL_CODEC(self->codec), message, &offset, error); + if (code_value == nullptr) + return FALSE; + if (fl_value_get_type(code_value) != FL_VALUE_TYPE_STRING) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, + "Error code wrong type"); + return FALSE; + } + + message_value = + fl_codec_read_value(FL_CODEC(self->codec), message, &offset, error); + if (message_value == nullptr) + return FALSE; + if (fl_value_get_type(message_value) != FL_VALUE_TYPE_STRING && + fl_value_get_type(message_value) != FL_VALUE_TYPE_NULL) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, + "Error message wrong type"); + return FALSE; + } + } else if (type != kEnvelopeTypeSuccess) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, + "Unknown envelope type %02x", type); + return FALSE; + } + + g_autoptr(FlValue) result_value = + fl_codec_read_value(FL_CODEC(self->codec), message, &offset, error); + if (result_value == nullptr) + return FALSE; + + if (offset != g_bytes_get_size(message)) { + g_set_error(error, FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED, + "Unexpected extra data"); + return FALSE; + } + + *error_code = code_value != nullptr + ? g_strdup(fl_value_get_string(code_value)) + : nullptr; + *error_message = + message_value != nullptr && + fl_value_get_type(message_value) == FL_VALUE_TYPE_STRING + ? g_strdup(fl_value_get_string(message_value)) + : nullptr; + *result = fl_value_ref(result_value); + + return TRUE; +} + +static void fl_standard_method_codec_class_init( + FlStandardMethodCodecClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_standard_method_codec_dispose; + FL_METHOD_CODEC_CLASS(klass)->encode_method_call = + fl_standard_method_codec_encode_method_call; + FL_METHOD_CODEC_CLASS(klass)->decode_method_call = + fl_standard_method_codec_decode_method_call; + FL_METHOD_CODEC_CLASS(klass)->encode_success_envelope = + fl_standard_method_codec_encode_success_envelope; + FL_METHOD_CODEC_CLASS(klass)->encode_error_envelope = + fl_standard_method_codec_encode_error_envelope; + FL_METHOD_CODEC_CLASS(klass)->decode_response = + fl_standard_method_codec_decode_response; +} + +static void fl_standard_method_codec_init(FlStandardMethodCodec* self) { + self->codec = fl_standard_codec_new(); +} + +G_MODULE_EXPORT FlStandardMethodCodec* fl_standard_method_codec_new() { + FlStandardMethodCodec* self = static_cast( + g_object_new(fl_standard_method_codec_get_type(), nullptr)); + return self; +} diff --git a/shell/platform/linux/fl_standard_method_codec_test.cc b/shell/platform/linux/fl_standard_method_codec_test.cc new file mode 100644 index 0000000000000..274dc00451f77 --- /dev/null +++ b/shell/platform/linux/fl_standard_method_codec_test.cc @@ -0,0 +1,434 @@ +// 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/linux/public/flutter_linux/fl_standard_method_codec.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_codec.h" +#include "gtest/gtest.h" + +// FIXME(robert-ancell) Have two cases where endianess matters + +static gchar* message_to_hex_string(GBytes* message) { + GString* hex_string = g_string_new(""); + gsize data_length; + const uint8_t* data = + static_cast(g_bytes_get_data(message, &data_length)); + for (guint i = 0; i < data_length; i++) + g_string_append_printf(hex_string, "%02x", data[i]); + return g_string_free(hex_string, FALSE); +} + +static gchar* encode_method_call(const gchar* name, FlValue* args) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = NULL; + g_autoptr(GBytes) message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), name, args, &error); + EXPECT_NE(message, nullptr); + EXPECT_EQ(error, nullptr); + + return message_to_hex_string(message); +} + +static gchar* encode_success_envelope(FlValue* result) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = NULL; + g_autoptr(GBytes) message = fl_method_codec_encode_success_envelope( + FL_METHOD_CODEC(codec), result, &error); + EXPECT_NE(message, nullptr); + EXPECT_EQ(error, nullptr); + + return message_to_hex_string(message); +} + +static gchar* encode_error_envelope(const gchar* error_code, + const gchar* error_message, + FlValue* details) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = NULL; + g_autoptr(GBytes) message = fl_method_codec_encode_error_envelope( + FL_METHOD_CODEC(codec), error_code, error_message, details, &error); + EXPECT_NE(message, nullptr); + EXPECT_EQ(error, nullptr); + + return message_to_hex_string(message); +} + +static uint8_t hex_digit_to_int(char value) { + if (value >= '0' && value <= '9') + return value - '0'; + else if (value >= 'a' && value <= 'f') + return value - 'a' + 10; + else if (value >= 'F' && value <= 'F') + return value - 'A' + 10; + else + return 0; +} + +static uint8_t parse_hex8(const gchar* hex_string) { + if (hex_string[0] == '\0') + return 0x00; + return hex_digit_to_int(hex_string[0]) << 4 | hex_digit_to_int(hex_string[1]); +} + +static GBytes* hex_string_to_bytes(const gchar* hex_string) { + GByteArray* buffer = g_byte_array_new(); + for (int i = 0; hex_string[i] != '\0' && hex_string[i + 1] != '\0'; i += 2) { + uint8_t value = parse_hex8(hex_string + i); + g_byte_array_append(buffer, &value, 1); + } + return g_byte_array_free_to_bytes(buffer); +} + +static void decode_method_call(const char* hex_string, + gchar** name, + FlValue** args) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) data = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = NULL; + gboolean result = fl_method_codec_decode_method_call( + FL_METHOD_CODEC(codec), data, name, args, &error); + EXPECT_TRUE(result); + EXPECT_EQ(error, nullptr); +} + +static void decode_error_method_call(const char* hex_string, + GQuark domain, + gint code) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) data = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = NULL; + g_autofree gchar* name = NULL; + g_autoptr(FlValue) args = NULL; + gboolean result = fl_method_codec_decode_method_call( + FL_METHOD_CODEC(codec), data, &name, &args, &error); + EXPECT_FALSE(result); + EXPECT_EQ(name, nullptr); + EXPECT_EQ(args, nullptr); + EXPECT_TRUE(g_error_matches(error, domain, code)); +} + +static void decode_response(const char* hex_string, + gchar** error_code, + gchar** error_message, + FlValue** result_or_details) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) data = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = NULL; + gboolean result = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), data, error_code, + error_message, result_or_details, &error); + EXPECT_TRUE(result); + EXPECT_EQ(error, nullptr); +} + +static void decode_response_no_error(const char* hex_string, + FlValue** out_result) { + g_autoptr(GError) error = NULL; + g_autofree gchar* error_code = NULL; + g_autofree gchar* error_message = NULL; + g_autoptr(FlValue) result_or_details = NULL; + decode_response(hex_string, &error_code, &error_message, &result_or_details); + EXPECT_EQ(error_code, nullptr); + EXPECT_EQ(error_message, nullptr); + EXPECT_NE(result_or_details, nullptr); + EXPECT_EQ(error, nullptr); + *out_result = fl_value_ref(result_or_details); +} + +static void decode_response_with_error(const char* hex_string, + const gchar* code, + gchar** message, + FlValue** details) { + g_autoptr(GError) error = NULL; + g_autofree gchar* error_code = NULL; + g_autofree gchar* error_message = NULL; + g_autoptr(FlValue) result_or_details = NULL; + decode_response(hex_string, &error_code, &error_message, &result_or_details); + EXPECT_NE(error_code, nullptr); + EXPECT_STREQ(error_code, code); + EXPECT_EQ(error, nullptr); + + *message = g_strdup(error_message); + *details = fl_value_ref(result_or_details); +} + +static void decode_error_response(const char* hex_string, + GQuark domain, + gint code) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) data = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = NULL; + g_autofree gchar* error_code = NULL; + g_autofree gchar* error_message = NULL; + g_autoptr(FlValue) result_or_details = NULL; + gboolean result = fl_method_codec_decode_response( + FL_METHOD_CODEC(codec), data, &error_code, &error_message, + &result_or_details, &error); + EXPECT_FALSE(result); + EXPECT_EQ(error_code, nullptr); + EXPECT_EQ(error_message, nullptr); + EXPECT_EQ(result_or_details, nullptr); + EXPECT_TRUE(g_error_matches(error, domain, code)); +} + +TEST(FlStandardMethodCodecTest, EncodeMethodCallNULLArgs) { + g_autofree gchar* hex_string = encode_method_call("hello", NULL); + EXPECT_STREQ(hex_string, "070568656c6c6f00"); +} + +TEST(FlStandardMethodCodecTest, EncodeMethodCallNullArgs) { + g_autoptr(FlValue) value = fl_value_null_new(); + g_autofree gchar* hex_string = encode_method_call("hello", value); + EXPECT_STREQ(hex_string, "070568656c6c6f00"); +} + +TEST(FlStandardMethodCodecTest, EncodeMethodCallStringArgs) { + g_autoptr(FlValue) args = fl_value_string_new("world"); + g_autofree gchar* hex_string = encode_method_call("hello", args); + EXPECT_STREQ(hex_string, "070568656c6c6f0705776f726c64"); +} + +TEST(FlStandardMethodCodecTest, EncodeMethodCallListArgs) { + g_autoptr(FlValue) args = fl_value_list_new(); + fl_value_list_add_take(args, fl_value_string_new("count")); + fl_value_list_add_take(args, fl_value_int_new(42)); + g_autofree gchar* hex_string = encode_method_call("hello", args); + EXPECT_STREQ(hex_string, "070568656c6c6f0c020705636f756e74032a000000"); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallNullArgs) { + g_autofree gchar* name = NULL; + g_autoptr(FlValue) args = NULL; + decode_method_call("070568656c6c6f00", &name, &args); + EXPECT_STREQ(name, "hello"); + ASSERT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_NULL); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallStringArgs) { + g_autofree gchar* name = NULL; + g_autoptr(FlValue) args = NULL; + decode_method_call("070568656c6c6f0705776f726c64", &name, &args); + EXPECT_STREQ(name, "hello"); + ASSERT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(args), "world"); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallListArgs) { + g_autofree gchar* name = NULL; + g_autoptr(FlValue) args = NULL; + decode_method_call("070568656c6c6f0c020705636f756e74032a000000", &name, + &args); + EXPECT_STREQ(name, "hello"); + ASSERT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_LIST); + EXPECT_EQ(fl_value_get_length(args), static_cast(2)); + + FlValue* arg0 = fl_value_list_get_value(args, 0); + ASSERT_EQ(fl_value_get_type(arg0), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(arg0), "count"); + + FlValue* arg1 = fl_value_list_get_value(args, 1); + ASSERT_EQ(fl_value_get_type(arg1), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(arg1), 42); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallNoData) { + decode_error_method_call("", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallNullMethodName) { + decode_error_method_call("000000", FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallMissingArgs) { + decode_error_method_call("070568656c6c6f", FL_CODEC_ERROR, + FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeNULL) { + g_autofree gchar* hex_string = encode_success_envelope(NULL); + EXPECT_STREQ(hex_string, "0000"); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeNull) { + g_autoptr(FlValue) result = fl_value_null_new(); + g_autofree gchar* hex_string = encode_success_envelope(result); + EXPECT_STREQ(hex_string, "0000"); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeString) { + g_autoptr(FlValue) result = fl_value_string_new("hello"); + g_autofree gchar* hex_string = encode_success_envelope(result); + EXPECT_STREQ(hex_string, "00070568656c6c6f"); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeList) { + g_autoptr(FlValue) result = fl_value_list_new(); + fl_value_list_add_take(result, fl_value_string_new("count")); + fl_value_list_add_take(result, fl_value_int_new(42)); + g_autofree gchar* hex_string = encode_success_envelope(result); + EXPECT_STREQ(hex_string, "000c020705636f756e74032a000000"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeEmptyCode) { + g_autofree gchar* hex_string = encode_error_envelope("", NULL, NULL); + EXPECT_STREQ(hex_string, "0107000000"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeNonMessageOrDetails) { + g_autofree gchar* hex_string = encode_error_envelope("error", NULL, NULL); + EXPECT_STREQ(hex_string, "0107056572726f720000"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeMessage) { + g_autofree gchar* hex_string = + encode_error_envelope("error", "message", NULL); + EXPECT_STREQ(hex_string, "0107056572726f7207076d65737361676500"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeDetails) { + g_autoptr(FlValue) details = fl_value_list_new(); + fl_value_list_add_take(details, fl_value_string_new("count")); + fl_value_list_add_take(details, fl_value_int_new(42)); + g_autofree gchar* hex_string = encode_error_envelope("error", NULL, details); + EXPECT_STREQ(hex_string, "0107056572726f72000c020705636f756e74032a000000"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeMessageAndDetails) { + g_autoptr(FlValue) details = fl_value_list_new(); + fl_value_list_add_take(details, fl_value_string_new("count")); + fl_value_list_add_take(details, fl_value_int_new(42)); + g_autofree gchar* hex_string = + encode_error_envelope("error", "message", details); + EXPECT_STREQ( + hex_string, + "0107056572726f7207076d6573736167650c020705636f756e74032a000000"); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessNull) { + g_autoptr(FlValue) result = NULL; + decode_response_no_error("0000", &result); + ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_NULL); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessString) { + g_autoptr(FlValue) result = NULL; + decode_response_no_error("00070568656c6c6f", &result); + ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(result), "hello"); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessList) { + g_autoptr(FlValue) result = NULL; + decode_response_no_error("000c020705636f756e74032a000000", &result); + ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_LIST); + EXPECT_EQ(fl_value_get_length(result), static_cast(2)); + + FlValue* value0 = fl_value_list_get_value(result, 0); + ASSERT_EQ(fl_value_get_type(value0), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value0), "count"); + + FlValue* value1 = fl_value_list_get_value(result, 1); + ASSERT_EQ(fl_value_get_type(value1), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value1), 42); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorEmptyCode) { + g_autofree gchar* message = NULL; + g_autoptr(FlValue) details = NULL; + decode_response_with_error("0107000000", "", &message, &details); + EXPECT_EQ(message, nullptr); + ASSERT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_NULL); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorNoMessageOrDetails) { + g_autofree gchar* message = NULL; + g_autoptr(FlValue) details = NULL; + decode_response_with_error("0107056572726f720000", "error", &message, + &details); + EXPECT_EQ(message, nullptr); + ASSERT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_NULL); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMessage) { + g_autofree gchar* message = NULL; + g_autoptr(FlValue) details = NULL; + decode_response_with_error("0107056572726f7207076d65737361676500", "error", + &message, &details); + EXPECT_NE(message, nullptr); + EXPECT_STREQ(message, "message"); + ASSERT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_NULL); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorDetails) { + g_autofree gchar* message = NULL; + g_autoptr(FlValue) details = NULL; + decode_response_with_error("0107056572726f72000c020705636f756e74032a000000", + "error", &message, &details); + EXPECT_EQ(message, nullptr); + ASSERT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_LIST); + EXPECT_EQ(fl_value_get_length(details), static_cast(2)); + + FlValue* value0 = fl_value_list_get_value(details, 0); + ASSERT_EQ(fl_value_get_type(value0), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value0), "count"); + + FlValue* value1 = fl_value_list_get_value(details, 1); + ASSERT_EQ(fl_value_get_type(value1), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value1), 42); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMessageAndDetails) { + g_autofree gchar* message = NULL; + g_autoptr(FlValue) details = NULL; + decode_response_with_error( + "0107056572726f7207076d6573736167650c020705636f756e74032a000000", "error", + &message, &details); + EXPECT_NE(message, nullptr); + EXPECT_STREQ(message, "message"); + ASSERT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_LIST); + EXPECT_EQ(fl_value_get_length(details), static_cast(2)); + + FlValue* value0 = fl_value_list_get_value(details, 0); + ASSERT_EQ(fl_value_get_type(value0), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value0), "count"); + + FlValue* value1 = fl_value_list_get_value(details, 1); + ASSERT_EQ(fl_value_get_type(value1), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value1), 42); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseNoData) { + decode_error_response("", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessNoData) { + decode_error_response("00", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessExtraData) { + decode_error_response("000000", FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorNoData) { + decode_error_response("01", FL_CODEC_ERROR, FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMissingMessageAndDetails) { + decode_error_response("0107056572726f72", FL_CODEC_ERROR, + FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMissingDetails) { + decode_error_response("0107056572726f7200", FL_CODEC_ERROR, + FL_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorExtraData) { + decode_error_response("0107056572726f72000000", FL_CODEC_ERROR, + FL_CODEC_ERROR_FAILED); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseUnknownEnvelope) { + decode_error_response("02", FL_CODEC_ERROR, FL_CODEC_ERROR_FAILED); +} diff --git a/shell/platform/linux/fl_value.cc b/shell/platform/linux/fl_value.cc new file mode 100644 index 0000000000000..0431483dba715 --- /dev/null +++ b/shell/platform/linux/fl_value.cc @@ -0,0 +1,539 @@ +// 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/linux/public/flutter_linux/fl_value.h" + +#include + +struct _FlValue { + FlValueType type; + gatomicrefcount ref_count; +}; + +typedef struct { + FlValue parent; + bool value; +} FlValueBool; + +typedef struct { + FlValue parent; + int64_t value; +} FlValueInt; + +typedef struct { + FlValue parent; + double value; +} FlValueDouble; + +typedef struct { + FlValue parent; + gchar* value; +} FlValueString; + +typedef struct { + FlValue parent; + uint8_t* values; + size_t values_length; +} FlValueUint8List; + +typedef struct { + FlValue parent; + int32_t* values; + size_t values_length; +} FlValueInt32List; + +typedef struct { + FlValue parent; + int64_t* values; + size_t values_length; +} FlValueInt64List; + +typedef struct { + FlValue parent; + double* values; + size_t values_length; +} FlValueFloatList; + +typedef struct { + FlValue parent; + GPtrArray* values; +} FlValueList; + +typedef struct { + FlValue parent; + GPtrArray* keys; + GPtrArray* values; +} FlValueMap; + +static FlValue* fl_value_new(FlValueType type, size_t size) { + FlValue* self = static_cast(g_malloc0(size)); + self->type = type; + g_atomic_ref_count_init(&self->ref_count); + return self; +} + +// Helper function to match GDestroyNotify type +static void fl_value_destroy(gpointer value) { + fl_value_unref(static_cast(value)); +} + +// Find the index of a key in a FlValueMap +static ssize_t fl_value_map_lookup_index(FlValue* self, FlValue* key) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_MAP, -1); + + for (size_t i = 0; i < fl_value_get_length(self); i++) { + FlValue* k = fl_value_map_get_key(self, i); + if (fl_value_equal(k, key)) + return i; + } + return -1; +} + +G_MODULE_EXPORT FlValue* fl_value_null_new() { + return fl_value_new(FL_VALUE_TYPE_NULL, sizeof(FlValue)); +} + +G_MODULE_EXPORT FlValue* fl_value_bool_new(bool value) { + FlValueBool* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_BOOL, sizeof(FlValueBool))); + self->value = value ? true : false; + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_int_new(int64_t value) { + FlValueInt* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_INT, sizeof(FlValueInt))); + self->value = value; + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_float_new(double value) { + FlValueDouble* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_FLOAT, sizeof(FlValueDouble))); + self->value = value; + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_string_new(const gchar* value) { + FlValueString* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_STRING, sizeof(FlValueString))); + self->value = g_strdup(value); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_string_new_sized(const gchar* value, + size_t value_length) { + FlValueString* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_STRING, sizeof(FlValueString))); + self->value = + value_length == 0 ? g_strdup("") : g_strndup(value, value_length); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_uint8_list_new(const uint8_t* data, + size_t data_length) { + FlValueUint8List* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_UINT8_LIST, sizeof(FlValueUint8List))); + self->values_length = data_length; + self->values = static_cast(g_malloc(sizeof(uint8_t) * data_length)); + memcpy(self->values, data, sizeof(uint8_t) * data_length); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_uint8_list_new_from_bytes(GBytes* data) { + gsize length; + const uint8_t* d = + static_cast(g_bytes_get_data(data, &length)); + return fl_value_uint8_list_new(d, length); +} + +G_MODULE_EXPORT FlValue* fl_value_int32_list_new(const int32_t* data, + size_t data_length) { + FlValueInt32List* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_INT32_LIST, sizeof(FlValueInt32List))); + self->values_length = data_length; + self->values = static_cast(g_malloc(sizeof(int32_t) * data_length)); + memcpy(self->values, data, sizeof(int32_t) * data_length); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_int64_list_new(const int64_t* data, + size_t data_length) { + FlValueInt64List* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_INT64_LIST, sizeof(FlValueInt64List))); + self->values_length = data_length; + self->values = static_cast(g_malloc(sizeof(int64_t) * data_length)); + memcpy(self->values, data, sizeof(int64_t) * data_length); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_float_list_new(const double* data, + size_t data_length) { + FlValueFloatList* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_FLOAT_LIST, sizeof(FlValueFloatList))); + self->values_length = data_length; + self->values = static_cast(g_malloc(sizeof(double) * data_length)); + memcpy(self->values, data, sizeof(double) * data_length); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_list_new() { + FlValueList* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_LIST, sizeof(FlValueList))); + self->values = g_ptr_array_new_with_free_func(fl_value_destroy); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_map_new() { + FlValueMap* self = reinterpret_cast( + fl_value_new(FL_VALUE_TYPE_MAP, sizeof(FlValueMap))); + self->keys = g_ptr_array_new_with_free_func(fl_value_destroy); + self->values = g_ptr_array_new_with_free_func(fl_value_destroy); + return reinterpret_cast(self); +} + +G_MODULE_EXPORT FlValue* fl_value_ref(FlValue* self) { + g_atomic_ref_count_inc(&self->ref_count); + return self; +} + +G_MODULE_EXPORT void fl_value_unref(FlValue* self) { + if (!g_atomic_ref_count_dec(&self->ref_count)) + return; + + switch (self->type) { + case FL_VALUE_TYPE_STRING: { + FlValueString* v = reinterpret_cast(self); + g_free(v->value); + break; + } + case FL_VALUE_TYPE_UINT8_LIST: { + FlValueUint8List* v = reinterpret_cast(self); + g_free(v->values); + break; + } + case FL_VALUE_TYPE_INT32_LIST: { + FlValueInt32List* v = reinterpret_cast(self); + g_free(v->values); + break; + } + case FL_VALUE_TYPE_INT64_LIST: { + FlValueInt64List* v = reinterpret_cast(self); + g_free(v->values); + break; + } + case FL_VALUE_TYPE_FLOAT_LIST: { + FlValueFloatList* v = reinterpret_cast(self); + g_free(v->values); + break; + } + case FL_VALUE_TYPE_LIST: { + FlValueList* v = reinterpret_cast(self); + g_ptr_array_unref(v->values); + break; + } + case FL_VALUE_TYPE_MAP: { + FlValueMap* v = reinterpret_cast(self); + g_ptr_array_unref(v->keys); + g_ptr_array_unref(v->values); + break; + } + case FL_VALUE_TYPE_NULL: + case FL_VALUE_TYPE_BOOL: + case FL_VALUE_TYPE_INT: + case FL_VALUE_TYPE_FLOAT: + break; + } + g_free(self); +} + +G_MODULE_EXPORT FlValueType fl_value_get_type(FlValue* self) { + g_return_val_if_fail(self != nullptr, FL_VALUE_TYPE_NULL); + return self->type; +} + +G_MODULE_EXPORT bool fl_value_equal(FlValue* a, FlValue* b) { + g_return_val_if_fail(a != nullptr, false); + g_return_val_if_fail(b != nullptr, false); + + if (a->type != b->type) + return false; + + switch (a->type) { + case FL_VALUE_TYPE_NULL: + return true; + case FL_VALUE_TYPE_BOOL: + return fl_value_get_bool(a) == fl_value_get_bool(b); + case FL_VALUE_TYPE_INT: + return fl_value_get_int(a) == fl_value_get_int(b); + case FL_VALUE_TYPE_FLOAT: + return fl_value_get_float(a) == fl_value_get_float(b); + case FL_VALUE_TYPE_STRING: { + FlValueString* a_ = reinterpret_cast(a); + FlValueString* b_ = reinterpret_cast(b); + return g_strcmp0(a_->value, b_->value) == 0; + } + case FL_VALUE_TYPE_UINT8_LIST: { + if (fl_value_get_length(a) != fl_value_get_length(b)) + return false; + const uint8_t* values_a = fl_value_get_uint8_list(a); + const uint8_t* values_b = fl_value_get_uint8_list(b); + for (size_t i = 0; i < fl_value_get_length(a); i++) { + if (values_a[i] != values_b[i]) + return false; + } + return true; + } + case FL_VALUE_TYPE_INT32_LIST: { + if (fl_value_get_length(a) != fl_value_get_length(b)) + return false; + const int32_t* values_a = fl_value_get_int32_list(a); + const int32_t* values_b = fl_value_get_int32_list(b); + for (size_t i = 0; i < fl_value_get_length(a); i++) { + if (values_a[i] != values_b[i]) + return false; + } + return true; + } + case FL_VALUE_TYPE_INT64_LIST: { + if (fl_value_get_length(a) != fl_value_get_length(b)) + return false; + const int64_t* values_a = fl_value_get_int64_list(a); + const int64_t* values_b = fl_value_get_int64_list(b); + for (size_t i = 0; i < fl_value_get_length(a); i++) { + if (values_a[i] != values_b[i]) + return false; + } + return true; + } + case FL_VALUE_TYPE_FLOAT_LIST: { + if (fl_value_get_length(a) != fl_value_get_length(b)) + return false; + const double* values_a = fl_value_get_float_list(a); + const double* values_b = fl_value_get_float_list(b); + for (size_t i = 0; i < fl_value_get_length(a); i++) { + if (values_a[i] != values_b[i]) + return false; + } + return true; + } + case FL_VALUE_TYPE_LIST: { + if (fl_value_get_length(a) != fl_value_get_length(b)) + return false; + for (size_t i = 0; i < fl_value_get_length(a); i++) { + if (!fl_value_equal(fl_value_list_get_value(a, i), + fl_value_list_get_value(b, i))) + return false; + } + return true; + } + case FL_VALUE_TYPE_MAP: { + if (fl_value_get_length(a) != fl_value_get_length(b)) + return false; + for (size_t i = 0; i < fl_value_get_length(a); i++) { + FlValue* key = fl_value_map_get_key(a, i); + FlValue* value_b = fl_value_map_lookup(b, key); + if (value_b == nullptr) + return false; + FlValue* value_a = fl_value_map_get_value(a, i); + if (!fl_value_equal(value_a, value_b)) + return false; + } + return true; + } + } +} + +G_MODULE_EXPORT void fl_value_list_add(FlValue* self, FlValue* value) { + g_return_if_fail(self->type == FL_VALUE_TYPE_LIST); + g_return_if_fail(value != nullptr); + + fl_value_list_add_take(self, fl_value_ref(value)); +} + +G_MODULE_EXPORT void fl_value_list_add_take(FlValue* self, FlValue* value) { + g_return_if_fail(self->type == FL_VALUE_TYPE_LIST); + g_return_if_fail(value != nullptr); + + FlValueList* v = reinterpret_cast(self); + g_ptr_array_add(v->values, value); +} + +G_MODULE_EXPORT void fl_value_map_set(FlValue* self, + FlValue* key, + FlValue* value) { + g_return_if_fail(self->type == FL_VALUE_TYPE_MAP); + g_return_if_fail(key != nullptr); + g_return_if_fail(value != nullptr); + + fl_value_map_set_take(self, fl_value_ref(key), fl_value_ref(value)); +} + +G_MODULE_EXPORT void fl_value_map_set_take(FlValue* self, + FlValue* key, + FlValue* value) { + g_return_if_fail(self->type == FL_VALUE_TYPE_MAP); + g_return_if_fail(key != nullptr); + g_return_if_fail(value != nullptr); + + FlValueMap* v = reinterpret_cast(self); + ssize_t index = fl_value_map_lookup_index(self, key); + if (index < 0) { + g_ptr_array_add(v->keys, key); + g_ptr_array_add(v->values, value); + } else { + fl_value_destroy(v->keys->pdata[index]); + v->keys->pdata[index] = key; + fl_value_destroy(v->values->pdata[index]); + v->values->pdata[index] = value; + } +} + +G_MODULE_EXPORT void fl_value_map_set_string(FlValue* self, + const gchar* key, + FlValue* value) { + g_return_if_fail(self->type == FL_VALUE_TYPE_MAP); + g_return_if_fail(key != nullptr); + g_return_if_fail(value != nullptr); + + fl_value_map_set_take(self, fl_value_string_new(key), fl_value_ref(value)); +} + +G_MODULE_EXPORT void fl_value_map_set_string_take(FlValue* self, + const gchar* key, + FlValue* value) { + g_return_if_fail(self->type == FL_VALUE_TYPE_MAP); + g_return_if_fail(key != nullptr); + g_return_if_fail(value != nullptr); + + fl_value_map_set_take(self, fl_value_string_new(key), value); +} + +G_MODULE_EXPORT bool fl_value_get_bool(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_BOOL, FALSE); + FlValueBool* v = reinterpret_cast(self); + return v->value; +} + +G_MODULE_EXPORT int64_t fl_value_get_int(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_INT, 0); + FlValueInt* v = reinterpret_cast(self); + return v->value; +} + +G_MODULE_EXPORT double fl_value_get_float(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_FLOAT, 0.0); + FlValueDouble* v = reinterpret_cast(self); + return v->value; +} + +G_MODULE_EXPORT const gchar* fl_value_get_string(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_STRING, nullptr); + FlValueString* v = reinterpret_cast(self); + return v->value; +} + +G_MODULE_EXPORT const uint8_t* fl_value_get_uint8_list(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_UINT8_LIST, nullptr); + FlValueUint8List* v = reinterpret_cast(self); + return v->values; +} + +G_MODULE_EXPORT const int32_t* fl_value_get_int32_list(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_INT32_LIST, nullptr); + FlValueInt32List* v = reinterpret_cast(self); + return v->values; +} + +G_MODULE_EXPORT const int64_t* fl_value_get_int64_list(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_INT64_LIST, nullptr); + FlValueInt64List* v = reinterpret_cast(self); + return v->values; +} + +G_MODULE_EXPORT const double* fl_value_get_float_list(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_FLOAT_LIST, nullptr); + FlValueFloatList* v = reinterpret_cast(self); + return v->values; +} + +G_MODULE_EXPORT size_t fl_value_get_length(FlValue* self) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_UINT8_LIST || + self->type == FL_VALUE_TYPE_INT32_LIST || + self->type == FL_VALUE_TYPE_INT64_LIST || + self->type == FL_VALUE_TYPE_FLOAT_LIST || + self->type == FL_VALUE_TYPE_LIST || + self->type == FL_VALUE_TYPE_MAP, + 0); + + switch (self->type) { + case FL_VALUE_TYPE_UINT8_LIST: { + FlValueUint8List* v = reinterpret_cast(self); + return v->values_length; + } + case FL_VALUE_TYPE_INT32_LIST: { + FlValueInt32List* v = reinterpret_cast(self); + return v->values_length; + } + case FL_VALUE_TYPE_INT64_LIST: { + FlValueInt64List* v = reinterpret_cast(self); + return v->values_length; + } + case FL_VALUE_TYPE_FLOAT_LIST: { + FlValueFloatList* v = reinterpret_cast(self); + return v->values_length; + } + case FL_VALUE_TYPE_LIST: { + FlValueList* v = reinterpret_cast(self); + return v->values->len; + } + case FL_VALUE_TYPE_MAP: { + FlValueMap* v = reinterpret_cast(self); + return v->keys->len; + } + case FL_VALUE_TYPE_NULL: + case FL_VALUE_TYPE_BOOL: + case FL_VALUE_TYPE_INT: + case FL_VALUE_TYPE_FLOAT: + case FL_VALUE_TYPE_STRING: + return 0; + } + + return 0; +} + +G_MODULE_EXPORT FlValue* fl_value_list_get_value(FlValue* self, size_t index) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_LIST, nullptr); + + FlValueList* v = reinterpret_cast(self); + return static_cast(g_ptr_array_index(v->values, index)); +} + +G_MODULE_EXPORT FlValue* fl_value_map_get_key(FlValue* self, size_t index) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_MAP, nullptr); + + FlValueMap* v = reinterpret_cast(self); + return static_cast(g_ptr_array_index(v->keys, index)); +} + +G_MODULE_EXPORT FlValue* fl_value_map_get_value(FlValue* self, size_t index) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_MAP, nullptr); + + FlValueMap* v = reinterpret_cast(self); + return static_cast(g_ptr_array_index(v->values, index)); +} + +G_MODULE_EXPORT FlValue* fl_value_map_lookup(FlValue* self, FlValue* key) { + g_return_val_if_fail(self->type == FL_VALUE_TYPE_MAP, nullptr); + + ssize_t index = fl_value_map_lookup_index(self, key); + if (index < 0) + return nullptr; + return fl_value_map_get_value(self, index); +} + +FlValue* fl_value_map_lookup_string(FlValue* self, const gchar* key) { + g_autoptr(FlValue) string_key = fl_value_string_new(key); + return fl_value_map_lookup(self, string_key); +} diff --git a/shell/platform/linux/public/flutter_linux/fl_codec.h b/shell/platform/linux/public/flutter_linux/fl_codec.h new file mode 100644 index 0000000000000..b651c96c19ee4 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_codec.h @@ -0,0 +1,116 @@ +// 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_LINUX_FL_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_CODEC_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +#include "fl_value.h" + +G_BEGIN_DECLS + +/** + * FlCodecError: + * Errors for #FlCodec objects to set on failures. + */ +#define FL_CODEC_ERROR fl_codec_error_quark() + +typedef enum { + FL_CODEC_ERROR_FAILED, + FL_CODEC_ERROR_OUT_OF_DATA, +} FlCodecError; + +GQuark fl_codec_error_quark(void) G_GNUC_CONST; + +G_DECLARE_DERIVABLE_TYPE(FlCodec, fl_codec, FL, CODEC, GObject) + +/** + * FlCodec: + * + * #FlCodec is an abstract class that converts #FlValue to and from a binary + * representation. + */ + +struct _FlCodecClass { + GObjectClass parent_class; + + /** + * FlCodec::write_value: + * @codec: A #FlCodec + * @buffer: buffer to write to + * @value: value to encode or %NULL to encode the null value. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Encode a value into the buffer. + * + * Returns: %TRUE on success. + */ + gboolean (*write_value)(FlCodec* codec, + GByteArray* buffer, + FlValue* value, + GError** error); + + /** + * FlCodec::read_value: + * @codec: a #FlCodec + * @buffer: buffer to read from + * @offset: (allow-none): index to read from buffer or %NULL to read from the + * start. If provided, this value will be increased by the amount of data this + * value used in the buffer. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Decode a value from a binary encoding. + * + * Returns: a #FlValue or %NULL on error. + */ + FlValue* (*read_value)(FlCodec* codec, + GBytes* buffer, + size_t* offset, + GError** error); +}; + +/** + * fl_codec_write_value: + * @codec: a #FlCodec + * @buffer: buffer to write to + * @value: value to encode or %NULL to encode the null value. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Encode a value into the buffer. + * + * Returns: %TRUE on success. + */ +gboolean fl_codec_write_value(FlCodec* codec, + GByteArray* buffer, + FlValue* value, + GError** error); + +/** + * fl_codec_read_value: + * @codec: a #FlCodec + * @buffer: buffer to read from + * @offset: (allow-none): index to read from buffer or %NULL to read from the + * start. If provided, this value will be increased by the amount of data this + * value used in the buffer. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Decode a value from a binary encoding. + * + * Returns: a #FlValue or %NULL on error. + */ +FlValue* fl_codec_read_value(FlCodec* codec, + GBytes* buffer, + size_t* offset, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_CODEC_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_method_channel.h b/shell/platform/linux/public/flutter_linux/fl_method_channel.h new file mode 100644 index 0000000000000..3536d962d0cb1 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_channel.h @@ -0,0 +1,171 @@ +// 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_LINUX_FL_METHOD_CHANNEL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CHANNEL_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include "fl_binary_messenger.h" +#include "fl_method_codec.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlMethodChannel, + fl_method_channel, + FL, + METHOD_CHANNEL, + GObject) + +/** + * FlMethodChannel: + * + * #FlMethodChannel is an object that allows calling methods in Dart code. + */ + +/** + * FlMethodChannelResponseHandle: + * + * A handle used to respond to method calls. + */ +typedef struct _FlMethodChannelResponseHandle FlMethodChannelResponseHandle; + +/** + * FlMethodChannelCallback: + * @channel: a #FlMethodChannel + * @method: a method name + * @args: arguments to the method + * @response_handle: handle used to respond to the method call + * @user_data: (closure): data provided when registering this callback + * + * Function called when a method call is received. + */ +typedef void (*FlMethodChannelCallback)( + FlMethodChannel* channel, + const gchar* method, + FlValue* args, + FlMethodChannelResponseHandle* response_handle, + gpointer user_data); + +/** + * fl_method_channel_new: + * @messenger: a #FlBinaryMessenger + * @name: a channel name + * @codec: the method codec + * + * Create a new method channel. @codec must match the codec used on the Dart end + * of the channel. + * + * Returns: a new #FlMethodChannel. + */ +FlMethodChannel* fl_method_channel_new(FlBinaryMessenger* messenger, + const gchar* name, + FlMethodCodec* codec); + +/** + * fl_method_channel_set_callback: + * @channel: a #FlMethodChannel + * @callback: function to call when a method call is received on this channel + * @user_data: (closure): user data to pass to @callback + * + * Set the function called when a method is called. Call + * fl_method_channel_respond() when the method completes. Ownership of + * #FlMethodChannelResponseHandle is transferred to the caller, and the call + * must be responded to to avoid memory leaks. + */ +void fl_method_channel_set_callback(FlMethodChannel* channel, + FlMethodChannelCallback callback, + gpointer user_data); + +/** + * fl_method_channel_invoke: + * @channel: a #FlMethodChannel + * @method: the method to call + * @args: (allow-none): arguments to the method, must match what the + * #FlMethodCodec supports + * @cancellable: (allow-none): a #GCancellable or %NULL + * @callback: (scope async): a #GAsyncReadyCallback to call when the request is + * satisfied + * @user_data: (closure): user data to pass to @callback + * + * Call a method on this channel. + */ +void fl_method_channel_invoke(FlMethodChannel* channel, + const gchar* method, + FlValue* args, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_method_channel_invoke_finish: + * @channel: a #FlMethodChannel + * @result: #GAsyncResult + * @error_code: (transfer full): location to write error code. + * @error_message: (transfer full): location to write error message. + * @result: (transfer full): location to write call response or error details if + * an error. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Complete request started with fl_method_channel_invoke(). + * + * Returns: %TRUE on success. + */ +gboolean fl_method_channel_invoke_finish(FlMethodChannel* channel, + GAsyncResult* result, + gchar** error_code, + gchar** error_message, + FlValue** value, + GError** error); + +/** + * fl_method_channel_respond: + * @channel: a #FlMethodChannel + * @response_handle: handle that was provided in a #FlMethodChannelCallback + * @result: (allow-none): value to respond with, must match what the + * #FlMethodCodec supports + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore + * + * Respond to a method call. + * + * Returns: %TRUE on success. + */ +gboolean fl_method_channel_respond( + FlMethodChannel* channel, + FlMethodChannelResponseHandle* response_handle, + FlValue* result, + GError** error); + +/** + * fl_method_channel_respond_error: + * @channel: a #FlMethodChannel + * @response_handle: handle that was provided in a #FlMethodChannelCallback + * @code: error code + * @message: (allow-none): error message + * @details: (allow-none): details for the error + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore + * + * Respond to a method call with an error. + * + * Returns: %TRUE on success. + */ +gboolean fl_method_channel_respond_error( + FlMethodChannel* channel, + FlMethodChannelResponseHandle* response_handle, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CHANNEL_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_method_codec.h b/shell/platform/linux/public/flutter_linux/fl_method_codec.h new file mode 100644 index 0000000000000..7e815940ca564 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_codec.h @@ -0,0 +1,134 @@ +// 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_LINUX_FL_METHOD_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +#include "fl_value.h" + +G_BEGIN_DECLS + +G_DECLARE_DERIVABLE_TYPE(FlMethodCodec, + fl_method_codec, + FL, + METHOD_CODEC, + GObject) + +/** + * FlMethodCodec: + * + * #FlMethodCodec is an abstract class that encode and decode method calls on a + * platform channel. Override this class to implemented an encoding. + */ + +struct _FlMethodCodecClass { + GObjectClass parent_class; + + /** + * FlMethodCodec::encode_method_call: + * @codec: a #FlCodec + * @name: method name + * @args: (allow-none): method arguments, or %NULL + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Encode a method call. + * + * Return: (transfer full): a binary encoding of this method call or %NULL if + * not able to encode. + */ + GBytes* (*encode_method_call)(FlMethodCodec* codec, + const gchar* name, + FlValue* args, + GError** error); + + /** + * FlMethodCodec::decode_method_call: + * @codec: a #FlCodec + * @message: message to decode. + * @name: (transfer full): location to write method name or %NULL if not + * required + * @args: (transfer full): location to write method arguments, or %NULL if not + * required + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Encode a method call. + * + * Return: %TRUE if successfully decoded. + */ + gboolean (*decode_method_call)(FlMethodCodec* codec, + GBytes* message, + gchar** name, + FlValue** args, + GError** error); + + /** + * FlMethodCodec::encode_success_envelope: + * @codec: a #FlCodec + * @result: (allow-none): method result, or %NULL + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Encode a successful response to a method call. + * + * Return: (transfer full): a binary encoding of this response or %NULL if not + * able to encode. + */ + GBytes* (*encode_success_envelope)(FlMethodCodec* codec, + FlValue* result, + GError** error); + + /** + * FlMethodCodec::encode_error_envelope: + * @codec: a #FlCodec + * @code: an error code. + * @message: (allow-none): an error message, or %NULL. + * @details: (allow-none): error details, or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Encode an error response to a method call. + * + * Return: (transfer full): a binary encoding of this response or %NULL if not + * able to encode. + */ + GBytes* (*encode_error_envelope)(FlMethodCodec* codec, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error); + + /** + * FlMethodCodec::decode_response: + * @codec: a #FlCodec + * @error_code: (transfer full): location to write error code. + * @error_message: (transfer full): location to write error message + * @result: (transfer full): location to write call response, or error details + * if an error. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Decode a response to a method call. If the call resulted in an error then + * @error_code is set, otherwise it is %NULL. + * + * Return: %TRUE if successfully decoded. + */ + gboolean (*decode_response)(FlMethodCodec* codec, + GBytes* message, + gchar** error_code, + gchar** error_message, + FlValue** result, + GError** error); +}; + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_standard_codec.h b/shell/platform/linux/public/flutter_linux/fl_standard_codec.h new file mode 100644 index 0000000000000..fb16d96e02d27 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_standard_codec.h @@ -0,0 +1,45 @@ +// 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_LINUX_FL_STANDARD_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_CODEC_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include "fl_codec.h" + +G_BEGIN_DECLS + +/** + * FlStandardCodecError: + * Errors for #FlStandardCodec objects to set on failures. + */ +#define FL_STANDARD_CODEC_ERROR fl_standard_codec_error_quark() + +typedef enum { + FL_STANDARD_CODEC_ERROR_UNKNOWN_TYPE, +} FlStandardCodecError; + +GQuark fl_standard_codec_error_quark(void) G_GNUC_CONST; + +G_DECLARE_FINAL_TYPE(FlStandardCodec, + fl_standard_codec, + FL, + STANDARD_CODEC, + FlCodec) + +/** + * FlStandardCodec: + * + * #FlStandardCodec is a #FlCodec that implements the Flutter standard message + * encoding. + */ + +FlStandardCodec* fl_standard_codec_new(); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_CODEC_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h b/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h new file mode 100644 index 0000000000000..3921258e2084f --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h @@ -0,0 +1,33 @@ +// 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_LINUX_FL_STANDARD_METHOD_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_METHOD_CODEC_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include "fl_method_codec.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlStandardMethodCodec, + fl_standard_method_codec, + FL, + STANDARD_METHOD_CODEC, + FlMethodCodec) + +/** + * FlStandardMethodCodec: + * + * #FlStandardCodec is a #FlMethodCodec that implements method calls using the + * Flutter standard message encoding. It should be used with a #FlMethodChannel. + */ + +FlStandardMethodCodec* fl_standard_method_codec_new(); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_METHOD_CODEC_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_value.h b/shell/platform/linux/public/flutter_linux/fl_value.h new file mode 100644 index 0000000000000..74edbdb13e8c8 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_value.h @@ -0,0 +1,470 @@ +// 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_LINUX_FL_VALUE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VALUE_H_ + +#include +#include +#include + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +G_BEGIN_DECLS + +/** + * FlValue: + * + * #FlValue is an object that contains data using Flutter platform channels. + * Values are encoded and decoded into a binary form using #FlCodec. + * Values are used in method calls with a #FlMethodChannel. + */ +typedef struct _FlValue FlValue; + +/** + * FlValueType: + * @FL_VALUE_TYPE_NULL: The null value. + * @FL_VALUE_TYPE_BOOL: A boolean. + * @FL_VALUE_TYPE_INT: An integer. + * @FL_VALUE_TYPE_FLOAT: A floating point number. + * @FL_VALUE_TYPE_STRING: UTF-8 text. + * @FL_VALUE_TYPE_UINT8_LIST: An ordered list of unsigned 8 bit integers. + * @FL_VALUE_TYPE_INT32_LIST: An ordered list of 32 bit integers. + * @FL_VALUE_TYPE_INT64_LIST: An ordered list of 64 bit integers. + * @FL_VALUE_TYPE_FLOAT_LIST: An ordered list of floating point numbers. + * @FL_VALUE_TYPE_LIST: An ordered list of #FlValue objects. + * @FL_VALUE_TYPE_MAP: A map of #FlValue objects keyed by #FlValue object. + * + * Types of #FlValue. + */ +typedef enum { + FL_VALUE_TYPE_NULL, + FL_VALUE_TYPE_BOOL, + FL_VALUE_TYPE_INT, + FL_VALUE_TYPE_FLOAT, + FL_VALUE_TYPE_STRING, + FL_VALUE_TYPE_UINT8_LIST, + FL_VALUE_TYPE_INT32_LIST, + FL_VALUE_TYPE_INT64_LIST, + FL_VALUE_TYPE_FLOAT_LIST, + FL_VALUE_TYPE_LIST, + FL_VALUE_TYPE_MAP, +} FlValueType; + +/** + * fl_value_null_new: + * + * Create a new #FlValue that contains a null value. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_null_new(); + +/** + * fl_value_bool_new: + * @value: the value. + * + * Create a new #FlValue that contains a boolean value. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_bool_new(bool value); + +/** + * fl_value_int_new: + * @value: the value. + * + * Create a new #FlValue that contains an integer number. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_int_new(int64_t value); + +/** + * fl_value_float_new: + * @value: the value. + * + * Create a new #FlValue that contains a floating point number. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_float_new(double value); + +/** + * fl_value_string_new: + * @value: the value. + * + * Create a new #FlValue that contains UTF-8 text. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_string_new(const gchar* value); + +/** + * fl_value_string_new: + * @value: the value. + * @value_length: the number of bytes to use from @value. + * + * Create a new #FlValue that contains UTF-8 text. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_string_new_sized(const gchar* value, size_t value_length); + +/** + * fl_value_uint8_list_new: + * @data: Data to copy. + * @data_length: number of elements in @data. + * + * Create a new ordered list containing 8 bit unsigned values. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_uint8_list_new(const uint8_t* data, size_t data_length); + +/** + * fl_value_uint8_list_new: + * @data: Data to copy + * + * Create a new ordered list containing 8 bit unsigned integers. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_uint8_list_new_from_bytes(GBytes* data); + +/** + * fl_value_int32_list_new: + * + * Create a new ordered list containing 32 bit integers. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_int32_list_new(const int32_t* data, size_t data_length); + +/** + * fl_value_int64_list_new: + * + * Create a new ordered list containing 64 bit integers. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_int64_list_new(const int64_t* data, size_t data_length); + +/** + * fl_value_float_list_new: + * + * Create a new ordered list containing floating point numbers. + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_float_list_new(const double* data, size_t data_length); + +/** + * fl_value_list_new: + * + * Create a new ordered list. Children can be added to the list using + * fl_value_list_add(). The children are accessed using fl_value_get_length() + * and fl_value_list_get_value(). + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_list_new(); + +/** + * fl_value_map_new: + * + * Create a new map. Children can be added to the map using fl_value_map_set(). + * The children are accessed using fl_value_get_length(), + * fl_value_map_get_key(), fl_value_map_get_value() and fl_value_map_lookup(). + * + * Returns: a new #FlValue. + */ +FlValue* fl_value_map_new(); + +/** + * fl_value_ref: + * @value: an #FlValue + * + * Increase the reference count of an #FlValue. + * + * Returns: the value that was referenced. + */ +FlValue* fl_value_ref(FlValue* value); + +/** + * fl_value_ref: + * @value: an #FlValue + * + * Derease the reference count of an #FlValue. When the refernece count hits + * zero the value is destroyed and no longer valid. + */ +void fl_value_unref(FlValue* value); + +/** + * fl_value_get_type: + * @value: an #FlValue + * + * Get the type of this value. + * + * Returns: an #FlValueType. + */ +FlValueType fl_value_get_type(FlValue* value); + +/** + * fl_value_equal: + * @a: an #FlValue + * @b: an #FlValue + * + * Compare two #FlValue to see if they are equivalent. Two values are considered + * equivalent if they are of the same type and their data is the same including + * any child values. + * + * Returns: %TRUE if both values are equivalent. + */ +bool fl_value_equal(FlValue* a, FlValue* b); + +/** + * fl_value_list_add: + * @value: an #FlValue of type #FL_VALUE_TYPE_LIST + * @child: an #FlValue + * + * Add a value to a list. Calling this with an #FlValue that is not of type + * #FL_VALUE_TYPE_LIST is a programming error. + */ +void fl_value_list_add(FlValue* value, FlValue* child); + +/** + * fl_value_list_add: + * @value: an #FlValue of type #FL_VALUE_TYPE_LIST + * @child: (transfer full): an #FlValue + * + * Add a value to a list taking ownership of that value. Calling this with an + * #FlValue that is not of type #FL_VALUE_TYPE_LIST is a programming error. + */ +void fl_value_list_add_take(FlValue* value, FlValue* child); + +/** + * fl_value_map_set: + * @value: an #FlValue of type #FL_VALUE_TYPE_MAP + * @key: an #FlValue + * @child_value: an #FlValue + * + * Set a value in the map. If an existing value was in the map with the same key + * it is replaced. Calling this with an #FlValue that is not of type + * #FL_VALUE_TYPE_MAP is a programming error. + */ +void fl_value_map_set(FlValue* value, FlValue* key, FlValue* child_value); + +/** + * fl_value_map_set_take: + * @value: an #FlValue of type #FL_VALUE_TYPE_MAP + * @key: (transfer full): an #FlValue + * @child_value: (transfer full): an #FlValue + * + * Set a value in the map, taking ownership of the keys and values. If an + * existing value was in the map with the same key it is replaced. Calling this + * with an #FlValue that is not of type #FL_VALUE_TYPE_MAP is a programming + * error. + */ +void fl_value_map_set_take(FlValue* value, FlValue* key, FlValue* child_value); + +/** + * fl_value_map_set_string: + * @value: an #FlValue of type #FL_VALUE_TYPE_MAP + * @key: a UTF-8 text key + * @child_value: an #FlValue + * + * Set a value in the map with a text key. If an existing value was in the map + * with the same key it is replaced. Calling this with an #FlValue that is not + * of type #FL_VALUE_TYPE_MAP is a programming error. + */ +void fl_value_map_set_string(FlValue* value, + const gchar* key, + FlValue* child_value); + +/** + * fl_value_map_set_string_take: + * @value: an #FlValue of type #FL_VALUE_TYPE_MAP + * @key: a UTF-8 text key + * @child_value: (transfer full): an #FlValue + * + * Set a value in the map with a text key, taking ownership of the value. If an + * existing value was in the map with the same key it is replaced. Calling this + * with an #FlValue that is not of type #FL_VALUE_TYPE_MAP is a programming + * error. + */ +void fl_value_map_set_string_take(FlValue* value, + const gchar* key, + FlValue* child_value); + +/** + * fl_value_get_bool: + * @value: an #FlValue of type #FL_VALUE_TYPE_BOOL + * + * Get the boolean value of this value. Calling this with an #FlValue that is + * not of type #FL_VALUE_TYPE_BOOL is a programming error. + * + * Returns: a boolean value. + */ +bool fl_value_get_bool(FlValue* value); + +/** + * fl_value_get_int: + * @value: an #FlValue of type #FL_VALUE_TYPE_INT + * + * Get the integer number of this value. Calling this with an #FlValue that is + * not of type #FL_VALUE_TYPE_INT is a programming error. + * + * Returns: an integer number. + */ +int64_t fl_value_get_int(FlValue* value); + +/** + * fl_value_get_double: + * @value: an #FlValue of type #FL_VALUE_TYPE_FLOAT + * + * Get the floating point number of this value. Calling this with an #FlValue + * that is not of type #FL_VALUE_TYPE_FLOAT is a programming error. + * + * Returns: a UTF-8 encoded string. + */ +double fl_value_get_float(FlValue* value); + +/** + * fl_value_get_string: + * @value: an #FlValue of type #FL_VALUE_TYPE_STRING + * + * Get the UTF-8 text contained in this value. + * + * Returns: a UTF-8 encoded string. + */ +const gchar* fl_value_get_string(FlValue* value); + +/** + * fl_value_get_length: + * @value: an #FlValue of type #FL_VALUE_TYPE_UINT8_LIST, + * #FL_VALUE_TYPE_INT32_LIST, #FL_VALUE_TYPE_INT64_LIST, + * #FL_VALUE_TYPE_FLOAT_LIST, #FL_VALUE_TYPE_LIST or #FL_VALUE_TYPE_MAP. + * + * Gets the number of elements this value contains. This is only valid for list + * and map types. Calling this with other types is a programming error. + * + * Returns: the number of elements inside this value. + */ +size_t fl_value_get_length(FlValue* value); + +/** + * fl_value_get_uint8_list: + * @value: an #FlValue of type #FL_VALUE_TYPE_UINT8_LIST + * + * Get the array of unisigned 8 bit integers this value contains. The data + * contains fl_list_get_length() elements. Calling this with an #FlValue that is + * not of type #FL_VALUE_TYPE_UINT8_LIST is a programming error. + * + * Returns: an array of unsigned 8 bit integers. + */ +const uint8_t* fl_value_get_uint8_list(FlValue* value); + +/** + * fl_value_get_int32_list: + * @value: an #FlValue of type #FL_VALUE_TYPE_INT32_LIST + * + * Get the array of 32 bit integers this value contains. The data contains + * fl_list_get_length() elements. Calling this with an #FlValue that is not of + * type #FL_VALUE_TYPE_INT32_LIST is a programming error. + * + * Returns: an array of 32 bit integers. + */ +const int32_t* fl_value_get_int32_list(FlValue* value); + +/** + * fl_value_get_int64_list: + * @value: an #FlValue of type #FL_VALUE_TYPE_INT64_LIST + * + * Get the array of 64 bit integers this value contains. The data contains + * fl_list_get_length() elements. Calling this with an #FlValue that is not of + * type #FL_VALUE_TYPE_INT64_LIST is a programming error. + * + * Returns: an array of 64 bit integers. + */ +const int64_t* fl_value_get_int64_list(FlValue* value); + +/** + * fl_value_get_float_list: + * @value: an #FlValue of type #FL_VALUE_TYPE_FLOAT_LIST + * + * Get the array of floating point numbers this value contains. The data + * contains fl_list_get_length() elements. Calling this with an #FlValue that is + * not of type #FL_VALUE_TYPE_FLOAT_LIST is a programming error. + * + * Returns: an array of floating point numbers. + */ +const double* fl_value_get_float_list(FlValue* value); + +/** + * fl_value_list_get_value: + * @value: an #FlValue of type #FL_VALUE_TYPE_LIST. + * @index: an index in the list. + * + * Get a child element of the list. It is a programming error to request an + * index that is outside the size of the list as returned from + * fl_value_get_length(). + * + * Returns: a #FlValue + */ +FlValue* fl_value_list_get_value(FlValue* value, size_t index); + +/** + * fl_value_map_get_key: + * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. + * @index: an index in the map. + * + * Get an key from the map. It is a programming error to request an index that + * is outside the size of the list as returned from fl_value_get_length(). + * + * Returns: a #FlValue + */ +FlValue* fl_value_map_get_key(FlValue* value, size_t index); + +/** + * fl_value_map_get_key: + * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. + * @index: an index in the map. + * + * Get a value from the map. It is a programming error to request an index that + * is outside the size of the list as returned from fl_value_get_length(). + * + * Returns: a #FlValue + */ +FlValue* fl_value_map_get_value(FlValue* value, size_t index); + +/** + * fl_value_map_lookup: + * @value: an #FlValue of type FL_VALUE_TYPE_MAP + * @key: a key value + * + * Get the map entry that matches the given key. + * + * Returns: (allow-none): the value with this key or %NULL if not one present. + */ +FlValue* fl_value_map_lookup(FlValue* value, FlValue* key); + +/** + * fl_value_map_lookup_string: + * @value: an #FlValue of type FL_VALUE_TYPE_MAP + * @key: a key value + * + * Get the map entry that matches the given text key. + * + * Returns: (allow-none): the value with this key or %NULL if not one present. + */ +FlValue* fl_value_map_lookup_string(FlValue* value, const gchar* key); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlValue, fl_value_unref) + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VALUE_H_ diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index 74e8a142496dd..bb08b12469db2 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -8,8 +8,14 @@ #define __FLUTTER_LINUX_INSIDE__ #include +#include #include #include +#include +#include +#include +#include +#include #include #undef __FLUTTER_LINUX_INSIDE__