Commit 80538aba authored by Allison Karlitskaya's avatar Allison Karlitskaya

merge the GVariant serialiser

parent eea6042f
......@@ -174,6 +174,8 @@ libglib_2_0_la_SOURCES = \
gunicodeprivate.h \
gurifuncs.c \
gutils.c \
gvariant-serialiser.h \
gvariant-serialiser.c \
gvarianttypeinfo.h \
gvarianttypeinfo.c \
gvarianttype.c \
......
/*
* Copyright © 2007, 2008 Ryan Lortie
* Copyright © 2010 Codethink Limited
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
/* Prologue {{{1 */
#include "gvariant-serialiser.h"
#include <glib/gtestutils.h>
#include <glib/gstrfuncs.h>
#include <glib/gtypes.h>
#include <string.h>
#include "galias.h"
/* GVariantSerialiser
*
* After this prologue section, this file has roughly 2 parts.
*
* The first part is split up into sections according to various
* container types. Maybe, Array, Tuple, Variant. The Maybe and Array
* sections are subdivided for element types being fixed or
* variable-sized types.
*
* Each section documents the format of that particular type of
* container and implements 5 functions for dealing with it:
*
* n_children:
* - determines (according to serialised data) how many child values
* are inside a particular container value.
*
* get_child:
* - gets the type of and the serialised data corresponding to a
* given child value within the container value.
*
* needed_size:
* - determines how much space would be required to serialise a
* container of this type, containing the given children so that
* buffers can be preallocated before serialising.
*
* serialise:
* - write the serialised data for a container of this type,
* containing the given children, to a buffer.
*
* is_normal:
* - check the given data to ensure that it is in normal form. For a
* given set of child values, there is exactly one normal form for
* the serialised data of a container. Other forms are possible
* while maintaining the same children (for example, by inserting
* something other than zero bytes as padding) but only one form is
* the normal form.
*
* The second part contains the main entry point for each of the above 5
* functions and logic to dispatch it to the handler for the appropriate
* container type code.
*
* The second part also contains a routine to byteswap serialised
* values. This code makes use of the n_children() and get_child()
* functions above to do its work so no extra support is needed on a
* per-container-type basis.
*
* There is also additional code for checking for normal form. All
* numeric types are always in normal form since the full range of
* values is permitted (eg: 0 to 255 is a valid byte). Special checks
* need to be performed for booleans (only 0 or 1 allowed), strings
* (properly nul-terminated) and object paths and signature strings
* (meeting the DBus specification requirements).
*/
/* < private >
* GVariantSerialised:
* @type_info: the #GVariantTypeInfo of this value
* @data: the serialised data of this value, or %NULL
* @size: the size of this value
*
* A structure representing a GVariant in serialised form. This
* structure is used with #GVariantSerialisedFiller functions and as the
* primary interface to the serialiser. See #GVariantSerialisedFiller
* for a description of its use there.
*
* When used with the serialiser API functions, the following invariants
* apply to all #GVariantTypeSerialised structures passed to and
* returned from the serialiser.
*
* @type_info must be non-%NULL.
*
* @data must be properly aligned for the type described by @type_info.
*
* If @type_info describes a fixed-sized type then @size must always be
* equal to the fixed size of that type.
*
* For fixed-sized types (and only fixed-sized types), @data may be
* %NULL even if @size is non-zero. This happens when a framing error
* occurs while attempting to extract a fixed-sized value out of a
* variable-sized container. There is no data to return for the
* fixed-sized type, yet @size must be non-zero. The effect of this
* combination should be as if @data were a pointer to an
* appropriately-sized zero-filled region.
*/
/* < private >
* g_variant_serialised_check:
* @serialised: a #GVariantSerialised struct
*
* Checks @serialised for validity according to the invariants described
* above.
*/
static void
g_variant_serialised_check (GVariantSerialised serialised)
{
gsize fixed_size;
guint alignment;
g_assert (serialised.type_info != NULL);
g_variant_type_info_query (serialised.type_info, &alignment, &fixed_size);
if (fixed_size)
g_assert_cmpint (serialised.size, ==, fixed_size);
else
g_assert (serialised.size == 0 || serialised.data != NULL);
g_assert_cmpint (alignment & (gsize) serialised.data, ==, 0);
}
/* < private >
* GVariantSerialisedFiller:
* @serialised: a #GVariantSerialised instance to fill
* @data: data from the children array
*
* This function is called back from g_variant_serialiser_needed_size()
* and g_variant_serialiser_serialise(). It fills in missing details
* from a partially-complete #GVariantSerialised.
*
* The @data parameter passed back to the function is one of the items
* that was passed to the serialiser in the @children array. It
* represents a single child item of the container that is being
* serialised. The information filled in to @serialised is the
* information for this child.
*
* If the @type_info field of @serialised is %NULL then the callback
* function must set it to the type information corresponding to the
* type of the child. No reference should be added. If it is non-%NULL
* then the callback should assert that it is equal to the actual type
* of the child.
*
* If the @size field is zero then the callback must fill it in with the
* required amount of space to store the serialised form of the child.
* If it is non-zero then the callback should assert that it is equal to
* the needed size of the child.
*
* If @data is non-%NULL then it points to a space that is properly
* aligned for and large enough to store the serialised data of the
* child. The callback must store the serialised form of the child at
* @data.
*
* If the child value is another container then the callback will likely
* recurse back into the serialiser by calling
* g_variant_serialiser_needed_size() to determine @size and
* g_variant_serialiser_serialise() to write to @data.
*/
/* PART 1: Container types {{{1
*
* This section contains the serialiser implementation functions for
* each container type.
*/
/* Maybe {{{2
*
* Maybe types are handled depending on if the element type of the maybe
* type is a fixed-sized or variable-sized type. Although all maybe
* types themselves are variable-sized types, herein, a maybe value with
* a fixed-sized element type is called a "fixed-sized maybe" for
* convenience and a maybe value with a variable-sized element type is
* called a "variable-sized maybe".
*/
/* Fixed-sized Maybe {{{3
*
* The size of a maybe value with a fixed-sized element type is either 0
* or equal to the fixed size of its element type. The case where the
* size of the maybe value is zero corresponds to the "Nothing" case and
* the case where the size of the maybe value is equal to the fixed size
* of the element type corresponds to the "Just" case; in that case, the
* serialised data of the child value forms the entire serialised data
* of the maybe value.
*
* In the event that a fixed-sized maybe value is presented with a size
* that is not equal to the fixed size of the element type then the
* value must be taken to be "Nothing".
*/
static gsize
gvs_fixed_sized_maybe_n_children (GVariantSerialised value)
{
gsize element_fixed_size;
g_variant_type_info_query_element (value.type_info, NULL,
&element_fixed_size);
return (element_fixed_size == value.size) ? 1 : 0;
}
static GVariantSerialised
gvs_fixed_sized_maybe_get_child (GVariantSerialised value,
gsize index_)
{
/* the child has the same bounds as the
* container, so just update the type.
*/
value.type_info = g_variant_type_info_element (value.type_info);
g_variant_type_info_ref (value.type_info);
return value;
}
static gsize
gvs_fixed_sized_maybe_needed_size (GVariantTypeInfo *type_info,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
if (n_children)
{
gsize element_fixed_size;
g_variant_type_info_query_element (type_info, NULL,
&element_fixed_size);
return element_fixed_size;
}
else
return 0;
}
static void
gvs_fixed_sized_maybe_serialise (GVariantSerialised value,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
if (n_children)
{
GVariantSerialised child = { NULL, value.data, value.size };
gvs_filler (&child, children[0]);
}
}
static gboolean
gvs_fixed_sized_maybe_is_normal (GVariantSerialised value)
{
if (value.size > 0)
{
gsize element_fixed_size;
g_variant_type_info_query_element (value.type_info,
NULL, &element_fixed_size);
if (value.size != element_fixed_size)
return FALSE;
/* proper element size: "Just". recurse to the child. */
value.type_info = g_variant_type_info_element (value.type_info);
return g_variant_serialised_is_normal (value);
}
/* size of 0: "Nothing" */
return TRUE;
}
/* Variable-sized Maybe
*
* The size of a maybe value with a variable-sized element type is
* either 0 or strictly greater than 0. The case where the size of the
* maybe value is zero corresponds to the "Nothing" case and the case
* where the size of the maybe value is greater than zero corresponds to
* the "Just" case; in that case, the serialised data of the child value
* forms the first part of the serialised data of the maybe value and is
* followed by a single zero byte. This zero byte is always appended,
* regardless of any zero bytes that may already be at the end of the
* serialised ata of the child value.
*/
static gsize
gvs_variable_sized_maybe_n_children (GVariantSerialised value)
{
return (value.size > 0) ? 1 : 0;
}
static GVariantSerialised
gvs_variable_sized_maybe_get_child (GVariantSerialised value,
gsize index_)
{
/* remove the padding byte and update the type. */
value.type_info = g_variant_type_info_element (value.type_info);
g_variant_type_info_ref (value.type_info);
value.size--;
/* if it's zero-sized then it may as well be NULL */
if (value.size == 0)
value.data = NULL;
return value;
}
static gsize
gvs_variable_sized_maybe_needed_size (GVariantTypeInfo *type_info,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
if (n_children)
{
GVariantSerialised child = { };
gvs_filler (&child, children[0]);
return child.size + 1;
}
else
return 0;
}
static void
gvs_variable_sized_maybe_serialise (GVariantSerialised value,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
if (n_children)
{
GVariantSerialised child = { NULL, value.data, value.size - 1 };
/* write the data for the child. */
gvs_filler (&child, children[0]);
value.data[child.size] = '\0';
}
}
static gboolean
gvs_variable_sized_maybe_is_normal (GVariantSerialised value)
{
if (value.size == 0)
return TRUE;
if (value.data[value.size - 1] != '\0')
return FALSE;
value.type_info = g_variant_type_info_element (value.type_info);
value.size--;
return g_variant_serialised_is_normal (value);
}
/* Arrays {{{2
*
* Just as with maybe types, array types are handled depending on if the
* element type of the array type is a fixed-sized or variable-sized
* type. Similar to maybe types, for convenience, an array value with a
* fixed-sized element type is called a "fixed-sized array" and an array
* value with a variable-sized element type is called a "variable sized
* array".
*/
/* Fixed-sized Array {{{3
*
* For fixed sized arrays, the serialised data is simply a concatenation
* of the serialised data of each element, in order. Since fixed-sized
* values always have a fixed size that is a multiple of their alignment
* requirement no extra padding is required.
*
* In the event that a fixed-sized array is presented with a size that
* is not an integer multiple of the element size then the value of the
* array must be taken as being empty.
*/
static gsize
gvs_fixed_sized_array_n_children (GVariantSerialised value)
{
gsize element_fixed_size;
g_variant_type_info_query_element (value.type_info, NULL,
&element_fixed_size);
if (value.size % element_fixed_size == 0)
return value.size / element_fixed_size;
return 0;
}
static GVariantSerialised
gvs_fixed_sized_array_get_child (GVariantSerialised value,
gsize index_)
{
GVariantSerialised child = { };
child.type_info = g_variant_type_info_element (value.type_info);
g_variant_type_info_query (child.type_info, NULL, &child.size);
child.data = value.data + (child.size * index_);
g_variant_type_info_ref (child.type_info);
return child;
}
static gsize
gvs_fixed_sized_array_needed_size (GVariantTypeInfo *type_info,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
gsize element_fixed_size;
g_variant_type_info_query_element (type_info, NULL, &element_fixed_size);
return element_fixed_size * n_children;
}
static void
gvs_fixed_sized_array_serialise (GVariantSerialised value,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
GVariantSerialised child = { };
gsize i;
child.type_info = g_variant_type_info_element (value.type_info);
g_variant_type_info_query (child.type_info, NULL, &child.size);
child.data = value.data;
for (i = 0; i < n_children; i++)
{
gvs_filler (&child, children[i]);
child.data += child.size;
}
}
static gboolean
gvs_fixed_sized_array_is_normal (GVariantSerialised value)
{
GVariantSerialised child = { };
child.type_info = g_variant_type_info_element (value.type_info);
g_variant_type_info_query (child.type_info, NULL, &child.size);
if (value.size % child.size != 0)
return FALSE;
for (child.data = value.data;
child.data < value.data + value.size;
child.data += child.size)
{
if (!g_variant_serialised_is_normal (child))
return FALSE;
}
return TRUE;
}
/* Variable-sized Array {{{3
*
* Variable sized arrays, containing variable-sized elements, must be
* able to determine the boundaries between the elements. The items
* cannot simply be concatenated. Additionally, we are faced with the
* fact that non-fixed-sized values do not neccessarily have a size that
* is a multiple of their alignment requirement, so we may need to
* insert zero-filled padding.
*
* While it is possible to find the start of an item by starting from
* the end of the item before it and padding for alignment, it is not
* generally possible to do the reverse operation. For this reason, we
* record the end point of each element in the array.
*
* GVariant works in terms of "offsets". An offset is a pointer to a
* boundary between two bytes. In 4 bytes of serialised data, there
* would be 5 possible offsets: one at the start ('0'), one between each
* pair of adjacent bytes ('1', '2', '3') and one at the end ('4').
*
* The numeric value of an offset is an unsigned integer given relative
* to the start of the serialised data of the array. Offsets are always
* stored in little endian byte order and are always only as big as they
* need to be. For example, in 255 bytes of serialised data, there are
* 256 offsets. All possibilities can be stored in an 8 bit unsigned
* integer. In 256 bytes of serialised data, however, there are 257
* possible offsets so 16 bit integers must be used. The size of an
* offset is always a power of 2.
*
* The offsets are stored at the end of the serialised data of the
* array. They are simply concatenated on without any particular
* alignment. The size of the offsets is included in the size of the
* serialised data for purposes of determining the size of the offsets.
* This presents a possibly ambiguity; in certain cases, a particular
* value of array could have two different serialised forms.
*
* Imagine an array containing a single string of 253 bytes in length
* (so, 254 bytes including the nul terminator). Now the offset must be
* written. If an 8 bit offset is written, it will bring the size of
* the array's serialised data to 255 -- which means that the use of an
* 8 bit offset was valid. If a 16 bit offset is used then the total
* size of the array will be 256 -- which means that the use of a 16 bit
* offset was valid. Although both of these will be accepted by the
* deserialiser, only the smaller of the two is considered to be in
* normal form and that is the one that the serialiser must produce.
*/
static inline gsize
gvs_read_unaligned_le (guchar *bytes,
guint size)
{
union
{
guchar bytes[GLIB_SIZEOF_SIZE_T];
gsize integer;
} tmpvalue;
tmpvalue.integer = 0;
memcpy (&tmpvalue.bytes, bytes, size);
return GSIZE_FROM_LE (tmpvalue.integer);
}
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;
}
static gsize
gvs_variable_sized_array_n_children (GVariantSerialised value)
{
gsize offsets_array_size;
gsize offset_size;
gsize last_end;
if (value.size == 0)
return 0;
offset_size = gvs_get_offset_size (value.size);
last_end = gvs_read_unaligned_le (value.data + value.size -
offset_size, offset_size);
if (last_end > value.size)
return 0;
offsets_array_size = value.size - last_end;
if (offsets_array_size % offset_size)
return 0;
return offsets_array_size / offset_size;
}
static GVariantSerialised
gvs_variable_sized_array_get_child (GVariantSerialised value,
gsize index_)
{
GVariantSerialised child = { };
gsize offset_size;
gsize last_end;
gsize start;
gsize end;
child.type_info = g_variant_type_info_element (value.type_info);
g_variant_type_info_ref (child.type_info);
offset_size = gvs_get_offset_size (value.size);
last_end = gvs_read_unaligned_le (value.data + value.size -
offset_size, offset_size);
if (index_ > 0)
{
guint alignment;
start = gvs_read_unaligned_le (value.data + last_end +
(offset_size * (index_ - 1)),
offset_size);
g_variant_type_info_query (child.type_info, &alignment, NULL);
start += (-start) & alignment;
}
else
start = 0;
end = gvs_read_unaligned_le (value.data + last_end +
(offset_size * index_),
offset_size);
if (start < end && end <= value.size)
{
child.data = value.data + start;
child.size = end - start;
}
return child;
}
static gsize
gvs_variable_sized_array_needed_size (GVariantTypeInfo *type_info,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
guint alignment;
gsize offset;
gsize i;
g_variant_type_info_query (type_info, &alignment, NULL);
offset = 0;
for (i = 0; i < n_children; i++)
{
GVariantSerialised child = { };
offset += (-offset) & alignment;
gvs_filler (&child, children[i]);
offset += child.size;
}
return gvs_calculate_total_size (offset, n_children);
}
static void
gvs_variable_sized_array_serialise (GVariantSerialised value,
GVariantSerialisedFiller gvs_filler,
const gpointer *children,
gsize n_children)
{
guchar *offset_ptr;
gsize offset_size;