blob: 8de9d4f81ee9e56f7a19d0484fea815145a18d0b [file] [log] [blame]
/*
*
* 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 <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <endian.h>
#include <limits.h>
#include "private.h"
#include "useful.h"
#include "util.h"
#include "queue.h"
#include "string.h"
#include "log.h"
#include "dbus.h"
#include "dbus-private.h"
#include "gvariant-private.h"
static const char *simple_types = "sogybnqiuxtdh";
static const char *variable_types = "sogav";
static const char *fixed_types = "bynqhiuxtd";
/*
* The alignment of a container type is equal to the largest alignment of
* any potential child of that container. This means that, even if an array
* of 32-bit integers is empty, it still must be aligned to the nearest
* multiple of 4 bytes. It also means that the variant type (described below)
* has an alignment of 8 (since it could potentially contain a value of any
* other type and the maximum alignment is 8).
*/
static int get_basic_alignment(const char type)
{
switch (type) {
case 'b':
return 1;
case 'y':
return 1;
case 'n':
case 'q':
return 2;
case 'i':
case 'u':
return 4;
case 'x':
case 't':
case 'd':
return 8;
case 's':
case 'g':
case 'o':
return 1;
case 'h':
return 4;
case 'v':
return 8;
default:
return 0;
}
}
static int get_basic_fixed_size(const char type)
{
switch (type) {
case 'b':
return 1;
case 'y':
return 1;
case 'n':
case 'q':
return 2;
case 'i':
case 'u':
return 4;
case 'x':
case 't':
case 'd':
return 8;
case 'h':
return 4;
default:
return 0;
}
}
static const char *validate_next_type(const char *sig, int *out_alignment)
{
char s = *sig;
int alignment;
if (s == '\0')
return NULL;
if (strchr(simple_types, s) || s == 'v') {
*out_alignment = get_basic_alignment(s);
return sig + 1;
}
switch (s) {
case 'a':
return validate_next_type(++sig, out_alignment);
case '{':
s = *++sig;
/* Dictionary keys can only be simple types */
if (!strchr(simple_types, s))
return NULL;
alignment = get_basic_alignment(s);
sig = validate_next_type(sig + 1, out_alignment);
if (!sig)
return NULL;
if (*sig != '}')
return NULL;
if (alignment > *out_alignment)
*out_alignment = alignment;
return sig + 1;
case '(':
{
int max_alignment = 1, alignment;
sig++;
while (sig && *sig != ')') {
sig = validate_next_type(sig, &alignment);
if (alignment > max_alignment)
max_alignment = alignment;
}
if (!sig)
return NULL;
if (*sig != ')')
return NULL;
*out_alignment = max_alignment;
return sig + 1;
}
}
return NULL;
}
bool _gvariant_valid_signature(const char *sig)
{
const char *s = sig;
int a;
if (strlen(sig) > 255)
return false;
do {
s = validate_next_type(s, &a);
if (!s)
return false;
} while (*s);
return true;
}
int _gvariant_num_children(const char *sig)
{
const char *s = sig;
int a;
int num_children = 0;
if (strlen(sig) > 255)
return false;
do {
s = validate_next_type(s, &a);
if (!s)
return -1;
num_children += 1;
} while (*s);
return num_children;
}
int _gvariant_get_alignment(const char *sig)
{
int max_alignment = 1, alignment;
const char *s = sig;
/* 8 is the largest alignment possible, so quit if we reach it */
while (*s && max_alignment != 8) {
s = validate_next_type(s, &alignment);
if (!s)
return 0;
if (alignment > max_alignment)
max_alignment = alignment;
}
return max_alignment;
}
bool _gvariant_is_fixed_size(const char *sig)
{
while (*sig != 0) {
if (strchr(variable_types, sig[0]))
return false;
sig += 1;
}
return true;
}
int _gvariant_get_fixed_size(const char *sig)
{
const char *s = sig;
const char *p;
int size = 0;
int alignment;
int max_alignment = 1;
int r;
while (*s) {
if (strchr(variable_types, *s))
return 0;
if (strchr(fixed_types, *s)) {
alignment = get_basic_alignment(*s);
if (alignment > max_alignment)
max_alignment = alignment;
size = align_len(size, alignment);
size += get_basic_fixed_size(*s);
s++;
continue;
}
if (*s == '}' || *s == ')')
break;
p = validate_next_type(s, &alignment);
if (!p)
return 0;
if (alignment > max_alignment)
max_alignment = alignment;
size = align_len(size, alignment);
/* Handle special case of unit type */
if (s[0] == '(' && s[1] == ')')
r = 1;
else
r = _gvariant_get_fixed_size(s + 1);
if (r == 0)
return 0;
size += r;
s = p;
}
size = align_len(size, max_alignment);
return size;
}
static inline size_t offset_length(size_t size, size_t n_offsets)
{
if (size + n_offsets <= 0xff)
return 1;
if (size + n_offsets * 2 <= 0xffff)
return 2;
if (size + n_offsets * 4 <= 0xffffffff)
return 4;
return 8;
}
static inline size_t read_word_le(const void *p, size_t sz) {
union {
uint16_t u16;
uint32_t u32;
uint64_t u64;
} x;
if (sz == 1)
return *(uint8_t *) p;
memcpy(&x, p, sz);
if (sz == 2)
return le16toh(x.u16);
if (sz == 4)
return le32toh(x.u32);
return le64toh(x.u64);
}
static inline void write_word_le(void *p, size_t value, size_t sz) {
union {
uint16_t u16;
uint32_t u32;
uint64_t u64;
} x;
if (sz == 1) {
*(uint8_t *) p = value;
return;
}
if (sz == 2)
x.u16 = htole16((uint16_t) value);
else if (sz == 4)
x.u32 = htole32((uint32_t) value);
else
x.u64 = htole64((uint64_t) value);
memcpy(p, &x, sz);
}
static bool gvariant_iter_init_internal(struct l_dbus_message_iter *iter,
struct l_dbus_message *message,
enum dbus_container_type type,
const char *sig_start,
const char *sig_end, const void *data,
size_t len)
{
const char *p;
int i;
int v;
char subsig[256];
unsigned int num_variable = 0;
unsigned int offset_len = offset_length(len, 0);
size_t last_offset;
struct gvariant_type_info {
uint8_t sig_start;
uint8_t sig_end;
bool fixed_size : 1;
unsigned int alignment : 4;
size_t end; /* Index past the end of the type */
} *children;
int n_children;
if (sig_end) {
size_t len = sig_end - sig_start;
memcpy(subsig, sig_start, len);
subsig[len] = '\0';
} else
strcpy(subsig, sig_start);
iter->message = message;
iter->sig_start = sig_start;
iter->sig_len = strlen(subsig);
iter->sig_pos = 0;
iter->data = data;
iter->len = len;
iter->pos = 0;
if (subsig[0] != '\0') {
n_children = _gvariant_num_children(subsig);
if (n_children < 0)
return false;
children = l_new(struct gvariant_type_info, n_children);
} else {
n_children = 0;
children = NULL;
}
for (p = sig_start, i = 0; i < n_children; i++) {
int alignment;
size_t size;
size_t len;
children[i].sig_start = p - sig_start;
p = validate_next_type(p, &alignment);
children[i].sig_end = p - sig_start;
len = children[i].sig_end - children[i].sig_start;
memcpy(subsig, sig_start + children[i].sig_start, len);
subsig[len] = '\0';
children[i].alignment = alignment;
children[i].fixed_size = _gvariant_is_fixed_size(subsig);
if (children[i].fixed_size) {
size = _gvariant_get_fixed_size(subsig);
children[i].end = size;
} else if (i + 1 < n_children)
num_variable += 1;
}
if (len < num_variable * offset_len)
goto fail;
last_offset = len - num_variable * offset_len;
if (num_variable > 0)
iter->offsets = iter->data + len - offset_len;
else
iter->offsets = NULL;
for (i = 0, v = 0; i < n_children; i++) {
size_t o;
if (children[i].fixed_size) {
if (i == 0)
continue;
o = align_len(children[i-1].end,
children[i].alignment);
children[i].end += o;
if (children[i].end > len)
goto fail;
continue;
}
if (num_variable == 0) {
children[i].end = last_offset;
continue;
}
v += 1;
children[i].end = read_word_le(data + len - offset_len * v,
offset_len);
num_variable -= 1;
if (children[i].end > len)
goto fail;
}
iter->container_type = type;
if (type == DBUS_CONTAINER_TYPE_ARRAY &&
!children[0].fixed_size) {
size_t offset = read_word_le(iter->data + iter->len -
offset_len, offset_len);
iter->offsets = iter->data + offset;
}
l_free(children);
return true;
fail:
l_free(children);
return false;
}
bool _gvariant_iter_init(struct l_dbus_message_iter *iter,
struct l_dbus_message *message,
const char *sig_start, const char *sig_end,
const void *data, size_t len)
{
return gvariant_iter_init_internal(iter, message,
DBUS_CONTAINER_TYPE_STRUCT,
sig_start, sig_end, data, len);
}
static const void *next_item(struct l_dbus_message_iter *iter,
size_t *out_item_size)
{
const void *start;
const char *p;
char sig[256];
int alignment;
bool fixed_size;
bool last_member;
unsigned int sig_len;
unsigned int offset_len;
memcpy(sig, iter->sig_start + iter->sig_pos,
iter->sig_len - iter->sig_pos);
sig[iter->sig_len - iter->sig_pos] = '\0';
/*
* Find the next type and make a note whether it is the last in the
* structure. Arrays will always have a single complete type, so
* last_member will always be true.
*/
p = validate_next_type(sig, &alignment);
if (!p)
return NULL;
sig_len = p - sig;
last_member = *p == '\0';
sig[sig_len] = '\0';
fixed_size = _gvariant_is_fixed_size(sig);
if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY)
iter->sig_pos += sig_len;
iter->pos = align_len(iter->pos, alignment);
if (fixed_size) {
*out_item_size = _gvariant_get_fixed_size(sig);
goto done;
}
if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY && last_member) {
unsigned int len = iter->len;
offset_len = offset_length(iter->len, 0);
if (iter->offsets && iter->offsets + offset_len <
iter->data + len)
len = iter->offsets + offset_len - iter->data;
*out_item_size = len - iter->pos;
goto done;
}
if (iter->offsets >= iter->data + iter->len)
return NULL;
offset_len = offset_length(iter->len, 0);
*out_item_size = read_word_le(iter->offsets, offset_len) - iter->pos;
/* In structures the offsets are in reverse order */
if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY)
iter->offsets += offset_len;
else
iter->offsets -= offset_len;
done:
start = iter->data + iter->pos;
if (start >= iter->data + iter->len)
return NULL;
iter->pos += *out_item_size;
return start;
}
bool _gvariant_iter_next_entry_basic(struct l_dbus_message_iter *iter,
char type, void *out)
{
size_t item_size = 0;
const void *start;
uint8_t uint8_val;
uint16_t uint16_val;
uint32_t uint32_val;
uint64_t uint64_val;
int16_t int16_val;
int32_t int32_val;
int64_t int64_val;
if (iter->pos >= iter->len)
return false;
if (iter->sig_start[iter->sig_pos] != type)
return false;
start = next_item(iter, &item_size);
if (!start)
return false;
switch (type) {
case 'o':
case 's':
case 'g':
{
const void *end = memchr(start, 0, item_size);
if (!end)
return false;
*(const char**) out = start;
break;
}
case 'b':
uint8_val = l_get_u8(start);
*(bool *) out = !!uint8_val;
break;
case 'y':
uint8_val = l_get_u8(start);
*(uint8_t *) out = uint8_val;
break;
case 'n':
int16_val = l_get_s16(start);
*(int16_t *) out = int16_val;
break;
case 'q':
uint16_val = l_get_u16(start);
*(uint16_t *) out = uint16_val;
break;
case 'i':
int32_val = l_get_s32(start);
*(int32_t *) out = int32_val;
break;
case 'h':
case 'u':
uint32_val = l_get_u32(start);
*(uint32_t *) out = uint32_val;
break;
case 'x':
int64_val = l_get_s64(start);
*(int64_t *) out = int64_val;
break;
case 't':
uint64_val = l_get_u64(start);
*(uint64_t *) out = uint64_val;
break;
case 'd':
uint64_val = l_get_u64(start);
*(uint64_t *) out = uint64_val;
break;
}
return true;
}
bool _gvariant_iter_enter_struct(struct l_dbus_message_iter *iter,
struct l_dbus_message_iter *structure)
{
bool is_dict = iter->sig_start[iter->sig_pos] == '{';
bool is_struct = iter->sig_start[iter->sig_pos] == '(';
const char *sig_start = iter->sig_start + iter->sig_pos + 1;
const char *sig_end;
const void *start;
size_t item_size;
enum dbus_container_type type;
if (!is_dict && !is_struct)
return false;
start = next_item(iter, &item_size);
if (!start)
return false;
/* For ARRAY containers the sig_pos is never incremented */
if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY)
sig_end = iter->sig_start + iter->sig_len - 1;
else
sig_end = iter->sig_start + iter->sig_pos - 1;
type = is_dict ? DBUS_CONTAINER_TYPE_DICT_ENTRY :
DBUS_CONTAINER_TYPE_STRUCT;
return gvariant_iter_init_internal(structure, iter->message,
type, sig_start, sig_end,
start, item_size);
}
bool _gvariant_iter_enter_variant(struct l_dbus_message_iter *iter,
struct l_dbus_message_iter *variant)
{
size_t item_size;
const void *start, *end, *nul;
char signature[256];
if (iter->sig_start[iter->sig_pos] != 'v')
return false;
start = next_item(iter, &item_size);
if (!start)
return false;
/* Find the signature */
end = start + item_size;
nul = memrchr(start, 0, end - start);
if (!nul)
return false;
if (end - nul - 1 > 255)
return false;
memcpy(signature, nul + 1, end - nul - 1);
signature[end - nul - 1] = '\0';
if (_gvariant_num_children(signature) != 1)
return false;
return gvariant_iter_init_internal(variant, iter->message,
DBUS_CONTAINER_TYPE_VARIANT,
nul + 1, end,
start, nul - start);
}
bool _gvariant_iter_enter_array(struct l_dbus_message_iter *iter,
struct l_dbus_message_iter *array)
{
const char *sig_start;
const char *sig_end;
size_t item_size;
const void *start;
if (iter->sig_start[iter->sig_pos] != 'a')
return false;
sig_start = iter->sig_start + iter->sig_pos + 1;
start = next_item(iter, &item_size);
if (!start)
return false;
/* For ARRAY containers the sig_pos is never incremented */
if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY)
sig_end = iter->sig_start + iter->sig_len;
else
sig_end = iter->sig_start + iter->sig_pos;
return gvariant_iter_init_internal(array, iter->message,
DBUS_CONTAINER_TYPE_ARRAY,
sig_start, sig_end,
start, item_size);
}
bool _gvariant_iter_skip_entry(struct l_dbus_message_iter *iter)
{
size_t size;
if (!next_item(iter, &size))
return false;
return true;
}
struct dbus_builder {
struct l_string *signature;
void *body;
size_t body_size;
size_t body_pos;
struct l_queue *containers;
struct {
struct container *container;
int sig_end;
size_t body_pos;
size_t offset_index;
bool variable_is_last : 1;
} mark;
};
struct container {
size_t *offsets;
size_t offsets_size;
size_t offset_index;
size_t start;
bool variable_is_last : 1;
enum dbus_container_type type;
char signature[256];
uint8_t sigindex;
};
static inline size_t grow_body(struct dbus_builder *builder,
size_t len, unsigned int alignment)
{
size_t size = align_len(builder->body_pos, alignment);
if (size + len > builder->body_size) {
builder->body = l_realloc(builder->body, size + len);
builder->body_size = size + len;
}
if (size - builder->body_pos > 0)
memset(builder->body + builder->body_pos, 0,
size - builder->body_pos);
builder->body_pos = size + len;
return size;
}
static inline bool grow_offsets(struct container *container)
{
size_t needed;
if (container->offset_index < container->offsets_size)
return true;
needed = container->offsets_size * 2;
if (needed > USHRT_MAX)
return false;
if (needed == 0)
needed = 8;
container->offsets = l_realloc(container->offsets,
needed * sizeof(size_t));
container->offsets_size = needed;
return true;
}
static struct container *container_new(enum dbus_container_type type,
const char *signature, size_t start)
{
struct container *ret;
ret = l_new(struct container, 1);
ret->type = type;
strcpy(ret->signature, signature);
ret->start = start;
return ret;
}
static void container_free(struct container *container)
{
l_free(container->offsets);
l_free(container);
}
static void container_append_struct_offsets(struct container *container,
struct dbus_builder *builder)
{
size_t offset_size;
int i;
size_t start;
if (container->variable_is_last)
container->offset_index -= 1;
if (container->offset_index == 0)
return;
offset_size = offset_length(builder->body_pos,
container->offset_index);
i = container->offset_index - 1;
start = grow_body(builder, offset_size * container->offset_index, 1);
for (i = container->offset_index - 1; i >= 0; i--) {
write_word_le(builder->body + start,
container->offsets[i], offset_size);
start += offset_size;
}
}
static void container_append_array_offsets(struct container *container,
struct dbus_builder *builder)
{
size_t offset_size;
unsigned int i;
size_t start;
if (container->offset_index == 0)
return;
offset_size = offset_length(builder->body_pos,
container->offset_index);
start = grow_body(builder, offset_size * container->offset_index, 1);
for (i = 0; i < container->offset_index; i++) {
write_word_le(builder->body + start,
container->offsets[i], offset_size);
start += offset_size;
}
}
struct dbus_builder *_gvariant_builder_new(void *body, size_t body_size)
{
struct dbus_builder *builder;
struct container *root;
builder = l_new(struct dbus_builder, 1);
builder->signature = l_string_new(63);
builder->containers = l_queue_new();
root = container_new(DBUS_CONTAINER_TYPE_STRUCT, "", 0);
l_queue_push_head(builder->containers, root);
builder->body = body;
builder->body_size = body_size;
builder->body_pos = body_size;
builder->mark.container = root;
return builder;
}
void _gvariant_builder_free(struct dbus_builder *builder)
{
if (unlikely(!builder))
return;
l_string_free(builder->signature);
l_queue_destroy(builder->containers,
(l_queue_destroy_func_t) container_free);
l_free(builder->body);
l_free(builder);
}
static bool enter_struct_dict_common(struct dbus_builder *builder,
const char *signature,
enum dbus_container_type type,
const char open,
const char close)
{
size_t qlen = l_queue_length(builder->containers);
struct container *container = l_queue_peek_head(builder->containers);
int alignment;
size_t start;
if (qlen == 1) {
if (l_string_length(builder->signature) +
strlen(signature) + 2 > 255)
return false;
} else {
/* Verify Signatures Match */
char expect[256];
const char *start;
const char *end;
start = container->signature + container->sigindex;
end = validate_next_type(start, &alignment) - 1;
if (*start != open || *end != close)
return false;
memcpy(expect, start + 1, end - start - 1);
expect[end - start - 1] = '\0';
if (strcmp(expect, signature))
return false;
}
alignment = _gvariant_get_alignment(signature);
start = grow_body(builder, 0, alignment);
container = container_new(type, signature, start);
l_queue_push_head(builder->containers, container);
return true;
}
bool _gvariant_builder_enter_struct(struct dbus_builder *builder,
const char *signature)
{
if (signature[0] && !_gvariant_valid_signature(signature))
return false;
return enter_struct_dict_common(builder, signature,
DBUS_CONTAINER_TYPE_STRUCT, '(', ')');
}
bool _gvariant_builder_enter_dict(struct dbus_builder *builder,
const char *signature)
{
if (_gvariant_num_children(signature) != 2)
return false;
if (!strchr(simple_types, signature[0]))
return false;
return enter_struct_dict_common(builder, signature,
DBUS_CONTAINER_TYPE_DICT_ENTRY,
'{', '}');
}
static bool leave_struct_dict_common(struct dbus_builder *builder,
enum dbus_container_type type,
const char open,
const char close)
{
struct container *container = l_queue_peek_head(builder->containers);
size_t qlen = l_queue_length(builder->containers);
struct container *parent;
if (unlikely(qlen <= 1))
return false;
if (unlikely(container->type != type))
return false;
l_queue_pop_head(builder->containers);
qlen -= 1;
parent = l_queue_peek_head(builder->containers);
if (_gvariant_is_fixed_size(container->signature)) {
int alignment = _gvariant_get_alignment(container->signature);
grow_body(builder, 0, alignment);
/* Empty struct or "unit type" is encoded as a zero byte */
if (container->signature[0] == '\0') {
size_t start = grow_body(builder, 1, 1);
memset(builder->body + start, 0, 1);
}
parent->variable_is_last = false;
} else {
size_t offset;
if (!grow_offsets(parent))
return false;
container_append_struct_offsets(container, builder);
offset = builder->body_pos - parent->start;
parent->offsets[parent->offset_index++] = offset;
parent->variable_is_last = true;
}
if (qlen == 1)
l_string_append_printf(builder->signature, "%c%s%c",
open,
container->signature,
close);
else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY)
parent->sigindex += strlen(container->signature) + 2;
container_free(container);
return true;
}
bool _gvariant_builder_leave_struct(struct dbus_builder *builder)
{
return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_STRUCT,
'(', ')');
}
bool _gvariant_builder_leave_dict(struct dbus_builder *builder)
{
return leave_struct_dict_common(builder,
DBUS_CONTAINER_TYPE_DICT_ENTRY,
'{', '}');
}
bool _gvariant_builder_enter_variant(struct dbus_builder *builder,
const char *signature)
{
size_t qlen = l_queue_length(builder->containers);
struct container *container = l_queue_peek_head(builder->containers);
size_t start;
if (_gvariant_num_children(signature) != 1)
return false;
if (qlen == 1) {
if (l_string_length(builder->signature) + 1 > 255)
return false;
} else if (container->signature[container->sigindex] != 'v')
return false;
start = grow_body(builder, 0, 8);
container = container_new(DBUS_CONTAINER_TYPE_VARIANT,
signature, start);
l_queue_push_head(builder->containers, container);
return true;
}
bool _gvariant_builder_leave_variant(struct dbus_builder *builder)
{
struct container *container = l_queue_peek_head(builder->containers);
size_t qlen = l_queue_length(builder->containers);
struct container *parent;
size_t start;
size_t siglen;
size_t offset;
if (unlikely(qlen <= 1))
return false;
if (unlikely(container->type != DBUS_CONTAINER_TYPE_VARIANT))
return false;
l_queue_pop_head(builder->containers);
qlen -= 1;
parent = l_queue_peek_head(builder->containers);
siglen = strlen(container->signature);
start = grow_body(builder, siglen + 1, 1);
memset(builder->body + start, 0, 1);
memcpy(builder->body + start + 1, container->signature, siglen);
if (!grow_offsets(parent))
return false;
offset = builder->body_pos - parent->start;
parent->offsets[parent->offset_index++] = offset;
parent->variable_is_last = true;
if (qlen == 1)
l_string_append_c(builder->signature, 'v');
else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY)
parent->sigindex += 1;
container_free(container);
return true;
}
bool _gvariant_builder_enter_array(struct dbus_builder *builder,
const char *signature)
{
size_t qlen = l_queue_length(builder->containers);
struct container *container = l_queue_peek_head(builder->containers);
size_t start;
int alignment;
if (_gvariant_num_children(signature) != 1)
return false;
if (qlen == 1) {
if (l_string_length(builder->signature) +
strlen(signature) + 1 > 255)
return false;
} else {
/* Verify Signatures Match */
char expect[256];
const char *start;
const char *end;
start = container->signature + container->sigindex;
end = validate_next_type(start, &alignment);
if (*start != 'a')
return false;
memcpy(expect, start + 1, end - start - 1);
expect[end - start - 1] = '\0';
if (strcmp(expect, signature))
return false;
}
alignment = _gvariant_get_alignment(signature);
start = grow_body(builder, 0, alignment);
container = container_new(DBUS_CONTAINER_TYPE_ARRAY, signature, start);
l_queue_push_head(builder->containers, container);
return true;
}
bool _gvariant_builder_leave_array(struct dbus_builder *builder)
{
struct container *container = l_queue_peek_head(builder->containers);
size_t qlen = l_queue_length(builder->containers);
struct container *parent;
size_t offset;
if (unlikely(qlen <= 1))
return false;
if (unlikely(container->type != DBUS_CONTAINER_TYPE_ARRAY))
return false;
l_queue_pop_head(builder->containers);
qlen -= 1;
parent = l_queue_peek_head(builder->containers);
if (!_gvariant_is_fixed_size(container->signature))
container_append_array_offsets(container, builder);
if (!grow_offsets(parent))
return false;
offset = builder->body_pos - parent->start;
parent->offsets[parent->offset_index++] = offset;
parent->variable_is_last = true;
if (qlen == 1)
l_string_append_printf(builder->signature, "a%s",
container->signature);
else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY)
parent->sigindex += strlen(container->signature) + 1;
container_free(container);
return true;
}
bool _gvariant_builder_append_basic(struct dbus_builder *builder,
char type, const void *value)
{
struct container *container = l_queue_peek_head(builder->containers);
size_t start;
unsigned int alignment;
size_t len;
size_t offset;
if (unlikely(!builder))
return false;
if (unlikely(!strchr(simple_types, type)))
return false;
alignment = get_basic_alignment(type);
if (!alignment)
return false;
if (l_queue_length(builder->containers) == 1)
l_string_append_c(builder->signature, type);
else if (container->signature[container->sigindex] != type)
return false;
len = get_basic_fixed_size(type);
if (len) {
start = grow_body(builder, len, alignment);
memcpy(builder->body + start, value, len);
container->variable_is_last = false;
if (container->type != DBUS_CONTAINER_TYPE_ARRAY)
container->sigindex += 1;
return true;
}
if (!grow_offsets(container))
return false;
len = strlen(value) + 1;
start = grow_body(builder, len, alignment);
memcpy(builder->body + start, value, len);
offset = builder->body_pos - container->start;
container->offsets[container->offset_index++] = offset;
container->variable_is_last = true;
if (container->type != DBUS_CONTAINER_TYPE_ARRAY)
container->sigindex += 1;
return true;
}
bool _gvariant_builder_mark(struct dbus_builder *builder)
{
struct container *container = l_queue_peek_head(builder->containers);
builder->mark.container = container;
if (l_queue_length(builder->containers) == 1)
builder->mark.sig_end = l_string_length(builder->signature);
else
builder->mark.sig_end = container->sigindex;
builder->mark.body_pos = builder->body_pos;
builder->mark.offset_index = container->offset_index;
builder->mark.variable_is_last = container->variable_is_last;
return true;
}
bool _gvariant_builder_rewind(struct dbus_builder *builder)
{
struct container *container;
while ((container = l_queue_peek_head(builder->containers)) !=
builder->mark.container) {
container_free(container);
l_queue_pop_head(builder->containers);
}
builder->body_pos = builder->mark.body_pos;
container->offset_index = builder->mark.offset_index;
container->variable_is_last = builder->mark.variable_is_last;
if (l_queue_length(builder->containers) == 1)
l_string_truncate(builder->signature, builder->mark.sig_end);
else
container->sigindex = builder->mark.sig_end;
return true;
}
char *_gvariant_builder_finish(struct dbus_builder *builder,
void **body, size_t *body_size)
{
char *signature;
struct container *root;
uint8_t *variant_buf;
size_t size;
if (unlikely(!builder))
return NULL;
if (unlikely(l_queue_length(builder->containers) != 1))
return NULL;
root = l_queue_peek_head(builder->containers);
signature = l_string_unwrap(builder->signature);
builder->signature = NULL;
if (_gvariant_is_fixed_size(signature)) {
int alignment = _gvariant_get_alignment(signature);
grow_body(builder, 0, alignment);
/* Empty struct or "unit type" is encoded as a zero byte */
if (signature[0] == '\0') {
size_t start = grow_body(builder, 1, 1);
memset(builder->body + start, 0, 1);
}
} else
container_append_struct_offsets(root, builder);
/*
* Make sure there's enough space after the body for the variant
* signature written here but not included in the body size and
* one framing offset value to be written in
* _gvariant_message_finalize.
*/
size = 3 + strlen(signature) + 8;
if (builder->body_pos + size > builder->body_size)
builder->body = l_realloc(builder->body,
builder->body_pos + size);
variant_buf = builder->body + builder->body_pos;
*variant_buf++ = 0;
*variant_buf++ = '(';
variant_buf = mempcpy(variant_buf, signature, strlen(signature));
*variant_buf++ = ')';
*body = builder->body;
*body_size = builder->body_pos;
builder->body = NULL;
builder->body_size = 0;
return signature;
}
/*
* Write the header's framing offset after the body variant which is the
* last piece of data in the message after the header, the padding and
* the builder has written the message body.
*/
size_t _gvariant_message_finalize(size_t header_end,
void *body, size_t body_size,
const char *signature)
{
size_t offset_start;
size_t offset_size;
offset_start = body_size + 3 + strlen(signature);
offset_size = offset_length(align_len(header_end, 8) + offset_start, 1);
write_word_le(body + offset_start, header_end, offset_size);
return align_len(header_end, 8) + offset_start + offset_size;
}