| /* GDBus - GLib D-Bus Library |
| * |
| * Copyright (C) 2008-2010 Red Hat, Inc. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General |
| * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| * |
| * Author: David Zeuthen <davidz@redhat.com> |
| */ |
| |
| /* Uncomment to debug serializer code */ |
| /* #define DEBUG_SERIALIZER */ |
| |
| #include "config.h" |
| |
| #include <string.h> |
| #include <errno.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #if MAJOR_IN_MKDEV |
| #include <sys/mkdev.h> |
| #elif MAJOR_IN_SYSMACROS |
| #include <sys/sysmacros.h> |
| #elif MAJOR_IN_TYPES |
| #include <sys/types.h> |
| #else |
| #define MAJOR_MINOR_NOT_FOUND 1 |
| #endif |
| |
| #include "gdbusutils.h" |
| #include "gdbusmessage.h" |
| #include "gdbuserror.h" |
| #include "gioenumtypes.h" |
| #include "ginputstream.h" |
| #include "gdatainputstream.h" |
| #include "gmemoryinputstream.h" |
| #include "goutputstream.h" |
| #include "gdataoutputstream.h" |
| #include "gmemoryoutputstream.h" |
| #include "gseekable.h" |
| #include "gioerror.h" |
| #include "gdbusprivate.h" |
| #include "gutilsprivate.h" |
| |
| #ifdef G_OS_UNIX |
| #include "gunixfdlist.h" |
| #endif |
| |
| #include "glibintl.h" |
| |
| /* See https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature |
| * This is 64 containers plus 1 value within them. */ |
| #define G_DBUS_MAX_TYPE_DEPTH (64 + 1) |
| |
| typedef struct _GMemoryBuffer GMemoryBuffer; |
| struct _GMemoryBuffer |
| { |
| gsize len; |
| gsize valid_len; |
| gsize pos; |
| gchar *data; |
| GDataStreamByteOrder byte_order; |
| }; |
| |
| static gboolean |
| g_memory_buffer_is_byteswapped (GMemoryBuffer *mbuf) |
| { |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| return mbuf->byte_order == G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; |
| #else |
| return mbuf->byte_order == G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; |
| #endif |
| } |
| |
| static guchar |
| g_memory_buffer_read_byte (GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); |
| |
| if (mbuf->pos >= mbuf->valid_len) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unexpected end of message while reading byte."); |
| return 0; |
| } |
| return mbuf->data [mbuf->pos++]; |
| } |
| |
| static gint16 |
| g_memory_buffer_read_int16 (GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| gint16 v; |
| |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); |
| |
| if (mbuf->pos > mbuf->valid_len - 2) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unexpected end of message while reading int16."); |
| return 0; |
| } |
| |
| memcpy (&v, mbuf->data + mbuf->pos, 2); |
| mbuf->pos += 2; |
| |
| if (g_memory_buffer_is_byteswapped (mbuf)) |
| v = GUINT16_SWAP_LE_BE (v); |
| |
| return v; |
| } |
| |
| static guint16 |
| g_memory_buffer_read_uint16 (GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| guint16 v; |
| |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); |
| |
| if (mbuf->pos > mbuf->valid_len - 2) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unexpected end of message while reading uint16."); |
| return 0; |
| } |
| |
| memcpy (&v, mbuf->data + mbuf->pos, 2); |
| mbuf->pos += 2; |
| |
| if (g_memory_buffer_is_byteswapped (mbuf)) |
| v = GUINT16_SWAP_LE_BE (v); |
| |
| return v; |
| } |
| |
| static gint32 |
| g_memory_buffer_read_int32 (GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| gint32 v; |
| |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); |
| |
| if (mbuf->pos > mbuf->valid_len - 4) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unexpected end of message while reading int32."); |
| return 0; |
| } |
| |
| memcpy (&v, mbuf->data + mbuf->pos, 4); |
| mbuf->pos += 4; |
| |
| if (g_memory_buffer_is_byteswapped (mbuf)) |
| v = GUINT32_SWAP_LE_BE (v); |
| |
| return v; |
| } |
| |
| static guint32 |
| g_memory_buffer_read_uint32 (GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| guint32 v; |
| |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); |
| |
| if (mbuf->pos > mbuf->valid_len - 4) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unexpected end of message while reading uint32."); |
| return 0; |
| } |
| |
| memcpy (&v, mbuf->data + mbuf->pos, 4); |
| mbuf->pos += 4; |
| |
| if (g_memory_buffer_is_byteswapped (mbuf)) |
| v = GUINT32_SWAP_LE_BE (v); |
| |
| return v; |
| } |
| |
| static gint64 |
| g_memory_buffer_read_int64 (GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| gint64 v; |
| |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); |
| |
| if (mbuf->pos > mbuf->valid_len - 8) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unexpected end of message while reading int64."); |
| return 0; |
| } |
| |
| memcpy (&v, mbuf->data + mbuf->pos, 8); |
| mbuf->pos += 8; |
| |
| if (g_memory_buffer_is_byteswapped (mbuf)) |
| v = GUINT64_SWAP_LE_BE (v); |
| |
| return v; |
| } |
| |
| static guint64 |
| g_memory_buffer_read_uint64 (GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| guint64 v; |
| |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); |
| |
| if (mbuf->pos > mbuf->valid_len - 8) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unexpected end of message while reading uint64."); |
| return 0; |
| } |
| |
| memcpy (&v, mbuf->data + mbuf->pos, 8); |
| mbuf->pos += 8; |
| |
| if (g_memory_buffer_is_byteswapped (mbuf)) |
| v = GUINT64_SWAP_LE_BE (v); |
| |
| return v; |
| } |
| |
| #define MIN_ARRAY_SIZE 128 |
| |
| static void |
| array_resize (GMemoryBuffer *mbuf, |
| gsize size) |
| { |
| gpointer data; |
| gsize len; |
| |
| if (mbuf->len == size) |
| return; |
| |
| len = mbuf->len; |
| data = g_realloc (mbuf->data, size); |
| |
| if (size > len) |
| memset ((guint8 *)data + len, 0, size - len); |
| |
| mbuf->data = data; |
| mbuf->len = size; |
| |
| if (mbuf->len < mbuf->valid_len) |
| mbuf->valid_len = mbuf->len; |
| } |
| |
| static gboolean |
| g_memory_buffer_write (GMemoryBuffer *mbuf, |
| const void *buffer, |
| gsize count) |
| { |
| guint8 *dest; |
| gsize new_size; |
| |
| if (count == 0) |
| return TRUE; |
| |
| /* Check for address space overflow, but only if the buffer is resizable. |
| Otherwise we just do a short write and don't worry. */ |
| if (mbuf->pos + count < mbuf->pos) |
| return FALSE; |
| |
| if (mbuf->pos + count > mbuf->len) |
| { |
| /* At least enough to fit the write, rounded up |
| for greater than linear growth. |
| TODO: This wastes a lot of memory at large buffer sizes. |
| Figure out a more rational allocation strategy. */ |
| new_size = g_nearest_pow (mbuf->pos + count); |
| /* Check for overflow again. We have checked if |
| pos + count > G_MAXSIZE, but now check if g_nearest_pow () has |
| overflowed */ |
| if (new_size == 0) |
| return FALSE; |
| |
| new_size = MAX (new_size, MIN_ARRAY_SIZE); |
| array_resize (mbuf, new_size); |
| } |
| |
| dest = (guint8 *)mbuf->data + mbuf->pos; |
| memcpy (dest, buffer, count); |
| mbuf->pos += count; |
| |
| if (mbuf->pos > mbuf->valid_len) |
| mbuf->valid_len = mbuf->pos; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| g_memory_buffer_put_byte (GMemoryBuffer *mbuf, |
| guchar data) |
| { |
| return g_memory_buffer_write (mbuf, &data, 1); |
| } |
| |
| static gboolean |
| g_memory_buffer_put_int16 (GMemoryBuffer *mbuf, |
| gint16 data) |
| { |
| switch (mbuf->byte_order) |
| { |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: |
| data = GINT16_TO_BE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: |
| data = GINT16_TO_LE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: |
| default: |
| break; |
| } |
| |
| return g_memory_buffer_write (mbuf, &data, 2); |
| } |
| |
| static gboolean |
| g_memory_buffer_put_uint16 (GMemoryBuffer *mbuf, |
| guint16 data) |
| { |
| switch (mbuf->byte_order) |
| { |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: |
| data = GUINT16_TO_BE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: |
| data = GUINT16_TO_LE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: |
| default: |
| break; |
| } |
| |
| return g_memory_buffer_write (mbuf, &data, 2); |
| } |
| |
| static gboolean |
| g_memory_buffer_put_int32 (GMemoryBuffer *mbuf, |
| gint32 data) |
| { |
| switch (mbuf->byte_order) |
| { |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: |
| data = GINT32_TO_BE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: |
| data = GINT32_TO_LE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: |
| default: |
| break; |
| } |
| |
| return g_memory_buffer_write (mbuf, &data, 4); |
| } |
| |
| static gboolean |
| g_memory_buffer_put_uint32 (GMemoryBuffer *mbuf, |
| guint32 data) |
| { |
| switch (mbuf->byte_order) |
| { |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: |
| data = GUINT32_TO_BE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: |
| data = GUINT32_TO_LE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: |
| default: |
| break; |
| } |
| |
| return g_memory_buffer_write (mbuf, &data, 4); |
| } |
| |
| static gboolean |
| g_memory_buffer_put_int64 (GMemoryBuffer *mbuf, |
| gint64 data) |
| { |
| switch (mbuf->byte_order) |
| { |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: |
| data = GINT64_TO_BE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: |
| data = GINT64_TO_LE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: |
| default: |
| break; |
| } |
| |
| return g_memory_buffer_write (mbuf, &data, 8); |
| } |
| |
| static gboolean |
| g_memory_buffer_put_uint64 (GMemoryBuffer *mbuf, |
| guint64 data) |
| { |
| switch (mbuf->byte_order) |
| { |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: |
| data = GUINT64_TO_BE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: |
| data = GUINT64_TO_LE (data); |
| break; |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: |
| default: |
| break; |
| } |
| |
| return g_memory_buffer_write (mbuf, &data, 8); |
| } |
| |
| static gboolean |
| g_memory_buffer_put_string (GMemoryBuffer *mbuf, |
| const char *str) |
| { |
| g_return_val_if_fail (str != NULL, FALSE); |
| |
| return g_memory_buffer_write (mbuf, str, strlen (str)); |
| } |
| |
| typedef struct _GDBusMessageClass GDBusMessageClass; |
| |
| /** |
| * GDBusMessageClass: |
| * |
| * Class structure for #GDBusMessage. |
| * |
| * Since: 2.26 |
| */ |
| struct _GDBusMessageClass |
| { |
| /*< private >*/ |
| GObjectClass parent_class; |
| }; |
| |
| /** |
| * GDBusMessage: |
| * |
| * A type for representing D-Bus messages that can be sent or received |
| * on a [class@Gio.DBusConnection]. |
| * |
| * Since: 2.26 |
| */ |
| struct _GDBusMessage |
| { |
| /*< private >*/ |
| GObject parent_instance; |
| |
| GDBusMessageType type; |
| GDBusMessageFlags flags; |
| gboolean locked; |
| GDBusMessageByteOrder byte_order; |
| guchar major_protocol_version; |
| guint32 serial; |
| GHashTable *headers; |
| GVariant *body; |
| GVariant *arg0_cache; /* (nullable) (owned) */ |
| #ifdef G_OS_UNIX |
| GUnixFDList *fd_list; |
| #endif |
| }; |
| |
| enum |
| { |
| PROP_0, |
| PROP_LOCKED |
| }; |
| |
| G_DEFINE_TYPE (GDBusMessage, g_dbus_message, G_TYPE_OBJECT) |
| |
| static void |
| g_dbus_message_finalize (GObject *object) |
| { |
| GDBusMessage *message = G_DBUS_MESSAGE (object); |
| |
| if (message->headers != NULL) |
| g_hash_table_unref (message->headers); |
| if (message->body != NULL) |
| g_variant_unref (message->body); |
| g_clear_pointer (&message->arg0_cache, g_variant_unref); |
| #ifdef G_OS_UNIX |
| if (message->fd_list != NULL) |
| g_object_unref (message->fd_list); |
| #endif |
| |
| if (G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize != NULL) |
| G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize (object); |
| } |
| |
| static void |
| g_dbus_message_get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec) |
| { |
| GDBusMessage *message = G_DBUS_MESSAGE (object); |
| |
| switch (prop_id) |
| { |
| case PROP_LOCKED: |
| g_value_set_boolean (value, g_dbus_message_get_locked (message)); |
| break; |
| |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| g_dbus_message_class_init (GDBusMessageClass *klass) |
| { |
| GObjectClass *gobject_class; |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| gobject_class->finalize = g_dbus_message_finalize; |
| gobject_class->get_property = g_dbus_message_get_property; |
| |
| /** |
| * GDBusConnection:locked: |
| * |
| * A boolean specifying whether the message is locked. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_LOCKED, |
| g_param_spec_boolean ("locked", NULL, NULL, |
| FALSE, |
| G_PARAM_READABLE | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| } |
| |
| static void |
| g_dbus_message_init (GDBusMessage *message) |
| { |
| /* Any D-Bus implementation is supposed to handle both Big and |
| * Little Endian encodings and the Endianness is part of the D-Bus |
| * message - we prefer to use Big Endian (since it's Network Byte |
| * Order and just easier to read for humans) but if the machine is |
| * Little Endian we use that for performance reasons. |
| */ |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_LITTLE_ENDIAN; |
| #else |
| /* this could also be G_PDP_ENDIAN */ |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN; |
| #endif |
| message->headers = g_hash_table_new_full (g_direct_hash, |
| g_direct_equal, |
| NULL, |
| (GDestroyNotify) g_variant_unref); |
| } |
| |
| /** |
| * g_dbus_message_new: |
| * |
| * Creates a new empty #GDBusMessage. |
| * |
| * Returns: A #GDBusMessage. Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_message_new (void) |
| { |
| return g_object_new (G_TYPE_DBUS_MESSAGE, NULL); |
| } |
| |
| /** |
| * g_dbus_message_new_method_call: |
| * @name: (nullable): A valid D-Bus name or %NULL. |
| * @path: A valid object path. |
| * @interface_: (nullable): A valid D-Bus interface name or %NULL. |
| * @method: A valid method name. |
| * |
| * Creates a new #GDBusMessage for a method call. |
| * |
| * Returns: A #GDBusMessage. Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_message_new_method_call (const gchar *name, |
| const gchar *path, |
| const gchar *interface_, |
| const gchar *method) |
| { |
| GDBusMessage *message; |
| |
| g_return_val_if_fail (name == NULL || g_dbus_is_name (name), NULL); |
| g_return_val_if_fail (g_variant_is_object_path (path), NULL); |
| g_return_val_if_fail (g_dbus_is_member_name (method), NULL); |
| g_return_val_if_fail (interface_ == NULL || g_dbus_is_interface_name (interface_), NULL); |
| |
| message = g_dbus_message_new (); |
| message->type = G_DBUS_MESSAGE_TYPE_METHOD_CALL; |
| |
| if (name != NULL) |
| g_dbus_message_set_destination (message, name); |
| g_dbus_message_set_path (message, path); |
| g_dbus_message_set_member (message, method); |
| if (interface_ != NULL) |
| g_dbus_message_set_interface (message, interface_); |
| |
| return message; |
| } |
| |
| /** |
| * g_dbus_message_new_signal: |
| * @path: A valid object path. |
| * @interface_: A valid D-Bus interface name. |
| * @signal: A valid signal name. |
| * |
| * Creates a new #GDBusMessage for a signal emission. |
| * |
| * Returns: A #GDBusMessage. Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_message_new_signal (const gchar *path, |
| const gchar *interface_, |
| const gchar *signal) |
| { |
| GDBusMessage *message; |
| |
| g_return_val_if_fail (g_variant_is_object_path (path), NULL); |
| g_return_val_if_fail (g_dbus_is_member_name (signal), NULL); |
| g_return_val_if_fail (g_dbus_is_interface_name (interface_), NULL); |
| |
| message = g_dbus_message_new (); |
| message->type = G_DBUS_MESSAGE_TYPE_SIGNAL; |
| message->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; |
| |
| g_dbus_message_set_path (message, path); |
| g_dbus_message_set_member (message, signal); |
| g_dbus_message_set_interface (message, interface_); |
| |
| return message; |
| } |
| |
| |
| /** |
| * g_dbus_message_new_method_reply: |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to |
| * create a reply message to. |
| * |
| * Creates a new #GDBusMessage that is a reply to @method_call_message. |
| * |
| * Returns: (transfer full): #GDBusMessage. Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_message_new_method_reply (GDBusMessage *method_call_message) |
| { |
| GDBusMessage *message; |
| const gchar *sender; |
| |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL); |
| g_return_val_if_fail (g_dbus_message_get_message_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL); |
| g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL); |
| |
| message = g_dbus_message_new (); |
| message->type = G_DBUS_MESSAGE_TYPE_METHOD_RETURN; |
| message->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; |
| /* reply with same endianness */ |
| message->byte_order = method_call_message->byte_order; |
| |
| g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message)); |
| sender = g_dbus_message_get_sender (method_call_message); |
| if (sender != NULL) |
| g_dbus_message_set_destination (message, sender); |
| |
| return message; |
| } |
| |
| /** |
| * g_dbus_message_new_method_error: |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to |
| * create a reply message to. |
| * @error_name: A valid D-Bus error name. |
| * @error_message_format: The D-Bus error message in a printf() format. |
| * @...: Arguments for @error_message_format. |
| * |
| * Creates a new #GDBusMessage that is an error reply to @method_call_message. |
| * |
| * Returns: (transfer full): A #GDBusMessage. Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_message_new_method_error (GDBusMessage *method_call_message, |
| const gchar *error_name, |
| const gchar *error_message_format, |
| ...) |
| { |
| GDBusMessage *ret; |
| va_list var_args; |
| |
| va_start (var_args, error_message_format); |
| ret = g_dbus_message_new_method_error_valist (method_call_message, |
| error_name, |
| error_message_format, |
| var_args); |
| va_end (var_args); |
| |
| return ret; |
| } |
| |
| /** |
| * g_dbus_message_new_method_error_literal: |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to |
| * create a reply message to. |
| * @error_name: A valid D-Bus error name. |
| * @error_message: The D-Bus error message. |
| * |
| * Creates a new #GDBusMessage that is an error reply to @method_call_message. |
| * |
| * Returns: (transfer full): A #GDBusMessage. Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_message_new_method_error_literal (GDBusMessage *method_call_message, |
| const gchar *error_name, |
| const gchar *error_message) |
| { |
| GDBusMessage *message; |
| const gchar *sender; |
| |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL); |
| g_return_val_if_fail (g_dbus_message_get_message_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL); |
| g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL); |
| g_return_val_if_fail (g_dbus_is_name (error_name), NULL); |
| g_return_val_if_fail (error_message != NULL, NULL); |
| |
| message = g_dbus_message_new (); |
| message->type = G_DBUS_MESSAGE_TYPE_ERROR; |
| message->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; |
| /* reply with same endianness */ |
| message->byte_order = method_call_message->byte_order; |
| |
| g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message)); |
| g_dbus_message_set_error_name (message, error_name); |
| g_dbus_message_set_body (message, g_variant_new ("(s)", error_message)); |
| |
| sender = g_dbus_message_get_sender (method_call_message); |
| if (sender != NULL) |
| g_dbus_message_set_destination (message, sender); |
| |
| return message; |
| } |
| |
| /** |
| * g_dbus_message_new_method_error_valist: |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to |
| * create a reply message to. |
| * @error_name: A valid D-Bus error name. |
| * @error_message_format: The D-Bus error message in a printf() format. |
| * @var_args: Arguments for @error_message_format. |
| * |
| * Like g_dbus_message_new_method_error() but intended for language bindings. |
| * |
| * Returns: (transfer full): A #GDBusMessage. Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| G_GNUC_PRINTF(3, 0) |
| GDBusMessage * |
| g_dbus_message_new_method_error_valist (GDBusMessage *method_call_message, |
| const gchar *error_name, |
| const gchar *error_message_format, |
| va_list var_args) |
| { |
| GDBusMessage *ret; |
| gchar *error_message; |
| error_message = g_strdup_vprintf (error_message_format, var_args); |
| ret = g_dbus_message_new_method_error_literal (method_call_message, |
| error_name, |
| error_message); |
| g_free (error_message); |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_get_byte_order: |
| * @message: A #GDBusMessage. |
| * |
| * Gets the byte order of @message. |
| * |
| * Returns: The byte order. |
| */ |
| GDBusMessageByteOrder |
| g_dbus_message_get_byte_order (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), (GDBusMessageByteOrder) 0); |
| return message->byte_order; |
| } |
| |
| /** |
| * g_dbus_message_set_byte_order: |
| * @message: A #GDBusMessage. |
| * @byte_order: The byte order. |
| * |
| * Sets the byte order of @message. |
| */ |
| void |
| g_dbus_message_set_byte_order (GDBusMessage *message, |
| GDBusMessageByteOrder byte_order) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| |
| if (message->locked) |
| { |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); |
| return; |
| } |
| |
| message->byte_order = byte_order; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* TODO: need GI annotations to specify that any guchar value goes for the type */ |
| |
| /** |
| * g_dbus_message_get_message_type: |
| * @message: A #GDBusMessage. |
| * |
| * Gets the type of @message. |
| * |
| * Returns: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessageType |
| g_dbus_message_get_message_type (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_TYPE_INVALID); |
| return message->type; |
| } |
| |
| /** |
| * g_dbus_message_set_message_type: |
| * @message: A #GDBusMessage. |
| * @type: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration). |
| * |
| * Sets @message to be of @type. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_message_type (GDBusMessage *message, |
| GDBusMessageType type) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail ((guint) type < 256); |
| |
| if (message->locked) |
| { |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); |
| return; |
| } |
| |
| message->type = type; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* TODO: need GI annotations to specify that any guchar value goes for flags */ |
| |
| /** |
| * g_dbus_message_get_flags: |
| * @message: A #GDBusMessage. |
| * |
| * Gets the flags for @message. |
| * |
| * Returns: Flags that are set (typically values from the #GDBusMessageFlags enumeration bitwise ORed together). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessageFlags |
| g_dbus_message_get_flags (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_FLAGS_NONE); |
| return message->flags; |
| } |
| |
| /** |
| * g_dbus_message_set_flags: |
| * @message: A #GDBusMessage. |
| * @flags: Flags for @message that are set (typically values from the #GDBusMessageFlags |
| * enumeration bitwise ORed together). |
| * |
| * Sets the flags to set on @message. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_flags (GDBusMessage *message, |
| GDBusMessageFlags flags) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail ((guint) flags < 256); |
| |
| if (message->locked) |
| { |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); |
| return; |
| } |
| |
| message->flags = flags; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_get_serial: |
| * @message: A #GDBusMessage. |
| * |
| * Gets the serial for @message. |
| * |
| * Returns: A #guint32. |
| * |
| * Since: 2.26 |
| */ |
| guint32 |
| g_dbus_message_get_serial (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); |
| return message->serial; |
| } |
| |
| /** |
| * g_dbus_message_set_serial: |
| * @message: A #GDBusMessage. |
| * @serial: A #guint32. |
| * |
| * Sets the serial for @message. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_serial (GDBusMessage *message, |
| guint32 serial) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| |
| if (message->locked) |
| { |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); |
| return; |
| } |
| |
| message->serial = serial; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* TODO: need GI annotations to specify that any guchar value goes for header_field */ |
| |
| /** |
| * g_dbus_message_get_header: |
| * @message: A #GDBusMessage. |
| * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration) |
| * |
| * Gets a header field on @message. |
| * |
| * The caller is responsible for checking the type of the returned #GVariant |
| * matches what is expected. |
| * |
| * Returns: (transfer none) (nullable): A #GVariant with the value if the header was found, %NULL |
| * otherwise. Do not free, it is owned by @message. |
| * |
| * Since: 2.26 |
| */ |
| GVariant * |
| g_dbus_message_get_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| g_return_val_if_fail ((guint) header_field < 256, NULL); |
| return g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); |
| } |
| |
| /** |
| * g_dbus_message_set_header: |
| * @message: A #GDBusMessage. |
| * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration) |
| * @value: (nullable): A #GVariant to set the header field or %NULL to clear the header field. |
| * |
| * Sets a header field on @message. |
| * |
| * If @value is floating, @message assumes ownership of @value. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field, |
| GVariant *value) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail ((guint) header_field < 256); |
| |
| if (message->locked) |
| { |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); |
| return; |
| } |
| |
| if (value == NULL) |
| { |
| g_hash_table_remove (message->headers, GUINT_TO_POINTER (header_field)); |
| } |
| else |
| { |
| g_hash_table_insert (message->headers, GUINT_TO_POINTER (header_field), g_variant_ref_sink (value)); |
| } |
| } |
| |
| /** |
| * g_dbus_message_get_header_fields: |
| * @message: A #GDBusMessage. |
| * |
| * Gets an array of all header fields on @message that are set. |
| * |
| * Returns: (array zero-terminated=1): An array of header fields |
| * terminated by %G_DBUS_MESSAGE_HEADER_FIELD_INVALID. Each element |
| * is a #guchar. Free with g_free(). |
| * |
| * Since: 2.26 |
| */ |
| guchar * |
| g_dbus_message_get_header_fields (GDBusMessage *message) |
| { |
| GPtrArray *keys; |
| GArray *array; |
| |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| |
| keys = g_hash_table_get_keys_as_ptr_array (message->headers); |
| array = g_array_sized_new (FALSE, FALSE, sizeof (guchar), keys->len + 1); |
| |
| for (guint i = 0; i < keys->len; ++i) |
| { |
| guchar val = GPOINTER_TO_UINT (g_ptr_array_index (keys, i)); |
| g_array_append_val (array, val); |
| } |
| |
| g_assert (array->len == keys->len); |
| g_clear_pointer (&keys, g_ptr_array_unref); |
| |
| guchar invalid_field = G_DBUS_MESSAGE_HEADER_FIELD_INVALID; |
| g_array_append_val (array, invalid_field); |
| |
| return (guchar *) g_array_free (array, FALSE); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_get_body: |
| * @message: A #GDBusMessage. |
| * |
| * Gets the body of a message. |
| * |
| * Returns: (nullable) (transfer none): A #GVariant or %NULL if the body is |
| * empty. Do not free, it is owned by @message. |
| * |
| * Since: 2.26 |
| */ |
| GVariant * |
| g_dbus_message_get_body (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| return message->body; |
| } |
| |
| /** |
| * g_dbus_message_set_body: |
| * @message: A #GDBusMessage. |
| * @body: Either %NULL or a #GVariant that is a tuple. |
| * |
| * Sets the body @message. As a side-effect the |
| * %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field is set to the |
| * type string of @body (or cleared if @body is %NULL). |
| * |
| * If @body is floating, @message assumes ownership of @body. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_body (GDBusMessage *message, |
| GVariant *body) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail ((body == NULL) || g_variant_is_of_type (body, G_VARIANT_TYPE_TUPLE)); |
| |
| if (message->locked) |
| { |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); |
| return; |
| } |
| |
| if (message->body != NULL) |
| g_variant_unref (message->body); |
| if (body == NULL) |
| { |
| message->body = NULL; |
| message->arg0_cache = NULL; |
| g_dbus_message_set_signature (message, NULL); |
| } |
| else |
| { |
| const gchar *type_string; |
| gsize type_string_len; |
| gchar *signature; |
| |
| message->body = g_variant_ref_sink (body); |
| |
| if (g_variant_is_of_type (message->body, G_VARIANT_TYPE_TUPLE) && |
| g_variant_n_children (message->body) > 0) |
| message->arg0_cache = g_variant_get_child_value (message->body, 0); |
| else |
| message->arg0_cache = NULL; |
| |
| type_string = g_variant_get_type_string (body); |
| type_string_len = strlen (type_string); |
| g_assert (type_string_len >= 2); |
| signature = g_strndup (type_string + 1, type_string_len - 2); |
| g_dbus_message_set_signature (message, signature); |
| g_free (signature); |
| } |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| #ifdef G_OS_UNIX |
| /** |
| * g_dbus_message_get_unix_fd_list: |
| * @message: A #GDBusMessage. |
| * |
| * Gets the UNIX file descriptors associated with @message, if any. |
| * |
| * This method is only available on UNIX. |
| * |
| * The file descriptors normally correspond to %G_VARIANT_TYPE_HANDLE |
| * values in the body of the message. For example, |
| * if g_variant_get_handle() returns 5, that is intended to be a reference |
| * to the file descriptor that can be accessed by |
| * `g_unix_fd_list_get (list, 5, ...)`. |
| * |
| * Returns: (nullable) (transfer none): A #GUnixFDList or %NULL if no file descriptors are |
| * associated. Do not free, this object is owned by @message. |
| * |
| * Since: 2.26 |
| */ |
| GUnixFDList * |
| g_dbus_message_get_unix_fd_list (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| return message->fd_list; |
| } |
| |
| /** |
| * g_dbus_message_set_unix_fd_list: |
| * @message: A #GDBusMessage. |
| * @fd_list: (nullable): A #GUnixFDList or %NULL. |
| * |
| * Sets the UNIX file descriptors associated with @message. As a |
| * side-effect the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header |
| * field is set to the number of fds in @fd_list (or cleared if |
| * @fd_list is %NULL). |
| * |
| * This method is only available on UNIX. |
| * |
| * When designing D-Bus APIs that are intended to be interoperable, |
| * please note that non-GDBus implementations of D-Bus can usually only |
| * access file descriptors if they are referenced by a value of type |
| * %G_VARIANT_TYPE_HANDLE in the body of the message. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_unix_fd_list (GDBusMessage *message, |
| GUnixFDList *fd_list) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail (fd_list == NULL || G_IS_UNIX_FD_LIST (fd_list)); |
| |
| if (message->locked) |
| { |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); |
| return; |
| } |
| |
| if (message->fd_list != NULL) |
| g_object_unref (message->fd_list); |
| if (fd_list != NULL) |
| { |
| message->fd_list = g_object_ref (fd_list); |
| g_dbus_message_set_num_unix_fds (message, g_unix_fd_list_get_length (fd_list)); |
| } |
| else |
| { |
| message->fd_list = NULL; |
| g_dbus_message_set_num_unix_fds (message, 0); |
| } |
| } |
| #endif |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static guint |
| get_type_fixed_size (const GVariantType *type) |
| { |
| /* NB: we do not treat 'b' as fixed-size here because GVariant and |
| * D-Bus disagree about the size. |
| */ |
| switch (*g_variant_type_peek_string (type)) |
| { |
| case 'y': |
| return 1; |
| case 'n': case 'q': |
| return 2; |
| case 'i': case 'u': case 'h': |
| return 4; |
| case 'x': case 't': case 'd': |
| return 8; |
| default: |
| return 0; |
| } |
| } |
| |
| static const char * |
| message_type_to_string (GDBusMessageType message_type) |
| { |
| switch (message_type) |
| { |
| case G_DBUS_MESSAGE_TYPE_INVALID: |
| return "INVALID"; |
| case G_DBUS_MESSAGE_TYPE_METHOD_CALL: |
| return "METHOD_CALL"; |
| case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: |
| return "METHOD_RETURN"; |
| case G_DBUS_MESSAGE_TYPE_ERROR: |
| return "ERROR"; |
| case G_DBUS_MESSAGE_TYPE_SIGNAL: |
| return "SIGNAL"; |
| default: |
| return "unknown-type"; |
| } |
| } |
| |
| static const char * |
| message_header_field_to_string (GDBusMessageHeaderField field) |
| { |
| switch (field) |
| { |
| case G_DBUS_MESSAGE_HEADER_FIELD_INVALID: |
| return "INVALID"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_PATH: |
| return "PATH"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE: |
| return "INTERFACE"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_MEMBER: |
| return "MEMBER"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME: |
| return "ERROR_NAME"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL: |
| return "REPLY_SERIAL"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: |
| return "DESTINATION"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_SENDER: |
| return "SENDER"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: |
| return "SIGNATURE"; |
| case G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS: |
| return "NUM_UNIX_FDS"; |
| default: |
| return "unknown-field"; |
| } |
| } |
| |
| static gboolean |
| validate_header (GDBusMessage *message, |
| GDBusMessageHeaderField field, |
| GVariant *header_value, |
| const GVariantType *expected_type, |
| GError **error) |
| { |
| g_assert (header_value != NULL); |
| |
| if (!g_variant_is_of_type (header_value, expected_type)) |
| { |
| char *expected_type_string = g_variant_type_dup_string (expected_type); |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: %s header field is invalid; expected a value of type ‘%s’"), |
| message_type_to_string (message->type), |
| message_header_field_to_string (field), |
| expected_type_string); |
| g_free (expected_type_string); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| require_header (GDBusMessage *message, |
| GDBusMessageHeaderField field, |
| GError **error) |
| { |
| GVariant *header_value = g_dbus_message_get_header (message, field); |
| |
| if (header_value == NULL) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: %s header field is missing or invalid"), |
| message_type_to_string (message->type), |
| message_header_field_to_string (field)); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Implement the validation rules given in |
| * https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields */ |
| static gboolean |
| validate_headers (GDBusMessage *message, |
| GError **error) |
| { |
| gboolean ret; |
| GHashTableIter headers_iter; |
| gpointer key; |
| GVariant *header_value; |
| |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
| |
| ret = FALSE; |
| |
| /* Validate the types of all known headers. */ |
| g_hash_table_iter_init (&headers_iter, message->headers); |
| while (g_hash_table_iter_next (&headers_iter, &key, (gpointer) &header_value)) |
| { |
| GDBusMessageHeaderField field_type = GPOINTER_TO_INT (key); |
| |
| switch (field_type) |
| { |
| case G_DBUS_MESSAGE_HEADER_FIELD_INVALID: |
| /* The invalid header must be rejected as per |
| * https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields */ |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: INVALID header field supplied"), |
| message_type_to_string (message->type)); |
| goto out; |
| case G_DBUS_MESSAGE_HEADER_FIELD_PATH: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_OBJECT_PATH, error)) |
| goto out; |
| if (g_strcmp0 (g_variant_get_string (header_value, NULL), "/org/freedesktop/DBus/Local") == 0) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: PATH header field is using the reserved value /org/freedesktop/DBus/Local"), |
| message_type_to_string (message->type)); |
| goto out; |
| } |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) |
| goto out; |
| if (!g_dbus_is_interface_name (g_variant_get_string (header_value, NULL))) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: INTERFACE header field does not contain a valid interface name"), |
| message_type_to_string (message->type)); |
| goto out; |
| } |
| if (g_strcmp0 (g_variant_get_string (header_value, NULL), "org.freedesktop.DBus.Local") == 0) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: INTERFACE header field is using the reserved value org.freedesktop.DBus.Local"), |
| message_type_to_string (message->type)); |
| goto out; |
| } |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_MEMBER: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) |
| goto out; |
| if (!g_dbus_is_member_name (g_variant_get_string (header_value, NULL))) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: MEMBER header field does not contain a valid member name"), |
| message_type_to_string (message->type)); |
| goto out; |
| } |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) |
| goto out; |
| if (!g_dbus_is_error_name (g_variant_get_string (header_value, NULL))) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("%s message: ERROR_NAME header field does not contain a valid error name"), |
| message_type_to_string (message->type)); |
| goto out; |
| } |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_UINT32, error)) |
| goto out; |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) |
| goto out; |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_SENDER: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) |
| goto out; |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_SIGNATURE, error)) |
| goto out; |
| break; |
| case G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS: |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_UINT32, error)) |
| goto out; |
| break; |
| default: |
| /* Ignore unknown fields as per |
| * https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields. */ |
| continue; |
| } |
| } |
| |
| /* Check for message-type-specific required headers. */ |
| switch (message->type) |
| { |
| case G_DBUS_MESSAGE_TYPE_INVALID: |
| g_set_error_literal (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("type is INVALID")); |
| goto out; |
| |
| case G_DBUS_MESSAGE_TYPE_METHOD_CALL: |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, error) || |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, error)) |
| goto out; |
| break; |
| |
| case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, error)) |
| goto out; |
| break; |
| |
| case G_DBUS_MESSAGE_TYPE_ERROR: |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME, error) || |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, error)) |
| goto out; |
| break; |
| |
| case G_DBUS_MESSAGE_TYPE_SIGNAL: |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, error) || |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, error) || |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, error)) |
| goto out; |
| break; |
| |
| default: |
| /* hitherto unknown type - nothing to check */ |
| break; |
| } |
| |
| ret = TRUE; |
| |
| out: |
| g_assert (ret || (error == NULL || *error != NULL)); |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static gboolean |
| ensure_input_padding (GMemoryBuffer *buf, |
| gsize padding_size) |
| { |
| gsize offset; |
| gsize wanted_offset; |
| |
| offset = buf->pos; |
| wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size; |
| buf->pos = wanted_offset; |
| return TRUE; |
| } |
| |
| static const gchar * |
| read_string (GMemoryBuffer *mbuf, |
| gsize len, |
| GError **error) |
| { |
| gchar *str; |
| const gchar *end_valid; |
| |
| if G_UNLIKELY (mbuf->pos + len >= mbuf->valid_len || mbuf->pos + len < mbuf->pos) |
| { |
| mbuf->pos = mbuf->valid_len; |
| /* G_GSIZE_FORMAT doesn't work with gettext, so we use %lu */ |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| g_dngettext (GETTEXT_PACKAGE, |
| "Wanted to read %lu byte but only got %lu", |
| "Wanted to read %lu bytes but only got %lu", |
| (gulong)len), |
| (gulong)len, |
| (gulong)(mbuf->valid_len - mbuf->pos)); |
| return NULL; |
| } |
| |
| if G_UNLIKELY (mbuf->data[mbuf->pos + len] != '\0') |
| { |
| str = g_strndup (mbuf->data + mbuf->pos, len); |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Expected NUL byte after the string “%s” but found byte %d"), |
| str, mbuf->data[mbuf->pos + len]); |
| g_free (str); |
| mbuf->pos += len + 1; |
| return NULL; |
| } |
| |
| str = mbuf->data + mbuf->pos; |
| mbuf->pos += len + 1; |
| |
| if G_UNLIKELY (!g_utf8_validate (str, -1, &end_valid)) |
| { |
| gint offset; |
| gchar *valid_str; |
| offset = (gint) (end_valid - str); |
| valid_str = g_strndup (str, offset); |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Expected valid UTF-8 string but found invalid bytes at byte offset %d (length of string is %d). " |
| "The valid UTF-8 string up until that point was “%s”"), |
| offset, |
| (gint) len, |
| valid_str); |
| g_free (valid_str); |
| return NULL; |
| } |
| |
| return str; |
| } |
| |
| static gconstpointer |
| read_bytes (GMemoryBuffer *mbuf, |
| gsize len, |
| GError **error) |
| { |
| gconstpointer result; |
| |
| if G_UNLIKELY (mbuf->pos + len > mbuf->valid_len || mbuf->pos + len < mbuf->pos) |
| { |
| mbuf->pos = mbuf->valid_len; |
| /* G_GSIZE_FORMAT doesn't work with gettext, so we use %lu */ |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| g_dngettext (GETTEXT_PACKAGE, |
| "Wanted to read %lu byte but only got %lu", |
| "Wanted to read %lu bytes but only got %lu", |
| (gulong)len), |
| (gulong)len, |
| (gulong)(mbuf->valid_len - mbuf->pos)); |
| return NULL; |
| } |
| |
| result = mbuf->data + mbuf->pos; |
| mbuf->pos += len; |
| |
| return result; |
| } |
| |
| /* if just_align==TRUE, don't read a value, just align the input stream wrt padding */ |
| |
| /* returns a non-floating GVariant! */ |
| static GVariant * |
| parse_value_from_blob (GMemoryBuffer *buf, |
| const GVariantType *type, |
| guint max_depth, |
| gboolean just_align, |
| guint indent, |
| GError **error) |
| { |
| GVariant *ret = NULL; |
| GError *local_error = NULL; |
| #ifdef DEBUG_SERIALIZER |
| gboolean is_leaf; |
| #endif /* DEBUG_SERIALIZER */ |
| const gchar *type_string; |
| |
| if (max_depth == 0) |
| { |
| g_set_error_literal (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Value nested too deeply")); |
| goto fail; |
| } |
| |
| type_string = g_variant_type_peek_string (type); |
| |
| #ifdef DEBUG_SERIALIZER |
| { |
| gchar *s; |
| s = g_variant_type_dup_string (type); |
| g_print ("%*s%s type %s from offset 0x%04x", |
| indent, "", |
| just_align ? "Aligning" : "Reading", |
| s, |
| (gint) buf->pos); |
| g_free (s); |
| } |
| #endif /* DEBUG_SERIALIZER */ |
| |
| #ifdef DEBUG_SERIALIZER |
| is_leaf = TRUE; |
| #endif /* DEBUG_SERIALIZER */ |
| switch (type_string[0]) |
| { |
| case 'b': /* G_VARIANT_TYPE_BOOLEAN */ |
| ensure_input_padding (buf, 4); |
| if (!just_align) |
| { |
| gboolean v; |
| v = g_memory_buffer_read_uint32 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_boolean (v); |
| } |
| break; |
| |
| case 'y': /* G_VARIANT_TYPE_BYTE */ |
| if (!just_align) |
| { |
| guchar v; |
| v = g_memory_buffer_read_byte (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_byte (v); |
| } |
| break; |
| |
| case 'n': /* G_VARIANT_TYPE_INT16 */ |
| ensure_input_padding (buf, 2); |
| if (!just_align) |
| { |
| gint16 v; |
| v = g_memory_buffer_read_int16 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_int16 (v); |
| } |
| break; |
| |
| case 'q': /* G_VARIANT_TYPE_UINT16 */ |
| ensure_input_padding (buf, 2); |
| if (!just_align) |
| { |
| guint16 v; |
| v = g_memory_buffer_read_uint16 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_uint16 (v); |
| } |
| break; |
| |
| case 'i': /* G_VARIANT_TYPE_INT32 */ |
| ensure_input_padding (buf, 4); |
| if (!just_align) |
| { |
| gint32 v; |
| v = g_memory_buffer_read_int32 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_int32 (v); |
| } |
| break; |
| |
| case 'u': /* G_VARIANT_TYPE_UINT32 */ |
| ensure_input_padding (buf, 4); |
| if (!just_align) |
| { |
| guint32 v; |
| v = g_memory_buffer_read_uint32 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_uint32 (v); |
| } |
| break; |
| |
| case 'x': /* G_VARIANT_TYPE_INT64 */ |
| ensure_input_padding (buf, 8); |
| if (!just_align) |
| { |
| gint64 v; |
| v = g_memory_buffer_read_int64 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_int64 (v); |
| } |
| break; |
| |
| case 't': /* G_VARIANT_TYPE_UINT64 */ |
| ensure_input_padding (buf, 8); |
| if (!just_align) |
| { |
| guint64 v; |
| v = g_memory_buffer_read_uint64 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_uint64 (v); |
| } |
| break; |
| |
| case 'd': /* G_VARIANT_TYPE_DOUBLE */ |
| ensure_input_padding (buf, 8); |
| if (!just_align) |
| { |
| union { |
| guint64 v_uint64; |
| gdouble v_double; |
| } u; |
| G_STATIC_ASSERT (sizeof (gdouble) == sizeof (guint64)); |
| u.v_uint64 = g_memory_buffer_read_uint64 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_double (u.v_double); |
| } |
| break; |
| |
| case 's': /* G_VARIANT_TYPE_STRING */ |
| ensure_input_padding (buf, 4); |
| if (!just_align) |
| { |
| guint32 len; |
| const gchar *v; |
| len = g_memory_buffer_read_uint32 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| v = read_string (buf, (gsize) len, &local_error); |
| if (v == NULL) |
| goto fail; |
| ret = g_variant_new_string (v); |
| } |
| break; |
| |
| case 'o': /* G_VARIANT_TYPE_OBJECT_PATH */ |
| ensure_input_padding (buf, 4); |
| if (!just_align) |
| { |
| guint32 len; |
| const gchar *v; |
| len = g_memory_buffer_read_uint32 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| v = read_string (buf, (gsize) len, &local_error); |
| if (v == NULL) |
| goto fail; |
| if (!g_variant_is_object_path (v)) |
| { |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Parsed value “%s” is not a valid D-Bus object path"), |
| v); |
| goto fail; |
| } |
| ret = g_variant_new_object_path (v); |
| } |
| break; |
| |
| case 'g': /* G_VARIANT_TYPE_SIGNATURE */ |
| if (!just_align) |
| { |
| guchar len; |
| const gchar *v; |
| len = g_memory_buffer_read_byte (buf, &local_error); |
| if (local_error) |
| goto fail; |
| v = read_string (buf, (gsize) len, &local_error); |
| if (v == NULL) |
| goto fail; |
| if (!g_variant_is_signature (v)) |
| { |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Parsed value “%s” is not a valid D-Bus signature"), |
| v); |
| goto fail; |
| } |
| ret = g_variant_new_signature (v); |
| } |
| break; |
| |
| case 'h': /* G_VARIANT_TYPE_HANDLE */ |
| ensure_input_padding (buf, 4); |
| if (!just_align) |
| { |
| gint32 v; |
| v = g_memory_buffer_read_int32 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| ret = g_variant_new_handle (v); |
| } |
| break; |
| |
| case 'a': /* G_VARIANT_TYPE_ARRAY */ |
| ensure_input_padding (buf, 4); |
| |
| /* If we are only aligning for this array type, it is the child type of |
| * another array, which is empty. So, we do not need to add padding for |
| * this nonexistent array's elements: we only need to align for this |
| * array itself (4 bytes). See |
| * <https://bugzilla.gnome.org/show_bug.cgi?id=673612>. |
| */ |
| if (!just_align) |
| { |
| guint32 array_len; |
| const GVariantType *element_type; |
| guint fixed_size; |
| |
| array_len = g_memory_buffer_read_uint32 (buf, &local_error); |
| if (local_error) |
| goto fail; |
| |
| #ifdef DEBUG_SERIALIZER |
| is_leaf = FALSE; |
| g_print (": array spans 0x%04x bytes\n", array_len); |
| #endif /* DEBUG_SERIALIZER */ |
| |
| if (array_len > (2<<26)) |
| { |
| /* G_GUINT32_FORMAT doesn't work with gettext, so use u */ |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| g_dngettext (GETTEXT_PACKAGE, |
| "Encountered array of length %u byte. Maximum length is 2<<26 bytes (64 MiB).", |
| "Encountered array of length %u bytes. Maximum length is 2<<26 bytes (64 MiB).", |
| array_len), |
| array_len); |
| goto fail; |
| } |
| |
| element_type = g_variant_type_element (type); |
| fixed_size = get_type_fixed_size (element_type); |
| |
| /* Fast-path the cases like 'ay', etc. */ |
| if (fixed_size != 0) |
| { |
| gconstpointer array_data; |
| |
| if (array_len % fixed_size != 0) |
| { |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Encountered array of type “a%c”, expected to have a length a multiple " |
| "of %u bytes, but found to be %u bytes in length"), |
| g_variant_type_peek_string (element_type)[0], fixed_size, array_len); |
| goto fail; |
| } |
| |
| if (max_depth == 1) |
| { |
| /* If we had recursed into parse_value_from_blob() again to |
| * parse the array values, this would have been emitted. */ |
| g_set_error_literal (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Value nested too deeply")); |
| goto fail; |
| } |
| |
| ensure_input_padding (buf, fixed_size); |
| array_data = read_bytes (buf, array_len, &local_error); |
| if (array_data == NULL) |
| goto fail; |
| |
| ret = g_variant_new_fixed_array (element_type, array_data, array_len / fixed_size, fixed_size); |
| |
| if (g_memory_buffer_is_byteswapped (buf)) |
| { |
| GVariant *tmp = g_variant_ref_sink (ret); |
| ret = g_variant_byteswap (tmp); |
| g_variant_unref (tmp); |
| } |
| } |
| else |
| { |
| GVariantBuilder builder; |
| goffset offset; |
| goffset target; |
| |
| g_variant_builder_init (&builder, type); |
| |
| if (array_len == 0) |
| { |
| GVariant *item G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; |
| item = parse_value_from_blob (buf, |
| element_type, |
| max_depth - 1, |
| TRUE, |
| indent + 2, |
| NULL); |
| g_assert (item == NULL); |
| } |
| else |
| { |
| offset = buf->pos; |
| target = offset + array_len; |
| while (offset < target) |
| { |
| GVariant *item; |
| item = parse_value_from_blob (buf, |
| element_type, |
| max_depth - 1, |
| FALSE, |
| indent + 2, |
| &local_error); |
| if (item == NULL) |
| { |
| g_variant_builder_clear (&builder); |
| goto fail; |
| } |
| g_variant_builder_add_value (&builder, item); |
| g_variant_unref (item); |
| |
| /* Array elements must not be zero-length. There are no |
| * valid zero-length serialisations of any types which |
| * can be array elements in the D-Bus wire format, so this |
| * assertion should always hold. |
| * |
| * See https://gitlab.gnome.org/GNOME/glib/-/issues/2557 |
| */ |
| g_assert (buf->pos > (gsize) offset); |
| |
| offset = buf->pos; |
| } |
| } |
| |
| ret = g_variant_builder_end (&builder); |
| } |
| } |
| break; |
| |
| default: |
| if (g_variant_type_is_dict_entry (type)) |
| { |
| const GVariantType *key_type; |
| const GVariantType *value_type; |
| GVariant *key; |
| GVariant *value; |
| |
| ensure_input_padding (buf, 8); |
| |
| #ifdef DEBUG_SERIALIZER |
| is_leaf = FALSE; |
| g_print ("\n"); |
| #endif /* DEBUG_SERIALIZER */ |
| |
| if (!just_align) |
| { |
| key_type = g_variant_type_key (type); |
| key = parse_value_from_blob (buf, |
| key_type, |
| max_depth - 1, |
| FALSE, |
| indent + 2, |
| &local_error); |
| if (key == NULL) |
| goto fail; |
| value_type = g_variant_type_value (type); |
| value = parse_value_from_blob (buf, |
| value_type, |
| max_depth - 1, |
| FALSE, |
| indent + 2, |
| &local_error); |
| if (value == NULL) |
| { |
| g_variant_unref (key); |
| goto fail; |
| } |
| ret = g_variant_new_dict_entry (key, value); |
| g_variant_unref (key); |
| g_variant_unref (value); |
| } |
| } |
| else if (g_variant_type_is_tuple (type)) |
| { |
| ensure_input_padding (buf, 8); |
| |
| #ifdef DEBUG_SERIALIZER |
| is_leaf = FALSE; |
| g_print ("\n"); |
| #endif /* DEBUG_SERIALIZER */ |
| |
| if (!just_align) |
| { |
| const GVariantType *element_type; |
| GVariantBuilder builder; |
| |
| g_variant_builder_init (&builder, type); |
| element_type = g_variant_type_first (type); |
| if (!element_type) |
| { |
| g_variant_builder_clear (&builder); |
| g_set_error_literal (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Empty structures (tuples) are not allowed in D-Bus")); |
| goto fail; |
| } |
| |
| while (element_type != NULL) |
| { |
| GVariant *item; |
| item = parse_value_from_blob (buf, |
| element_type, |
| max_depth - 1, |
| FALSE, |
| indent + 2, |
| &local_error); |
| if (item == NULL) |
| { |
| g_variant_builder_clear (&builder); |
| goto fail; |
| } |
| g_variant_builder_add_value (&builder, item); |
| g_variant_unref (item); |
| |
| element_type = g_variant_type_next (element_type); |
| } |
| ret = g_variant_builder_end (&builder); |
| } |
| } |
| else if (g_variant_type_is_variant (type)) |
| { |
| #ifdef DEBUG_SERIALIZER |
| is_leaf = FALSE; |
| g_print ("\n"); |
| #endif /* DEBUG_SERIALIZER */ |
| |
| if (!just_align) |
| { |
| guchar siglen; |
| const gchar *sig; |
| GVariantType *variant_type; |
| GVariant *value; |
| |
| siglen = g_memory_buffer_read_byte (buf, &local_error); |
| if (local_error) |
| goto fail; |
| sig = read_string (buf, (gsize) siglen, &local_error); |
| if (sig == NULL) |
| goto fail; |
| if (!g_variant_is_signature (sig) || |
| !g_variant_type_string_is_valid (sig)) |
| { |
| /* A D-Bus signature can contain zero or more complete types, |
| * but a GVariant has to be exactly one complete type. */ |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Parsed value “%s” for variant is not a valid D-Bus signature"), |
| sig); |
| goto fail; |
| } |
| |
| if (max_depth <= g_variant_type_string_get_depth_ (sig)) |
| { |
| /* Catch the type nesting being too deep without having to |
| * parse the data. We don’t have to check this for static |
| * container types (like arrays and tuples, above) because |
| * the g_variant_type_string_is_valid() check performed before |
| * the initial parse_value_from_blob() call should check the |
| * static type nesting. */ |
| g_set_error_literal (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Value nested too deeply")); |
| goto fail; |
| } |
| |
| variant_type = g_variant_type_new (sig); |
| value = parse_value_from_blob (buf, |
| variant_type, |
| max_depth - 1, |
| FALSE, |
| indent + 2, |
| &local_error); |
| g_variant_type_free (variant_type); |
| if (value == NULL) |
| goto fail; |
| ret = g_variant_new_variant (value); |
| g_variant_unref (value); |
| } |
| } |
| else |
| { |
| gchar *s; |
| s = g_variant_type_dup_string (type); |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Error deserializing GVariant with type string “%s” from the D-Bus wire format"), |
| s); |
| g_free (s); |
| goto fail; |
| } |
| break; |
| } |
| |
| g_assert ((just_align && ret == NULL) || (!just_align && ret != NULL)); |
| |
| #ifdef DEBUG_SERIALIZER |
| if (ret != NULL) |
| { |
| if (is_leaf) |
| { |
| gchar *s; |
| if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE)) |
| { |
| s = g_strdup_printf ("0x%02x '%c'", g_variant_get_byte (ret), g_variant_get_byte (ret)); |
| } |
| else |
| { |
| s = g_variant_print (ret, FALSE); |
| } |
| g_print (": %s\n", s); |
| g_free (s); |
| } |
| } |
| #endif /* DEBUG_SERIALIZER */ |
| |
| /* sink the reference, if floating */ |
| if (ret != NULL) |
| g_variant_take_ref (ret); |
| return ret; |
| |
| fail: |
| #ifdef DEBUG_SERIALIZER |
| g_print ("\n" |
| "%*sFAILURE: %s (%s, %d)\n", |
| indent, "", |
| local_error->message, |
| g_quark_to_string (local_error->domain), |
| local_error->code); |
| #endif /* DEBUG_SERIALIZER */ |
| g_propagate_error (error, local_error); |
| return NULL; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* message_header must be at least 16 bytes */ |
| |
| /** |
| * g_dbus_message_bytes_needed: |
| * @blob: (array length=blob_len) (element-type guint8): A blob representing a binary D-Bus message. |
| * @blob_len: The length of @blob (must be at least 16). |
| * @error: Return location for error or %NULL. |
| * |
| * Utility function to calculate how many bytes are needed to |
| * completely deserialize the D-Bus message stored at @blob. |
| * |
| * Returns: Number of bytes needed or -1 if @error is set (e.g. if |
| * @blob contains invalid data or not enough data is available to |
| * determine the size). |
| * |
| * Since: 2.26 |
| */ |
| gssize |
| g_dbus_message_bytes_needed (guchar *blob, |
| gsize blob_len, |
| GError **error) |
| { |
| gssize ret; |
| |
| ret = -1; |
| |
| g_return_val_if_fail (blob != NULL, -1); |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); |
| g_return_val_if_fail (blob_len >= 16, -1); |
| |
| if (blob[0] == 'l') |
| { |
| /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */ |
| ret = 12 + 4 + GUINT32_FROM_LE (((guint32 *) blob)[3]); |
| /* round up so it's a multiple of 8 */ |
| ret = 8 * ((ret + 7)/8); |
| /* finally add the body size */ |
| ret += GUINT32_FROM_LE (((guint32 *) blob)[1]); |
| } |
| else if (blob[0] == 'B') |
| { |
| /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */ |
| ret = 12 + 4 + GUINT32_FROM_BE (((guint32 *) blob)[3]); |
| /* round up so it's a multiple of 8 */ |
| ret = 8 * ((ret + 7)/8); |
| /* finally add the body size */ |
| ret += GUINT32_FROM_BE (((guint32 *) blob)[1]); |
| } |
| else |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Unable to determine message blob length - given blob is malformed"); |
| } |
| |
| if (ret > (1<<27)) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Blob indicates that message exceeds maximum message length (128MiB)"); |
| ret = -1; |
| } |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_new_from_blob: |
| * @blob: (array length=blob_len) (element-type guint8): A blob representing a binary D-Bus message. |
| * @blob_len: The length of @blob. |
| * @capabilities: A #GDBusCapabilityFlags describing what protocol features are supported. |
| * @error: Return location for error or %NULL. |
| * |
| * Creates a new #GDBusMessage from the data stored at @blob. The byte |
| * order that the message was in can be retrieved using |
| * g_dbus_message_get_byte_order(). |
| * |
| * If the @blob cannot be parsed, contains invalid fields, or contains invalid |
| * headers, %G_IO_ERROR_INVALID_ARGUMENT will be returned. |
| * |
| * Returns: A new #GDBusMessage or %NULL if @error is set. Free with |
| * g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_message_new_from_blob (guchar *blob, |
| gsize blob_len, |
| GDBusCapabilityFlags capabilities, |
| GError **error) |
| { |
| GError *local_error = NULL; |
| GMemoryBuffer mbuf; |
| GDBusMessage *message; |
| guchar endianness; |
| guchar major_protocol_version; |
| guint32 message_body_len; |
| GVariant *headers; |
| GVariant *item; |
| GVariantIter iter; |
| GVariant *signature; |
| |
| /* TODO: check against @capabilities */ |
| |
| g_return_val_if_fail (blob != NULL, NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| message = g_dbus_message_new (); |
| |
| memset (&mbuf, 0, sizeof (mbuf)); |
| mbuf.data = (gchar *)blob; |
| mbuf.len = mbuf.valid_len = blob_len; |
| |
| endianness = g_memory_buffer_read_byte (&mbuf, &local_error); |
| if (local_error) |
| goto fail; |
| |
| switch (endianness) |
| { |
| case 'l': |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_LITTLE_ENDIAN; |
| break; |
| case 'B': |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN; |
| break; |
| default: |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Invalid endianness value. Expected 0x6c (“l”) or 0x42 (“B”) but found value 0x%02x"), |
| endianness); |
| goto fail; |
| } |
| |
| message->type = g_memory_buffer_read_byte (&mbuf, &local_error); |
| if (local_error) |
| goto fail; |
| message->flags = g_memory_buffer_read_byte (&mbuf, &local_error); |
| if (local_error) |
| goto fail; |
| major_protocol_version = g_memory_buffer_read_byte (&mbuf, &local_error); |
| if (local_error) |
| goto fail; |
| if (major_protocol_version != 1) |
| { |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Invalid major protocol version. Expected 1 but found %d"), |
| major_protocol_version); |
| goto fail; |
| } |
| message_body_len = g_memory_buffer_read_uint32 (&mbuf, &local_error); |
| if (local_error) |
| goto fail; |
| message->serial = g_memory_buffer_read_uint32 (&mbuf, &local_error); |
| if (local_error) |
| goto fail; |
| |
| #ifdef DEBUG_SERIALIZER |
| g_print ("Parsing blob (blob_len = 0x%04x bytes)\n", (gint) blob_len); |
| { |
| gchar *s; |
| s = _g_dbus_hexdump ((const gchar *) blob, blob_len, 2); |
| g_print ("%s\n", s); |
| g_free (s); |
| } |
| #endif /* DEBUG_SERIALIZER */ |
| |
| #ifdef DEBUG_SERIALIZER |
| g_print ("Parsing headers (blob_len = 0x%04x bytes)\n", (gint) blob_len); |
| #endif /* DEBUG_SERIALIZER */ |
| headers = parse_value_from_blob (&mbuf, |
| G_VARIANT_TYPE ("a{yv}"), |
| G_DBUS_MAX_TYPE_DEPTH + 2 /* for the a{yv} */, |
| FALSE, |
| 2, |
| &local_error); |
| if (headers == NULL) |
| goto fail; |
| g_variant_iter_init (&iter, headers); |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) |
| { |
| guchar header_field; |
| GVariant *value; |
| g_variant_get (item, |
| "{yv}", |
| &header_field, |
| &value); |
| g_dbus_message_set_header (message, header_field, value); |
| g_variant_unref (value); |
| g_variant_unref (item); |
| } |
| g_variant_unref (headers); |
| |
| signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); |
| if (signature != NULL) |
| { |
| const gchar *signature_str; |
| gsize signature_str_len; |
| |
| if (!g_variant_is_of_type (signature, G_VARIANT_TYPE_SIGNATURE)) |
| { |
| g_set_error_literal (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Signature header found but is not of type signature")); |
| goto fail; |
| } |
| |
| signature_str = g_variant_get_string (signature, &signature_str_len); |
| |
| /* signature but no body */ |
| if (message_body_len == 0 && signature_str_len > 0) |
| { |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Signature header with signature “%s” found but message body is empty"), |
| signature_str); |
| goto fail; |
| } |
| else if (signature_str_len > 0) |
| { |
| GVariantType *variant_type; |
| gchar *tupled_signature_str = g_strdup_printf ("(%s)", signature_str); |
| |
| if (!g_variant_is_signature (signature_str) || |
| !g_variant_type_string_is_valid (tupled_signature_str)) |
| { |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Parsed value “%s” is not a valid D-Bus signature (for body)"), |
| signature_str); |
| g_free (tupled_signature_str); |
| goto fail; |
| } |
| |
| variant_type = g_variant_type_new (tupled_signature_str); |
| g_free (tupled_signature_str); |
| #ifdef DEBUG_SERIALIZER |
| g_print ("Parsing body (blob_len = 0x%04x bytes)\n", (gint) blob_len); |
| #endif /* DEBUG_SERIALIZER */ |
| message->body = parse_value_from_blob (&mbuf, |
| variant_type, |
| G_DBUS_MAX_TYPE_DEPTH + 1 /* for the surrounding tuple */, |
| FALSE, |
| 2, |
| &local_error); |
| g_variant_type_free (variant_type); |
| |
| if (message->body != NULL && |
| g_variant_is_of_type (message->body, G_VARIANT_TYPE_TUPLE) && |
| g_variant_n_children (message->body) > 0) |
| message->arg0_cache = g_variant_get_child_value (message->body, 0); |
| else |
| message->arg0_cache = NULL; |
| |
| if (message->body == NULL) |
| goto fail; |
| } |
| } |
| else |
| { |
| /* no signature, this is only OK if the body is empty */ |
| if (message_body_len != 0) |
| { |
| /* G_GUINT32_FORMAT doesn't work with gettext, just use %u */ |
| g_set_error (&local_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| g_dngettext (GETTEXT_PACKAGE, |
| "No signature header in message but the message body is %u byte", |
| "No signature header in message but the message body is %u bytes", |
| message_body_len), |
| message_body_len); |
| goto fail; |
| } |
| } |
| |
| if (!validate_headers (message, &local_error)) |
| { |
| g_prefix_error (&local_error, _("Cannot deserialize message: ")); |
| goto fail; |
| } |
| |
| return message; |
| |
| fail: |
| g_clear_object (&message); |
| g_propagate_error (error, local_error); |
| return NULL; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static gsize |
| ensure_output_padding (GMemoryBuffer *mbuf, |
| gsize padding_size) |
| { |
| gsize offset; |
| gsize wanted_offset; |
| gsize padding_needed; |
| guint n; |
| |
| offset = mbuf->pos; |
| wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size; |
| padding_needed = wanted_offset - offset; |
| |
| for (n = 0; n < padding_needed; n++) |
| g_memory_buffer_put_byte (mbuf, '\0'); |
| |
| return padding_needed; |
| } |
| |
| /* note that value can be NULL for e.g. empty arrays - type is never NULL */ |
| static gboolean |
| append_value_to_blob (GVariant *value, |
| const GVariantType *type, |
| GMemoryBuffer *mbuf, |
| gsize *out_padding_added, |
| GError **error) |
| { |
| gsize padding_added; |
| const gchar *type_string; |
| |
| type_string = g_variant_type_peek_string (type); |
| |
| padding_added = 0; |
| |
| switch (type_string[0]) |
| { |
| case 'b': /* G_VARIANT_TYPE_BOOLEAN */ |
| padding_added = ensure_output_padding (mbuf, 4); |
| if (value != NULL) |
| { |
| gboolean v = g_variant_get_boolean (value); |
| g_memory_buffer_put_uint32 (mbuf, v); |
| } |
| break; |
| |
| case 'y': /* G_VARIANT_TYPE_BYTE */ |
| if (value != NULL) |
| { |
| guint8 v = g_variant_get_byte (value); |
| g_memory_buffer_put_byte (mbuf, v); |
| } |
| break; |
| |
| case 'n': /* G_VARIANT_TYPE_INT16 */ |
| padding_added = ensure_output_padding (mbuf, 2); |
| if (value != NULL) |
| { |
| gint16 v = g_variant_get_int16 (value); |
| g_memory_buffer_put_int16 (mbuf, v); |
| } |
| break; |
| |
| case 'q': /* G_VARIANT_TYPE_UINT16 */ |
| padding_added = ensure_output_padding (mbuf, 2); |
| if (value != NULL) |
| { |
| guint16 v = g_variant_get_uint16 (value); |
| g_memory_buffer_put_uint16 (mbuf, v); |
| } |
| break; |
| |
| case 'i': /* G_VARIANT_TYPE_INT32 */ |
| padding_added = ensure_output_padding (mbuf, 4); |
| if (value != NULL) |
| { |
| gint32 v = g_variant_get_int32 (value); |
| g_memory_buffer_put_int32 (mbuf, v); |
| } |
| break; |
| |
| case 'u': /* G_VARIANT_TYPE_UINT32 */ |
| padding_added = ensure_output_padding (mbuf, 4); |
| if (value != NULL) |
| { |
| guint32 v = g_variant_get_uint32 (value); |
| g_memory_buffer_put_uint32 (mbuf, v); |
| } |
| break; |
| |
| case 'x': /* G_VARIANT_TYPE_INT64 */ |
| padding_added = ensure_output_padding (mbuf, 8); |
| if (value != NULL) |
| { |
| gint64 v = g_variant_get_int64 (value); |
| g_memory_buffer_put_int64 (mbuf, v); |
| } |
| break; |
| |
| case 't': /* G_VARIANT_TYPE_UINT64 */ |
| padding_added = ensure_output_padding (mbuf, 8); |
| if (value != NULL) |
| { |
| guint64 v = g_variant_get_uint64 (value); |
| g_memory_buffer_put_uint64 (mbuf, v); |
| } |
| break; |
| |
| case 'd': /* G_VARIANT_TYPE_DOUBLE */ |
| padding_added = ensure_output_padding (mbuf, 8); |
| if (value != NULL) |
| { |
| union { |
| guint64 v_uint64; |
| gdouble v_double; |
| } u; |
| G_STATIC_ASSERT (sizeof (gdouble) == sizeof (guint64)); |
| u.v_double = g_variant_get_double (value); |
| g_memory_buffer_put_uint64 (mbuf, u.v_uint64); |
| } |
| break; |
| |
| case 's': /* G_VARIANT_TYPE_STRING */ |
| padding_added = ensure_output_padding (mbuf, 4); |
| if (value != NULL) |
| { |
| gsize len; |
| const gchar *v; |
| #ifndef G_DISABLE_ASSERT |
| const gchar *end; |
| #endif |
| |
| v = g_variant_get_string (value, &len); |
| g_assert (g_utf8_validate (v, -1, &end) && (end == v + len)); |
| g_memory_buffer_put_uint32 (mbuf, len); |
| g_memory_buffer_put_string (mbuf, v); |
| g_memory_buffer_put_byte (mbuf, '\0'); |
| } |
| break; |
| |
| case 'o': /* G_VARIANT_TYPE_OBJECT_PATH */ |
| padding_added = ensure_output_padding (mbuf, 4); |
| if (value != NULL) |
| { |
| gsize len; |
| const gchar *v = g_variant_get_string (value, &len); |
| g_assert (g_variant_is_object_path (v)); |
| g_memory_buffer_put_uint32 (mbuf, len); |
| g_memory_buffer_put_string (mbuf, v); |
| g_memory_buffer_put_byte (mbuf, '\0'); |
| } |
| break; |
| |
| case 'g': /* G_VARIANT_TYPE_SIGNATURE */ |
| if (value != NULL) |
| { |
| gsize len; |
| const gchar *v = g_variant_get_string (value, &len); |
| g_assert (g_variant_is_signature (v)); |
| g_memory_buffer_put_byte (mbuf, len); |
| g_memory_buffer_put_string (mbuf, v); |
| g_memory_buffer_put_byte (mbuf, '\0'); |
| } |
| break; |
| |
| case 'h': /* G_VARIANT_TYPE_HANDLE */ |
| padding_added = ensure_output_padding (mbuf, 4); |
| if (value != NULL) |
| { |
| gint32 v = g_variant_get_handle (value); |
| g_memory_buffer_put_int32 (mbuf, v); |
| } |
| break; |
| |
| case 'a': /* G_VARIANT_TYPE_ARRAY */ |
| { |
| const GVariantType *element_type; |
| GVariant *item; |
| GVariantIter iter; |
| goffset array_len_offset; |
| goffset array_payload_begin_offset; |
| goffset cur_offset; |
| gsize array_len; |
| guint fixed_size; |
| |
| padding_added = ensure_output_padding (mbuf, 4); |
| if (value != NULL) |
| { |
| /* array length - will be filled in later */ |
| array_len_offset = mbuf->valid_len; |
| g_memory_buffer_put_uint32 (mbuf, 0xF00DFACE); |
| |
| /* From the D-Bus spec: |
| * |
| * "A UINT32 giving the length of the array data in bytes, |
| * followed by alignment padding to the alignment boundary of |
| * the array element type, followed by each array element. The |
| * array length is from the end of the alignment padding to |
| * the end of the last element, i.e. it does not include the |
| * padding after the length, or any padding after the last |
| * element." |
| * |
| * Thus, we need to count how much padding the first element |
| * contributes and subtract that from the array length. |
| */ |
| array_payload_begin_offset = mbuf->valid_len; |
| |
| element_type = g_variant_type_element (type); |
| fixed_size = get_type_fixed_size (element_type); |
| |
| if (g_variant_n_children (value) == 0) |
| { |
| gsize padding_added_for_item; |
| if (!append_value_to_blob (NULL, |
| element_type, |
| mbuf, |
| &padding_added_for_item, |
| error)) |
| goto fail; |
| array_payload_begin_offset += padding_added_for_item; |
| } |
| else if (fixed_size != 0) |
| { |
| GVariant *use_value; |
| |
| if (g_memory_buffer_is_byteswapped (mbuf)) |
| use_value = g_variant_byteswap (value); |
| else |
| use_value = g_variant_ref (value); |
| |
| array_payload_begin_offset += ensure_output_padding (mbuf, fixed_size); |
| |
| array_len = g_variant_get_size (use_value); |
| g_memory_buffer_write (mbuf, g_variant_get_data (use_value), array_len); |
| g_variant_unref (use_value); |
| } |
| else |
| { |
| guint n; |
| n = 0; |
| g_variant_iter_init (&iter, value); |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) |
| { |
| gsize padding_added_for_item; |
| if (!append_value_to_blob (item, |
| g_variant_get_type (item), |
| mbuf, |
| &padding_added_for_item, |
| error)) |
| { |
| g_variant_unref (item); |
| goto fail; |
| } |
| g_variant_unref (item); |
| if (n == 0) |
| { |
| array_payload_begin_offset += padding_added_for_item; |
| } |
| n++; |
| } |
| } |
| |
| cur_offset = mbuf->valid_len; |
| array_len = cur_offset - array_payload_begin_offset; |
| mbuf->pos = array_len_offset; |
| |
| g_memory_buffer_put_uint32 (mbuf, array_len); |
| mbuf->pos = cur_offset; |
| } |
| } |
| break; |
| |
| default: |
| if (g_variant_type_is_dict_entry (type) || g_variant_type_is_tuple (type)) |
| { |
| if (!g_variant_type_first (type)) |
| { |
| g_set_error_literal (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Empty structures (tuples) are not allowed in D-Bus")); |
| goto fail; |
| } |
| |
| padding_added = ensure_output_padding (mbuf, 8); |
| if (value != NULL) |
| { |
| GVariant *item; |
| GVariantIter iter; |
| g_variant_iter_init (&iter, value); |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) |
| { |
| if (!append_value_to_blob (item, |
| g_variant_get_type (item), |
| mbuf, |
| NULL, |
| error)) |
| { |
| g_variant_unref (item); |
| goto fail; |
| } |
| g_variant_unref (item); |
| } |
| } |
| } |
| else if (g_variant_type_is_variant (type)) |
| { |
| if (value != NULL) |
| { |
| GVariant *child; |
| const gchar *signature; |
| child = g_variant_get_child_value (value, 0); |
| signature = g_variant_get_type_string (child); |
| g_memory_buffer_put_byte (mbuf, strlen (signature)); |
| g_memory_buffer_put_string (mbuf, signature); |
| g_memory_buffer_put_byte (mbuf, '\0'); |
| if (!append_value_to_blob (child, |
| g_variant_get_type (child), |
| mbuf, |
| NULL, |
| error)) |
| { |
| g_variant_unref (child); |
| goto fail; |
| } |
| g_variant_unref (child); |
| } |
| } |
| else |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Error serializing GVariant with type string “%s” to the D-Bus wire format"), |
| g_variant_get_type_string (value)); |
| goto fail; |
| } |
| break; |
| } |
| |
| if (out_padding_added != NULL) |
| *out_padding_added = padding_added; |
| |
| return TRUE; |
| |
| fail: |
| return FALSE; |
| } |
| |
| static gboolean |
| append_body_to_blob (GVariant *value, |
| GMemoryBuffer *mbuf, |
| GError **error) |
| { |
| GVariant *item; |
| GVariantIter iter; |
| |
| if (!g_variant_is_of_type (value, G_VARIANT_TYPE_TUPLE)) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| "Expected a tuple for the body of the GDBusMessage."); |
| goto fail; |
| } |
| |
| g_variant_iter_init (&iter, value); |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) |
| { |
| if (!append_value_to_blob (item, |
| g_variant_get_type (item), |
| mbuf, |
| NULL, |
| error)) |
| { |
| g_variant_unref (item); |
| goto fail; |
| } |
| g_variant_unref (item); |
| } |
| return TRUE; |
| |
| fail: |
| return FALSE; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_to_blob: |
| * @message: A #GDBusMessage. |
| * @out_size: Return location for size of generated blob. |
| * @capabilities: A #GDBusCapabilityFlags describing what protocol features are supported. |
| * @error: Return location for error. |
| * |
| * Serializes @message to a blob. The byte order returned by |
| * g_dbus_message_get_byte_order() will be used. |
| * |
| * Returns: (array length=out_size) (transfer full): A pointer to a |
| * valid binary D-Bus message of @out_size bytes generated by @message |
| * or %NULL if @error is set. Free with g_free(). |
| * |
| * Since: 2.26 |
| */ |
| guchar * |
| g_dbus_message_to_blob (GDBusMessage *message, |
| gsize *out_size, |
| GDBusCapabilityFlags capabilities, |
| GError **error) |
| { |
| GMemoryBuffer mbuf; |
| guchar *ret; |
| gsize size; |
| goffset body_len_offset; |
| goffset body_start_offset; |
| gsize body_size; |
| GVariant *header_fields; |
| GVariantBuilder builder; |
| GHashTableIter hash_iter; |
| gpointer key; |
| GVariant *header_value; |
| GVariant *signature; |
| const gchar *signature_str; |
| gint num_fds_in_message; |
| gint num_fds_according_to_header; |
| |
| /* TODO: check against @capabilities */ |
| |
| ret = NULL; |
| |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| g_return_val_if_fail (out_size != NULL, NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| memset (&mbuf, 0, sizeof (mbuf)); |
| mbuf.len = MIN_ARRAY_SIZE; |
| mbuf.data = g_malloc (mbuf.len); |
| |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN; |
| switch (message->byte_order) |
| { |
| case G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN: |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; |
| break; |
| case G_DBUS_MESSAGE_BYTE_ORDER_LITTLE_ENDIAN: |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; |
| break; |
| } |
| |
| /* Core header */ |
| g_memory_buffer_put_byte (&mbuf, (guchar) message->byte_order); |
| g_memory_buffer_put_byte (&mbuf, message->type); |
| g_memory_buffer_put_byte (&mbuf, message->flags); |
| g_memory_buffer_put_byte (&mbuf, 1); /* major protocol version */ |
| body_len_offset = mbuf.valid_len; |
| /* body length - will be filled in later */ |
| g_memory_buffer_put_uint32 (&mbuf, 0xF00DFACE); |
| g_memory_buffer_put_uint32 (&mbuf, message->serial); |
| |
| num_fds_in_message = 0; |
| #ifdef G_OS_UNIX |
| if (message->fd_list != NULL) |
| num_fds_in_message = g_unix_fd_list_get_length (message->fd_list); |
| #endif |
| num_fds_according_to_header = g_dbus_message_get_num_unix_fds (message); |
| if (num_fds_in_message != num_fds_according_to_header) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Number of file descriptors in message (%d) differs from header field (%d)"), |
| num_fds_in_message, |
| num_fds_according_to_header); |
| goto out; |
| } |
| |
| if (!validate_headers (message, error)) |
| { |
| g_prefix_error (error, _("Cannot serialize message: ")); |
| goto out; |
| } |
| |
| g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{yv}")); |
| g_hash_table_iter_init (&hash_iter, message->headers); |
| while (g_hash_table_iter_next (&hash_iter, &key, (gpointer) &header_value)) |
| { |
| g_variant_builder_add (&builder, |
| "{yv}", |
| (guchar) GPOINTER_TO_UINT (key), |
| header_value); |
| } |
| header_fields = g_variant_builder_end (&builder); |
| |
| if (!append_value_to_blob (header_fields, |
| g_variant_get_type (header_fields), |
| &mbuf, |
| NULL, |
| error)) |
| { |
| g_variant_unref (header_fields); |
| goto out; |
| } |
| g_variant_unref (header_fields); |
| |
| /* header size must be a multiple of 8 */ |
| ensure_output_padding (&mbuf, 8); |
| |
| body_start_offset = mbuf.valid_len; |
| |
| signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); |
| |
| if (signature != NULL && !g_variant_is_of_type (signature, G_VARIANT_TYPE_SIGNATURE)) |
| { |
| g_set_error_literal (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Signature header found but is not of type signature")); |
| goto out; |
| } |
| |
| signature_str = NULL; |
| if (signature != NULL) |
| signature_str = g_variant_get_string (signature, NULL); |
| if (message->body != NULL) |
| { |
| gchar *tupled_signature_str; |
| if (signature == NULL) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Message body has signature “%s” but there is no signature header"), |
| g_variant_get_type_string (message->body)); |
| goto out; |
| } |
| tupled_signature_str = g_strdup_printf ("(%s)", signature_str); |
| if (g_strcmp0 (tupled_signature_str, g_variant_get_type_string (message->body)) != 0) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Message body has type signature “%s” but signature in the header field is “%s”"), |
| g_variant_get_type_string (message->body), tupled_signature_str); |
| g_free (tupled_signature_str); |
| goto out; |
| } |
| g_free (tupled_signature_str); |
| if (!append_body_to_blob (message->body, &mbuf, error)) |
| goto out; |
| } |
| else |
| { |
| if (signature != NULL && strlen (signature_str) > 0) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Message body is empty but signature in the header field is “(%s)”"), |
| signature_str); |
| goto out; |
| } |
| } |
| |
| /* OK, we're done writing the message - set the body length */ |
| size = mbuf.valid_len; |
| body_size = size - body_start_offset; |
| |
| mbuf.pos = body_len_offset; |
| |
| g_memory_buffer_put_uint32 (&mbuf, body_size); |
| |
| *out_size = size; |
| ret = (guchar *)mbuf.data; |
| |
| out: |
| if (ret == NULL) |
| g_free (mbuf.data); |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static guint32 |
| get_uint32_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field) |
| { |
| GVariant *value; |
| guint32 ret; |
| |
| ret = 0; |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) |
| ret = g_variant_get_uint32 (value); |
| |
| return ret; |
| } |
| |
| static const gchar * |
| get_string_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field) |
| { |
| GVariant *value; |
| const gchar *ret; |
| |
| ret = NULL; |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) |
| ret = g_variant_get_string (value, NULL); |
| |
| return ret; |
| } |
| |
| static const gchar * |
| get_object_path_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field) |
| { |
| GVariant *value; |
| const gchar *ret; |
| |
| ret = NULL; |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) |
| ret = g_variant_get_string (value, NULL); |
| |
| return ret; |
| } |
| |
| static const gchar * |
| get_signature_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field) |
| { |
| GVariant *value; |
| const gchar *ret; |
| |
| ret = NULL; |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_SIGNATURE)) |
| ret = g_variant_get_string (value, NULL); |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static void |
| set_uint32_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field, |
| guint32 value) |
| { |
| g_dbus_message_set_header (message, |
| header_field, |
| g_variant_new_uint32 (value)); |
| } |
| |
| static void |
| set_string_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field, |
| const gchar *value) |
| { |
| g_dbus_message_set_header (message, |
| header_field, |
| value == NULL ? NULL : g_variant_new_string (value)); |
| } |
| |
| static void |
| set_object_path_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field, |
| const gchar *value) |
| { |
| g_dbus_message_set_header (message, |
| header_field, |
| value == NULL ? NULL : g_variant_new_object_path (value)); |
| } |
| |
| static void |
| set_signature_header (GDBusMessage *message, |
| GDBusMessageHeaderField header_field, |
| const gchar *value) |
| { |
| g_dbus_message_set_header (message, |
| header_field, |
| value == NULL ? NULL : g_variant_new_signature (value)); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_get_reply_serial: |
| * @message: A #GDBusMessage. |
| * |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field. |
| * |
| * Returns: The value. |
| * |
| * Since: 2.26 |
| */ |
| guint32 |
| g_dbus_message_get_reply_serial (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); |
| return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL); |
| } |
| |
| /** |
| * g_dbus_message_set_reply_serial: |
| * @message: A #GDBusMessage. |
| * @value: The value to set. |
| * |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_reply_serial (GDBusMessage *message, |
| guint32 value) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, value); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_get_interface: |
| * @message: A #GDBusMessage. |
| * |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field. |
| * |
| * Returns: (nullable): The value. |
| * |
| * Since: 2.26 |
| */ |
| const gchar * |
| g_dbus_message_get_interface (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE); |
| } |
| |
| /** |
| * g_dbus_message_set_interface: |
| * @message: A #GDBusMessage. |
| * @value: (nullable): The value to set. |
| * |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_interface (GDBusMessage *message, |
| const gchar *value) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail (value == NULL || g_dbus_is_interface_name (value)); |
| set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, value); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_get_member: |
| * @message: A #GDBusMessage. |
| * |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field. |
| * |
| * Returns: (nullable): The value. |
| * |
| * Since: 2.26 |
| */ |
| const gchar * |
| g_dbus_message_get_member (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER); |
| } |
| |
| /** |
| * g_dbus_message_set_member: |
| * @message: A #GDBusMessage. |
| * @value: (nullable): The value to set. |
| * |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_message_set_member (GDBusMessage *message, |
| const gchar *value) |
| { |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail (value == NULL || g_dbus_is_member_name (value)); |
| set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, value); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_message_get_path: |
| * @message: A #GDBusMessage. |
| * |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field. |
| * |
| * Returns: (nullable): The value. |
| * |
| * Since: 2.26 |
| */ |
| const gchar * |
| g_dbus_message_get_path (GDBusMessage *message) |
| { |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| return get_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH); |
| } |
| |
| /** |
| * g_dbus_message_set_path: |
| * @message: A #GDBusMessage.<
|