| /* |
| * |
| * Embedded Linux library |
| * |
| * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. |
| * |
| * 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, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include "util.h" |
| #include "private.h" |
| #include "useful.h" |
| #include "dbus.h" |
| #include "dbus-private.h" |
| #include "gvariant-private.h" |
| |
| #define DBUS_MESSAGE_LITTLE_ENDIAN ('l') |
| #define DBUS_MESSAGE_BIG_ENDIAN ('B') |
| |
| #define DBUS_MESSAGE_PROTOCOL_VERSION 1 |
| |
| #define DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED 0x01 |
| #define DBUS_MESSAGE_FLAG_NO_AUTO_START 0x02 |
| |
| #define DBUS_MESSAGE_FIELD_PATH 1 |
| #define DBUS_MESSAGE_FIELD_INTERFACE 2 |
| #define DBUS_MESSAGE_FIELD_MEMBER 3 |
| #define DBUS_MESSAGE_FIELD_ERROR_NAME 4 |
| #define DBUS_MESSAGE_FIELD_REPLY_SERIAL 5 |
| #define DBUS_MESSAGE_FIELD_DESTINATION 6 |
| #define DBUS_MESSAGE_FIELD_SENDER 7 |
| #define DBUS_MESSAGE_FIELD_SIGNATURE 8 |
| #define DBUS_MESSAGE_FIELD_UNIX_FDS 9 |
| |
| #define DBUS_MAX_NESTING 32 |
| |
| struct l_dbus_message { |
| int refcount; |
| void *header; |
| size_t header_size; |
| size_t header_end; |
| char *signature; |
| void *body; |
| size_t body_size; |
| char *path; |
| char *interface; |
| char *member; |
| char *error_name; |
| uint32_t reply_serial; |
| char *destination; |
| char *sender; |
| int fds[16]; |
| uint32_t num_fds; |
| |
| bool sealed : 1; |
| bool signature_free : 1; |
| }; |
| |
| struct l_dbus_message_builder { |
| struct l_dbus_message *message; |
| struct dbus_builder *builder; |
| struct builder_driver *driver; |
| }; |
| |
| static inline bool _dbus_message_is_gvariant(struct l_dbus_message *msg) |
| { |
| struct dbus_header *hdr = msg->header; |
| |
| return hdr->version == 2; |
| } |
| |
| void *_dbus_message_get_header(struct l_dbus_message *msg, size_t *out_size) |
| { |
| if (out_size) |
| *out_size = msg->header_size; |
| |
| return msg->header; |
| } |
| |
| void *_dbus_message_get_body(struct l_dbus_message *msg, size_t *out_size) |
| { |
| if (out_size) |
| *out_size = msg->body_size; |
| |
| return msg->body; |
| } |
| |
| /* Get a buffer containing the final message contents except the header */ |
| void *_dbus_message_get_footer(struct l_dbus_message *msg, size_t *out_size) |
| { |
| size_t size; |
| |
| if (_dbus_message_is_gvariant(msg)) { |
| size = _gvariant_message_finalize(msg->header_end, |
| msg->body, msg->body_size, |
| msg->signature); |
| size -= msg->header_size; |
| } else |
| size = msg->body_size; |
| |
| if (out_size) |
| *out_size = size; |
| |
| return msg->body; |
| } |
| |
| int *_dbus_message_get_fds(struct l_dbus_message *msg, uint32_t *num_fds) |
| { |
| *num_fds = msg->num_fds; |
| |
| return msg->fds; |
| } |
| |
| void _dbus_message_set_serial(struct l_dbus_message *msg, uint32_t serial) |
| { |
| struct dbus_header *hdr = msg->header; |
| |
| hdr->dbus1.serial = serial; |
| } |
| |
| uint32_t _dbus_message_get_serial(struct l_dbus_message *msg) |
| { |
| struct dbus_header *hdr = msg->header; |
| |
| return hdr->dbus1.serial; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_set_no_reply(struct l_dbus_message *msg, bool on) |
| { |
| struct dbus_header *hdr; |
| |
| if (unlikely(!msg)) |
| return false; |
| |
| hdr = msg->header; |
| |
| if (on) |
| hdr->flags |= DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; |
| else |
| hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_get_no_reply(struct l_dbus_message *msg) |
| { |
| struct dbus_header *hdr; |
| |
| if (unlikely(!msg)) |
| return false; |
| |
| hdr = msg->header; |
| |
| if (hdr->flags & DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED) |
| return true; |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_set_no_autostart(struct l_dbus_message *msg, |
| bool on) |
| { |
| struct dbus_header *hdr; |
| |
| if (unlikely(!msg)) |
| return false; |
| |
| hdr = msg->header; |
| |
| if (on) |
| hdr->flags |= DBUS_MESSAGE_FLAG_NO_AUTO_START; |
| else |
| hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_AUTO_START; |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_get_no_autostart(struct l_dbus_message *msg) |
| { |
| struct dbus_header *hdr; |
| |
| if (unlikely(!msg)) |
| return false; |
| |
| hdr = msg->header; |
| |
| if (hdr->flags & DBUS_MESSAGE_FLAG_NO_AUTO_START) |
| return true; |
| |
| return false; |
| |
| } |
| |
| static struct l_dbus_message *message_new_common(uint8_t type, uint8_t flags, |
| uint8_t version) |
| { |
| struct l_dbus_message *message; |
| struct dbus_header *hdr; |
| |
| message = l_new(struct l_dbus_message, 1); |
| message->refcount = 1; |
| |
| /* |
| * We allocate the header with the initial 12 bytes (up to the field |
| * length) so that we can store the basic information here. For |
| * GVariant we need 16 bytes. |
| */ |
| message->header_size = version == 1 ? 12 : 16; |
| message->header_end = message->header_size; |
| message->header = l_realloc(NULL, message->header_size); |
| |
| hdr = message->header; |
| hdr->endian = DBUS_NATIVE_ENDIAN; |
| hdr->message_type = type; |
| hdr->flags = flags; |
| hdr->version = version; |
| |
| return message; |
| } |
| |
| struct l_dbus_message *_dbus_message_new_method_call(uint8_t version, |
| const char *destination, |
| const char *path, |
| const char *interface, |
| const char *method) |
| { |
| struct l_dbus_message *message; |
| |
| message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_CALL, 0, version); |
| |
| message->destination = l_strdup(destination); |
| message->path = l_strdup(path); |
| message->interface = l_strdup(interface); |
| message->member = l_strdup(method); |
| |
| return message; |
| } |
| |
| LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_call( |
| struct l_dbus *dbus, |
| const char *destination, |
| const char *path, |
| const char *interface, |
| const char *method) |
| { |
| uint8_t version; |
| |
| if (unlikely(!dbus)) |
| return NULL; |
| |
| version = _dbus_get_version(dbus); |
| |
| return _dbus_message_new_method_call(version, destination, path, |
| interface, method); |
| } |
| |
| struct l_dbus_message *_dbus_message_new_signal(uint8_t version, |
| const char *path, |
| const char *interface, |
| const char *name) |
| { |
| struct l_dbus_message *message; |
| |
| message = message_new_common(DBUS_MESSAGE_TYPE_SIGNAL, |
| DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, |
| version); |
| |
| message->path = l_strdup(path); |
| message->interface = l_strdup(interface); |
| message->member = l_strdup(name); |
| |
| return message; |
| } |
| |
| LIB_EXPORT struct l_dbus_message *l_dbus_message_new_signal(struct l_dbus *dbus, |
| const char *path, |
| const char *interface, |
| const char *name) |
| { |
| uint8_t version; |
| |
| if (unlikely(!dbus)) |
| return NULL; |
| |
| version = _dbus_get_version(dbus); |
| |
| return _dbus_message_new_signal(version, path, interface, name); |
| } |
| |
| LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_return( |
| struct l_dbus_message *method_call) |
| { |
| struct l_dbus_message *message; |
| struct dbus_header *hdr = method_call->header; |
| const char *sender; |
| |
| message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_RETURN, |
| DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, |
| hdr->version); |
| |
| if (!l_dbus_message_get_no_reply(method_call)) |
| message->reply_serial = _dbus_message_get_serial(method_call); |
| |
| sender = l_dbus_message_get_sender(method_call); |
| if (sender) |
| message->destination = l_strdup(sender); |
| |
| return message; |
| } |
| |
| struct l_dbus_message *_dbus_message_new_error(uint8_t version, |
| uint32_t reply_serial, |
| const char *destination, |
| const char *name, |
| const char *error) |
| { |
| struct l_dbus_message *reply; |
| |
| if (!_dbus_valid_interface(name)) |
| return NULL; |
| |
| reply = message_new_common(DBUS_MESSAGE_TYPE_ERROR, |
| DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, |
| version); |
| |
| reply->error_name = l_strdup(name); |
| reply->destination = l_strdup(destination); |
| reply->reply_serial = reply_serial; |
| |
| if (!l_dbus_message_set_arguments(reply, "s", error)) { |
| l_dbus_message_unref(reply); |
| return NULL; |
| } |
| |
| return reply; |
| } |
| |
| LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error_valist( |
| struct l_dbus_message *method_call, |
| const char *name, |
| const char *format, va_list args) |
| { |
| char str[1024]; |
| struct dbus_header *hdr = method_call->header; |
| uint32_t reply_serial = 0; |
| |
| vsnprintf(str, sizeof(str), format, args); |
| |
| if (!l_dbus_message_get_no_reply(method_call)) |
| reply_serial = _dbus_message_get_serial(method_call); |
| |
| return _dbus_message_new_error(hdr->version, reply_serial, |
| l_dbus_message_get_sender(method_call), |
| name, str); |
| } |
| |
| LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error( |
| struct l_dbus_message *method_call, |
| const char *name, |
| const char *format, ...) |
| { |
| va_list args; |
| struct l_dbus_message *reply; |
| |
| va_start(args, format); |
| reply = l_dbus_message_new_error_valist(method_call, name, |
| format, args); |
| va_end(args); |
| |
| return reply; |
| } |
| |
| LIB_EXPORT struct l_dbus_message *l_dbus_message_ref(struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return NULL; |
| |
| __atomic_fetch_add(&message->refcount, 1, __ATOMIC_SEQ_CST); |
| |
| return message; |
| } |
| |
| LIB_EXPORT void l_dbus_message_unref(struct l_dbus_message *message) |
| { |
| unsigned int i; |
| |
| if (unlikely(!message)) |
| return; |
| |
| if (__atomic_sub_fetch(&message->refcount, 1, __ATOMIC_SEQ_CST)) |
| return; |
| |
| for (i = 0; i < message->num_fds; i++) |
| close(message->fds[i]); |
| |
| if (!message->sealed) { |
| l_free(message->destination); |
| l_free(message->path); |
| l_free(message->interface); |
| l_free(message->member); |
| l_free(message->error_name); |
| l_free(message->sender); |
| } |
| |
| if (message->signature_free) |
| l_free(message->signature); |
| |
| l_free(message->header); |
| l_free(message->body); |
| l_free(message); |
| } |
| |
| const char *_dbus_message_get_nth_string_argument( |
| struct l_dbus_message *message, int n) |
| { |
| struct l_dbus_message_iter iter; |
| const char *signature, *value; |
| void *body; |
| size_t size; |
| char type; |
| bool (*skip_entry)(struct l_dbus_message_iter *); |
| bool (*get_basic)(struct l_dbus_message_iter *, char, void *); |
| |
| signature = l_dbus_message_get_signature(message); |
| body = _dbus_message_get_body(message, &size); |
| |
| if (!signature) |
| return NULL; |
| |
| if (_dbus_message_is_gvariant(message)) { |
| if (!_gvariant_iter_init(&iter, message, signature, NULL, |
| body, size)) |
| return NULL; |
| |
| skip_entry = _gvariant_iter_skip_entry; |
| get_basic = _gvariant_iter_next_entry_basic; |
| } else { |
| _dbus1_iter_init(&iter, message, signature, NULL, body, size); |
| |
| skip_entry = _dbus1_iter_skip_entry; |
| get_basic = _dbus1_iter_next_entry_basic; |
| } |
| |
| while (n--) |
| if (!skip_entry(&iter)) |
| return NULL; |
| |
| if (!iter.sig_start) |
| return NULL; |
| |
| type = iter.sig_start[iter.sig_pos]; |
| if (!strchr("sog", type)) |
| return NULL; |
| |
| if (!get_basic(&iter, type, &value)) |
| return NULL; |
| |
| return value; |
| } |
| |
| static bool message_iter_next_entry_valist(struct l_dbus_message_iter *orig, |
| va_list args) |
| { |
| static const char *simple_types = "sogybnqiuxtd"; |
| struct l_dbus_message_iter *iter = orig; |
| const char *signature = orig->sig_start + orig->sig_pos; |
| const char *end; |
| struct l_dbus_message_iter *sub_iter; |
| struct l_dbus_message_iter stack[DBUS_MAX_NESTING]; |
| unsigned int indent = 0; |
| uint32_t uint32_val; |
| int fd; |
| void *arg; |
| bool (*get_basic)(struct l_dbus_message_iter *, char ,void *); |
| bool (*enter_struct)(struct l_dbus_message_iter *, |
| struct l_dbus_message_iter *); |
| bool (*enter_array)(struct l_dbus_message_iter *, |
| struct l_dbus_message_iter *); |
| bool (*enter_variant)(struct l_dbus_message_iter *, |
| struct l_dbus_message_iter *); |
| |
| if (_dbus_message_is_gvariant(orig->message)) { |
| get_basic = _gvariant_iter_next_entry_basic; |
| enter_struct = _gvariant_iter_enter_struct; |
| enter_array = _gvariant_iter_enter_array; |
| enter_variant = _gvariant_iter_enter_variant; |
| } else { |
| get_basic = _dbus1_iter_next_entry_basic; |
| enter_struct = _dbus1_iter_enter_struct; |
| enter_array = _dbus1_iter_enter_array; |
| enter_variant = _dbus1_iter_enter_variant; |
| } |
| |
| while (signature < orig->sig_start + orig->sig_len) { |
| if (strchr(simple_types, *signature)) { |
| arg = va_arg(args, void *); |
| if (!get_basic(iter, *signature, arg)) |
| return false; |
| |
| signature += 1; |
| continue; |
| } |
| |
| switch (*signature) { |
| case 'h': |
| if (!get_basic(iter, 'h', &uint32_val)) |
| return false; |
| |
| if (uint32_val < iter->message->num_fds) |
| fd = fcntl(iter->message->fds[uint32_val], |
| F_DUPFD_CLOEXEC, 3); |
| else |
| fd = -1; |
| |
| *va_arg(args, int *) = fd; |
| signature += 1; |
| break; |
| case '(': |
| case '{': |
| signature += 1; |
| indent += 1; |
| |
| if (indent > DBUS_MAX_NESTING) |
| return false; |
| |
| if (!enter_struct(iter, &stack[indent - 1])) |
| return false; |
| |
| iter = &stack[indent - 1]; |
| |
| break; |
| case ')': |
| case '}': |
| /* |
| * Sanity check in case of an unmatched paren/brace |
| * that isn't caught elsewhere. |
| */ |
| if (unlikely(indent == 0)) |
| return false; |
| |
| signature += 1; |
| indent -= 1; |
| |
| if (indent == 0) |
| iter = orig; |
| else |
| iter = &stack[indent - 1]; |
| break; |
| case 'a': |
| sub_iter = va_arg(args, void *); |
| |
| if (!enter_array(iter, sub_iter)) |
| return false; |
| |
| end = _dbus_signature_end(signature + 1); |
| signature = end + 1; |
| break; |
| case 'v': |
| sub_iter = va_arg(args, void *); |
| |
| if (!enter_variant(iter, sub_iter)) |
| return false; |
| |
| signature += 1; |
| break; |
| default: |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static inline bool message_iter_next_entry(struct l_dbus_message_iter *iter, |
| ...) |
| { |
| va_list args; |
| bool result; |
| |
| va_start(args, iter); |
| result = message_iter_next_entry_valist(iter, args); |
| va_end(args); |
| |
| return result; |
| } |
| |
| static bool get_header_field_from_iter_valist(struct l_dbus_message *message, |
| uint8_t type, char data_type, |
| va_list args) |
| { |
| struct l_dbus_message_iter header; |
| struct l_dbus_message_iter array, iter; |
| uint8_t endian, message_type, flags, version; |
| uint32_t body_length, serial; |
| bool found; |
| |
| if (!message->sealed) |
| return false; |
| |
| if (_dbus_message_is_gvariant(message)) { |
| uint64_t field_type; |
| |
| if (!_gvariant_iter_init(&header, message, "a(tv)", NULL, |
| message->header + 16, |
| message->header_end - 16)) |
| return false; |
| |
| if (!_gvariant_iter_enter_array(&header, &array)) |
| return false; |
| |
| while ((found = message_iter_next_entry(&array, |
| &field_type, &iter))) |
| if (field_type == type) |
| break; |
| } else { |
| uint8_t field_type; |
| |
| _dbus1_iter_init(&header, message, "yyyyuua(yv)", NULL, |
| message->header, message->header_size); |
| |
| if (!message_iter_next_entry(&header, &endian, |
| &message_type, &flags, &version, |
| &body_length, &serial, &array)) |
| return false; |
| |
| while ((found = message_iter_next_entry(&array, |
| &field_type, &iter))) |
| if (field_type == type) |
| break; |
| } |
| |
| if (!found) |
| return false; |
| |
| if (iter.sig_start[iter.sig_pos] != data_type) |
| return false; |
| |
| return message_iter_next_entry_valist(&iter, args); |
| } |
| |
| static inline bool get_header_field(struct l_dbus_message *message, |
| uint8_t type, int data_type, ...) |
| { |
| va_list args; |
| bool result; |
| |
| va_start(args, data_type); |
| result = get_header_field_from_iter_valist(message, type, data_type, |
| args); |
| va_end(args); |
| |
| return result; |
| } |
| |
| static bool valid_header(const struct dbus_header *hdr) |
| { |
| if (hdr->endian != DBUS_MESSAGE_LITTLE_ENDIAN && |
| hdr->endian != DBUS_MESSAGE_BIG_ENDIAN) |
| return false; |
| |
| if (hdr->message_type < DBUS_MESSAGE_TYPE_METHOD_CALL || |
| hdr->message_type > DBUS_MESSAGE_TYPE_SIGNAL) |
| return false; |
| |
| if (hdr->version != 1 && hdr->version != 2) |
| return false; |
| |
| if (hdr->version == 1) { |
| if (hdr->dbus1.serial == 0) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| unsigned int _dbus_message_unix_fds_from_header(const void *data, size_t size) |
| { |
| struct l_dbus_message message; |
| uint32_t unix_fds; |
| |
| message.header = (uint8_t *) data; |
| message.header_size = size; |
| message.body_size = 0; |
| message.sealed = true; |
| |
| if (!get_header_field(&message, DBUS_MESSAGE_FIELD_UNIX_FDS, |
| 'u', &unix_fds)) |
| return 0; |
| |
| return unix_fds; |
| } |
| |
| struct l_dbus_message *dbus_message_from_blob(const void *data, size_t size, |
| int fds[], uint32_t num_fds) |
| { |
| const struct dbus_header *hdr = data; |
| struct l_dbus_message *message; |
| size_t body_pos; |
| unsigned int i; |
| |
| if (unlikely(size < DBUS_HEADER_SIZE)) |
| return NULL; |
| |
| message = l_new(struct l_dbus_message, 1); |
| |
| message->refcount = 1; |
| |
| if (hdr->version == 1) { |
| message->header_size = align_len(DBUS_HEADER_SIZE + |
| hdr->dbus1.field_length, 8); |
| message->body_size = hdr->dbus1.body_length; |
| |
| if (message->header_size + message->body_size < size) |
| goto free; |
| |
| body_pos = message->header_size; |
| } else { |
| struct l_dbus_message_iter iter; |
| struct l_dbus_message_iter header, variant, body; |
| |
| /* |
| * GVariant message structure as per |
| * https://wiki.gnome.org/Projects/GLib/GDBus/Version2 |
| * is "(yyyyuta{tv}v)". As noted this is equivalent to |
| * some other types, this one lets us get iterators for |
| * the header and the body in the fewest steps. |
| */ |
| if (!_gvariant_iter_init(&iter, message, "(yyyyuta{tv})v", |
| NULL, data, size)) |
| goto free; |
| |
| if (!_gvariant_iter_enter_struct(&iter, &header)) |
| goto free; |
| |
| if (!_gvariant_iter_enter_variant(&iter, &variant)) |
| goto free; |
| |
| if (!_gvariant_iter_enter_struct(&variant, &body)) |
| goto free; |
| |
| message->header_size = align_len(header.len - header.pos, 8); |
| message->body_size = body.len - body.pos; |
| message->signature = l_strndup(body.sig_start + body.sig_pos, |
| body.sig_len - body.sig_pos); |
| message->signature_free = true; |
| message->header_end = header.len; |
| body_pos = body.data + body.pos - data; |
| } |
| |
| message->header = l_malloc(message->header_size); |
| message->body = l_malloc(message->body_size); |
| |
| memcpy(message->header, data, message->header_size); |
| memcpy(message->body, data + body_pos, message->body_size); |
| |
| message->sealed = true; |
| |
| /* If the field is absent message->signature will remain NULL */ |
| if (hdr->version == 1) |
| get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, |
| 'g', &message->signature); |
| |
| if (num_fds) { |
| uint32_t unix_fds, orig_fds = num_fds; |
| |
| if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, |
| 'u', &unix_fds)) |
| goto free; |
| |
| if (num_fds > unix_fds) |
| num_fds = unix_fds; |
| |
| if (num_fds > L_ARRAY_SIZE(message->fds)) |
| num_fds = L_ARRAY_SIZE(message->fds); |
| |
| for (i = num_fds; i < orig_fds; i++) |
| close(fds[i]); |
| |
| message->num_fds = num_fds; |
| memcpy(message->fds, fds, num_fds * sizeof(int)); |
| } |
| |
| return message; |
| |
| free: |
| l_dbus_message_unref(message); |
| |
| return NULL; |
| } |
| |
| struct l_dbus_message *dbus_message_build(void *header, size_t header_size, |
| void *body, size_t body_size, |
| int fds[], uint32_t num_fds) |
| { |
| const struct dbus_header *hdr = header; |
| struct l_dbus_message *message; |
| unsigned int i; |
| |
| if (unlikely(header_size < DBUS_HEADER_SIZE)) |
| return NULL; |
| |
| if (unlikely(!valid_header(hdr))) |
| return NULL; |
| |
| /* |
| * With GVariant we need to know the signature, use |
| * dbus_message_from_blob instead. |
| */ |
| if (unlikely(hdr->version != 1)) |
| return NULL; |
| |
| message = l_new(struct l_dbus_message, 1); |
| |
| message->refcount = 1; |
| message->header_size = header_size; |
| message->header = header; |
| message->body_size = body_size; |
| message->body = body; |
| message->sealed = true; |
| |
| if (num_fds) { |
| uint32_t unix_fds, orig_fds = num_fds; |
| |
| if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, |
| 'u', &unix_fds)) { |
| l_free(message); |
| return NULL; |
| } |
| |
| if (num_fds > unix_fds) |
| num_fds = unix_fds; |
| |
| if (num_fds > L_ARRAY_SIZE(message->fds)) |
| num_fds = L_ARRAY_SIZE(message->fds); |
| |
| for (i = num_fds; i < orig_fds; i++) |
| close(fds[i]); |
| |
| message->num_fds = num_fds; |
| memcpy(message->fds, fds, num_fds * sizeof(int)); |
| } |
| |
| /* If the field is absent message->signature will remain NULL */ |
| get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, 'g', |
| &message->signature); |
| |
| return message; |
| } |
| |
| bool dbus_message_compare(struct l_dbus_message *message, |
| const void *data, size_t size) |
| { |
| struct l_dbus_message *other; |
| bool ret = false; |
| |
| other = dbus_message_from_blob(data, size, NULL, 0); |
| |
| if (message->signature) { |
| if (!other->signature) |
| goto done; |
| |
| if (strcmp(message->signature, other->signature)) |
| goto done; |
| } else { |
| if (other->signature) |
| goto done; |
| } |
| |
| if (message->body_size != other->body_size) |
| goto done; |
| |
| if (message->header_size != other->header_size) |
| goto done; |
| |
| ret = !memcmp(message->body, other->body, message->body_size); |
| |
| done: |
| l_dbus_message_unref(other); |
| |
| return ret; |
| } |
| |
| struct builder_driver { |
| bool (*append_basic)(struct dbus_builder *, char, const void *); |
| bool (*enter_struct)(struct dbus_builder *, const char *); |
| bool (*leave_struct)(struct dbus_builder *); |
| bool (*enter_dict)(struct dbus_builder *, const char *); |
| bool (*leave_dict)(struct dbus_builder *); |
| bool (*enter_array)(struct dbus_builder *, const char *); |
| bool (*leave_array)(struct dbus_builder *); |
| bool (*enter_variant)(struct dbus_builder *, const char *); |
| bool (*leave_variant)(struct dbus_builder *); |
| char *(*finish)(struct dbus_builder *, void **, size_t *); |
| bool (*mark)(struct dbus_builder *); |
| bool (*rewind)(struct dbus_builder *); |
| struct dbus_builder *(*new)(void *, size_t); |
| void (*free)(struct dbus_builder *); |
| }; |
| |
| static struct builder_driver dbus1_driver = { |
| .append_basic = _dbus1_builder_append_basic, |
| .enter_struct = _dbus1_builder_enter_struct, |
| .leave_struct = _dbus1_builder_leave_struct, |
| .enter_dict = _dbus1_builder_enter_dict, |
| .leave_dict = _dbus1_builder_leave_dict, |
| .enter_variant = _dbus1_builder_enter_variant, |
| .leave_variant = _dbus1_builder_leave_variant, |
| .enter_array = _dbus1_builder_enter_array, |
| .leave_array = _dbus1_builder_leave_array, |
| .finish = _dbus1_builder_finish, |
| .mark = _dbus1_builder_mark, |
| .rewind = _dbus1_builder_rewind, |
| .new = _dbus1_builder_new, |
| .free = _dbus1_builder_free, |
| }; |
| |
| static struct builder_driver gvariant_driver = { |
| .append_basic = _gvariant_builder_append_basic, |
| .enter_struct = _gvariant_builder_enter_struct, |
| .leave_struct = _gvariant_builder_leave_struct, |
| .enter_dict = _gvariant_builder_enter_dict, |
| .leave_dict = _gvariant_builder_leave_dict, |
| .enter_variant = _gvariant_builder_enter_variant, |
| .leave_variant = _gvariant_builder_leave_variant, |
| .enter_array = _gvariant_builder_enter_array, |
| .leave_array = _gvariant_builder_leave_array, |
| .finish = _gvariant_builder_finish, |
| .mark = _gvariant_builder_mark, |
| .rewind = _gvariant_builder_rewind, |
| .new = _gvariant_builder_new, |
| .free = _gvariant_builder_free, |
| }; |
| |
| static void add_field(struct dbus_builder *builder, |
| struct builder_driver *driver, |
| uint8_t field, const char *type, const void *value) |
| { |
| if (driver == &gvariant_driver) { |
| uint64_t long_field = field; |
| |
| driver->enter_struct(builder, "tv"); |
| driver->append_basic(builder, 't', &long_field); |
| } else { |
| driver->enter_struct(builder, "yv"); |
| driver->append_basic(builder, 'y', &field); |
| } |
| driver->enter_variant(builder, type); |
| driver->append_basic(builder, type[0], value); |
| driver->leave_variant(builder); |
| driver->leave_struct(builder); |
| } |
| |
| static void build_header(struct l_dbus_message *message, const char *signature) |
| { |
| struct dbus_builder *builder; |
| struct builder_driver *driver; |
| char *generated_signature; |
| size_t header_size; |
| bool gvariant; |
| |
| gvariant = _dbus_message_is_gvariant(message); |
| |
| if (gvariant) |
| driver = &gvariant_driver; |
| else |
| driver = &dbus1_driver; |
| |
| builder = driver->new(message->header, message->header_size); |
| |
| driver->enter_array(builder, gvariant ? "(tv)" : "(yv)"); |
| |
| if (message->path) { |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_PATH, |
| "o", message->path); |
| l_free(message->path); |
| message->path = NULL; |
| } |
| |
| if (message->member) { |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_MEMBER, |
| "s", message->member); |
| l_free(message->member); |
| message->member = NULL; |
| } |
| |
| if (message->interface) { |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_INTERFACE, |
| "s", message->interface); |
| l_free(message->interface); |
| message->interface = NULL; |
| } |
| |
| if (message->destination) { |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_DESTINATION, |
| "s", message->destination); |
| l_free(message->destination); |
| message->destination = NULL; |
| } |
| |
| if (message->error_name != 0) { |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_ERROR_NAME, |
| "s", message->error_name); |
| l_free(message->error_name); |
| message->error_name = NULL; |
| } |
| |
| if (message->reply_serial != 0) { |
| if (gvariant) { |
| uint64_t reply_serial = message->reply_serial; |
| |
| add_field(builder, driver, |
| DBUS_MESSAGE_FIELD_REPLY_SERIAL, |
| "t", &reply_serial); |
| } else { |
| add_field(builder, driver, |
| DBUS_MESSAGE_FIELD_REPLY_SERIAL, |
| "u", &message->reply_serial); |
| } |
| |
| message->reply_serial = 0; |
| } |
| |
| if (message->sender) { |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_SENDER, |
| "s", message->sender); |
| l_free(message->sender); |
| message->sender = NULL; |
| } |
| |
| if (signature[0] != '\0' && !gvariant) |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_SIGNATURE, |
| "g", signature); |
| |
| if (message->num_fds) |
| add_field(builder, driver, DBUS_MESSAGE_FIELD_UNIX_FDS, |
| "u", &message->num_fds); |
| |
| driver->leave_array(builder); |
| |
| generated_signature = driver->finish(builder, &message->header, |
| &header_size); |
| l_free(generated_signature); |
| |
| driver->free(builder); |
| |
| if (!_dbus_message_is_gvariant(message)) { |
| struct dbus_header *hdr = message->header; |
| |
| hdr->dbus1.body_length = message->body_size; |
| } |
| |
| /* We must align the end of the header to an 8-byte boundary */ |
| message->header_size = align_len(header_size, 8); |
| message->header = l_realloc(message->header, message->header_size); |
| memset(message->header + header_size, 0, |
| message->header_size - header_size); |
| message->header_end = header_size; |
| } |
| |
| struct container { |
| char type; |
| const char *sig_start; |
| const char *sig_end; |
| unsigned int n_items; |
| }; |
| |
| static bool append_arguments(struct l_dbus_message *message, |
| const char *signature, va_list args) |
| { |
| struct l_dbus_message_builder *builder; |
| bool ret; |
| |
| builder = l_dbus_message_builder_new(message); |
| if (!builder) |
| return false; |
| |
| if (!l_dbus_message_builder_append_from_valist(builder, signature, |
| args)) { |
| l_dbus_message_builder_destroy(builder); |
| return false; |
| } |
| |
| l_dbus_message_builder_finalize(builder); |
| |
| ret = strcmp(signature, builder->message->signature) == 0; |
| |
| l_dbus_message_builder_destroy(builder); |
| |
| return ret; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_get_error(struct l_dbus_message *message, |
| const char **name, const char **text) |
| { |
| struct dbus_header *hdr; |
| const char *str; |
| |
| if (unlikely(!message)) |
| return false; |
| |
| hdr = message->header; |
| |
| if (hdr->message_type != DBUS_MESSAGE_TYPE_ERROR) |
| return false; |
| |
| if (!message->signature) |
| return false; |
| |
| if (message->signature[0] != 's') |
| return false; |
| |
| str = _dbus_message_get_nth_string_argument(message, 0); |
| if (!str) |
| return false; |
| |
| if (!message->error_name) |
| get_header_field(message, DBUS_MESSAGE_FIELD_ERROR_NAME, 's', |
| &message->error_name); |
| |
| if (name) |
| *name = message->error_name; |
| |
| if (text) |
| *text = str; |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_is_error(struct l_dbus_message *message) |
| { |
| struct dbus_header *hdr; |
| |
| if (unlikely(!message)) |
| return false; |
| |
| hdr = message->header; |
| return hdr->message_type == DBUS_MESSAGE_TYPE_ERROR; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_get_arguments_valist( |
| struct l_dbus_message *message, |
| const char *signature, va_list args) |
| { |
| struct l_dbus_message_iter iter; |
| |
| if (unlikely(!message)) |
| return false; |
| |
| if (!message->signature) { |
| /* An empty signature is valid */ |
| if (!signature || *signature == '\0') |
| return true; |
| |
| return false; |
| } |
| |
| if (!signature || strcmp(message->signature, signature)) |
| return false; |
| |
| if (_dbus_message_is_gvariant(message)) { |
| if (!_gvariant_iter_init(&iter, message, message->signature, |
| NULL, message->body, |
| message->body_size)) |
| return false; |
| } else |
| _dbus1_iter_init(&iter, message, message->signature, NULL, |
| message->body, message->body_size); |
| |
| return message_iter_next_entry_valist(&iter, args); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_get_arguments(struct l_dbus_message *message, |
| const char *signature, ...) |
| { |
| va_list args; |
| bool result; |
| |
| va_start(args, signature); |
| result = l_dbus_message_get_arguments_valist(message, signature, args); |
| va_end(args); |
| |
| return result; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_set_arguments(struct l_dbus_message *message, |
| const char *signature, ...) |
| { |
| va_list args; |
| bool result; |
| |
| if (unlikely(!message)) |
| return false; |
| |
| if (unlikely(message->sealed)) |
| return false; |
| |
| if (!signature) |
| return true; |
| |
| va_start(args, signature); |
| result = append_arguments(message, signature, args); |
| va_end(args); |
| |
| return result; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_set_arguments_valist( |
| struct l_dbus_message *message, |
| const char *signature, va_list args) |
| { |
| bool result; |
| |
| if (unlikely(!message)) |
| return false; |
| |
| if (!signature) |
| return true; |
| |
| result = append_arguments(message, signature, args); |
| |
| return result; |
| } |
| |
| LIB_EXPORT const char *l_dbus_message_get_path(struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return NULL; |
| |
| if (!message->path && message->sealed) |
| get_header_field(message, DBUS_MESSAGE_FIELD_PATH, 'o', |
| &message->path); |
| |
| return message->path; |
| } |
| |
| LIB_EXPORT const char *l_dbus_message_get_interface(struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return NULL; |
| |
| if (!message->interface && message->sealed) |
| get_header_field(message, DBUS_MESSAGE_FIELD_INTERFACE, 's', |
| &message->interface); |
| |
| return message->interface; |
| } |
| |
| LIB_EXPORT const char *l_dbus_message_get_member(struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return NULL; |
| |
| if (!message->member && message->sealed) |
| get_header_field(message, DBUS_MESSAGE_FIELD_MEMBER, 's', |
| &message->member); |
| |
| return message->member; |
| } |
| |
| LIB_EXPORT const char *l_dbus_message_get_destination(struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return NULL; |
| |
| if (!message->destination && message->sealed) |
| get_header_field(message, DBUS_MESSAGE_FIELD_DESTINATION, 's', |
| &message->destination); |
| |
| return message->destination; |
| } |
| |
| LIB_EXPORT const char *l_dbus_message_get_sender(struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return NULL; |
| |
| if (!message->sender && message->sealed) |
| get_header_field(message, DBUS_MESSAGE_FIELD_SENDER, 's', |
| &message->sender); |
| |
| return message->sender; |
| } |
| |
| LIB_EXPORT const char *l_dbus_message_get_signature( |
| struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return NULL; |
| |
| return message->signature; |
| } |
| |
| uint32_t _dbus_message_get_reply_serial(struct l_dbus_message *message) |
| { |
| if (unlikely(!message)) |
| return 0; |
| |
| if (message->reply_serial == 0 && message->sealed) { |
| if (_dbus_message_is_gvariant(message)) { |
| uint64_t reply_serial = 0; |
| |
| get_header_field(message, |
| DBUS_MESSAGE_FIELD_REPLY_SERIAL, 't', |
| &reply_serial); |
| |
| message->reply_serial = reply_serial; |
| } else |
| get_header_field(message, |
| DBUS_MESSAGE_FIELD_REPLY_SERIAL, 'u', |
| &message->reply_serial); |
| } |
| |
| return message->reply_serial; |
| } |
| |
| enum dbus_message_type _dbus_message_get_type(struct l_dbus_message *message) |
| { |
| struct dbus_header *header; |
| |
| header = message->header; |
| return header->message_type; |
| } |
| |
| const char * _dbus_message_get_type_as_string(struct l_dbus_message *message) |
| { |
| struct dbus_header *header; |
| |
| header = message->header; |
| |
| switch (header->message_type) { |
| case DBUS_MESSAGE_TYPE_METHOD_CALL: |
| return "method_call"; |
| case DBUS_MESSAGE_TYPE_METHOD_RETURN: |
| return "method_return"; |
| case DBUS_MESSAGE_TYPE_ERROR: |
| return "error"; |
| case DBUS_MESSAGE_TYPE_SIGNAL: |
| return "signal"; |
| } |
| |
| return NULL; |
| } |
| |
| uint8_t _dbus_message_get_endian(struct l_dbus_message *message) |
| { |
| struct dbus_header *header = message->header; |
| |
| return header->endian; |
| } |
| |
| uint8_t _dbus_message_get_version(struct l_dbus_message *message) |
| { |
| struct dbus_header *header = message->header; |
| |
| return header->version; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_iter_next_entry(struct l_dbus_message_iter *iter, |
| ...) |
| { |
| va_list args; |
| bool result; |
| |
| if (unlikely(!iter)) |
| return false; |
| |
| va_start(args, iter); |
| result = message_iter_next_entry_valist(iter, args); |
| va_end(args); |
| |
| return result; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_iter_get_variant( |
| struct l_dbus_message_iter *iter, |
| const char *signature, ...) |
| { |
| va_list args; |
| bool result; |
| |
| if (unlikely(!iter)) |
| return false; |
| |
| if (!iter->sig_start || strlen(signature) != iter->sig_len || |
| memcmp(iter->sig_start, signature, iter->sig_len)) |
| return false; |
| |
| va_start(args, signature); |
| result = message_iter_next_entry_valist(iter, args); |
| va_end(args); |
| |
| return result; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_iter_get_fixed_array( |
| struct l_dbus_message_iter *iter, |
| void *out, uint32_t *n_elem) |
| { |
| if (unlikely(!iter)) |
| return false; |
| |
| if (_dbus_message_is_gvariant(iter->message)) |
| return false; |
| |
| return _dbus1_iter_get_fixed_array(iter, out, n_elem); |
| } |
| |
| void _dbus_message_set_sender(struct l_dbus_message *message, |
| const char *sender) |
| { |
| if (!_dbus_message_is_gvariant(message)) |
| return; |
| |
| l_free(message->sender); |
| |
| message->sender = l_strdup(sender); |
| } |
| |
| void _dbus_message_set_destination(struct l_dbus_message *message, |
| const char *destination) |
| { |
| if (!_dbus_message_is_gvariant(message)) |
| return; |
| |
| l_free(message->destination); |
| |
| message->destination = l_strdup(destination); |
| } |
| |
| LIB_EXPORT struct l_dbus_message_builder *l_dbus_message_builder_new( |
| struct l_dbus_message *message) |
| { |
| struct l_dbus_message_builder *ret; |
| |
| if (unlikely(!message)) |
| return NULL; |
| |
| if (message->sealed) |
| return NULL; |
| |
| ret = l_new(struct l_dbus_message_builder, 1); |
| ret->message = l_dbus_message_ref(message); |
| |
| if (_dbus_message_is_gvariant(message)) |
| ret->driver = &gvariant_driver; |
| else |
| ret->driver = &dbus1_driver; |
| |
| ret->builder = ret->driver->new(NULL, 0); |
| |
| return ret; |
| } |
| |
| LIB_EXPORT void l_dbus_message_builder_destroy( |
| struct l_dbus_message_builder *builder) |
| { |
| if (unlikely(!builder)) |
| return; |
| |
| builder->driver->free(builder->builder); |
| l_dbus_message_unref(builder->message); |
| |
| l_free(builder); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_append_basic( |
| struct l_dbus_message_builder *builder, |
| char type, const void *value) |
| { |
| if (unlikely(!builder)) |
| return false; |
| |
| return builder->driver->append_basic(builder->builder, type, value); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_enter_container( |
| struct l_dbus_message_builder *builder, |
| char container_type, |
| const char *signature) |
| { |
| if (unlikely(!builder)) |
| return false; |
| |
| switch (container_type) { |
| case DBUS_CONTAINER_TYPE_ARRAY: |
| return builder->driver->enter_array(builder->builder, |
| signature); |
| case DBUS_CONTAINER_TYPE_DICT_ENTRY: |
| return builder->driver->enter_dict(builder->builder, signature); |
| case DBUS_CONTAINER_TYPE_STRUCT: |
| return builder->driver->enter_struct(builder->builder, |
| signature); |
| case DBUS_CONTAINER_TYPE_VARIANT: |
| return builder->driver->enter_variant(builder->builder, |
| signature); |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_leave_container( |
| struct l_dbus_message_builder *builder, |
| char container_type) |
| { |
| if (unlikely(!builder)) |
| return false; |
| |
| switch (container_type) { |
| case DBUS_CONTAINER_TYPE_ARRAY: |
| return builder->driver->leave_array(builder->builder); |
| case DBUS_CONTAINER_TYPE_DICT_ENTRY: |
| return builder->driver->leave_dict(builder->builder); |
| case DBUS_CONTAINER_TYPE_STRUCT: |
| return builder->driver->leave_struct(builder->builder); |
| case DBUS_CONTAINER_TYPE_VARIANT: |
| return builder->driver->leave_variant(builder->builder); |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_enter_struct( |
| struct l_dbus_message_builder *builder, |
| const char *signature) |
| { |
| return l_dbus_message_builder_enter_container(builder, 'r', signature); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_leave_struct( |
| struct l_dbus_message_builder *builder) |
| { |
| return l_dbus_message_builder_leave_container(builder, 'r'); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_enter_array( |
| struct l_dbus_message_builder *builder, |
| const char *signature) |
| { |
| return l_dbus_message_builder_enter_container(builder, 'a', signature); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_leave_array( |
| struct l_dbus_message_builder *builder) |
| { |
| return l_dbus_message_builder_leave_container(builder, 'a'); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_enter_dict( |
| struct l_dbus_message_builder *builder, |
| const char *signature) |
| { |
| return l_dbus_message_builder_enter_container(builder, 'e', signature); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_leave_dict( |
| struct l_dbus_message_builder *builder) |
| { |
| return l_dbus_message_builder_leave_container(builder, 'e'); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_enter_variant( |
| struct l_dbus_message_builder *builder, |
| const char *signature) |
| { |
| return l_dbus_message_builder_enter_container(builder, 'v', signature); |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_leave_variant( |
| struct l_dbus_message_builder *builder) |
| { |
| return l_dbus_message_builder_leave_container(builder, 'v'); |
| } |
| |
| /** |
| * l_dbus_message_builder_append_from_iter: |
| * @builder: message builder to receive a new value |
| * @from: message iterator to have its position moved by one value |
| * |
| * Copy one value from a message iterator onto a message builder. The |
| * value's signature is also copied. |
| * |
| * Returns: whether the value was correctly copied. On failure both |
| * the @from iterator and the @builder may have their positions |
| * moved to somewhere within the new value if it's of a |
| * container type. |
| **/ |
| LIB_EXPORT bool l_dbus_message_builder_append_from_iter( |
| struct l_dbus_message_builder *builder, |
| struct l_dbus_message_iter *from) |
| { |
| static const char *simple_types = "sogybnqiuxtd"; |
| char type = from->sig_start[from->sig_pos]; |
| char container_type; |
| char signature[256]; |
| struct l_dbus_message_iter iter; |
| void *basic_ptr; |
| uint64_t basic; |
| uint32_t uint32_val; |
| bool (*get_basic)(struct l_dbus_message_iter *, char, void *); |
| bool (*enter_func)(struct l_dbus_message_iter *, |
| struct l_dbus_message_iter *); |
| bool (*enter_struct)(struct l_dbus_message_iter *, |
| struct l_dbus_message_iter *); |
| bool (*enter_array)(struct l_dbus_message_iter *, |
| struct l_dbus_message_iter *); |
| bool (*enter_variant)(struct l_dbus_message_iter *, |
| struct l_dbus_message_iter *); |
| |
| if (_dbus_message_is_gvariant(from->message)) { |
| get_basic = _gvariant_iter_next_entry_basic; |
| enter_struct = _gvariant_iter_enter_struct; |
| enter_array = _gvariant_iter_enter_array; |
| enter_variant = _gvariant_iter_enter_variant; |
| } else { |
| get_basic = _dbus1_iter_next_entry_basic; |
| enter_struct = _dbus1_iter_enter_struct; |
| enter_array = _dbus1_iter_enter_array; |
| enter_variant = _dbus1_iter_enter_variant; |
| } |
| |
| if (strchr(simple_types, type)) { |
| if (strchr("sog", type)) { |
| if (!get_basic(from, type, &basic_ptr)) |
| return false; |
| } else { |
| basic_ptr = &basic; |
| |
| if (!get_basic(from, type, basic_ptr)) |
| return false; |
| } |
| |
| if (!l_dbus_message_builder_append_basic(builder, type, |
| basic_ptr)) |
| return false; |
| |
| return true; |
| } |
| |
| switch (type) { |
| case 'h': |
| if (!get_basic(from, type, &uint32_val)) |
| return false; |
| |
| if (!l_dbus_message_builder_append_basic(builder, type, |
| &builder->message->num_fds)) |
| return false; |
| |
| if (builder->message->num_fds < |
| L_ARRAY_SIZE(builder->message->fds)) { |
| int fd; |
| |
| if (uint32_val < from->message->num_fds) |
| fd = fcntl(from->message->fds[uint32_val], |
| F_DUPFD_CLOEXEC, 3); |
| else |
| fd = -1; |
| |
| builder->message->fds[builder->message->num_fds++] = fd; |
| } |
| |
| return true; |
| case '(': |
| enter_func = enter_struct; |
| container_type = DBUS_CONTAINER_TYPE_STRUCT; |
| break; |
| case '{': |
| enter_func = enter_struct; |
| container_type = DBUS_CONTAINER_TYPE_DICT_ENTRY; |
| break; |
| case 'a': |
| enter_func = enter_array; |
| container_type = DBUS_CONTAINER_TYPE_ARRAY; |
| break; |
| case 'v': |
| enter_func = enter_variant; |
| container_type = DBUS_CONTAINER_TYPE_VARIANT; |
| break; |
| default: |
| return false; |
| } |
| |
| if (!enter_func(from, &iter)) |
| return false; |
| |
| memcpy(signature, iter.sig_start, iter.sig_len); |
| signature[iter.sig_len] = '\0'; |
| |
| if (!l_dbus_message_builder_enter_container(builder, |
| container_type, signature)) |
| return false; |
| |
| if (container_type == DBUS_CONTAINER_TYPE_ARRAY) |
| while(l_dbus_message_builder_append_from_iter(builder, &iter)); |
| else |
| while (iter.sig_pos < iter.sig_len) |
| if (!l_dbus_message_builder_append_from_iter(builder, |
| &iter)) |
| return false; |
| |
| if (!l_dbus_message_builder_leave_container(builder, |
| container_type)) |
| return false; |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_dbus_message_builder_append_from_valist( |
| struct l_dbus_message_builder *builder, |
| const char *signature, va_list args) |
| { |
| struct builder_driver *driver; |
| char subsig[256]; |
| const char *sigend; |
| /* Nesting requires an extra stack entry for the base level */ |
| struct container stack[DBUS_MAX_NESTING + 1]; |
| unsigned int stack_index = 0; |
| |
| if (unlikely(!builder)) |
| return false; |
| |
| driver = builder->driver; |
| |
| stack[stack_index].type = DBUS_CONTAINER_TYPE_STRUCT; |
| stack[stack_index].sig_start = signature; |
| stack[stack_index].sig_end = signature + strlen(signature); |
| stack[stack_index].n_items = 0; |
| |
| while (stack_index != 0 || stack[0].sig_start != stack[0].sig_end) { |
| const char *s; |
| const char *str; |
| |
| if (stack[stack_index].type == DBUS_CONTAINER_TYPE_ARRAY && |
| stack[stack_index].n_items == 0) |
| stack[stack_index].sig_start = |
| stack[stack_index].sig_end; |
| |
| if (stack[stack_index].sig_start == |
| stack[stack_index].sig_end) { |
| bool ret; |
| |
| /* |
| * Sanity check in case of an invalid signature that |
| * isn't caught elsewhere. |
| */ |
| if (unlikely(stack_index == 0)) |
| return false; |
| |
| switch (stack[stack_index].type) { |
| case DBUS_CONTAINER_TYPE_STRUCT: |
| ret = driver->leave_struct(builder->builder); |
| break; |
| case DBUS_CONTAINER_TYPE_DICT_ENTRY: |
| ret = driver->leave_dict(builder->builder); |
| break; |
| case DBUS_CONTAINER_TYPE_VARIANT: |
| ret = driver->leave_variant(builder->builder); |
| break; |
| case DBUS_CONTAINER_TYPE_ARRAY: |
| ret = driver->leave_array(builder->builder); |
| break; |
| default: |
| ret = false; |
| } |
| |
| if (!ret) |
| return false; |
| |
| stack_index -= 1; |
| continue; |
| } |
| |
| s = stack[stack_index].sig_start; |
| |
| if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) |
| stack[stack_index].sig_start += 1; |
| else |
| stack[stack_index].n_items -= 1; |
| |
| switch (*s) { |
| case 'o': |
| case 's': |
| case 'g': |
| str = va_arg(args, const char *); |
| |
| if (!driver->append_basic(builder->builder, *s, str)) |
| return false; |
| break; |
| case 'b': |
| case 'y': |
| { |
| uint8_t y = (uint8_t) va_arg(args, int); |
| |
| if (!driver->append_basic(builder->builder, *s, &y)) |
| return false; |
| |
| break; |
| } |
| case 'n': |
| case 'q': |
| { |
| uint16_t n = (uint16_t) va_arg(args, int); |
| |
| if (!driver->append_basic(builder->builder, *s, &n)) |
| return false; |
| |
| break; |
| } |
| case 'i': |
| case 'u': |
| { |
| uint32_t u = va_arg(args, uint32_t); |
| |
| if (!driver->append_basic(builder->builder, *s, &u)) |
| return false; |
| |
| break; |
| } |
| case 'h': |
| { |
| int fd = va_arg(args, int); |
| struct l_dbus_message *message = builder->message; |
| |
| if (!driver->append_basic(builder->builder, *s, |
| &message->num_fds)) |
| return false; |
| |
| if (message->num_fds < L_ARRAY_SIZE(message->fds)) |
| message->fds[message->num_fds++] = |
| fcntl(fd, F_DUPFD_CLOEXEC, 3); |
| |
| break; |
| } |
| case 'x': |
| case 't': |
| { |
| uint64_t x = va_arg(args, uint64_t); |
| |
| if (!driver->append_basic(builder->builder, *s, &x)) |
| return false; |
| break; |
| } |
| case 'd': |
| { |
| double d = va_arg(args, double); |
| |
| if (!driver->append_basic(builder->builder, *s, &d)) |
| return false; |
| break; |
| } |
| case '(': |
| case '{': |
| if (stack_index == DBUS_MAX_NESTING) |
| return false; |
| |
| sigend = _dbus_signature_end(s); |
| memcpy(subsig, s + 1, sigend - s - 1); |
| subsig[sigend - s - 1] = '\0'; |
| |
| if (*s == '(' && !driver->enter_struct(builder->builder, |
| subsig)) |
| return false; |
| |
| if (*s == '{' && !driver->enter_dict(builder->builder, |
| subsig)) |
| return false; |
| |
| if (stack[stack_index].type != |
| DBUS_CONTAINER_TYPE_ARRAY) |
| stack[stack_index].sig_start = sigend + 1; |
| |
| stack_index += 1; |
| stack[stack_index].sig_start = s + 1; |
| stack[stack_index].sig_end = sigend; |
| stack[stack_index].n_items = 0; |
| stack[stack_index].type = *s == '(' ? |
| DBUS_CONTAINER_TYPE_STRUCT : |
| DBUS_CONTAINER_TYPE_DICT_ENTRY; |
| |
| break; |
| case 'v': |
| if (stack_index == DBUS_MAX_NESTING) |
| return false; |
| |
| str = va_arg(args, const char *); |
| |
| if (!str) |
| return false; |
| |
| if (!driver->enter_variant(builder->builder, str)) |
| return false; |
| |
| stack_index += 1; |
| stack[stack_index].type = DBUS_CONTAINER_TYPE_VARIANT; |
| stack[stack_index].sig_start = str; |
| stack[stack_index].sig_end = str + strlen(str); |
| stack[stack_index].n_items = 0; |
| |
| break; |
| case 'a': |
| if (stack_index == DBUS_MAX_NESTING) |
| return false; |
| |
| sigend = _dbus_signature_end(s + 1) + 1; |
| memcpy(subsig, s + 1, sigend - s - 1); |
| subsig[sigend - s - 1] = '\0'; |
| |
| if (!driver->enter_array(builder->builder, subsig)) |
| return false; |
| |
| if (stack[stack_index].type != |
| DBUS_CONTAINER_TYPE_ARRAY) |
| stack[stack_index].sig_start = sigend; |
| |
| stack_index += 1; |
| stack[stack_index].sig_start = s + 1; |
| stack[stack_index].sig_end = sigend; |
| stack[stack_index].n_items = va_arg(args, unsigned int); |
| stack[stack_index].type = DBUS_CONTAINER_TYPE_ARRAY; |
| |
| break; |
| default: |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| LIB_EXPORT struct l_dbus_message *l_dbus_message_builder_finalize( |
| struct l_dbus_message_builder *builder) |
| { |
| char *generated_signature; |
| |
| if (unlikely(!builder)) |
| return NULL; |
| |
| generated_signature = builder->driver->finish(builder->builder, |
| &builder->message->body, |
| &builder->message->body_size); |
| |
| build_header(builder->message, generated_signature); |
| builder->message->sealed = true; |
| builder->message->signature = generated_signature; |
| builder->message->signature_free = true; |
| |
| return builder->message; |
| } |
| |
| bool _dbus_message_builder_mark(struct l_dbus_message_builder *builder) |
| { |
| if (unlikely(!builder)) |
| return false; |
| |
| return builder->driver->mark(builder->builder); |
| } |
| |
| bool _dbus_message_builder_rewind(struct l_dbus_message_builder *builder) |
| { |
| if (unlikely(!builder)) |
| return false; |
| |
| return builder->driver->rewind(builder->builder); |
| } |