From 20f527bf44c8ffd9a2d8533e3b1bcb61b4e15c0e Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Fri, 8 May 2020 13:49:45 +1200 Subject: [PATCH 1/3] Add FlMethodChannel, FlMethodResponse, FlMethodCodec and FlStandardMethodCodec --- ci/licenses_golden/licenses_flutter | 12 + shell/platform/linux/BUILD.gn | 11 + shell/platform/linux/fl_method_channel.cc | 249 +++++++++++ shell/platform/linux/fl_method_codec.cc | 75 ++++ .../platform/linux/fl_method_codec_private.h | 101 +++++ shell/platform/linux/fl_method_codec_test.cc | 402 ++++++++++++++++++ shell/platform/linux/fl_method_response.cc | 172 ++++++++ .../platform/linux/fl_method_response_test.cc | 96 +++++ .../linux/fl_standard_method_codec.cc | 238 +++++++++++ .../linux/fl_standard_method_codec_test.cc | 375 ++++++++++++++++ .../public/flutter_linux/fl_method_channel.h | 189 ++++++++ .../public/flutter_linux/fl_method_codec.h | 131 ++++++ .../public/flutter_linux/fl_method_response.h | 187 ++++++++ .../flutter_linux/fl_standard_method_codec.h | 37 ++ .../public/flutter_linux/flutter_linux.h | 4 + 15 files changed, 2279 insertions(+) create mode 100644 shell/platform/linux/fl_method_channel.cc create mode 100644 shell/platform/linux/fl_method_codec.cc create mode 100644 shell/platform/linux/fl_method_codec_private.h create mode 100644 shell/platform/linux/fl_method_codec_test.cc create mode 100644 shell/platform/linux/fl_method_response.cc create mode 100644 shell/platform/linux/fl_method_response_test.cc create mode 100644 shell/platform/linux/fl_standard_method_codec.cc create mode 100644 shell/platform/linux/fl_standard_method_codec_test.cc create mode 100644 shell/platform/linux/public/flutter_linux/fl_method_channel.h create mode 100644 shell/platform/linux/public/flutter_linux/fl_method_codec.h create mode 100644 shell/platform/linux/public/flutter_linux/fl_method_response.h create mode 100644 shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 425a3152820ef..a263c49a09728 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1195,6 +1195,12 @@ FILE: ../../../flutter/shell/platform/linux/fl_engine.cc FILE: ../../../flutter/shell/platform/linux/fl_engine_private.h FILE: ../../../flutter/shell/platform/linux/fl_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_message_codec_test.cc +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_method_response.cc +FILE: ../../../flutter/shell/platform/linux/fl_method_response_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 @@ -1202,6 +1208,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_renderer_x11.h FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec_private.h FILE: ../../../flutter/shell/platform/linux/fl_standard_message_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_string_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_value.cc @@ -1212,7 +1220,11 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messe 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_message_codec.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_method_response.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_string_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_value.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index cae44d2c3566d..fb71bb14c0814 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -49,7 +49,11 @@ _public_headers = [ "public/flutter_linux/fl_dart_project.h", "public/flutter_linux/fl_engine.h", "public/flutter_linux/fl_message_codec.h", + "public/flutter_linux/fl_method_channel.h", + "public/flutter_linux/fl_method_codec.h", + "public/flutter_linux/fl_method_response.h", "public/flutter_linux/fl_standard_message_codec.h", + "public/flutter_linux/fl_standard_method_codec.h", "public/flutter_linux/fl_string_codec.h", "public/flutter_linux/fl_value.h", "public/flutter_linux/fl_view.h", @@ -69,9 +73,13 @@ source_set("flutter_linux") { "fl_dart_project.cc", "fl_engine.cc", "fl_message_codec.cc", + "fl_method_channel.cc", + "fl_method_codec.cc", + "fl_method_response.cc", "fl_renderer.cc", "fl_renderer_x11.cc", "fl_standard_message_codec.cc", + "fl_standard_method_codec.cc", "fl_string_codec.cc", "fl_value.cc", "fl_view.cc", @@ -102,7 +110,10 @@ executable("flutter_linux_unittests") { "fl_binary_codec_test.cc", "fl_dart_project_test.cc", "fl_message_codec_test.cc", + "fl_method_codec_test.cc", + "fl_method_response_test.cc", "fl_standard_message_codec_test.cc", + "fl_standard_method_codec_test.cc", "fl_string_codec_test.cc", "fl_value_test.cc", "testing/fl_test.cc", diff --git a/shell/platform/linux/fl_method_channel.cc b/shell/platform/linux/fl_method_channel.cc new file mode 100644 index 0000000000000..d587f3863845f --- /dev/null +++ b/shell/platform/linux/fl_method_channel.cc @@ -0,0 +1,249 @@ +// 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; + + // Messenger to communicate on + FlBinaryMessenger* messenger; + + // Channel name + gchar* name; + + // Codec to en/decode messages + FlMethodCodec* codec; + + // Function called when a method call is received + FlMethodChannelMethodCallHandler method_call_handler; + gpointer method_call_handler_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) + +// Wrap the binary messenger handle for type safety and to make the API +// consistent +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); + +// Called when a binary message is received on this channel +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->method_call_handler == nullptr) { + fl_method_channel_respond_not_implemented( + self, response_handle_new(response_handle), 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_method_channel_respond_not_implemented( + self, response_handle_new(response_handle), nullptr); + return; + } + + self->method_call_handler(self, method, args, + response_handle_new(response_handle), + self->method_call_handler_data); +} + +// Called when a response is received to a sent message +static void message_response_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { + GTask* task = G_TASK(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); + + if (self->messenger != nullptr) + fl_binary_messenger_set_message_handler_on_channel( + self->messenger, self->name, nullptr, nullptr); + + g_clear_object(&self->messenger); + g_clear_pointer(&self->name, g_free); + g_clear_object(&self->codec); + + 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(FL_IS_BINARY_MESSENGER(messenger), nullptr); + g_return_val_if_fail(name != nullptr, nullptr); + g_return_val_if_fail(FL_IS_METHOD_CODEC(codec), nullptr); + + FlMethodChannel* self = + FL_METHOD_CHANNEL(g_object_new(fl_method_channel_get_type(), nullptr)); + + self->messenger = FL_BINARY_MESSENGER(g_object_ref(messenger)); + self->name = g_strdup(name); + self->codec = FL_METHOD_CODEC(g_object_ref(codec)); + + fl_binary_messenger_set_message_handler_on_channel( + self->messenger, self->name, message_cb, self); + + return self; +} + +G_MODULE_EXPORT void fl_method_channel_set_method_call_handler( + FlMethodChannel* self, + FlMethodChannelMethodCallHandler handler, + gpointer user_data) { + g_return_if_fail(FL_IS_METHOD_CHANNEL(self)); + + self->method_call_handler = handler; + self->method_call_handler_data = user_data; +} + +G_MODULE_EXPORT void fl_method_channel_invoke_method( + 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 ? message_response_cb : nullptr, + g_steal_pointer(&task)); +} + +G_MODULE_EXPORT FlMethodResponse* fl_method_channel_invoke_method_finish( + FlMethodChannel* self, + GAsyncResult* result, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CHANNEL(self), nullptr); + g_return_val_if_fail(g_task_is_valid(result, self), nullptr); + + g_autoptr(GTask) task = G_TASK(result); + GAsyncResult* r = G_ASYNC_RESULT(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 nullptr; + + return fl_method_codec_decode_response(self->codec, response, 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; + + return fl_binary_messenger_send_response( + self->messenger, owned_response_handle->response_handle, response, error); +} + +G_MODULE_EXPORT gboolean fl_method_channel_respond_not_implemented( + FlMethodChannel* self, + FlMethodChannelResponseHandle* response_handle, + 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) owned_response_handle = + response_handle; + + return fl_binary_messenger_send_response( + self->messenger, owned_response_handle->response_handle, nullptr, error); +} diff --git a/shell/platform/linux/fl_method_codec.cc b/shell/platform/linux/fl_method_codec.cc new file mode 100644 index 0000000000000..11bed276e89be --- /dev/null +++ b/shell/platform/linux/fl_method_codec.cc @@ -0,0 +1,75 @@ +// 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); +} + +FlMethodResponse* fl_method_codec_decode_response(FlMethodCodec* self, + GBytes* message, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CODEC(self), nullptr); + g_return_val_if_fail(message != nullptr, nullptr); + + if (g_bytes_get_size(message) == 0) + return FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + + return FL_METHOD_CODEC_GET_CLASS(self)->decode_response(self, message, 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..385dcec0c193e --- /dev/null +++ b/shell/platform/linux/fl_method_codec_private.h @@ -0,0 +1,101 @@ +// 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" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_response.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: (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: a new #FlMethodResponse or %NULL on error. + */ +FlMethodResponse* fl_method_codec_decode_response(FlMethodCodec* codec, + GBytes* message, + 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..cb216cb06f128 --- /dev/null +++ b/shell/platform/linux/fl_method_codec_test.cc @@ -0,0 +1,402 @@ +// 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_message_codec.h" +#include "gtest/gtest.h" + +G_DECLARE_FINAL_TYPE(FlTestMethodCodec, + fl_test_method_codec, + FL, + TEST_METHOD_CODEC, + FlMethodCodec) + +// Implement the FlMethodCodec API for the following tests to check it works as +// expected +struct _FlTestMethodCodec { + FlMethodCodec parent_instance; +}; + +G_DEFINE_TYPE(FlTestMethodCodec, + fl_test_method_codec, + fl_method_codec_get_type()) + +// Helper function to convert binary data to text +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); +} + +// Helper function to convert text to binary data +static GBytes* text_to_message(const gchar* text) { + return g_bytes_new(text, strlen(text)); +} + +// Implements FlMethodCodec::encode_method_call +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_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "ERROR"); + return nullptr; + } + + return text_to_message(text); +} + +// Implements FlMethodCodec::decode_method_call +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_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "ERROR"); + return FALSE; + } else { + *name = g_strdup(m); + *args = fl_value_new_null(); + return TRUE; + } +} + +// Implements FlMethodCodec::encode_success_envelope +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_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "ERROR"); + return nullptr; + } + + return text_to_message(text); +} + +// Implements FlMethodCodec::encode_error_envelope +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_MESSAGE_CODEC_ERROR, FL_MESSAGE_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); +} + +// Implements FlMethodCodec::decode_response +static FlMethodResponse* fl_test_codec_decode_response(FlMethodCodec* codec, + GBytes* message, + 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_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "ERROR"); + return nullptr; + } else if (strcmp(m, "error") == 0) { + g_autoptr(FlValue) details = fl_value_new_int(42); + return FL_METHOD_RESPONSE( + fl_method_error_response_new("code", "message", details)); + } else { + g_autoptr(FlValue) result = fl_value_new_string(m); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } +} + +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_new_int(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_new_bool(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_MESSAGE_CODEC_ERROR, + FL_MESSAGE_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_new_int(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_new_string("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_MESSAGE_CODEC_ERROR, + FL_MESSAGE_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_new_int(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_new_int(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_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(response, nullptr); + ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + FlValue* result = fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_NE(result, nullptr); + ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(result), "echo"); +} + +TEST(FlMethodCodecTest, DecodeResponseNotImplemented) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GBytes) message = g_bytes_new(nullptr, 0); + + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(response, nullptr); + ASSERT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)); +} + +TEST(FlMethodCodecTest, DecodeResponseCodecError) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GBytes) message = text_to_message("codec-error"); + + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_TRUE(g_error_matches(error, FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED)); + EXPECT_EQ(response, nullptr); +} + +TEST(FlMethodCodecTest, DecodeResponseError) { + g_autoptr(FlTestMethodCodec) codec = fl_test_method_codec_new(); + + g_autoptr(GBytes) message = text_to_message("error"); + + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(response, nullptr); + ASSERT_TRUE(FL_METHOD_ERROR_RESPONSE(response)); + EXPECT_STREQ( + fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)), + "code"); + EXPECT_STREQ( + fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)), + "message"); + FlValue* details = + fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(response)); + ASSERT_NE(details, nullptr); + 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_method_response.cc b/shell/platform/linux/fl_method_response.cc new file mode 100644 index 0000000000000..b014330831cdb --- /dev/null +++ b/shell/platform/linux/fl_method_response.cc @@ -0,0 +1,172 @@ +// 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_response.h" + +#include + +G_DEFINE_QUARK(fl_method_response_error_quark, fl_method_response_error) + +struct _FlMethodSuccessResponse { + FlMethodResponse parent_instance; + + FlValue* result; +}; + +struct _FlMethodErrorResponse { + FlMethodResponse parent_instance; + + gchar* code; + gchar* message; + FlValue* details; +}; + +struct _FlMethodNotImplementedResponse { + FlMethodResponse parent_instance; +}; + +G_DEFINE_TYPE(FlMethodResponse, fl_method_response, G_TYPE_OBJECT) +G_DEFINE_TYPE(FlMethodSuccessResponse, + fl_method_success_response, + fl_method_response_get_type()) +G_DEFINE_TYPE(FlMethodErrorResponse, + fl_method_error_response, + fl_method_response_get_type()) +G_DEFINE_TYPE(FlMethodNotImplementedResponse, + fl_method_not_implemented_response, + fl_method_response_get_type()) + +static void fl_method_response_class_init(FlMethodResponseClass* klass) {} + +static void fl_method_response_init(FlMethodResponse* self) {} + +static void fl_method_success_response_dispose(GObject* object) { + FlMethodSuccessResponse* self = FL_METHOD_SUCCESS_RESPONSE(object); + + g_clear_pointer(&self->result, fl_value_unref); + + G_OBJECT_CLASS(fl_method_success_response_parent_class)->dispose(object); +} + +static void fl_method_success_response_class_init( + FlMethodSuccessResponseClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_method_success_response_dispose; +} + +static void fl_method_success_response_init(FlMethodSuccessResponse* self) {} + +static void fl_method_error_response_dispose(GObject* object) { + FlMethodErrorResponse* self = FL_METHOD_ERROR_RESPONSE(object); + + g_clear_pointer(&self->code, g_free); + g_clear_pointer(&self->message, g_free); + g_clear_pointer(&self->details, fl_value_unref); + + G_OBJECT_CLASS(fl_method_error_response_parent_class)->dispose(object); +} + +static void fl_method_error_response_class_init( + FlMethodErrorResponseClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_method_error_response_dispose; +} + +static void fl_method_error_response_init(FlMethodErrorResponse* self) {} + +static void fl_method_not_implemented_response_class_init( + FlMethodNotImplementedResponseClass* klass) {} + +static void fl_method_not_implemented_response_init( + FlMethodNotImplementedResponse* self) {} + +G_MODULE_EXPORT FlValue* fl_method_response_get_result(FlMethodResponse* self, + GError** error) { + if (FL_IS_METHOD_SUCCESS_RESPONSE(self)) + return fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(self)); + + if (FL_IS_METHOD_ERROR_RESPONSE(self)) { + const gchar* code = + fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(self)); + const gchar* message = + fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(self)); + // FIXME(robert-ancell) Encode the error_details with JSON and show in the + // message FlValue *details = + // fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(self)); + g_autofree gchar* text = nullptr; + if (message != nullptr) + text = + g_strdup_printf("Remote code returned error %s: %s", code, message); + else + text = g_strdup_printf("Remote code returned error %s", code); + g_set_error_literal(error, FL_METHOD_RESPONSE_ERROR, + FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR, text); + return nullptr; + } else if (FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(self)) { + g_set_error(error, FL_METHOD_RESPONSE_ERROR, + FL_METHOD_RESPONSE_ERROR_NOT_IMPLEMENTED, + "Requested method is not implemented"); + return nullptr; + } else { + g_set_error(error, FL_METHOD_RESPONSE_ERROR, + FL_METHOD_RESPONSE_ERROR_FAILED, "Unknown response type"); + return nullptr; + } +} + +G_MODULE_EXPORT FlMethodSuccessResponse* fl_method_success_response_new( + FlValue* result) { + FlMethodSuccessResponse* self = FL_METHOD_SUCCESS_RESPONSE( + g_object_new(fl_method_success_response_get_type(), nullptr)); + g_return_val_if_fail(result != nullptr, nullptr); + + self->result = fl_value_ref(result); + + return self; +} + +G_MODULE_EXPORT FlValue* fl_method_success_response_get_result( + FlMethodSuccessResponse* self) { + g_return_val_if_fail(FL_IS_METHOD_SUCCESS_RESPONSE(self), nullptr); + return self->result; +} + +G_MODULE_EXPORT FlMethodErrorResponse* fl_method_error_response_new( + const gchar* code, + const gchar* message, + FlValue* details) { + g_return_val_if_fail(code != nullptr, nullptr); + + FlMethodErrorResponse* self = FL_METHOD_ERROR_RESPONSE( + g_object_new(fl_method_error_response_get_type(), nullptr)); + + self->code = g_strdup(code); + self->message = g_strdup(message); + self->details = details != nullptr ? fl_value_ref(details) : nullptr; + + return self; +} + +G_MODULE_EXPORT const gchar* fl_method_error_response_get_code( + FlMethodErrorResponse* self) { + g_return_val_if_fail(FL_IS_METHOD_ERROR_RESPONSE(self), nullptr); + return self->code; +} + +G_MODULE_EXPORT const gchar* fl_method_error_response_get_message( + FlMethodErrorResponse* self) { + g_return_val_if_fail(FL_IS_METHOD_ERROR_RESPONSE(self), nullptr); + return self->message; +} + +G_MODULE_EXPORT FlValue* fl_method_error_response_get_details( + FlMethodErrorResponse* self) { + g_return_val_if_fail(FL_IS_METHOD_ERROR_RESPONSE(self), nullptr); + return self->details; +} + +G_MODULE_EXPORT FlMethodNotImplementedResponse* +fl_method_not_implemented_response_new() { + return FL_METHOD_NOT_IMPLEMENTED_RESPONSE( + g_object_new(fl_method_not_implemented_response_get_type(), nullptr)); +} diff --git a/shell/platform/linux/fl_method_response_test.cc b/shell/platform/linux/fl_method_response_test.cc new file mode 100644 index 0000000000000..0309a30b8f5f2 --- /dev/null +++ b/shell/platform/linux/fl_method_response_test.cc @@ -0,0 +1,96 @@ +// 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_response.h" +#include "gtest/gtest.h" + +TEST(FlMethodResponseTest, Success) { + g_autoptr(FlValue) result = fl_value_new_int(42); + g_autoptr(FlMethodSuccessResponse) response = + fl_method_success_response_new(result); + g_autoptr(FlValue) expected = fl_value_new_int(42); + ASSERT_TRUE(fl_value_equal(fl_method_success_response_get_result(response), + expected)); +} + +TEST(FlMethodResponseTest, Error) { + g_autoptr(FlMethodErrorResponse) response = + fl_method_error_response_new("code", nullptr, nullptr); + EXPECT_STREQ(fl_method_error_response_get_code(response), "code"); + EXPECT_EQ(fl_method_error_response_get_message(response), nullptr); + EXPECT_EQ(fl_method_error_response_get_details(response), nullptr); +} + +TEST(FlMethodResponseTest, ErrorMessage) { + g_autoptr(FlMethodErrorResponse) response = + fl_method_error_response_new("code", "message", nullptr); + EXPECT_STREQ(fl_method_error_response_get_code(response), "code"); + EXPECT_STREQ(fl_method_error_response_get_message(response), "message"); + EXPECT_EQ(fl_method_error_response_get_details(response), nullptr); +} + +TEST(FlMethodResponseTest, ErrorDetails) { + g_autoptr(FlValue) details = fl_value_new_int(42); + g_autoptr(FlMethodErrorResponse) response = + fl_method_error_response_new("code", nullptr, details); + EXPECT_STREQ(fl_method_error_response_get_code(response), "code"); + EXPECT_EQ(fl_method_error_response_get_message(response), nullptr); + g_autoptr(FlValue) expected_details = fl_value_new_int(42); + EXPECT_TRUE(fl_value_equal(fl_method_error_response_get_details(response), + expected_details)); +} + +TEST(FlMethodResponseTest, ErrorMessageAndDetails) { + g_autoptr(FlValue) details = fl_value_new_int(42); + g_autoptr(FlMethodErrorResponse) response = + fl_method_error_response_new("code", "message", details); + EXPECT_STREQ(fl_method_error_response_get_code(response), "code"); + EXPECT_STREQ(fl_method_error_response_get_message(response), "message"); + g_autoptr(FlValue) expected_details = fl_value_new_int(42); + EXPECT_TRUE(fl_value_equal(fl_method_error_response_get_details(response), + expected_details)); +} + +TEST(FlMethodResponseTest, NotImplemented) { + g_autoptr(FlMethodNotImplementedResponse) response = + fl_method_not_implemented_response_new(); + // Trivial check to stop the compiler deciding that 'response' is an unused + // variable + EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)); +} + +TEST(FlMethodResponseTest, SuccessGetResult) { + g_autoptr(FlValue) r = fl_value_new_int(42); + g_autoptr(FlMethodSuccessResponse) response = + fl_method_success_response_new(r); + g_autoptr(GError) error = nullptr; + FlValue* result = + fl_method_response_get_result(FL_METHOD_RESPONSE(response), &error); + ASSERT_NE(result, nullptr); + EXPECT_EQ(error, nullptr); + g_autoptr(FlValue) expected = fl_value_new_int(42); + ASSERT_TRUE(fl_value_equal(result, expected)); +} + +TEST(FlMethodResponseTest, ErrorGetResult) { + g_autoptr(FlMethodErrorResponse) response = + fl_method_error_response_new("code", nullptr, nullptr); + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) result = + fl_method_response_get_result(FL_METHOD_RESPONSE(response), &error); + EXPECT_EQ(result, nullptr); + EXPECT_TRUE(g_error_matches(error, FL_METHOD_RESPONSE_ERROR, + FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR)); +} + +TEST(FlMethodResponseTest, NotImplementedGetResult) { + g_autoptr(FlMethodNotImplementedResponse) response = + fl_method_not_implemented_response_new(); + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) result = + fl_method_response_get_result(FL_METHOD_RESPONSE(response), &error); + EXPECT_EQ(result, nullptr); + EXPECT_TRUE(g_error_matches(error, FL_METHOD_RESPONSE_ERROR, + FL_METHOD_RESPONSE_ERROR_NOT_IMPLEMENTED)); +} 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..429b35a3edad3 --- /dev/null +++ b/shell/platform/linux/fl_standard_method_codec.cc @@ -0,0 +1,238 @@ +// 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_standard_message_codec_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h" + +#include + +// See lib/src/services/message_codecs.dart in Flutter source for description of +// encoding + +// Envelope codes +static constexpr guint8 kEnvelopeTypeSuccess = 0; +static constexpr guint8 kEnvelopeTypeError = 1; + +struct _FlStandardMethodCodec { + FlMethodCodec parent_instance; + + FlStandardMessageCodec* 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); +} + +// Implements FlMethodCodec::encode_method_code +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_new_string(name); + if (!fl_standard_message_codec_write_value(self->codec, buffer, name_value, + error)) + return nullptr; + if (!fl_standard_message_codec_write_value(self->codec, buffer, args, error)) + return nullptr; + + return g_byte_array_free_to_bytes( + static_cast(g_steal_pointer(&buffer))); +} + +// Implements FlMethodCodec::decode_method_code +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_standard_message_codec_read_value( + 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_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Method call name wrong type"); + return FALSE; + } + + g_autoptr(FlValue) args_value = fl_standard_message_codec_read_value( + self->codec, message, &offset, error); + if (args_value == nullptr) + return FALSE; + + if (offset != g_bytes_get_size(message)) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_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; +} + +// Implements FlMethodCodec::encode_success_envelope +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_standard_message_codec_write_value(self->codec, buffer, result, + error)) + return nullptr; + + return g_byte_array_free_to_bytes( + static_cast(g_steal_pointer(&buffer))); +} + +// Implements FlMethodCodec::encode_error_envelope +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_new_string(code); + if (!fl_standard_message_codec_write_value(self->codec, buffer, code_value, + error)) + return nullptr; + g_autoptr(FlValue) message_value = + message != nullptr ? fl_value_new_string(message) : nullptr; + if (!fl_standard_message_codec_write_value(self->codec, buffer, message_value, + error)) + return nullptr; + if (!fl_standard_message_codec_write_value(self->codec, buffer, details, + error)) + return nullptr; + + return g_byte_array_free_to_bytes( + static_cast(g_steal_pointer(&buffer))); +} + +// Implements FlMethodCodec::encode_decode_reponse +static FlMethodResponse* fl_standard_method_codec_decode_response( + FlMethodCodec* codec, + GBytes* message, + GError** error) { + FlStandardMethodCodec* self = FL_STANDARD_METHOD_CODEC(codec); + + if (g_bytes_get_size(message) == 0) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA, "Empty response"); + return nullptr; + } + + // First byte is response type + const guint8* data = + static_cast(g_bytes_get_data(message, nullptr)); + guint8 type = data[0]; + size_t offset = 1; + + g_autoptr(FlMethodResponse) response = nullptr; + if (type == kEnvelopeTypeError) { + g_autoptr(FlValue) code = fl_standard_message_codec_read_value( + self->codec, message, &offset, error); + if (code == nullptr) + return nullptr; + if (fl_value_get_type(code) != FL_VALUE_TYPE_STRING) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Error code wrong type"); + return nullptr; + } + + g_autoptr(FlValue) error_message = fl_standard_message_codec_read_value( + self->codec, message, &offset, error); + if (error_message == nullptr) + return nullptr; + if (fl_value_get_type(error_message) != FL_VALUE_TYPE_STRING && + fl_value_get_type(error_message) != FL_VALUE_TYPE_NULL) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Error message wrong type"); + return nullptr; + } + + g_autoptr(FlValue) details = fl_standard_message_codec_read_value( + self->codec, message, &offset, error); + if (details == nullptr) + return nullptr; + + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + fl_value_get_string(code), + fl_value_get_type(error_message) == FL_VALUE_TYPE_STRING + ? fl_value_get_string(error_message) + : nullptr, + fl_value_get_type(details) != FL_VALUE_TYPE_NULL ? details : nullptr)); + } else if (type == kEnvelopeTypeSuccess) { + g_autoptr(FlValue) result = fl_standard_message_codec_read_value( + self->codec, message, &offset, error); + + if (result == nullptr) + return nullptr; + + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } else { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Unknown envelope type %02x", type); + return nullptr; + } + + if (offset != g_bytes_get_size(message)) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Unexpected extra data"); + return nullptr; + } + + return FL_METHOD_RESPONSE(g_object_ref(response)); +} + +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_message_codec_new(); +} + +G_MODULE_EXPORT FlStandardMethodCodec* fl_standard_method_codec_new() { + return FL_STANDARD_METHOD_CODEC( + g_object_new(fl_standard_method_codec_get_type(), nullptr)); +} 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..ef0b55458dcc3 --- /dev/null +++ b/shell/platform/linux/fl_standard_method_codec_test.cc @@ -0,0 +1,375 @@ +// 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_message_codec.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "gtest/gtest.h" + +// NOTE(robert-ancell) These test cases assumes a little-endian architecture. +// These tests will need to be updated if tested on a big endian architecture. + +// Encodes a method call using StandardMethodCodec to a hex string. +static gchar* encode_method_call(const gchar* name, FlValue* args) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = nullptr; + 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 bytes_to_hex_string(message); +} + +// Encodes a success envelope response using StandardMethodCodec to a hex +// string. +static gchar* encode_success_envelope(FlValue* result) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GError) error = nullptr; + 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 bytes_to_hex_string(message); +} + +// Encodes a error envelope response using StandardMethodCodec to a hex string. +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 = nullptr; + 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 bytes_to_hex_string(message); +} + +// Decodes a method call using StandardMethodCodec with a hex string. +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) message = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = nullptr; + gboolean result = fl_method_codec_decode_method_call( + FL_METHOD_CODEC(codec), message, name, args, &error); + EXPECT_TRUE(result); + EXPECT_EQ(error, nullptr); +} + +// Decodes a method call using StandardMethodCodec. Expect the given error. +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) message = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = nullptr; + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + gboolean result = fl_method_codec_decode_method_call( + FL_METHOD_CODEC(codec), message, &name, &args, &error); + EXPECT_FALSE(result); + EXPECT_EQ(name, nullptr); + EXPECT_EQ(args, nullptr); + EXPECT_TRUE(g_error_matches(error, domain, code)); +} + +// Decodes a response using StandardMethodCodec. Expect the response is a +// result. +static void decode_response_with_success(const char* hex_string, + FlValue* result) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) message = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + ASSERT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + result)); +} + +// Decodes a response using StandardMethodCodec. Expect the response contains +// the given error. +static void decode_response_with_error(const char* hex_string, + const gchar* code, + const gchar* error_message, + FlValue* details) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) message = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + ASSERT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response)); + EXPECT_STREQ( + fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)), + code); + if (error_message == nullptr) + EXPECT_EQ(fl_method_error_response_get_message( + FL_METHOD_ERROR_RESPONSE(response)), + nullptr); + else + EXPECT_STREQ(fl_method_error_response_get_message( + FL_METHOD_ERROR_RESPONSE(response)), + error_message); + if (details == nullptr) + EXPECT_EQ(fl_method_error_response_get_details( + FL_METHOD_ERROR_RESPONSE(response)), + nullptr); + else + EXPECT_TRUE(fl_value_equal(fl_method_error_response_get_details( + FL_METHOD_ERROR_RESPONSE(response)), + 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) message = hex_string_to_bytes(hex_string); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_EQ(response, nullptr); + EXPECT_TRUE(g_error_matches(error, domain, code)); +} + +TEST(FlStandardMethodCodecTest, EncodeMethodCallNullptrArgs) { + g_autofree gchar* hex_string = encode_method_call("hello", nullptr); + EXPECT_STREQ(hex_string, "070568656c6c6f00"); +} + +TEST(FlStandardMethodCodecTest, EncodeMethodCallNullArgs) { + g_autoptr(FlValue) value = fl_value_new_null(); + g_autofree gchar* hex_string = encode_method_call("hello", value); + EXPECT_STREQ(hex_string, "070568656c6c6f00"); +} + +TEST(FlStandardMethodCodecTest, EncodeMethodCallStringArgs) { + g_autoptr(FlValue) args = fl_value_new_string("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_new_list(); + fl_value_append_take(args, fl_value_new_string("count")); + fl_value_append_take(args, fl_value_new_int(42)); + g_autofree gchar* hex_string = encode_method_call("hello", args); + EXPECT_STREQ(hex_string, "070568656c6c6f0c020705636f756e74032a000000"); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallNullArgs) { + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + 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 = nullptr; + g_autoptr(FlValue) args = nullptr; + 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 = nullptr; + g_autoptr(FlValue) args = nullptr; + 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_get_list_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_get_list_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_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallNullMethodName) { + decode_error_method_call("000000", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlStandardMethodCodecTest, DecodeMethodCallMissingArgs) { + decode_error_method_call("070568656c6c6f", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeNullptr) { + g_autofree gchar* hex_string = encode_success_envelope(nullptr); + EXPECT_STREQ(hex_string, "0000"); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeNull) { + g_autoptr(FlValue) result = fl_value_new_null(); + g_autofree gchar* hex_string = encode_success_envelope(result); + EXPECT_STREQ(hex_string, "0000"); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeString) { + g_autoptr(FlValue) result = fl_value_new_string("hello"); + g_autofree gchar* hex_string = encode_success_envelope(result); + EXPECT_STREQ(hex_string, "00070568656c6c6f"); +} + +TEST(FlStandardMethodCodecTest, EncodeSuccessEnvelopeList) { + g_autoptr(FlValue) result = fl_value_new_list(); + fl_value_append_take(result, fl_value_new_string("count")); + fl_value_append_take(result, fl_value_new_int(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("", nullptr, nullptr); + EXPECT_STREQ(hex_string, "0107000000"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeNonMessageOrDetails) { + g_autofree gchar* hex_string = + encode_error_envelope("error", nullptr, nullptr); + EXPECT_STREQ(hex_string, "0107056572726f720000"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeMessage) { + g_autofree gchar* hex_string = + encode_error_envelope("error", "message", nullptr); + EXPECT_STREQ(hex_string, "0107056572726f7207076d65737361676500"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + g_autofree gchar* hex_string = + encode_error_envelope("error", nullptr, details); + EXPECT_STREQ(hex_string, "0107056572726f72000c020705636f756e74032a000000"); +} + +TEST(FlStandardMethodCodecTest, EncodeErrorEnvelopeMessageAndDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + g_autofree gchar* hex_string = + encode_error_envelope("error", "message", details); + EXPECT_STREQ( + hex_string, + "0107056572726f7207076d6573736167650c020705636f756e74032a000000"); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessNull) { + g_autoptr(FlValue) result = fl_value_new_null(); + decode_response_with_success("0000", result); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessString) { + g_autoptr(FlValue) result = fl_value_new_string("hello"); + decode_response_with_success("00070568656c6c6f", result); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessList) { + g_autoptr(FlValue) result = fl_value_new_list(); + fl_value_append_take(result, fl_value_new_string("count")); + fl_value_append_take(result, fl_value_new_int(42)); + decode_response_with_success("000c020705636f756e74032a000000", result); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorEmptyCode) { + decode_response_with_error("0107000000", "", nullptr, nullptr); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorNoMessageOrDetails) { + decode_response_with_error("0107056572726f720000", "error", nullptr, nullptr); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMessage) { + decode_response_with_error("0107056572726f7207076d65737361676500", "error", + "message", nullptr); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + decode_response_with_error("0107056572726f72000c020705636f756e74032a000000", + "error", nullptr, details); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMessageAndDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + decode_response_with_error( + "0107056572726f7207076d6573736167650c020705636f756e74032a000000", "error", + "message", details); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessNoData) { + decode_error_response("00", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseSuccessExtraData) { + decode_error_response("000000", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorNoData) { + decode_error_response("01", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMissingMessageAndDetails) { + decode_error_response("0107056572726f72", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorMissingDetails) { + decode_error_response("0107056572726f7200", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseErrorExtraData) { + decode_error_response("0107056572726f72000000", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseNotImplemented) { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) message = g_bytes_new(nullptr, 0); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + ASSERT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)); +} + +TEST(FlStandardMethodCodecTest, DecodeResponseUnknownEnvelope) { + decode_error_response("02", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} 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..8152ef067c42f --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_channel.h @@ -0,0 +1,189 @@ +// 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" +#include "fl_method_response.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. + * + * #FlMethodChannel matches the MethodChannel class in the Flutter services + * library. + */ + +/** + * FlMethodChannelResponseHandle: + * + * A handle used to respond to method calls. + */ +typedef struct _FlMethodChannelResponseHandle FlMethodChannelResponseHandle; + +/** + * FlMethodChannelMethodCallHandler: + * @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 handler + * + * Function called when a method call is received. + */ +typedef void (*FlMethodChannelMethodCallHandler)( + 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 + * + * Creates 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_method_call_handler: + * @channel: a #FlMethodChannel + * @handler: function to call when a method call is received on this channel + * @user_data: (closure): user data to pass to @handler + * + * Sets 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_method_call_handler( + FlMethodChannel* channel, + FlMethodChannelMethodCallHandler handler, + gpointer user_data); + +/** + * fl_method_channel_invoke_method: + * @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): (allow-none): a #GAsyncReadyCallback to call when + * the request is satisfied or %NULL to ignore the response + * @user_data: (closure): user data to pass to @callback + * + * Calls a method on this channel. + */ +void fl_method_channel_invoke_method(FlMethodChannel* channel, + const gchar* method, + FlValue* args, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_method_channel_invoke_method_finish: + * @channel: a #FlMethodChannel + * @result: #GAsyncResult + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with fl_method_channel_invoke_method(). + * + * Returns: (transfer full): an #FlMethodResponse or %NULL on error. + */ +FlMethodResponse* fl_method_channel_invoke_method_finish( + FlMethodChannel* channel, + GAsyncResult* result, + GError** error); + +/** + * fl_method_channel_respond: + * @channel: a #FlMethodChannel + * @response_handle: handle that was provided in a + * #FlMethodChannelMethodCallHandler + * @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 + * + * Responds 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 + * #FlMethodChannelMethodCallHandler + * @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 + * + * Responds 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); + +/** + * fl_method_channel_respond_not_implemented: + * @channel: a #FlMethodChannel + * @response_handle: handle that was provided in a + * #FlMethodChannelMethodCallHandler + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore + * + * Responds to a method call that is not implemented. + * + * Returns: %TRUE on success. + */ +gboolean fl_method_channel_respond_not_implemented( + FlMethodChannel* channel, + FlMethodChannelResponseHandle* response_handle, + 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..e3def786996ab --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_codec.h @@ -0,0 +1,131 @@ +// 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_method_response.h" +#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 implement an encoding. + * + * #FlMethodCodec matches the MethodCodec class in the Flutter services + * library. + */ + +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 + * @message: message to decode + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL + * + * Decode a response to a method call. + * + * Return: a new #FlMethodResponse or %NULL on error. + */ + FlMethodResponse* (*decode_response)(FlMethodCodec* codec, + GBytes* message, + GError** error); +}; + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_method_response.h b/shell/platform/linux/public/flutter_linux/fl_method_response.h new file mode 100644 index 0000000000000..b75ad23316b91 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_response.h @@ -0,0 +1,187 @@ +// 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_RESPONSE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_RESPONSE_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 + +/** + * FlMethodResponseError: + * @FL_METHOD_RESPONSE_ERROR_FAILED: Call failed due to an unspecified error. + * @FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR: An error was returned by the other + * side of the channel. + * @FL_METHOD_RESPONSE_ERROR_NOT_IMPLEMENTED: The requested method is not + * implemented. + * + * Errors for #FlMessageResponse objects to set on failures. + */ +#define FL_METHOD_RESPONSE_ERROR fl_method_response_error_quark() + +typedef enum { + FL_METHOD_RESPONSE_ERROR_FAILED, + FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR, + FL_METHOD_RESPONSE_ERROR_NOT_IMPLEMENTED, +} FlMethodResponseError; + +GQuark fl_method_response_error_quark(void) G_GNUC_CONST; + +G_DECLARE_DERIVABLE_TYPE(FlMethodResponse, + fl_method_response, + FL, + METHOD_RESPONSE, + GObject) + +struct _FlMethodResponseClass { + GObjectClass parent_class; +}; + +G_DECLARE_FINAL_TYPE(FlMethodSuccessResponse, + fl_method_success_response, + FL, + METHOD_SUCCESS_RESPONSE, + FlMethodResponse) + +G_DECLARE_FINAL_TYPE(FlMethodErrorResponse, + fl_method_error_response, + FL, + METHOD_ERROR_RESPONSE, + FlMethodResponse) + +G_DECLARE_FINAL_TYPE(FlMethodNotImplementedResponse, + fl_method_not_implemented_response, + FL, + METHOD_NOT_IMPLEMENTED_RESPONSE, + FlMethodResponse) + +/** + * FlMethodResponse: + * + * #FlMethodResponse contains the information returned when a #FlMethodChannel + * method call returns successfully. + */ + +/** + * FlMethodSuccessResponse: + * + * #FlMethodError contains the information returned when a #FlMethodChannel + * method call returns successfully. + */ + +/** + * FlMethodErrorResponse: + * + * #FlMethodError contains the information returned when a #FlMethodChannel + * method call returns an error. + */ + +/** + * FlMethodNotImplementedResponse: + * + * #FlMethodError indicates when a #FlMethodChannel method call is not + * implemented on the other side of the channel. + */ + +/** + * fl_method_response_get_result: + * @response: a #FlMethodResponse + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Gets the result of a method call, or an error if the response wasn't + * successful. + * + * Returns: a #FlValue or %NULL on error. + */ +FlValue* fl_method_response_get_result(FlMethodResponse* response, + GError** error); + +/** + * fl_method_success_response_new: + * @result: the #FlValue returned by the method call + * + * Creates a response to a method call when that method has successfully + * completed. + * + * Returns: a new #FlMethodResponse. + */ +FlMethodSuccessResponse* fl_method_success_response_new(FlValue* result); + +/** + * fl_method_success_response_get_result: + * @response: an #FlMethodSuccessResponse + * + * Gets the result of the method call. + * + * Returns: an #FlValue + */ +FlValue* fl_method_success_response_get_result( + FlMethodSuccessResponse* response); + +/** + * fl_method_error_response_new: + * @result: a #FlValue + * @code: an error code + * @message: (allow-none): an error message. + * @details: (allow-none): error details. + * + * Creates a response to a method call when that method has returned an error. + * + * Returns: a new #FlMethodErrorResponse. + */ +FlMethodErrorResponse* fl_method_error_response_new(const gchar* code, + const gchar* message, + FlValue* details); + +/** + * fl_method_error_response_get_code: + * @response: an #FlMethodErrorResponse + * + * Gets the error code reported. + * + * Returns: an error code. + */ +const gchar* fl_method_error_response_get_code(FlMethodErrorResponse* response); + +/** + * fl_method_error_response_get_message: + * @response: an #FlMethodErrorResponse + * + * Gets the error message reported. + * + * Returns: an error message or %NULL if no error message provided. + */ +const gchar* fl_method_error_response_get_message( + FlMethodErrorResponse* response); + +/** + * fl_method_error_response_get_details: + * @response: an #FlMethodErrorResponse + * + * Gets the details provided with this error. + * + * Returns: an #FlValue or %NULL if no details provided. + */ +FlValue* fl_method_error_response_get_details(FlMethodErrorResponse* response); + +/** + * fl_method_not_implemented_response_new: + * + * Creates a response to a method call when that method does not exist. + * + * Returns: a new #FlMethodNotImplementedResponse. + */ +FlMethodNotImplementedResponse* fl_method_not_implemented_response_new(); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_RESPONSE_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..8ff79beaa1996 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h @@ -0,0 +1,37 @@ +// 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: + * + * #FlStandardMethodCodec is an #FlMethodCodec that implements method calls + * using the Flutter standard message encoding. It should be used with a + * #FlMethodChannel. + * + * #FlStandardMethodCodec matches the StandardMethodCodec class in the Flutter + * services library. + */ + +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/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index 62449e81abf6f..24d21cb1a65f0 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -12,7 +12,11 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include From e7a04e85dec463df8db87284ed7704573e55d2d4 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Fri, 8 May 2020 15:10:15 +1200 Subject: [PATCH 2/3] Add FlJsonMessageCodec --- ci/licenses_golden/licenses_flutter | 3 + shell/platform/linux/BUILD.gn | 4 + shell/platform/linux/fl_json_message_codec.cc | 300 +++++++ .../linux/fl_json_message_codec_test.cc | 769 ++++++++++++++++++ .../flutter_linux/fl_json_message_codec.h | 93 +++ .../public/flutter_linux/flutter_linux.h | 1 + 6 files changed, 1170 insertions(+) create mode 100644 shell/platform/linux/fl_json_message_codec.cc create mode 100644 shell/platform/linux/fl_json_message_codec_test.cc create mode 100644 shell/platform/linux/public/flutter_linux/fl_json_message_codec.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a263c49a09728..ac56f4106fd2f 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1193,6 +1193,8 @@ 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_json_message_codec.cc +FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_method_channel.cc @@ -1219,6 +1221,7 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_codec FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.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_json_message_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_message_codec.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 diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index fb71bb14c0814..55ba45017fadd 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -48,6 +48,7 @@ _public_headers = [ "public/flutter_linux/fl_binary_messenger.h", "public/flutter_linux/fl_dart_project.h", "public/flutter_linux/fl_engine.h", + "public/flutter_linux/fl_json_message_codec.h", "public/flutter_linux/fl_message_codec.h", "public/flutter_linux/fl_method_channel.h", "public/flutter_linux/fl_method_codec.h", @@ -72,6 +73,7 @@ source_set("flutter_linux") { "fl_binary_messenger.cc", "fl_dart_project.cc", "fl_engine.cc", + "fl_json_message_codec.cc", "fl_message_codec.cc", "fl_method_channel.cc", "fl_method_codec.cc", @@ -96,6 +98,7 @@ source_set("flutter_linux") { deps = [ "//flutter/shell/platform/embedder:embedder_with_symbol_prefix", + "//third_party/rapidjson", ] } @@ -109,6 +112,7 @@ executable("flutter_linux_unittests") { sources = [ "fl_binary_codec_test.cc", "fl_dart_project_test.cc", + "fl_json_message_codec_test.cc", "fl_message_codec_test.cc", "fl_method_codec_test.cc", "fl_method_response_test.cc", diff --git a/shell/platform/linux/fl_json_message_codec.cc b/shell/platform/linux/fl_json_message_codec.cc new file mode 100644 index 0000000000000..8197e59d2882d --- /dev/null +++ b/shell/platform/linux/fl_json_message_codec.cc @@ -0,0 +1,300 @@ +// 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_json_message_codec.h" + +#include "rapidjson/reader.h" +#include "rapidjson/writer.h" + +#include + +G_DEFINE_QUARK(fl_json_message_codec_error_quark, fl_json_message_codec_error) + +struct _FlJsonMessageCodec { + FlMessageCodec parent_instance; +}; + +G_DEFINE_TYPE(FlJsonMessageCodec, + fl_json_message_codec, + fl_message_codec_get_type()) + +// Recursively writes #FlValue objects using rapidjson +static gboolean write_value(rapidjson::Writer& writer, + FlValue* value, + GError** error) { + if (value == nullptr) { + writer.Null(); + return TRUE; + } + + switch (fl_value_get_type(value)) { + case FL_VALUE_TYPE_NULL: + writer.Null(); + break; + case FL_VALUE_TYPE_BOOL: + writer.Bool(fl_value_get_bool(value)); + break; + case FL_VALUE_TYPE_INT: + writer.Int64(fl_value_get_int(value)); + break; + case FL_VALUE_TYPE_FLOAT: + writer.Double(fl_value_get_float(value)); + break; + case FL_VALUE_TYPE_STRING: + writer.String(fl_value_get_string(value)); + break; + case FL_VALUE_TYPE_UINT8_LIST: { + writer.StartArray(); + const uint8_t* data = fl_value_get_uint8_list(value); + for (size_t i = 0; i < fl_value_get_length(value); i++) + writer.Int(data[i]); + writer.EndArray(); + break; + } + case FL_VALUE_TYPE_INT32_LIST: { + writer.StartArray(); + const int32_t* data = fl_value_get_int32_list(value); + for (size_t i = 0; i < fl_value_get_length(value); i++) + writer.Int(data[i]); + writer.EndArray(); + break; + } + case FL_VALUE_TYPE_INT64_LIST: { + writer.StartArray(); + const int64_t* data = fl_value_get_int64_list(value); + for (size_t i = 0; i < fl_value_get_length(value); i++) + writer.Int64(data[i]); + writer.EndArray(); + break; + } + case FL_VALUE_TYPE_FLOAT_LIST: { + writer.StartArray(); + const double* data = fl_value_get_float_list(value); + for (size_t i = 0; i < fl_value_get_length(value); i++) + writer.Double(data[i]); + writer.EndArray(); + break; + } + case FL_VALUE_TYPE_LIST: + writer.StartArray(); + for (size_t i = 0; i < fl_value_get_length(value); i++) + if (!write_value(writer, fl_value_get_list_value(value, i), error)) + return FALSE; + writer.EndArray(); + break; + case FL_VALUE_TYPE_MAP: + writer.StartObject(); + for (size_t i = 0; i < fl_value_get_length(value); i++) { + FlValue* key = fl_value_get_map_key(value, i); + if (fl_value_get_type(key) != FL_VALUE_TYPE_STRING) { + g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE, + "Invalid object key type"); + return FALSE; + } + writer.Key(fl_value_get_string(key)); + if (!write_value(writer, fl_value_get_map_value(value, i), error)) + return FALSE; + } + writer.EndObject(); + break; + default: + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Encountered unknown FlValue type"); + return FALSE; + } + + return TRUE; +} + +// Handler to parse JSON using rapidjson in SAX mode +struct FlValueHandler { + GPtrArray* stack; + FlValue* key; + + FlValueHandler() { + stack = g_ptr_array_new_with_free_func( + reinterpret_cast(fl_value_unref)); + key = nullptr; + } + + ~FlValueHandler() { + g_ptr_array_unref(stack); + if (key != nullptr) + fl_value_unref(key); + } + + // Get the current head of the stack + FlValue* get_head() { + if (stack->len == 0) + return nullptr; + return static_cast(g_ptr_array_index(stack, stack->len - 1)); + } + + // Push a value onto the stack + void push(FlValue* value) { g_ptr_array_add(stack, fl_value_ref(value)); } + + // Pop the stack + void pop() { g_ptr_array_remove_index(stack, stack->len - 1); } + + // Add a new value to the stack + bool add(FlValue* value) { + g_autoptr(FlValue) owned_value = value; + FlValue* head = get_head(); + if (head == nullptr) + push(owned_value); + else if (fl_value_get_type(head) == FL_VALUE_TYPE_LIST) + fl_value_append(head, owned_value); + else if (fl_value_get_type(head) == FL_VALUE_TYPE_MAP) { + fl_value_set_take(head, key, fl_value_ref(owned_value)); + key = nullptr; + } else + return false; + + if (fl_value_get_type(owned_value) == FL_VALUE_TYPE_LIST || + fl_value_get_type(owned_value) == FL_VALUE_TYPE_MAP) + push(value); + + return true; + } + + // The following implements the rapidjson SAX API + + bool Null() { return add(fl_value_new_null()); } + + bool Bool(bool b) { return add(fl_value_new_bool(b)); } + + bool Int(int i) { return add(fl_value_new_int(i)); } + + bool Uint(unsigned i) { return add(fl_value_new_int(i)); } + + bool Int64(int64_t i) { return add(fl_value_new_int(i)); } + + bool Uint64(uint64_t i) { + return add(fl_value_new_int(i)); // FIXME: Too big! + } + + bool Double(double d) { return add(fl_value_new_float(d)); } + + bool RawNumber(const char* str, rapidjson::SizeType length, bool copy) { + return false; + } + + bool String(const char* str, rapidjson::SizeType length, bool copy) { + FlValue* v = fl_value_new_string_sized(str, length); + return add(v); + } + + bool StartObject() { return add(fl_value_new_map()); } + + bool Key(const char* str, rapidjson::SizeType length, bool copy) { + if (key != nullptr) + fl_value_unref(key); + key = fl_value_new_string_sized(str, length); + return true; + } + + bool EndObject(rapidjson::SizeType memberCount) { + pop(); + return true; + } + + bool StartArray() { return add(fl_value_new_list()); } + + bool EndArray(rapidjson::SizeType elementCount) { + pop(); + return true; + } +}; + +// Implements FlMessageCodec:encode_message +static GBytes* fl_json_message_codec_encode_message(FlMessageCodec* codec, + FlValue* message, + GError** error) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + + if (!write_value(writer, message, error)) + return nullptr; + + const gchar* text = buffer.GetString(); + return g_bytes_new(text, strlen(text)); +} + +// Implements FlMessageCodec:decode_message +static FlValue* fl_json_message_codec_decode_message(FlMessageCodec* codec, + GBytes* message, + GError** error) { + gsize data_length; + const gchar* data = + static_cast(g_bytes_get_data(message, &data_length)); + if (!g_utf8_validate(data, data_length, nullptr)) { + g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8, + "Message is not valid UTF8"); + return nullptr; + } + + FlValueHandler handler; + rapidjson::Reader reader; + rapidjson::MemoryStream ss(data, data_length); + if (!reader.Parse(ss, handler)) { + g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON, + "Message is not valid JSON"); + return nullptr; + } + + FlValue* value = handler.get_head(); + if (value == nullptr) { + g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON, + "Message is not valid JSON"); + return nullptr; + } + + return fl_value_ref(value); +} + +static void fl_json_message_codec_class_init(FlJsonMessageCodecClass* klass) { + FL_MESSAGE_CODEC_CLASS(klass)->encode_message = + fl_json_message_codec_encode_message; + FL_MESSAGE_CODEC_CLASS(klass)->decode_message = + fl_json_message_codec_decode_message; +} + +static void fl_json_message_codec_init(FlJsonMessageCodec* self) {} + +G_MODULE_EXPORT FlJsonMessageCodec* fl_json_message_codec_new() { + return static_cast( + g_object_new(fl_json_message_codec_get_type(), nullptr)); +} + +G_MODULE_EXPORT gchar* fl_json_message_codec_encode(FlJsonMessageCodec* codec, + FlValue* value, + GError** error) { + g_return_val_if_fail(FL_IS_JSON_CODEC(codec), nullptr); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + + if (!write_value(writer, value, error)) + return nullptr; + + return g_strdup(buffer.GetString()); +} + +G_MODULE_EXPORT FlValue* fl_json_message_codec_decode(FlJsonMessageCodec* codec, + const gchar* text, + GError** error) { + g_return_val_if_fail(FL_IS_JSON_CODEC(codec), nullptr); + + g_autoptr(GBytes) data = g_bytes_new_static(text, strlen(text)); + g_autoptr(FlValue) value = fl_json_message_codec_decode_message( + FL_MESSAGE_CODEC(codec), data, error); + if (value == nullptr) + return nullptr; + + return fl_value_ref(value); +} diff --git a/shell/platform/linux/fl_json_message_codec_test.cc b/shell/platform/linux/fl_json_message_codec_test.cc new file mode 100644 index 0000000000000..e10e868ae6539 --- /dev/null +++ b/shell/platform/linux/fl_json_message_codec_test.cc @@ -0,0 +1,769 @@ +// 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_json_message_codec.h" +#include "gtest/gtest.h" + +#include + +// TODO(robert-ancell): Test decoding integers that can't fit into 64 bits + +// Encodes a message using FlJsonMessageCodec to a UTF-8 string. +static gchar* encode_message(FlValue* value) { + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(GError) error = nullptr; + g_autofree gchar* result = fl_json_message_codec_encode(codec, value, &error); + EXPECT_EQ(error, nullptr); + return static_cast(g_steal_pointer(&result)); +} + +// Encodes a message using FlJsonMessageCodec to a UTF-8 string. Expect the +// given error. +static void encode_error_message(FlValue* value, GQuark domain, gint code) { + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(GError) error = nullptr; + g_autofree gchar* result = fl_json_message_codec_encode(codec, value, &error); + EXPECT_TRUE(g_error_matches(error, domain, code)); + EXPECT_EQ(result, nullptr); +} + +// Decodes a message using FlJsonMessageCodec from UTF-8 string. +static FlValue* decode_message(const char* text) { + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) value = fl_json_message_codec_decode(codec, text, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(value, nullptr); + return fl_value_ref(value); +} + +// Decodes a message using FlJsonMessageCodec from UTF-8 string. Expect the +// given error. +static void decode_error_message(const char* text, GQuark domain, gint code) { + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) value = fl_json_message_codec_decode(codec, text, &error); + EXPECT_TRUE(g_error_matches(error, domain, code)); + EXPECT_EQ(value, nullptr); +} + +TEST(FlJsonMessageCodecTest, EncodeNullptr) { + g_autofree gchar* text = encode_message(nullptr); + EXPECT_STREQ(text, "null"); +} + +TEST(FlJsonMessageCodecTest, EncodeNull) { + g_autoptr(FlValue) value = fl_value_new_null(); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "null"); +} + +TEST(FlJsonMessageCodecTest, DecodeNull) { + g_autoptr(FlValue) value = decode_message("null"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_NULL); +} + +static gchar* encode_bool(gboolean value) { + g_autoptr(FlValue) v = fl_value_new_bool(value); + return encode_message(v); +} + +TEST(FlJsonMessageCodecTest, EncodeBoolFalse) { + g_autofree gchar* text = encode_bool(FALSE); + EXPECT_STREQ(text, "false"); +} + +TEST(FlJsonMessageCodecTest, EncodeBoolTrue) { + g_autofree gchar* text = encode_bool(TRUE); + EXPECT_STREQ(text, "true"); +} + +TEST(FlJsonMessageCodecTest, DecodeBoolFalse) { + g_autoptr(FlValue) value = decode_message("false"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_BOOL); + EXPECT_FALSE(fl_value_get_bool(value)); +} + +TEST(FlJsonMessageCodecTest, DecodeBoolTrue) { + g_autoptr(FlValue) value = decode_message("true"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_BOOL); + EXPECT_TRUE(fl_value_get_bool(value)); +} + +static gchar* encode_int(int64_t value) { + g_autoptr(FlValue) v = fl_value_new_int(value); + return encode_message(v); +} + +TEST(FlJsonMessageCodecTest, EncodeIntZero) { + g_autofree gchar* text = encode_int(0); + EXPECT_STREQ(text, "0"); +} + +TEST(FlJsonMessageCodecTest, EncodeIntOne) { + g_autofree gchar* text = encode_int(1); + EXPECT_STREQ(text, "1"); +} + +TEST(FlJsonMessageCodecTest, EncodeInt12345) { + g_autofree gchar* text = encode_int(12345); + EXPECT_STREQ(text, "12345"); +} + +TEST(FlJsonMessageCodecTest, EncodeIntMin) { + g_autofree gchar* text = encode_int(G_MININT64); + EXPECT_STREQ(text, "-9223372036854775808"); +} + +TEST(FlJsonMessageCodecTest, EncodeIntMax) { + g_autofree gchar* text = encode_int(G_MAXINT64); + EXPECT_STREQ(text, "9223372036854775807"); +} + +TEST(FlJsonMessageCodecTest, DecodeIntZero) { + g_autoptr(FlValue) value = decode_message("0"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 0); +} + +TEST(FlJsonMessageCodecTest, DecodeIntOne) { + g_autoptr(FlValue) value = decode_message("1"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 1); +} + +TEST(FlJsonMessageCodecTest, DecodeInt12345) { + g_autoptr(FlValue) value = decode_message("12345"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), 12345); +} + +TEST(FlJsonMessageCodecTest, DecodeIntMin) { + g_autoptr(FlValue) value = decode_message("-9223372036854775808"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), G_MININT64); +} + +TEST(FlJsonMessageCodecTest, DecodeIntMax) { + g_autoptr(FlValue) value = decode_message("9223372036854775807"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(value), G_MAXINT64); +} + +TEST(FlJsonMessageCodecTest, DecodeIntLeadingZero1) { + decode_error_message("00", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeIntLeadingZero2) { + decode_error_message("01", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeIntDoubleNegative) { + decode_error_message("--1", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeIntPositiveSign) { + decode_error_message("+1", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeIntHexChar) { + decode_error_message("0a", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +static gchar* encode_float(double value) { + g_autoptr(FlValue) v = fl_value_new_float(value); + return encode_message(v); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatZero) { + g_autofree gchar* text = encode_float(0); + EXPECT_STREQ(text, "0.0"); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatOne) { + g_autofree gchar* text = encode_float(1); + EXPECT_STREQ(text, "1.0"); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatMinusOne) { + g_autofree gchar* text = encode_float(-1); + EXPECT_STREQ(text, "-1.0"); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatHalf) { + g_autofree gchar* text = encode_float(0.5); + EXPECT_STREQ(text, "0.5"); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatPi) { + g_autofree gchar* text = encode_float(M_PI); + EXPECT_STREQ(text, "3.141592653589793"); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatMinusZero) { + g_autofree gchar* text = encode_float(-0.0); + EXPECT_STREQ(text, "-0.0"); +} + +// NOTE(robert-ancell): JSON doesn't support encoding of NAN and INFINITY, but +// rapidjson doesn't seem to either encode them or treat them as an error. + +TEST(FlJsonMessageCodecTest, DecodeFloatZero) { + g_autoptr(FlValue) value = decode_message("0.0"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 0.0); +} + +TEST(FlJsonMessageCodecTest, DecodeFloatOne) { + g_autoptr(FlValue) value = decode_message("1.0"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 1.0); +} + +TEST(FlJsonMessageCodecTest, DecodeFloatMinusOne) { + g_autoptr(FlValue) value = decode_message("-1.0"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), -1.0); +} + +TEST(FlJsonMessageCodecTest, DecodeFloatHalf) { + g_autoptr(FlValue) value = decode_message("0.5"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), 0.5); +} + +TEST(FlJsonMessageCodecTest, DecodeFloatPi) { + g_autoptr(FlValue) value = decode_message("3.1415926535897931"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), M_PI); +} + +TEST(FlJsonMessageCodecTest, DecodeFloatMinusZero) { + g_autoptr(FlValue) value = decode_message("-0.0"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT); + EXPECT_EQ(fl_value_get_float(value), -0.0); +} + +TEST(FlJsonMessageCodecTest, DecodeFloatMissingFraction) { + decode_error_message("0.", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeFloatInvalidFraction) { + decode_error_message("0.a", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +static gchar* encode_string(const gchar* value) { + g_autoptr(FlValue) v = fl_value_new_string(value); + return encode_message(v); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEmpty) { + g_autofree gchar* text = encode_string(""); + EXPECT_STREQ(text, "\"\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringHello) { + g_autofree gchar* text = encode_string("hello"); + EXPECT_STREQ(text, "\"hello\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEmptySized) { + g_autoptr(FlValue) value = fl_value_new_string_sized(nullptr, 0); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "\"\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringHelloSized) { + g_autoptr(FlValue) value = fl_value_new_string_sized("Hello World", 5); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "\"Hello\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeQuote) { + g_autofree gchar* text = encode_string("\""); + EXPECT_STREQ(text, "\"\\\"\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeBackslash) { + g_autofree gchar* text = encode_string("\\"); + EXPECT_STREQ(text, "\"\\\\\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeBackspace) { + g_autofree gchar* text = encode_string("\b"); + EXPECT_STREQ(text, "\"\\b\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeFormFeed) { + g_autofree gchar* text = encode_string("\f"); + EXPECT_STREQ(text, "\"\\f\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeNewline) { + g_autofree gchar* text = encode_string("\n"); + EXPECT_STREQ(text, "\"\\n\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeCarriageReturn) { + g_autofree gchar* text = encode_string("\r"); + EXPECT_STREQ(text, "\"\\r\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeTab) { + g_autofree gchar* text = encode_string("\t"); + EXPECT_STREQ(text, "\"\\t\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEscapeUnicode) { + g_autofree gchar* text = encode_string("\u0001"); + EXPECT_STREQ(text, "\"\\u0001\""); +} + +TEST(FlJsonMessageCodecTest, EncodeStringEmoji) { + g_autofree gchar* text = encode_string("😀"); + EXPECT_STREQ(text, "\"😀\""); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEmpty) { + g_autoptr(FlValue) value = decode_message("\"\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), ""); +} + +TEST(FlJsonMessageCodecTest, DecodeStringHello) { + g_autoptr(FlValue) value = decode_message("\"hello\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "hello"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeQuote) { + g_autoptr(FlValue) value = decode_message("\"\\\"\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\""); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeBackslash) { + g_autoptr(FlValue) value = decode_message("\"\\\\\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\\"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeSlash) { + g_autoptr(FlValue) value = decode_message("\"\\/\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "/"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeBackspace) { + g_autoptr(FlValue) value = decode_message("\"\\b\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\b"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeFormFeed) { + g_autoptr(FlValue) value = decode_message("\"\\f\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\f"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeNewline) { + g_autoptr(FlValue) value = decode_message("\"\\n\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\n"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeCarriageReturn) { + g_autoptr(FlValue) value = decode_message("\"\\r\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\r"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeTab) { + g_autoptr(FlValue) value = decode_message("\"\\t\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\t"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeUnicode) { + g_autoptr(FlValue) value = decode_message("\"\\u0001\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "\u0001"); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEmoji) { + g_autoptr(FlValue) value = decode_message("\"😀\""); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + EXPECT_STREQ(fl_value_get_string(value), "😀"); +} + +TEST(FlJsonMessageCodecTest, DecodeInvalidUTF8) { + decode_error_message("\xff", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8); +} + +TEST(FlJsonMessageCodecTest, DecodeStringInvalidUTF8) { + decode_error_message("\"\xff\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8); +} + +TEST(FlJsonMessageCodecTest, DecodeStringBinary) { + decode_error_message("\"Hello\x01World\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringNewline) { + decode_error_message("\"Hello\nWorld\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringCarriageReturn) { + decode_error_message("\"Hello\rWorld\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringTab) { + decode_error_message("\"Hello\tWorld\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringUnterminatedEmpty) { + decode_error_message("\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringExtraQuote) { + decode_error_message("\"\"\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapedClosingQuote) { + decode_error_message("\"\\\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringUnknownEscape) { + decode_error_message("\"\\z\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringInvalidEscapeUnicode) { + decode_error_message("\"\\uxxxx\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeUnicodeNoData) { + decode_error_message("\"\\u\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeStringEscapeUnicodeShortData) { + decode_error_message("\"\\uxx\"", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, EncodeUint8ListEmpty) { + g_autoptr(FlValue) value = fl_value_new_uint8_list(nullptr, 0); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[]"); +} + +TEST(FlJsonMessageCodecTest, EncodeUint8List) { + uint8_t data[] = {0, 1, 2, 3, 4}; + g_autoptr(FlValue) value = fl_value_new_uint8_list(data, 5); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[0,1,2,3,4]"); +} + +TEST(FlJsonMessageCodecTest, EncodeInt32ListEmpty) { + g_autoptr(FlValue) value = fl_value_new_int32_list(nullptr, 0); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[]"); +} + +TEST(FlJsonMessageCodecTest, EncodeInt32List) { + int32_t data[] = {0, -1, 2, -3, 4}; + g_autoptr(FlValue) value = fl_value_new_int32_list(data, 5); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[0,-1,2,-3,4]"); +} + +TEST(FlJsonMessageCodecTest, EncodeInt64ListEmpty) { + g_autoptr(FlValue) value = fl_value_new_int64_list(nullptr, 0); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[]"); +} + +TEST(FlJsonMessageCodecTest, EncodeInt64List) { + int64_t data[] = {0, -1, 2, -3, 4}; + g_autoptr(FlValue) value = fl_value_new_int64_list(data, 5); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[0,-1,2,-3,4]"); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatListEmpty) { + g_autoptr(FlValue) value = fl_value_new_float_list(nullptr, 0); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[]"); +} + +TEST(FlJsonMessageCodecTest, EncodeFloatList) { + double data[] = {0, -0.5, 0.25, -0.125, 0.0625}; + g_autoptr(FlValue) value = fl_value_new_float_list(data, 5); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[0.0,-0.5,0.25,-0.125,0.0625]"); +} + +TEST(FlJsonMessageCodecTest, EncodeListEmpty) { + g_autoptr(FlValue) value = fl_value_new_list(); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[]"); +} + +TEST(FlJsonMessageCodecTest, EncodeListTypes) { + g_autoptr(FlValue) value = fl_value_new_list(); + fl_value_append_take(value, fl_value_new_null()); + fl_value_append_take(value, fl_value_new_bool(TRUE)); + fl_value_append_take(value, fl_value_new_int(42)); + fl_value_append_take(value, fl_value_new_float(-1.5)); + fl_value_append_take(value, fl_value_new_string("hello")); + fl_value_append_take(value, fl_value_new_list()); + fl_value_append_take(value, fl_value_new_map()); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[null,true,42,-1.5,\"hello\",[],{}]"); +} + +TEST(FlJsonMessageCodecTest, EncodeListNested) { + g_autoptr(FlValue) even_numbers = fl_value_new_list(); + g_autoptr(FlValue) odd_numbers = fl_value_new_list(); + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) + fl_value_append_take(even_numbers, fl_value_new_int(i)); + else + fl_value_append_take(odd_numbers, fl_value_new_int(i)); + } + g_autoptr(FlValue) value = fl_value_new_list(); + fl_value_append(value, even_numbers); + fl_value_append(value, odd_numbers); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "[[0,2,4,6,8],[1,3,5,7,9]]"); +} + +TEST(FlJsonMessageCodecTest, DecodeListEmpty) { + g_autoptr(FlValue) value = decode_message("[]"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_LIST); + EXPECT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlJsonMessageCodecTest, DecodeListNoComma) { + decode_error_message("[0,1,2,3 4]", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeListUnterminatedEmpty) { + decode_error_message("[", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeListStartUnterminate) { + decode_error_message("]", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeListUnterminated) { + decode_error_message("[0,1,2,3,4", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeListDoubleTerminated) { + decode_error_message("[0,1,2,3,4]]", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, EncodeMapEmpty) { + g_autoptr(FlValue) value = fl_value_new_map(); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, "{}"); +} + +TEST(FlJsonMessageCodecTest, EncodeMapNullKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_null(), fl_value_new_string("null")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapBoolKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_bool(TRUE), + fl_value_new_string("bool")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapIntKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_int(42), fl_value_new_string("int")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapFloatKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_float(M_PI), + fl_value_new_string("float")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapUint8ListKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_uint8_list(nullptr, 0), + fl_value_new_string("uint8_list")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapInt32ListKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_int32_list(nullptr, 0), + fl_value_new_string("int32_list")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapInt64ListKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_int64_list(nullptr, 0), + fl_value_new_string("int64_list")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapFloatListKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_float_list(nullptr, 0), + fl_value_new_string("float_list")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapListKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_list(), fl_value_new_string("list")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapMapKey) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_map(), fl_value_new_string("map")); + encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE); +} + +TEST(FlJsonMessageCodecTest, EncodeMapValueTypes) { + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_take(value, fl_value_new_string("null"), fl_value_new_null()); + fl_value_set_take(value, fl_value_new_string("bool"), + fl_value_new_bool(TRUE)); + fl_value_set_take(value, fl_value_new_string("int"), fl_value_new_int(42)); + fl_value_set_take(value, fl_value_new_string("float"), + fl_value_new_float(-1.5)); + fl_value_set_take(value, fl_value_new_string("string"), + fl_value_new_string("hello")); + fl_value_set_take(value, fl_value_new_string("list"), fl_value_new_list()); + fl_value_set_take(value, fl_value_new_string("map"), fl_value_new_map()); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, + "{\"null\":null,\"bool\":true,\"int\":42,\"float\":-" + "1.5,\"string\":\"hello\",\"list\":[],\"map\":{}}"); +} + +TEST(FlJsonMessageCodecTest, EncodeMapNested) { + g_autoptr(FlValue) str_to_int = fl_value_new_map(); + const char* numbers[] = {"zero", "one", "two", "three", nullptr}; + for (int i = 0; numbers[i] != nullptr; i++) { + fl_value_set_take(str_to_int, fl_value_new_string(numbers[i]), + fl_value_new_int(i)); + } + g_autoptr(FlValue) value = fl_value_new_map(); + fl_value_set_string(value, "str-to-int", str_to_int); + g_autofree gchar* text = encode_message(value); + EXPECT_STREQ(text, + "{\"str-to-int\":{\"zero\":0,\"one\":1,\"two\":2,\"three\":3}}"); +} + +TEST(FlJsonMessageCodecTest, DecodeMapEmpty) { + g_autoptr(FlValue) value = decode_message("{}"); + ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + EXPECT_EQ(fl_value_get_length(value), static_cast(0)); +} + +TEST(FlJsonMessageCodecTest, DecodeMapUnterminatedEmpty) { + decode_error_message("{", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeMapStartUnterminate) { + decode_error_message("}", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeMapNoComma) { + decode_error_message("{\"zero\":0 \"one\":1}", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeMapNoColon) { + decode_error_message("{\"zero\" 0,\"one\":1}", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeMapUnterminated) { + decode_error_message("{\"zero\":0,\"one\":1", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeMapDoubleTerminated) { + decode_error_message("{\"zero\":0,\"one\":1}}", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, DecodeUnknownWord) { + decode_error_message("foo", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMessageCodecTest, EncodeDecode) { + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + + g_autoptr(FlValue) input = fl_value_new_list(); + fl_value_append_take(input, fl_value_new_null()); + fl_value_append_take(input, fl_value_new_bool(TRUE)); + fl_value_append_take(input, fl_value_new_int(42)); + fl_value_append_take(input, fl_value_new_float(M_PI)); + fl_value_append_take(input, fl_value_new_string("hello")); + fl_value_append_take(input, fl_value_new_list()); + fl_value_append_take(input, fl_value_new_map()); + + g_autoptr(GError) error = nullptr; + g_autofree gchar* message = + fl_json_message_codec_encode(codec, input, &error); + ASSERT_NE(message, nullptr); + EXPECT_EQ(error, nullptr); + + g_autoptr(FlValue) output = + fl_json_message_codec_decode(codec, message, &error); + EXPECT_EQ(error, nullptr); + EXPECT_NE(output, nullptr); + + EXPECT_TRUE(fl_value_equal(input, output)); +} diff --git a/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h b/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h new file mode 100644 index 0000000000000..5796ffecd6f2b --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h @@ -0,0 +1,93 @@ +// 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_JSON_MESSAGE_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_ + +#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) +#error "Only can be included directly." +#endif + +#include "fl_message_codec.h" + +G_BEGIN_DECLS + +/** + * FlJsonMessageCodecError: + * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8: Message is not valid UTF-8. + * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON: Message is not valid JSON. + * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE: Invalid object key type + * + * Errors for #FlJsonMessageCodec objects to set on failures. + */ +#define FL_JSON_MESSAGE_CODEC_ERROR fl_json_message_codec_error_quark() + +typedef enum { + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE, +} FlJsonMessageCodecError; + +GQuark fl_json_message_codec_error_quark(void) G_GNUC_CONST; + +G_DECLARE_FINAL_TYPE(FlJsonMessageCodec, + fl_json_message_codec, + FL, + JSON_CODEC, + FlMessageCodec) + +/** + * FlJsonMessageCodec: + * + * #FlJsonMessageCodec is an #FlMessageCodec that implements the Flutter + * standard message encoding. This encodes and decodes #FlValue of type + * #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, and #FL_VALUE_TYPE_MAP + * + * #FlJsonMessageCodec matches the JSONMessageCodec class in the Flutter + * services library. + */ + +/** + * fl_json_message_codec_new: + * + * Creates a #FlJsonMessageCodec. + * + * Returns: a new #FlJsonMessageCodec + */ +FlJsonMessageCodec* fl_json_message_codec_new(); + +/** + * fl_json_message_codec_encode: + * @codec: a #FlJsonMessageCodec + * @value: value to encode + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Encode a value to a JSON string. + * + * Returns: a JSON representation of this value or %NULL on error. + */ +gchar* fl_json_message_codec_encode(FlJsonMessageCodec* codec, + FlValue* value, + GError** error); + +/** + * fl_json_message_codec_decode: + * @codec: a #FlJsonMessageCodec + * @text: UTF-8 text in JSON format + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * + * Decode a value from a JSON string. + * + * Returns: a #FlValue or %NULL on error + */ +FlValue* fl_json_message_codec_decode(FlJsonMessageCodec* codec, + const gchar* text, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_ diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index 24d21cb1a65f0..e42e702b5977b 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include From bc29e7c4dc3106ab2f30a8b087a12a6245318c9b Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Wed, 13 May 2020 09:15:55 +1200 Subject: [PATCH 3/3] Add FlJsonMethodCodec --- ci/licenses_golden/licenses_flutter | 3 + shell/platform/linux/BUILD.gn | 3 + shell/platform/linux/fl_json_method_codec.cc | 202 +++++++++ .../linux/fl_json_method_codec_test.cc | 398 ++++++++++++++++++ .../flutter_linux/fl_json_method_codec.h | 36 ++ .../public/flutter_linux/flutter_linux.h | 1 + 6 files changed, 643 insertions(+) create mode 100644 shell/platform/linux/fl_json_method_codec.cc create mode 100644 shell/platform/linux/fl_json_method_codec_test.cc create mode 100644 shell/platform/linux/public/flutter_linux/fl_json_method_codec.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ac56f4106fd2f..9513cd859a90d 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1195,6 +1195,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_engine.cc FILE: ../../../flutter/shell/platform/linux/fl_engine_private.h FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec.cc +FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_message_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_message_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_method_channel.cc @@ -1222,6 +1224,7 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messe 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_json_message_codec.h +FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_message_codec.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 diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 55ba45017fadd..2fde7c7daee6e 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -49,6 +49,7 @@ _public_headers = [ "public/flutter_linux/fl_dart_project.h", "public/flutter_linux/fl_engine.h", "public/flutter_linux/fl_json_message_codec.h", + "public/flutter_linux/fl_json_method_codec.h", "public/flutter_linux/fl_message_codec.h", "public/flutter_linux/fl_method_channel.h", "public/flutter_linux/fl_method_codec.h", @@ -74,6 +75,7 @@ source_set("flutter_linux") { "fl_dart_project.cc", "fl_engine.cc", "fl_json_message_codec.cc", + "fl_json_method_codec.cc", "fl_message_codec.cc", "fl_method_channel.cc", "fl_method_codec.cc", @@ -113,6 +115,7 @@ executable("flutter_linux_unittests") { "fl_binary_codec_test.cc", "fl_dart_project_test.cc", "fl_json_message_codec_test.cc", + "fl_json_method_codec_test.cc", "fl_message_codec_test.cc", "fl_method_codec_test.cc", "fl_method_response_test.cc", diff --git a/shell/platform/linux/fl_json_method_codec.cc b/shell/platform/linux/fl_json_method_codec.cc new file mode 100644 index 0000000000000..16d3adbe210da --- /dev/null +++ b/shell/platform/linux/fl_json_method_codec.cc @@ -0,0 +1,202 @@ +// 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_json_method_codec.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" + +#include + +struct _FlJsonMethodCodec { + FlMethodCodec parent_instance; + + FlJsonMessageCodec* codec; +}; + +G_DEFINE_TYPE(FlJsonMethodCodec, + fl_json_method_codec, + fl_method_codec_get_type()) + +static void fl_json_method_codec_dispose(GObject* object) { + FlJsonMethodCodec* self = FL_JSON_METHOD_CODEC(object); + + g_clear_object(&self->codec); + + G_OBJECT_CLASS(fl_json_method_codec_parent_class)->dispose(object); +} + +// Implements FlMethodCodec::encode_method_call +static GBytes* fl_json_method_codec_encode_method_call(FlMethodCodec* codec, + const gchar* name, + FlValue* args, + GError** error) { + FlJsonMethodCodec* self = FL_JSON_METHOD_CODEC(codec); + + g_autoptr(FlValue) message = fl_value_new_map(); + fl_value_set_take(message, fl_value_new_string("method"), + fl_value_new_string(name)); + fl_value_set_take(message, fl_value_new_string("args"), + args != nullptr ? fl_value_ref(args) : fl_value_new_null()); + + return fl_message_codec_encode_message(FL_MESSAGE_CODEC(self->codec), message, + error); +} + +// Implements FlMethodCodec::decode_method_call +static gboolean fl_json_method_codec_decode_method_call(FlMethodCodec* codec, + GBytes* message, + gchar** name, + FlValue** args, + GError** error) { + FlJsonMethodCodec* self = FL_JSON_METHOD_CODEC(codec); + + g_autoptr(FlValue) value = fl_message_codec_decode_message( + FL_MESSAGE_CODEC(self->codec), message, error); + if (value == nullptr) + return FALSE; + + if (fl_value_get_type(value) != FL_VALUE_TYPE_MAP) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Expected JSON map in method resonse, got %d instead", + fl_value_get_type(value)); + return FALSE; + } + + FlValue* method_value = fl_value_lookup_string(value, "method"); + if (method_value == nullptr) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Missing JSON method field in method resonse"); + return FALSE; + } + if (fl_value_get_type(method_value) != FL_VALUE_TYPE_STRING) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Expected JSON string for method name, got %d instead", + fl_value_get_type(method_value)); + return FALSE; + } + FlValue* args_value = fl_value_lookup_string(value, "args"); + + *name = g_strdup(fl_value_get_string(method_value)); + *args = args_value != nullptr ? fl_value_ref(args_value) : nullptr; + + return TRUE; +} + +// Implements FlMethodCodec::encode_success_envelope +static GBytes* fl_json_method_codec_encode_success_envelope( + FlMethodCodec* codec, + FlValue* result, + GError** error) { + FlJsonMethodCodec* self = FL_JSON_METHOD_CODEC(codec); + + g_autoptr(FlValue) message = fl_value_new_list(); + fl_value_append_take( + message, result != nullptr ? fl_value_ref(result) : fl_value_new_null()); + + return fl_message_codec_encode_message(FL_MESSAGE_CODEC(self->codec), message, + error); +} + +// Implements FlMethodCodec::encode_error_envelope +static GBytes* fl_json_method_codec_encode_error_envelope( + FlMethodCodec* codec, + const gchar* code, + const gchar* error_message, + FlValue* details, + GError** error) { + FlJsonMethodCodec* self = FL_JSON_METHOD_CODEC(codec); + + g_autoptr(FlValue) message = fl_value_new_list(); + fl_value_append_take(message, fl_value_new_string(code)); + fl_value_append_take(message, error_message != nullptr + ? fl_value_new_string(error_message) + : fl_value_new_null()); + fl_value_append_take(message, details != nullptr ? fl_value_ref(details) + : fl_value_new_null()); + + return fl_message_codec_encode_message(FL_MESSAGE_CODEC(self->codec), message, + error); +} + +// Implements FlMethodCodec::decode_response +static FlMethodResponse* fl_json_method_codec_decode_response( + FlMethodCodec* codec, + GBytes* message, + GError** error) { + FlJsonMethodCodec* self = FL_JSON_METHOD_CODEC(codec); + + g_autoptr(FlValue) value = fl_message_codec_decode_message( + FL_MESSAGE_CODEC(self->codec), message, error); + if (value == nullptr) + return nullptr; + + if (fl_value_get_type(value) != FL_VALUE_TYPE_LIST) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Expected JSON list in method resonse, got %d instead", + fl_value_get_type(value)); + return nullptr; + } + + size_t length = fl_value_get_length(value); + if (length == 1) { + return FL_METHOD_RESPONSE( + fl_method_success_response_new(fl_value_get_list_value(value, 0))); + } else if (length == 3) { + FlValue* code_value = fl_value_get_list_value(value, 0); + if (fl_value_get_type(code_value) != FL_VALUE_TYPE_STRING) { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Error code wrong type"); + return nullptr; + } + const gchar* code = fl_value_get_string(code_value); + + FlValue* message_value = fl_value_get_list_value(value, 1); + 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_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Error message wrong type"); + return nullptr; + } + const gchar* message = + fl_value_get_type(message_value) == FL_VALUE_TYPE_STRING + ? fl_value_get_string(message_value) + : nullptr; + + FlValue* args = fl_value_get_list_value(value, 2); + if (fl_value_get_type(args) == FL_VALUE_TYPE_NULL) + args = nullptr; + + return FL_METHOD_RESPONSE( + fl_method_error_response_new(code, message, args)); + } else { + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Got response envelope of length %zi, expected 1 (success) or " + "3 (error)", + length); + return nullptr; + } +} + +static void fl_json_method_codec_class_init(FlJsonMethodCodecClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_json_method_codec_dispose; + FL_METHOD_CODEC_CLASS(klass)->encode_method_call = + fl_json_method_codec_encode_method_call; + FL_METHOD_CODEC_CLASS(klass)->decode_method_call = + fl_json_method_codec_decode_method_call; + FL_METHOD_CODEC_CLASS(klass)->encode_success_envelope = + fl_json_method_codec_encode_success_envelope; + FL_METHOD_CODEC_CLASS(klass)->encode_error_envelope = + fl_json_method_codec_encode_error_envelope; + FL_METHOD_CODEC_CLASS(klass)->decode_response = + fl_json_method_codec_decode_response; +} + +static void fl_json_method_codec_init(FlJsonMethodCodec* self) { + self->codec = fl_json_message_codec_new(); +} + +G_MODULE_EXPORT FlJsonMethodCodec* fl_json_method_codec_new() { + return static_cast( + g_object_new(fl_json_method_codec_get_type(), nullptr)); +} diff --git a/shell/platform/linux/fl_json_method_codec_test.cc b/shell/platform/linux/fl_json_method_codec_test.cc new file mode 100644 index 0000000000000..c8691a01fc8e1 --- /dev/null +++ b/shell/platform/linux/fl_json_method_codec_test.cc @@ -0,0 +1,398 @@ +// 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_json_method_codec.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_message_codec.h" +#include "gtest/gtest.h" + +// Convert a binary blob to a string +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); +} + +// Convert a string to a binary blob +static GBytes* text_to_message(const gchar* text) { + return g_bytes_new(text, strlen(text)); +} + +// Encode a method call using JsonMethodCodec to a UTF-8 string. +static gchar* encode_method_call(const gchar* name, FlValue* args) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GError) error = nullptr; + 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_text(message); +} + +// Encode a success envelope response using JsonMethodCodec to a UTF-8 string. +static gchar* encode_success_envelope(FlValue* result) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GError) error = nullptr; + 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_text(message); +} + +// Encode a error envelope response using JsonMethodCodec to a UTF8 string. +static gchar* encode_error_envelope(const gchar* error_code, + const gchar* error_message, + FlValue* details) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GError) error = nullptr; + 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_text(message); +} + +// Decode a method call using JsonMethodCodec with a UTF8 string. +static void decode_method_call(const char* text, gchar** name, FlValue** args) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GBytes) data = text_to_message(text); + g_autoptr(GError) error = nullptr; + gboolean result = fl_method_codec_decode_method_call( + FL_METHOD_CODEC(codec), data, name, args, &error); + EXPECT_TRUE(result); + EXPECT_EQ(error, nullptr); +} + +// Decode a method call using JsonMethodCodec. Expect the given error. +static void decode_error_method_call(const char* text, + GQuark domain, + gint code) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GBytes) data = text_to_message(text); + g_autoptr(GError) error = nullptr; + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + 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)); +} + +// Decode a response using JsonMethodCodec. Expect the response is a result. +static void decode_response_with_success(const char* text, FlValue* result) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GBytes) message = text_to_message(text); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + ASSERT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)), + result)); +} + +// Decode a response using JsonMethodCodec. Expect the response contains the +// given error. +static void decode_response_with_error(const char* text, + const gchar* code, + const gchar* error_message, + FlValue* details) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GBytes) message = text_to_message(text); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + ASSERT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + ASSERT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response)); + EXPECT_STREQ( + fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)), + code); + if (error_message == nullptr) + EXPECT_EQ(fl_method_error_response_get_message( + FL_METHOD_ERROR_RESPONSE(response)), + nullptr); + else + EXPECT_STREQ(fl_method_error_response_get_message( + FL_METHOD_ERROR_RESPONSE(response)), + error_message); + if (details == nullptr) + EXPECT_EQ(fl_method_error_response_get_details( + FL_METHOD_ERROR_RESPONSE(response)), + nullptr); + else + EXPECT_TRUE(fl_value_equal(fl_method_error_response_get_details( + FL_METHOD_ERROR_RESPONSE(response)), + details)); +} + +// Decode a response using JsonMethodCodec. Expect the given error. +static void decode_error_response(const char* text, GQuark domain, gint code) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GBytes) message = text_to_message(text); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + EXPECT_EQ(response, nullptr); + EXPECT_TRUE(g_error_matches(error, domain, code)); +} + +TEST(FlJsonMethodCodecTest, EncodeMethodCallNullptrArgs) { + g_autofree gchar* text = encode_method_call("hello", nullptr); + EXPECT_STREQ(text, "{\"method\":\"hello\",\"args\":null}"); +} + +TEST(FlJsonMethodCodecTest, EncodeMethodCallNullArgs) { + g_autoptr(FlValue) value = fl_value_new_null(); + g_autofree gchar* text = encode_method_call("hello", value); + EXPECT_STREQ(text, "{\"method\":\"hello\",\"args\":null}"); +} + +TEST(FlJsonMethodCodecTest, EncodeMethodCallStringArgs) { + g_autoptr(FlValue) args = fl_value_new_string("world"); + g_autofree gchar* text = encode_method_call("hello", args); + EXPECT_STREQ(text, "{\"method\":\"hello\",\"args\":\"world\"}"); +} + +TEST(FlJsonMethodCodecTest, EncodeMethodCallListArgs) { + g_autoptr(FlValue) args = fl_value_new_list(); + fl_value_append_take(args, fl_value_new_string("count")); + fl_value_append_take(args, fl_value_new_int(42)); + g_autofree gchar* text = encode_method_call("hello", args); + EXPECT_STREQ(text, "{\"method\":\"hello\",\"args\":[\"count\",42]}"); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallNoArgs) { + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + decode_method_call("{\"method\":\"hello\"}", &name, &args); + EXPECT_STREQ(name, "hello"); + ASSERT_EQ(args, nullptr); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallNullArgs) { + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + decode_method_call("{\"method\":\"hello\",\"args\":null}", &name, &args); + EXPECT_STREQ(name, "hello"); + ASSERT_EQ(fl_value_get_type(args), FL_VALUE_TYPE_NULL); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallStringArgs) { + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + decode_method_call("{\"method\":\"hello\",\"args\":\"world\"}", &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(FlJsonMethodCodecTest, DecodeMethodCallListArgs) { + g_autofree gchar* name = nullptr; + g_autoptr(FlValue) args = nullptr; + decode_method_call("{\"method\":\"hello\",\"args\":[\"count\",42]}", &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_get_list_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_get_list_value(args, 1); + ASSERT_EQ(fl_value_get_type(arg1), FL_VALUE_TYPE_INT); + EXPECT_EQ(fl_value_get_int(arg1), 42); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallNoData) { + decode_error_method_call("", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallNoMethodOrArgs) { + decode_error_method_call("{}", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallInvalidJson) { + decode_error_method_call("X", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallWrongType) { + decode_error_method_call("42", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallNoMethod) { + decode_error_method_call("{\"args\":\"world\"}", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallNoTerminator) { + decode_error_method_call("{\"method\":\"hello\",\"args\":\"world\"", + FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMethodCodecTest, DecodeMethodCallExtraData) { + decode_error_method_call("{\"method\":\"hello\"}XXX", + FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMethodCodecTest, EncodeSuccessEnvelopeNullptr) { + g_autofree gchar* text = encode_success_envelope(nullptr); + EXPECT_STREQ(text, "[null]"); +} + +TEST(FlJsonMethodCodecTest, EncodeSuccessEnvelopeNull) { + g_autoptr(FlValue) result = fl_value_new_null(); + g_autofree gchar* text = encode_success_envelope(result); + EXPECT_STREQ(text, "[null]"); +} + +TEST(FlJsonMethodCodecTest, EncodeSuccessEnvelopeString) { + g_autoptr(FlValue) result = fl_value_new_string("hello"); + g_autofree gchar* text = encode_success_envelope(result); + EXPECT_STREQ(text, "[\"hello\"]"); +} + +TEST(FlJsonMethodCodecTest, EncodeSuccessEnvelopeList) { + g_autoptr(FlValue) result = fl_value_new_list(); + fl_value_append_take(result, fl_value_new_string("count")); + fl_value_append_take(result, fl_value_new_int(42)); + g_autofree gchar* text = encode_success_envelope(result); + EXPECT_STREQ(text, "[[\"count\",42]]"); +} + +TEST(FlJsonMethodCodecTest, EncodeErrorEnvelopeEmptyCode) { + g_autofree gchar* text = encode_error_envelope("", nullptr, nullptr); + EXPECT_STREQ(text, "[\"\",null,null]"); +} + +TEST(FlJsonMethodCodecTest, EncodeErrorEnvelopeNonMessageOrDetails) { + g_autofree gchar* text = encode_error_envelope("error", nullptr, nullptr); + EXPECT_STREQ(text, "[\"error\",null,null]"); +} + +TEST(FlJsonMethodCodecTest, EncodeErrorEnvelopeMessage) { + g_autofree gchar* text = encode_error_envelope("error", "message", nullptr); + EXPECT_STREQ(text, "[\"error\",\"message\",null]"); +} + +TEST(FlJsonMethodCodecTest, EncodeErrorEnvelopeDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + g_autofree gchar* text = encode_error_envelope("error", nullptr, details); + EXPECT_STREQ(text, "[\"error\",null,[\"count\",42]]"); +} + +TEST(FlJsonMethodCodecTest, EncodeErrorEnvelopeMessageAndDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + g_autofree gchar* text = encode_error_envelope("error", "message", details); + EXPECT_STREQ(text, "[\"error\",\"message\",[\"count\",42]]"); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseSuccessNull) { + g_autoptr(FlValue) result = fl_value_new_null(); + decode_response_with_success("[null]", result); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseSuccessString) { + g_autoptr(FlValue) result = fl_value_new_string("hello"); + decode_response_with_success("[\"hello\"]", result); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseSuccessList) { + g_autoptr(FlValue) result = fl_value_new_list(); + fl_value_append_take(result, fl_value_new_string("count")); + fl_value_append_take(result, fl_value_new_int(42)); + decode_response_with_success("[[\"count\",42]]", result); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseErrorEmptyCode) { + decode_response_with_error("[\"\",null,null]", "", nullptr, nullptr); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseErrorNoMessageOrDetails) { + decode_response_with_error("[\"error\",null,null]", "error", nullptr, + nullptr); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseErrorMessage) { + decode_response_with_error("[\"error\",\"message\",null]", "error", "message", + nullptr); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseErrorDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + decode_response_with_error("[\"error\",null,[\"count\",42]]", "error", + nullptr, details); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseErrorMessageAndDetails) { + g_autoptr(FlValue) details = fl_value_new_list(); + fl_value_append_take(details, fl_value_new_string("count")); + fl_value_append_take(details, fl_value_new_int(42)); + decode_response_with_error("[\"error\",\"message\",[\"count\",42]]", "error", + "message", details); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseNotImplemented) { + g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(GBytes) message = g_bytes_new(nullptr, 0); + g_autoptr(GError) error = nullptr; + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error); + ASSERT_NE(response, nullptr); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseNoTerminator) { + decode_error_response("[42", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseInvalidJson) { + decode_error_response("X", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseMissingDetails) { + decode_error_response("[\"error\",\"message\"]", FL_MESSAGE_CODEC_ERROR, + FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseExtraDetails) { + decode_error_response("[\"error\",\"message\",true,42]", + FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseSuccessExtraData) { + decode_error_response("[null]X", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} + +TEST(FlJsonMethodCodecTest, DecodeResponseErrorExtraData) { + decode_error_response("[\"error\",null,null]X", FL_JSON_MESSAGE_CODEC_ERROR, + FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON); +} diff --git a/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h b/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h new file mode 100644 index 0000000000000..de6d2b13a88ce --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h @@ -0,0 +1,36 @@ +// 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_JSON_METHOD_CODEC_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_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(FlJsonMethodCodec, + fl_json_method_codec, + FL, + JSON_METHOD_CODEC, + FlMethodCodec) + +/** + * FlJsonMethodCodec: + * + * #FlJsonMessageCodec is an #FlMethodCodec that implements method calls using + * the Flutter JSON message encoding. It should be used with a #FlMethodChannel. + * + * #FlJsonMethodCodec matches the JSONMethodCodec class in the Flutter services + * library. + */ + +FlJsonMethodCodec* fl_json_method_codec_new(); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_METHOD_CODEC_H_ diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index e42e702b5977b..1957b938741a3 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include