From 6d9ba2e7a115a102de9999c4091facaacbfdc17a 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, FlMethodCall, FlMethodResponse and FlMethodCodec --- ci/licenses_golden/licenses_flutter | 13 + shell/platform/linux/BUILD.gn | 10 + shell/platform/linux/fl_method_call.cc | 119 ++++++ shell/platform/linux/fl_method_call_private.h | 35 ++ shell/platform/linux/fl_method_channel.cc | 197 +++++++++ .../linux/fl_method_channel_private.h | 35 ++ shell/platform/linux/fl_method_codec.cc | 75 ++++ .../platform/linux/fl_method_codec_private.h | 106 +++++ shell/platform/linux/fl_method_codec_test.cc | 402 ++++++++++++++++++ shell/platform/linux/fl_method_response.cc | 178 ++++++++ .../platform/linux/fl_method_response_test.cc | 96 +++++ .../public/flutter_linux/fl_method_call.h | 115 +++++ .../public/flutter_linux/fl_method_channel.h | 181 ++++++++ .../public/flutter_linux/fl_method_codec.h | 131 ++++++ .../public/flutter_linux/fl_method_response.h | 212 +++++++++ .../public/flutter_linux/flutter_linux.h | 4 + 16 files changed, 1909 insertions(+) create mode 100644 shell/platform/linux/fl_method_call.cc create mode 100644 shell/platform/linux/fl_method_call_private.h create mode 100644 shell/platform/linux/fl_method_channel.cc create mode 100644 shell/platform/linux/fl_method_channel_private.h 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/public/flutter_linux/fl_method_call.h 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 diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 1ef562879b837..5d3e6ff23ae19 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1200,6 +1200,15 @@ 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_call.cc +FILE: ../../../flutter/shell/platform/linux/fl_method_call_private.h +FILE: ../../../flutter/shell/platform/linux/fl_method_channel.cc +FILE: ../../../flutter/shell/platform/linux/fl_method_channel_private.h +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 @@ -1219,6 +1228,10 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_dart_project 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_call.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_string_codec.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_value.h diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index aa2c35af47b6e..ac12f71c6536e 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -51,6 +51,10 @@ _public_headers = [ "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_call.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_string_codec.h", "public/flutter_linux/fl_value.h", @@ -73,6 +77,10 @@ source_set("flutter_linux") { "fl_engine.cc", "fl_json_message_codec.cc", "fl_message_codec.cc", + "fl_method_call.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", @@ -108,6 +116,8 @@ executable("flutter_linux_unittests") { "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", "fl_standard_message_codec_test.cc", "fl_string_codec_test.cc", "fl_value_test.cc", diff --git a/shell/platform/linux/fl_method_call.cc b/shell/platform/linux/fl_method_call.cc new file mode 100644 index 0000000000000..0b6589155cb29 --- /dev/null +++ b/shell/platform/linux/fl_method_call.cc @@ -0,0 +1,119 @@ +// 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_call.h" +#include "flutter/shell/platform/linux/fl_method_call_private.h" +#include "flutter/shell/platform/linux/fl_method_channel_private.h" + +#include + +struct _FlMethodCall { + GObject parent_instance; + + // Name of method being called + gchar* name; + + // Arguments provided to method call + FlValue* args; + + // Channel to respond on + FlMethodChannel* channel; + FlBinaryMessengerResponseHandle* response_handle; +}; + +G_DEFINE_TYPE(FlMethodCall, fl_method_call, G_TYPE_OBJECT) + +static void fl_method_call_dispose(GObject* object) { + FlMethodCall* self = FL_METHOD_CALL(object); + + g_clear_pointer(&self->name, g_free); + g_clear_pointer(&self->args, fl_value_unref); + g_clear_object(&self->channel); + g_clear_object(&self->response_handle); + + G_OBJECT_CLASS(fl_method_call_parent_class)->dispose(object); +} + +static void fl_method_call_class_init(FlMethodCallClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_method_call_dispose; +} + +static void fl_method_call_init(FlMethodCall* self) {} + +FlMethodCall* fl_method_call_new( + const gchar* name, + FlValue* args, + FlMethodChannel* channel, + FlBinaryMessengerResponseHandle* response_handle) { + g_return_val_if_fail(name != nullptr, nullptr); + g_return_val_if_fail(args != nullptr, nullptr); + g_return_val_if_fail(FL_IS_METHOD_CHANNEL(channel), nullptr); + g_return_val_if_fail(FL_IS_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle), + nullptr); + + FlMethodCall* self = + FL_METHOD_CALL(g_object_new(fl_method_call_get_type(), nullptr)); + + self->name = g_strdup(name); + self->args = fl_value_ref(args); + self->channel = FL_METHOD_CHANNEL(g_object_ref(channel)); + self->response_handle = + FL_BINARY_MESSENGER_RESPONSE_HANDLE(g_object_ref(response_handle)); + + return self; +} + +G_MODULE_EXPORT const gchar* fl_method_call_get_name(FlMethodCall* self) { + g_return_val_if_fail(FL_IS_METHOD_CALL(self), nullptr); + return self->name; +} + +G_MODULE_EXPORT FlValue* fl_method_call_get_args(FlMethodCall* self) { + g_return_val_if_fail(FL_IS_METHOD_CALL(self), nullptr); + return self->args; +} + +gboolean fl_method_call_respond(FlMethodCall* self, + FlMethodResponse* response, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE); + g_return_val_if_fail(FL_IS_METHOD_RESPONSE(response), FALSE); + return fl_method_channel_respond(self->channel, self->response_handle, + response, error); +} + +G_MODULE_EXPORT gboolean fl_method_call_respond_success(FlMethodCall* self, + FlValue* result, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE); + + g_autoptr(FlMethodResponse) response = + FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + return fl_method_channel_respond(self->channel, self->response_handle, + response, error); +} + +G_MODULE_EXPORT gboolean fl_method_call_respond_error(FlMethodCall* self, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE); + g_return_val_if_fail(code != nullptr, FALSE); + + g_autoptr(FlMethodResponse) response = + FL_METHOD_RESPONSE(fl_method_error_response_new(code, message, details)); + return fl_method_channel_respond(self->channel, self->response_handle, + response, error); +} + +G_MODULE_EXPORT gboolean +fl_method_call_respond_not_implemented(FlMethodCall* self, GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CALL(self), FALSE); + + g_autoptr(FlMethodResponse) response = + FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + return fl_method_channel_respond(self->channel, self->response_handle, + response, error); +} diff --git a/shell/platform/linux/fl_method_call_private.h b/shell/platform/linux/fl_method_call_private.h new file mode 100644 index 0000000000000..3388fa8c06214 --- /dev/null +++ b/shell/platform/linux/fl_method_call_private.h @@ -0,0 +1,35 @@ +// 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_CALL_PRIVATE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CALL_PRIVATE_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_call.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h" + +G_BEGIN_DECLS + +/** + * fl_method_call_new: + * @name: a method name. + * @args: arguments provided to a method. + * @channel: channel call received on. + * @response_handle: handle to respond with. + * + * Creates a method call. + * + * Returns: a new #FlMethodCall. + */ +FlMethodCall* fl_method_call_new( + const gchar* name, + FlValue* args, + FlMethodChannel* channel, + FlBinaryMessengerResponseHandle* response_handle); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CALL_PRIVATE_H_ diff --git a/shell/platform/linux/fl_method_channel.cc b/shell/platform/linux/fl_method_channel.cc new file mode 100644 index 0000000000000..c8d61fa1ef477 --- /dev/null +++ b/shell/platform/linux/fl_method_channel.cc @@ -0,0 +1,197 @@ +// 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_call_private.h" +#include "flutter/shell/platform/linux/fl_method_channel_private.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) + +// 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) + 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); + return; + } + + g_autoptr(FlMethodCall) method_call = + fl_method_call_new(method, args, self, response_handle); + self->method_call_handler(self, method_call, 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); +} + +gboolean fl_method_channel_respond( + FlMethodChannel* self, + FlBinaryMessengerResponseHandle* response_handle, + FlMethodResponse* response, + GError** error) { + g_return_val_if_fail(FL_IS_METHOD_CHANNEL(self), FALSE); + g_return_val_if_fail(FL_IS_BINARY_MESSENGER_RESPONSE_HANDLE(response_handle), + FALSE); + g_return_val_if_fail(FL_IS_METHOD_SUCCESS_RESPONSE(response) || + FL_IS_METHOD_ERROR_RESPONSE(response) || + FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response), + FALSE); + + g_autoptr(GBytes) message = nullptr; + if (FL_IS_METHOD_SUCCESS_RESPONSE(response)) { + FlMethodSuccessResponse* r = FL_METHOD_SUCCESS_RESPONSE(response); + message = fl_method_codec_encode_success_envelope( + self->codec, fl_method_success_response_get_result(r), error); + } else if (FL_IS_METHOD_ERROR_RESPONSE(response)) { + FlMethodErrorResponse* r = FL_METHOD_ERROR_RESPONSE(response); + message = fl_method_codec_encode_error_envelope( + self->codec, fl_method_error_response_get_code(r), + fl_method_error_response_get_message(r), + fl_method_error_response_get_details(r), error); + } else if (FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response)) + message = nullptr; + else + g_assert_not_reached(); + + return fl_binary_messenger_send_response(self->messenger, response_handle, + message, error); +} diff --git a/shell/platform/linux/fl_method_channel_private.h b/shell/platform/linux/fl_method_channel_private.h new file mode 100644 index 0000000000000..f31b91f96cda3 --- /dev/null +++ b/shell/platform/linux/fl_method_channel_private.h @@ -0,0 +1,35 @@ +// 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_PRIVATE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CHANNEL_PRIVATE_H_ + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" + +#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_response.h" + +G_BEGIN_DECLS + +/** + * fl_method_channel_respond: + * @channel: an #FlMethodChannel. + * @response_handle: an #FlBinaryMessengerResponseHandle. + * @response: an #FlMethodResponse. + * @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, + FlBinaryMessengerResponseHandle* response_handle, + FlMethodResponse* response, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CHANNEL_PRIVATE_H_ 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..8912093adc29c --- /dev/null +++ b/shell/platform/linux/fl_method_codec_private.h @@ -0,0 +1,106 @@ +// 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: an #FlMethodCodec. + * @name: method name. + * @args: (allow-none): method arguments, or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL. + * + * Encodes a method call. + * + * Returns: (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: an #FlMethodCodec. + * @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. + * + * Decodes a method call. + * + * Returns: %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: an #FlMethodCodec. + * @result: (allow-none): method result, or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL. + * + * Encodes a successful response to a method call. + * + * Returns: (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: an #FlMethodCodec. + * @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. + * + * Encodes an error response to a method call. + * + * Returns: (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: an #FlMethodCodec. + * @message: message to decode. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL. + * + * Decodes a response to a method call. If the call resulted in an error then + * @error_code is set, otherwise it is %NULL. + * + * Returns: 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..9082b9d50ae7c --- /dev/null +++ b/shell/platform/linux/fl_method_response.cc @@ -0,0 +1,178 @@ +// 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)); + FlValue* details = + fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(self)); + g_autofree gchar* details_text = nullptr; + if (details != nullptr) { + // TODO(robert-ancell): Update this when we have fl_value_to_string() + } + + g_autoptr(GString) error_message = g_string_new(""); + g_string_append_printf(error_message, "Remote code returned error %s", + code); + if (message != nullptr) + g_string_append_printf(error_message, ": %s", message); + if (details_text != nullptr) + g_string_append_printf(error_message, " %s", details_text); + g_set_error_literal(error, FL_METHOD_RESPONSE_ERROR, + FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR, + error_message->str); + 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)); + + if (result != 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/public/flutter_linux/fl_method_call.h b/shell/platform/linux/public/flutter_linux/fl_method_call.h new file mode 100644 index 0000000000000..5a5ebc7419af2 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_call.h @@ -0,0 +1,115 @@ +// 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_CALL_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CALL_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_FINAL_TYPE(FlMethodCall, fl_method_call, FL, METHOD_CALL, GObject) + +/** + * FlMethodCall: + * + * #FlMethodCall represents and incoming method call as returned by an + * #FlMethodChannel. + */ + +/** + * fl_method_call_get_name: + * @method_call: an #FlMethodCall. + * + * Gets the name of the method call. + * + * Returns: a method name. + */ +const gchar* fl_method_call_get_name(FlMethodCall* method_call); + +/** + * fl_method_call_get_args: + * @method_call: an #FlMethodCall. + * + * Gets the arguments passed to the method. + * + * Returns: an #FlValue. + */ +FlValue* fl_method_call_get_args(FlMethodCall* method_call); + +/** + * fl_method_call_respond: + * @method_call: an #FlMethodCall. + * @response: an #FlMethodResponse. + * @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_call_respond(FlMethodCall* method_call, + FlMethodResponse* response, + GError** error); + +/** + * fl_method_call_respond_success: + * @method_call: an #FlMethodCall. + * @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. + * + * Convenience method that responds to method call with + * #FlMethodSuccessResponse. + * + * Returns: %TRUE on success. + */ +gboolean fl_method_call_respond_success(FlMethodCall* method_call, + FlValue* result, + GError** error); + +/** + * fl_method_call_respond_error: + * @method_call: an #FlMethodCall. + * @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. + * + * Convenience method that responds to method call with #FlMethodErrorResponse. + * + * Returns: %TRUE on success. + */ +gboolean fl_method_call_respond_error(FlMethodCall* method_call, + const gchar* code, + const gchar* message, + FlValue* details, + GError** error); + +/** + * fl_method_call_respond_not_implemented: + * @method_call: an #FlMethodCall. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Convenience method that responds to method call with + * #FlMethodNotImplementedResponse. + * + * Returns: %TRUE on success. + */ +gboolean fl_method_call_respond_not_implemented(FlMethodCall* method_call, + GError** error); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CALL_H_ diff --git a/shell/platform/linux/public/flutter_linux/fl_method_channel.h b/shell/platform/linux/public/flutter_linux/fl_method_channel.h new file mode 100644 index 0000000000000..ea9265a1312fa --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_channel.h @@ -0,0 +1,181 @@ +// 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_call.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. + * + * The following example shows how to call and handle methods on a channel. + * See #FlMethodResponse for how to handle errors in more detail. + * + * |[ + * static FlMethodChannel *channel = NULL; + * + * static void method_call_cb (FlMethodChannel* channel, + * FlMethodCall* method_call, + * gpointer user_data) { + * g_autoptr(FlMethodResponse) response = NULL; + * if (strcmp (fl_method_call_get_name (method_call), "Foo.bar") == 0) { + * g_autoptr(GError) bar_error = NULL; + * g_autoptr(FlValue) result = + * do_bar (fl_method_call_get_args (method_call), &bar_error); + * if (result == NULL) + * response = + * FL_METHOD_RESPONSE (fl_method_error_response_new ("bar error", + * bar_error->message)); + * else + * response = FL_METHOD_RESPONSE (fl_method_success_response_new + * (result)); } else response = FL_METHOD_RESPONSE + * (fl_method_not_implemented_response_new ()); + * + * g_autoptr(GError) error = NULL; + * if (!fl_method_call_respond(method_call, response)) + * g_warning ("Failed to send response: %s", error->message); + * } + * + * static void method_response_cb(GObject *object, + * GAsyncResult *result, + * gpointer user_data) { + * g_autoptr(GError) error = NULL; + * g_autoptr(FlMethodResponse) response = + * fl_method_channel_invoke_method_finish (FL_METHOD_CODEC (object), result, + * &error); + * if (response == NULL) { + * g_warning ("Failed to call method: %s", error->message); + * return; + * } + * + * g_autoptr(FlValue) value = + * fl_method_response_get_result (response, &error); + * if (response == NULL) { + * g_warning ("Method returned error: %s", error->message); + * return; + * } + * + * use_result (value); + * } + * + * static void call_method () { + * g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new (); + * channel = + * fl_method_channel_new(messenger, "flutter/foo", FL_METHOD_CODEC (codec)); + * fl_method_channel_set_method_call_handler (channel, method_call_cb, NULL); + * + * g_autoptr(FlValue) args = fl_value_new_string ("Hello World"); + * fl_method_channel_invoke_method (channel, "Foo.foo", args, + * cancellable, method_response_cb, NULL); + * } + * ]| + * + * #FlMethodChannel matches the MethodChannel class in the Flutter services + * library. + */ + +/** + * FlMethodChannelMethodCallHandler: + * @channel: an #FlMethodChannel. + * @method_call: an #FlMethodCall. + * @user_data: (closure): data provided when registering this handler. + * + * Function called when a method call is received. Respond to the method call + * with fl_method_call_respond(). If the response is not occurring in this + * callback take a reference to @method_call and release that once it has been + * responded to. + */ +typedef void (*FlMethodChannelMethodCallHandler)(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data); + +/** + * fl_method_channel_new: + * @messenger: an #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: an #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_call_respond() when the method completes. + */ +void fl_method_channel_set_method_call_handler( + FlMethodChannel* channel, + FlMethodChannelMethodCallHandler handler, + gpointer user_data); + +/** + * fl_method_channel_invoke_method: + * @channel: an #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: an #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); + +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..97bf8e929b6e1 --- /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 encodes and decodes 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: an #FlMethodCodec. + * @name: method name. + * @args: (allow-none): method arguments, or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL. + * + * Encodes a method call. + * + * Returns: (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: an #FlMethodCodec + * @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 + * + * Decodes a method call. + * + * Returns: %TRUE if successfully decoded. + */ + gboolean (*decode_method_call)(FlMethodCodec* codec, + GBytes* message, + gchar** name, + FlValue** args, + GError** error); + + /** + * FlMethodCodec::encode_success_envelope: + * @codec: an #FlMethodCodec. + * @result: (allow-none): method result, or %NULL. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL. + * + * Encodes a successful response to a method call. + * + * Returns: (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: an #FlMethodCodec. + * @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. + * + * Encodes an error response to a method call. + * + * Returns: (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: an #FlMethodCodec. + * @message: message to decode. + * @error: (allow-none): #GError location to store the error occurring, or + * %NULL. + * + * Decodes a response to a method call. + * + * Returns: 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..949cddc33c439 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_method_response.h @@ -0,0 +1,212 @@ +// 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 set by `fl_method_response_get_result` when the method call response + * is not #FlMethodSuccessResponse. + */ +#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 an #FlMethodChannel + * method call returns. If you expect the method call to be successful use + * fl_method_response_get_result(). If you want to handle error cases then you + * should use code like: + * + * |[ + * if (FL_IS_METHOD_SUCCESS_RESPONSE (response)) { + * FlValue *result = + * fl_method_success_response_get_result( + * FL_METHOD_SUCCESS_RESPONSE (response)); + * handle_result (result); + * } else if (FL_IS_METHOD_ERROR_RESPONSE (response)) { + * FlMethodErrorResponse *error_response = + * FL_METHOD_ERROR_RESPONSE (response); + * handle_error (fl_method_error_response_get_code (error_response), + * fl_method_error_response_get_message (error_response), + * fl_method_error_response_get_details (error_response)); + * } + * else if (FL_IS_METHOD_ERROR_RESPONSE (response)) { + * handle_not_implemented (); + * } + * } + * ]| + */ + +/** + * FlMethodSuccessResponse: + * + * #FlMethodSuccessResponse is the #FlMethodResponse returned when a method call + * has successfully completed. The result of the method call is obtained using + * `fl_method_success_response_get_result`. + */ + +/** + * FlMethodErrorResponse: + * + * #FlMethodErrorResponse is the #FlMethodResponse returned when a method call + * results in an error. The error details are obtained using + * `fl_method_error_response_get_code`, `fl_method_error_response_get_message` + * and `fl_method_error_response_get_details`. + */ + +/** + * FlMethodNotImplementedResponse: + * + * #FlMethodNotImplementedResponse is the #FlMethodResponse returned when a + * method call is not implemented. + */ + +/** + * fl_method_response_get_result: + * @response: an #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: an #FlValue or %NULL on error. + */ +FlValue* fl_method_response_get_result(FlMethodResponse* response, + GError** error); + +/** + * fl_method_success_response_new: + * @result: (allow-none): the #FlValue returned by the method call or %NULL. + * + * 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: an #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/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index b26e9abab40f6..a0fc55b47826d 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -14,6 +14,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include From fe11c77f39f7346bc14a6bb765a8c5a04a5df527 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Thu, 14 May 2020 14:04:32 +1200 Subject: [PATCH 2/3] Add FlStandardMethodCodec --- ci/licenses_golden/licenses_flutter | 3 + shell/platform/linux/BUILD.gn | 3 + .../linux/fl_standard_method_codec.cc | 238 +++++++++++ .../linux/fl_standard_method_codec_test.cc | 375 ++++++++++++++++++ .../flutter_linux/fl_standard_method_codec.h | 44 ++ .../public/flutter_linux/flutter_linux.h | 1 + 6 files changed, 664 insertions(+) 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_standard_method_codec.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5d3e6ff23ae19..b43bf7181a1c9 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1216,6 +1216,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 @@ -1233,6 +1235,7 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_chann 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 ac12f71c6536e..22310a04708d3 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -56,6 +56,7 @@ _public_headers = [ "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", @@ -84,6 +85,7 @@ source_set("flutter_linux") { "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", @@ -119,6 +121,7 @@ executable("flutter_linux_unittests") { "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_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_standard_method_codec.h b/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h new file mode 100644 index 0000000000000..1685ebced9830 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h @@ -0,0 +1,44 @@ +// 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. + */ + +/** + * fl_standard_method_codec_new: + * + * Creates an #FlStandardMethodCodec. + * + * Returns: a new #FlStandardMethodCodec. + */ +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 a0fc55b47826d..645886c68873a 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include From b26875208363eb3ec4c4e7fc96e5a6646f4786a7 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 | 205 +++++++++ .../linux/fl_json_method_codec_test.cc | 398 ++++++++++++++++++ .../flutter_linux/fl_json_method_codec.h | 44 ++ .../public/flutter_linux/fl_method_channel.h | 5 +- .../public/flutter_linux/flutter_linux.h | 1 + 7 files changed, 657 insertions(+), 2 deletions(-) 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 b43bf7181a1c9..39c247024e067 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1198,6 +1198,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_call.cc @@ -1229,6 +1231,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_call.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 22310a04708d3..d32c572f75536 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -50,6 +50,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_call.h", "public/flutter_linux/fl_method_channel.h", @@ -77,6 +78,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_call.cc", "fl_method_channel.cc", @@ -117,6 +119,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..0a0cd7374b3da --- /dev/null +++ b/shell/platform/linux/fl_json_method_codec.cc @@ -0,0 +1,205 @@ +// 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 + +static constexpr char kMethodKey[] = "method"; +static constexpr char kArgsKey[] = "args"; + +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(kMethodKey), + fl_value_new_string(name)); + fl_value_set_take(message, fl_value_new_string(kArgsKey), + 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, kMethodKey); + 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, kArgsKey); + + *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..c994500ca4275 --- /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" + +// Converts 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); +} + +// Converts a string to a binary blob. +static GBytes* text_to_message(const gchar* text) { + return g_bytes_new(text, strlen(text)); +} + +// Encodes 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); +} + +// Encodes 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); +} + +// Encodes 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); +} + +// Decodes 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); +} + +// Decodes 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)); +} + +// Decodes 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)); +} + +// Decodes 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..70ddf793b1039 --- /dev/null +++ b/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h @@ -0,0 +1,44 @@ +// 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 an + * #FlMethodChannel. + * + * #FlJsonMethodCodec matches the JSONMethodCodec class in the Flutter services + * library. + */ + +/** + * fl_json_method_codec_new: + * + * Creates an #FlJsonMethodCodec. + * + * Returns: a new #FlJsonMethodCodec. + */ +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/fl_method_channel.h b/shell/platform/linux/public/flutter_linux/fl_method_channel.h index ea9265a1312fa..0ef04ff153d65 100644 --- a/shell/platform/linux/public/flutter_linux/fl_method_channel.h +++ b/shell/platform/linux/public/flutter_linux/fl_method_channel.h @@ -28,7 +28,7 @@ G_DECLARE_FINAL_TYPE(FlMethodChannel, /** * FlMethodChannel: * - * #FlMethodChannel is an object that allows calling methods in Dart code. + * #FlMethodChannel is an object that allows method calls to and from Dart code. * * The following example shows how to call and handle methods on a channel. * See #FlMethodResponse for how to handle errors in more detail. @@ -105,7 +105,8 @@ G_DECLARE_FINAL_TYPE(FlMethodChannel, * Function called when a method call is received. Respond to the method call * with fl_method_call_respond(). If the response is not occurring in this * callback take a reference to @method_call and release that once it has been - * responded to. + * responded to.Failing to respond before the last reference to @method_call is + * dropped is a programming error. */ typedef void (*FlMethodChannelMethodCallHandler)(FlMethodChannel* channel, FlMethodCall* method_call, diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h index 645886c68873a..03f9dfed85e32 100644 --- a/shell/platform/linux/public/flutter_linux/flutter_linux.h +++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include