/* * Copyright (C) 2017 Alexander Larsson . * * SPDX-License-Identifier: LGPL-2.0+ * * 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 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 . * * Author: Alexander Larsson . */ #include "config.h" #include "ot-variant-builder.h" #include "libglnx/libglnx.h" /***************************************************************************************** * This code is copied from gvariant in glib. With the following copyright: * * SPDX-License-Identifier: LGPL-2.0+ * * 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 . * * Copyright © 2007, 2008 Ryan Lortie * Copyright © 2010 Codethink Limited *****************************************************************************************/ typedef struct _GVariantTypeInfo GVariantTypeInfo; #define G_VARIANT_TYPE_INFO_CHAR_MAYBE 'm' #define G_VARIANT_TYPE_INFO_CHAR_ARRAY 'a' #define G_VARIANT_TYPE_INFO_CHAR_TUPLE '(' #define G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY '{' #define G_VARIANT_TYPE_INFO_CHAR_VARIANT 'v' #define g_variant_type_info_get_type_char(info) \ (g_variant_type_info_get_type_string(info)[0]) struct _GVariantTypeInfo { gsize fixed_size; guchar alignment; guchar container_class; }; typedef struct { GVariantTypeInfo *type_info; gsize i, a; gint8 b, c; guint8 ending_type; } GVariantMemberInfo; #define G_VARIANT_MEMBER_ENDING_FIXED 0 #define G_VARIANT_MEMBER_ENDING_LAST 1 #define G_VARIANT_MEMBER_ENDING_OFFSET 2 typedef struct { GVariantTypeInfo info; gchar *type_string; gint ref_count; } ContainerInfo; typedef struct { ContainerInfo container; GVariantTypeInfo *element; } ArrayInfo; typedef struct { ContainerInfo container; GVariantMemberInfo *members; gsize n_members; } TupleInfo; /* Hard-code the base types in a constant array */ static const GVariantTypeInfo g_variant_type_info_basic_table[24] = { #define fixed_aligned(x) x, x - 1 #define not_a_type 0, #define unaligned 0, 0 #define aligned(x) 0, x - 1 /* 'b' */ { fixed_aligned(1) }, /* boolean */ /* 'c' */ { not_a_type }, /* 'd' */ { fixed_aligned(8) }, /* double */ /* 'e' */ { not_a_type }, /* 'f' */ { not_a_type }, /* 'g' */ { unaligned }, /* signature string */ /* 'h' */ { fixed_aligned(4) }, /* file handle (int32) */ /* 'i' */ { fixed_aligned(4) }, /* int32 */ /* 'j' */ { not_a_type }, /* 'k' */ { not_a_type }, /* 'l' */ { not_a_type }, /* 'm' */ { not_a_type }, /* 'n' */ { fixed_aligned(2) }, /* int16 */ /* 'o' */ { unaligned }, /* object path string */ /* 'p' */ { not_a_type }, /* 'q' */ { fixed_aligned(2) }, /* uint16 */ /* 'r' */ { not_a_type }, /* 's' */ { unaligned }, /* string */ /* 't' */ { fixed_aligned(8) }, /* uint64 */ /* 'u' */ { fixed_aligned(4) }, /* uint32 */ /* 'v' */ { aligned(8) }, /* variant */ /* 'w' */ { not_a_type }, /* 'x' */ { fixed_aligned(8) }, /* int64 */ /* 'y' */ { fixed_aligned(1) }, /* byte */ #undef fixed_aligned #undef not_a_type #undef unaligned #undef aligned }; static GRecMutex g_variant_type_info_lock; static GHashTable *g_variant_type_info_table; static GVariantTypeInfo * g_variant_type_info_ref (GVariantTypeInfo *info); static void g_variant_type_info_unref (GVariantTypeInfo *info); static GVariantTypeInfo * g_variant_type_info_get (const GVariantType *type); #define GV_ARRAY_INFO_CLASS 'a' static ArrayInfo * GV_ARRAY_INFO (GVariantTypeInfo *info) { return (ArrayInfo *) info; } static void array_info_free (GVariantTypeInfo *info) { ArrayInfo *array_info; g_assert (info->container_class == GV_ARRAY_INFO_CLASS); array_info = (ArrayInfo *) info; g_variant_type_info_unref (array_info->element); g_slice_free (ArrayInfo, array_info); } static ContainerInfo * array_info_new (const GVariantType *type) { ArrayInfo *info; info = g_slice_new (ArrayInfo); info->container.info.container_class = GV_ARRAY_INFO_CLASS; info->element = g_variant_type_info_get (g_variant_type_element (type)); info->container.info.alignment = info->element->alignment; info->container.info.fixed_size = 0; return (ContainerInfo *) info; } /* == tuple == */ #define GV_TUPLE_INFO_CLASS 'r' static TupleInfo * GV_TUPLE_INFO (GVariantTypeInfo *info) { return (TupleInfo *) info; } static void tuple_info_free (GVariantTypeInfo *info) { TupleInfo *tuple_info; gint i; g_assert (info->container_class == GV_TUPLE_INFO_CLASS); tuple_info = (TupleInfo *) info; for (i = 0; i < tuple_info->n_members; i++) g_variant_type_info_unref (tuple_info->members[i].type_info); g_slice_free1 (sizeof (GVariantMemberInfo) * tuple_info->n_members, tuple_info->members); g_slice_free (TupleInfo, tuple_info); } static void tuple_allocate_members (const GVariantType *type, GVariantMemberInfo **members, gsize *n_members) { const GVariantType *item_type; gsize i = 0; *n_members = g_variant_type_n_items (type); *members = g_slice_alloc (sizeof (GVariantMemberInfo) * *n_members); item_type = g_variant_type_first (type); while (item_type) { GVariantMemberInfo *member = &(*members)[i++]; member->type_info = g_variant_type_info_get (item_type); item_type = g_variant_type_next (item_type); if (member->type_info->fixed_size) member->ending_type = G_VARIANT_MEMBER_ENDING_FIXED; else if (item_type == NULL) member->ending_type = G_VARIANT_MEMBER_ENDING_LAST; else member->ending_type = G_VARIANT_MEMBER_ENDING_OFFSET; } g_assert (i == *n_members); } /* this is g_variant_type_info_query for a given member of the tuple. * before the access is done, it is ensured that the item is within * range and %FALSE is returned if not. */ static gboolean tuple_get_item (TupleInfo *info, GVariantMemberInfo *item, gsize *d, gsize *e) { if (&info->members[info->n_members] == item) return FALSE; *d = item->type_info->alignment; *e = item->type_info->fixed_size; return TRUE; } /* Read the documentation for #GVariantMemberInfo in gvarianttype.h * before attempting to understand this. * * This function adds one set of "magic constant" values (for one item * in the tuple) to the table. * * The algorithm in tuple_generate_table() calculates values of 'a', 'b' * and 'c' for each item, such that the procedure for finding the item * is to start at the end of the previous variable-sized item, add 'a', * then round up to the nearest multiple of 'b', then add 'c'. * Note that 'b' is stored in the usual "one less than" form. ie: * * start = ROUND_UP(prev_end + a, (b + 1)) + c; * * We tweak these values a little to allow for a slightly easier * computation and more compact storage. */ static void tuple_table_append (GVariantMemberInfo **items, gsize i, gsize a, gsize b, gsize c) { GVariantMemberInfo *item = (*items)++; /* We can shift multiples of the alignment size from 'c' into 'a'. * As long as we're shifting whole multiples, it won't affect the * result. This means that we can take the "aligned" portion off of * 'c' and add it into 'a'. * * Imagine (for sake of clarity) that ROUND_10 rounds up to the * nearest 10. It is clear that: * * ROUND_10(a) + c == ROUND_10(a + 10*(c / 10)) + (c % 10) * * ie: remove the 10s portion of 'c' and add it onto 'a'. * * To put some numbers on it, imagine we start with a = 34 and c = 27: * * ROUND_10(34) + 27 = 40 + 27 = 67 * * but also, we can split 27 up into 20 and 7 and do this: * * ROUND_10(34 + 20) + 7 = ROUND_10(54) + 7 = 60 + 7 = 67 * ^^ ^ * without affecting the result. We do that here. * * This reduction in the size of 'c' means that we can store it in a * gchar instead of a gsize. Due to how the structure is packed, this * ends up saving us 'two pointer sizes' per item in each tuple when * allocating using GSlice. */ a += ~b & c; /* take the "aligned" part of 'c' and add to 'a' */ c &= b; /* chop 'c' to contain only the unaligned part */ /* Finally, we made one last adjustment. Recall: * * start = ROUND_UP(prev_end + a, (b + 1)) + c; * * Forgetting the '+ c' for the moment: * * ROUND_UP(prev_end + a, (b + 1)); * * we can do a "round up" operation by adding 1 less than the amount * to round up to, then rounding down. ie: * * #define ROUND_UP(x, y) ROUND_DOWN(x + (y-1), y) * * Of course, for rounding down to a power of two, we can just mask * out the appropriate number of low order bits: * * #define ROUND_DOWN(x, y) (x & ~(y - 1)) * * Which gives us * * #define ROUND_UP(x, y) (x + (y - 1) & ~(y - 1)) * * but recall that our alignment value 'b' is already "one less". * This means that to round 'prev_end + a' up to 'b' we can just do: * * ((prev_end + a) + b) & ~b * * Associativity, and putting the 'c' back on: * * (prev_end + (a + b)) & ~b + c * * Now, since (a + b) is constant, we can just add 'b' to 'a' now and * store that as the number to add to prev_end. Then we use ~b as the * number to take a bitwise 'and' with. Finally, 'c' is added on. * * Note, however, that all the low order bits of the 'aligned' value * are masked out and that all of the high order bits of 'c' have been * "moved" to 'a' (in the previous step). This means that there are * no overlapping bits in the addition -- so we can do a bitwise 'or' * equivalently. * * This means that we can now compute the start address of a given * item in the tuple using the algorithm given in the documentation * for #GVariantMemberInfo: * * item_start = ((prev_end + a) & b) | c; */ item->i = i; item->a = a + b; item->b = ~b; item->c = c; } static gsize tuple_align (gsize offset, guint alignment) { return offset + ((-offset) & alignment); } /* This function is the heart of the algorithm for calculating 'i', 'a', * 'b' and 'c' for each item in the tuple. * * Imagine we want to find the start of the "i" in the type "(su(qx)ni)". * That's a string followed by a uint32, then a tuple containing a * uint16 and a int64, then an int16, then our "i". In order to get to * our "i" we: * * Start at the end of the string, align to 4 (for the uint32), add 4. * Align to 8, add 16 (for the tuple). Align to 2, add 2 (for the * int16). Then we're there. It turns out that, given 3 simple rules, * we can flatten this iteration into one addition, one alignment, then * one more addition. * * The loop below plays through each item in the tuple, querying its * alignment and fixed_size into 'd' and 'e', respectively. At all * times the variables 'a', 'b', and 'c' are maintained such that in * order to get to the current point, you add 'a', align to 'b' then add * 'c'. 'b' is kept in "one less than" form. For each item, the proper * alignment is applied to find the values of 'a', 'b' and 'c' to get to * the start of that item. Those values are recorded into the table. * The fixed size of the item (if applicable) is then added on. * * These 3 rules are how 'a', 'b' and 'c' are modified for alignment and * addition of fixed size. They have been proven correct but are * presented here, without proof: * * 1) in order to "align to 'd'" where 'd' is less than or equal to the * largest level of alignment seen so far ('b'), you align 'c' to * 'd'. * 2) in order to "align to 'd'" where 'd' is greater than the largest * level of alignment seen so far, you add 'c' aligned to 'b' to the * value of 'a', set 'b' to 'd' (ie: increase the 'largest alignment * seen') and reset 'c' to 0. * 3) in order to "add 'e'", just add 'e' to 'c'. */ static void tuple_generate_table (TupleInfo *info) { GVariantMemberInfo *items = info->members; gsize i = -1, a = 0, b = 0, c = 0, d, e; /* iterate over each item in the tuple. * 'd' will be the alignment of the item (in one-less form) * 'e' will be the fixed size (or 0 for variable-size items) */ while (tuple_get_item (info, items, &d, &e)) { /* align to 'd' */ if (d <= b) c = tuple_align (c, d); /* rule 1 */ else a += tuple_align (c, b), b = d, c = 0; /* rule 2 */ /* the start of the item is at this point (ie: right after we * have aligned for it). store this information in the table. */ tuple_table_append (&items, i, a, b, c); /* "move past" the item by adding in its size. */ if (e == 0) /* variable size: * * we'll have an offset stored to mark the end of this item, so * just bump the offset index to give us a new starting point * and reset all the counters. */ i++, a = b = c = 0; else /* fixed size */ c += e; /* rule 3 */ } } static void tuple_set_base_info (TupleInfo *info) { GVariantTypeInfo *base = &info->container.info; if (info->n_members > 0) { GVariantMemberInfo *m; /* the alignment requirement of the tuple is the alignment * requirement of its largest item. */ base->alignment = 0; for (m = info->members; m < &info->members[info->n_members]; m++) /* can find the max of a list of "one less than" powers of two * by 'or'ing them */ base->alignment |= m->type_info->alignment; m--; /* take 'm' back to the last item */ /* the structure only has a fixed size if no variable-size * offsets are stored and the last item is fixed-sized too (since * an offset is never stored for the last item). */ if (m->i == -1 && m->type_info->fixed_size) /* in that case, the fixed size can be found by finding the * start of the last item (in the usual way) and adding its * fixed size. * * if a tuple has a fixed size then it is always a multiple of * the alignment requirement (to make packing into arrays * easier) so we round up to that here. */ base->fixed_size = tuple_align (((m->a & m->b) | m->c) + m->type_info->fixed_size, base->alignment); else /* else, the tuple is not fixed size */ base->fixed_size = 0; } else { /* the empty tuple: '()'. * * has a size of 1 and an no alignment requirement. * * It has a size of 1 (not 0) for two practical reasons: * * 1) So we can determine how many of them are in an array * without dividing by zero or without other tricks. * * 2) Even if we had some trick to know the number of items in * the array (as GVariant did at one time) this would open a * potential denial of service attack: an attacker could send * you an extremely small array (in terms of number of bytes) * containing trillions of zero-sized items. If you iterated * over this array you would effectively infinite-loop your * program. By forcing a size of at least one, we bound the * amount of computation done in response to a message to a * reasonable function of the size of that message. */ base->alignment = 0; base->fixed_size = 1; } } static ContainerInfo * tuple_info_new (const GVariantType *type) { TupleInfo *info; info = g_slice_new (TupleInfo); info->container.info.container_class = GV_TUPLE_INFO_CLASS; tuple_allocate_members (type, &info->members, &info->n_members); tuple_generate_table (info); tuple_set_base_info (info); return (ContainerInfo *) info; } static const GVariantMemberInfo * g_variant_type_info_member_info (GVariantTypeInfo *info, gsize index) { TupleInfo *tuple_info = GV_TUPLE_INFO (info); if (index < tuple_info->n_members) return &tuple_info->members[index]; return NULL; } static GVariantTypeInfo * g_variant_type_info_element (GVariantTypeInfo *info) { return GV_ARRAY_INFO (info)->element; } static GVariantTypeInfo * g_variant_type_info_ref (GVariantTypeInfo *info) { if (info->container_class) { ContainerInfo *container = (ContainerInfo *) info; g_assert_cmpint (container->ref_count, >, 0); g_atomic_int_inc (&container->ref_count); } return info; } static void g_variant_type_info_unref (GVariantTypeInfo *info) { if (info->container_class) { ContainerInfo *container = (ContainerInfo *) info; g_rec_mutex_lock (&g_variant_type_info_lock); if (g_atomic_int_dec_and_test (&container->ref_count)) { g_hash_table_remove (g_variant_type_info_table, container->type_string); if (g_hash_table_size (g_variant_type_info_table) == 0) { g_hash_table_unref (g_variant_type_info_table); g_variant_type_info_table = NULL; } g_rec_mutex_unlock (&g_variant_type_info_lock); g_free (container->type_string); if (info->container_class == GV_ARRAY_INFO_CLASS) array_info_free (info); else if (info->container_class == GV_TUPLE_INFO_CLASS) tuple_info_free (info); else g_assert_not_reached (); } else g_rec_mutex_unlock (&g_variant_type_info_lock); } } static GVariantTypeInfo * g_variant_type_info_get (const GVariantType *type) { char type_char; type_char = g_variant_type_peek_string (type)[0]; if (type_char == G_VARIANT_TYPE_INFO_CHAR_MAYBE || type_char == G_VARIANT_TYPE_INFO_CHAR_ARRAY || type_char == G_VARIANT_TYPE_INFO_CHAR_TUPLE || type_char == G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY) { GVariantTypeInfo *info; gchar *type_string; type_string = g_variant_type_dup_string (type); g_rec_mutex_lock (&g_variant_type_info_lock); if (g_variant_type_info_table == NULL) g_variant_type_info_table = g_hash_table_new (g_str_hash, g_str_equal); info = g_hash_table_lookup (g_variant_type_info_table, type_string); if (info == NULL) { ContainerInfo *container; if (type_char == G_VARIANT_TYPE_INFO_CHAR_MAYBE || type_char == G_VARIANT_TYPE_INFO_CHAR_ARRAY) { container = array_info_new (type); } else /* tuple or dict entry */ { container = tuple_info_new (type); } info = (GVariantTypeInfo *) container; container->type_string = type_string; container->ref_count = 1; g_hash_table_insert (g_variant_type_info_table, type_string, info); type_string = NULL; } else g_variant_type_info_ref (info); g_rec_mutex_unlock (&g_variant_type_info_lock); g_free (type_string); return info; } else { const GVariantTypeInfo *info; int index; index = type_char - 'b'; g_assert (G_N_ELEMENTS (g_variant_type_info_basic_table) == 24); g_assert_cmpint (0, <=, index); g_assert_cmpint (index, <, 24); info = g_variant_type_info_basic_table + index; return (GVariantTypeInfo *) info; } } static inline void gvs_write_unaligned_le (guchar *bytes, gsize value, guint size) { union { guchar bytes[GLIB_SIZEOF_SIZE_T]; gsize integer; } tmpvalue; tmpvalue.integer = GSIZE_TO_LE (value); memcpy (bytes, &tmpvalue.bytes, size); } static guint gvs_get_offset_size (gsize size) { if (size > G_MAXUINT32) return 8; else if (size > G_MAXUINT16) return 4; else if (size > G_MAXUINT8) return 2; else if (size > 0) return 1; return 0; } static gsize gvs_calculate_total_size (gsize body_size, gsize offsets) { if (body_size + 1 * offsets <= G_MAXUINT8) return body_size + 1 * offsets; if (body_size + 2 * offsets <= G_MAXUINT16) return body_size + 2 * offsets; if (body_size + 4 * offsets <= G_MAXUINT32) return body_size + 4 * offsets; return body_size + 8 * offsets; } /***************************************************************************************** * End of glib code *****************************************************************************************/ typedef struct _OtVariantBuilderInfo OtVariantBuilderInfo; struct _OtVariantBuilderInfo { OtVariantBuilderInfo *parent; OtVariantBuilder *builder; GVariantType *type; GVariantTypeInfo *type_info; guint64 offset; int n_children; GArray *child_ends; /* type constraint explicitly specified by 'type'. * for tuple types, this moves along as we add more items. */ const GVariantType *expected_type; /* type constraint implied by previous array item. */ const GVariantType *prev_item_type; GVariantType *prev_item_type_base; /* constraints on the number of children. max = -1 for unlimited. */ gsize min_items; gsize max_items; /* set to '1' if all items in the container will have the same type * (ie: maybe, array, variant) '0' if not (ie: tuple, dict entry) */ guint uniform_item_types : 1; } ; struct _OtVariantBuilder { gint ref_count; int fd; /* This is only useful for the topmost builder and points to the top * of the builder stack. Public APIs take the topmost builder reference * and use this to find the currently active builder */ OtVariantBuilderInfo *head; }; static OtVariantBuilderInfo * ot_variant_builder_info_new (OtVariantBuilder *builder, const GVariantType *type) { OtVariantBuilderInfo *info; g_return_val_if_fail (g_variant_type_is_container (type), NULL); info = (OtVariantBuilderInfo *) g_slice_new0 (OtVariantBuilderInfo); info->builder = builder; info->type = g_variant_type_copy (type); info->type_info = g_variant_type_info_get (type); info->offset = 0; info->n_children = 0; info->child_ends = g_array_new (FALSE, TRUE, sizeof (guint64)); switch (*(const gchar *) type) { case G_VARIANT_CLASS_VARIANT: info->uniform_item_types = TRUE; info->expected_type = NULL; info->min_items = 1; info->max_items = 1; break; case G_VARIANT_CLASS_ARRAY: info->uniform_item_types = TRUE; info->expected_type = g_variant_type_element (info->type); info->min_items = 0; info->max_items = -1; break; case G_VARIANT_CLASS_MAYBE: info->uniform_item_types = TRUE; info->expected_type = g_variant_type_element (info->type); info->min_items = 0; info->max_items = 1; break; case G_VARIANT_CLASS_DICT_ENTRY: info->uniform_item_types = FALSE; info->expected_type = g_variant_type_key (info->type); info->min_items = 2; info->max_items = 2; break; case 'r': /* G_VARIANT_TYPE_TUPLE was given */ info->uniform_item_types = FALSE; info->expected_type = NULL; info->min_items = 0; info->max_items = -1; break; case G_VARIANT_CLASS_TUPLE: /* a definite tuple type was given */ info->expected_type = g_variant_type_first (info->type); info->min_items = g_variant_type_n_items (type); info->max_items = info->min_items; info->uniform_item_types = FALSE; break; default: g_assert_not_reached (); } return info; } static void ot_variant_builder_info_free (OtVariantBuilderInfo *info) { if (info->parent) ot_variant_builder_info_free (info); g_variant_type_free (info->type); g_array_unref (info->child_ends); g_free (info->prev_item_type_base); g_slice_free (OtVariantBuilderInfo, info); } OtVariantBuilder * ot_variant_builder_new (const GVariantType *type, int fd) { OtVariantBuilder *builder; g_return_val_if_fail (g_variant_type_is_container (type), NULL); builder = (OtVariantBuilder *) g_slice_new0 (OtVariantBuilder); builder->head = ot_variant_builder_info_new (builder, type); builder->ref_count = 1; builder->fd = fd; return builder; } void ot_variant_builder_unref (OtVariantBuilder *builder) { if (--builder->ref_count) return; ot_variant_builder_info_free (builder->head); g_slice_free (OtVariantBuilder, builder); } OtVariantBuilder * ot_variant_builder_ref (OtVariantBuilder *builder) { builder->ref_count++; return builder; } /* This is called before adding a child to the container. It updates the internal state and does the needed alignment */ static gboolean ot_variant_builder_pre_add (OtVariantBuilderInfo *info, const GVariantType *type, GError **error) { guint alignment = 0; if (!info->uniform_item_types) { /* advance our expected type pointers */ if (info->expected_type) info->expected_type = g_variant_type_next (info->expected_type); if (info->prev_item_type) info->prev_item_type = g_variant_type_next (info->prev_item_type); } else { g_free (info->prev_item_type_base); info->prev_item_type_base = (GVariantType *)g_strdup ((char *)type); info->prev_item_type = info->prev_item_type_base; } if (g_variant_type_is_tuple (info->type) || g_variant_type_is_dict_entry (info->type)) { const GVariantMemberInfo *member_info; member_info = g_variant_type_info_member_info (info->type_info, info->n_children); g_assert (member_info); alignment = member_info->type_info->alignment; } else if (g_variant_type_is_array (info->type)) { GVariantTypeInfo *element_info = g_variant_type_info_element (info->type_info); alignment = element_info->alignment; } else if (g_variant_type_is_variant (info->type)) { alignment = info->type_info->alignment; } else return glnx_throw (error, "adding to type %s not supported", (char *)info->type); while (info->offset & alignment) { if (glnx_loop_write (info->builder->fd, "\0", 1) < 0) return glnx_throw_errno (error); info->offset++; } return TRUE; } static void ot_variant_builder_add_child_end (OtVariantBuilderInfo *info) { guint64 v = info->offset; g_array_append_val (info->child_ends, v); } /* This is called after adding a child to the container. It updates offset, n_children and keeps track of an offset table if needed */ static gboolean ot_variant_builder_post_add (OtVariantBuilderInfo *info, const GVariantType *type, guint64 bytes_added, GError **error) { info->offset += bytes_added; if (g_variant_type_is_tuple (info->type) || g_variant_type_is_dict_entry (info->type)) { const GVariantMemberInfo *member_info; member_info = g_variant_type_info_member_info (info->type_info, info->n_children); g_assert (member_info); if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET) ot_variant_builder_add_child_end (info); } else if (g_variant_type_is_array (info->type)) { GVariantTypeInfo *element_info = g_variant_type_info_element (info->type_info); if (!element_info->fixed_size) ot_variant_builder_add_child_end (info); } else if (g_variant_type_is_variant (info->type)) { /* Zero separate */ if (glnx_loop_write (info->builder->fd, "\0", 1) < 0) return glnx_throw_errno (error); if (glnx_loop_write (info->builder->fd, (char *)type, strlen ((char *)type)) < 0) return glnx_throw_errno (error); info->offset += 1 + strlen ((char *)type); } else return glnx_throw (error, "adding to type %s not supported", (char *)info->type); info->n_children++; return TRUE; } gboolean ot_variant_builder_add_from_fd (OtVariantBuilder *builder, const GVariantType *type, int fd, guint64 size, GError **error) { OtVariantBuilderInfo *info = builder->head; g_return_val_if_fail (info->n_children < info->max_items, FALSE); g_return_val_if_fail (!info->expected_type || g_variant_type_is_subtype_of (type, info->expected_type), FALSE); g_return_val_if_fail (!info->prev_item_type || g_variant_type_is_subtype_of (info->prev_item_type, type), FALSE); if (!ot_variant_builder_pre_add (info, type, error)) return FALSE; if (glnx_regfile_copy_bytes (fd, builder->fd, size) < 0) return glnx_throw_errno (error); if (!ot_variant_builder_post_add (info, type, size, error)) return FALSE; return TRUE; } gboolean ot_variant_builder_add_value (OtVariantBuilder *builder, GVariant *value, GError **error) { OtVariantBuilderInfo *info = builder->head; gconstpointer data; gsize data_size; /* We ref-sink value, just like g_variant_builder_add_value does */ g_autoptr(GVariant) keep_around_until_return G_GNUC_UNUSED = g_variant_ref_sink (value); g_return_val_if_fail (info->n_children < info->max_items, FALSE); g_return_val_if_fail (!info->expected_type || g_variant_is_of_type (value, info->expected_type), FALSE); g_return_val_if_fail (!info->prev_item_type || g_variant_is_of_type (value, info->prev_item_type), FALSE); if (!ot_variant_builder_pre_add (info, g_variant_get_type (value), error)) return FALSE; data = g_variant_get_data (value); data_size = g_variant_get_size (value); if (data) { if (glnx_loop_write (builder->fd, data, data_size) < 0) return glnx_throw_errno (error); } if (!ot_variant_builder_post_add (info, g_variant_get_type (value), data_size, error)) return FALSE; return TRUE; } gboolean ot_variant_builder_add (OtVariantBuilder *builder, GError **error, const gchar *format_string, ...) { GVariant *variant; va_list ap; va_start (ap, format_string); variant = g_variant_new_va (format_string, NULL, &ap); va_end (ap); return ot_variant_builder_add_value (builder, variant, error); } gboolean ot_variant_builder_open (OtVariantBuilder *builder, const GVariantType *type, GError **error) { OtVariantBuilderInfo *info = builder->head; OtVariantBuilderInfo *new_info; g_assert (info->n_children < info->max_items); g_assert (!info->expected_type || g_variant_type_is_subtype_of (type, info->expected_type)); g_assert (!info->prev_item_type || g_variant_type_is_subtype_of (info->prev_item_type, type)); if (!ot_variant_builder_pre_add (info, type, error)) return FALSE; new_info = ot_variant_builder_info_new (builder, type); new_info->parent = info; /* push the prev_item_type down into the subcontainer */ if (info->prev_item_type) { if (!new_info->uniform_item_types) /* tuples and dict entries */ new_info->prev_item_type = g_variant_type_first (info->prev_item_type); else if (!g_variant_type_is_variant (new_info->type)) /* maybes and arrays */ new_info->prev_item_type = g_variant_type_element (info->prev_item_type); } builder->head = new_info; return TRUE; } gboolean ot_variant_builder_close (OtVariantBuilder *builder, GError **error) { OtVariantBuilderInfo *info = builder->head; OtVariantBuilderInfo *parent; g_return_val_if_fail (info->parent != NULL, FALSE); if (!ot_variant_builder_end (builder, error)) return FALSE; parent = info->parent; if (!ot_variant_builder_post_add (parent, info->type, info->offset, error)) return FALSE; builder->head = parent; info->parent = NULL; ot_variant_builder_info_free (info); return TRUE; } gboolean ot_variant_builder_end (OtVariantBuilder *builder, GError **error) { OtVariantBuilderInfo *info = builder->head; gboolean add_offset_table = FALSE; gboolean reverse_offset_table = FALSE; g_return_val_if_fail (info->n_children >= info->min_items, FALSE); g_return_val_if_fail (!info->uniform_item_types || info->prev_item_type != NULL || g_variant_type_is_definite (info->type), FALSE); if (g_variant_type_is_tuple (info->type) || g_variant_type_is_dict_entry (info->type)) { add_offset_table = TRUE; reverse_offset_table = TRUE; } else if (g_variant_type_is_array (info->type)) { GVariantTypeInfo *element_info = g_variant_type_info_element (info->type_info); if (!element_info->fixed_size) add_offset_table = TRUE; } else if (g_variant_type_is_variant (info->type)) { /* noop */ } else return glnx_throw (error, "closing type %s not supported", (char *)info->type); if (add_offset_table) { const gsize total_size = gvs_calculate_total_size (info->offset, info->child_ends->len); const gsize offset_size = gvs_get_offset_size (total_size); const gsize offset_table_size = total_size - info->offset; g_autofree guchar *offset_table = g_malloc (offset_table_size); guchar *p = offset_table; if (reverse_offset_table) { for (int i = info->child_ends->len - 1; i >= 0; i--) { gvs_write_unaligned_le (p, g_array_index (info->child_ends, guint64, i), offset_size); p += offset_size; } } else { for (int i = 0; i < info->child_ends->len; i++) { gvs_write_unaligned_le (p, g_array_index (info->child_ends, guint64, i), offset_size); p += offset_size; } } if (glnx_loop_write (builder->fd, offset_table, offset_table_size) < 0) return glnx_throw_errno (error); info->offset += offset_table_size; } else g_assert (info->child_ends->len == 0); return TRUE; }