GVariant: add GVariantVectors utility struct
[platform/upstream/glib.git] / glib / gvariant-vectors.c
1 #include "config.h"
2
3 #include "gvariant-vectors.h"
4 #include "gtestutils.h"
5
6 static void
7 append_zeros (GByteArray *array,
8               guint       n)
9 {
10   guchar zeros[8] = "";
11
12   g_byte_array_append (array, zeros, n);
13 }
14
15 void
16 g_variant_vectors_init (GVariantVectors *vectors)
17 {
18
19   /* The first 8 bytes of 'extra_bytes' is always 0.  We use this for
20    * inserting padding in between two GBytes records.
21    */
22   vectors->extra_bytes = g_byte_array_new ();
23   append_zeros (vectors->extra_bytes, 8);
24
25   vectors->vectors = g_array_new (FALSE, FALSE, sizeof (GVariantVector));
26
27   vectors->offsets = g_byte_array_new ();
28 }
29
30 gsize
31 g_variant_vectors_append_pad (GVariantVectors *vectors,
32                               gsize            padding)
33 {
34   /* If the last vector that we stored was 'pad' or 'copy' then we will
35    * expand it instead of adding a new one.
36    */
37   if (vectors->vectors->len)
38     {
39       GVariantVector *expand_vector = &g_array_index (vectors->vectors, GVariantVector, vectors->vectors->len - 1);
40
41       if (expand_vector->gbytes == NULL)
42         {
43           expand_vector->size += padding;
44
45           /* If the vector points to data, we need to add the padding to
46            * the end of that data.  If it points to the zero bytes at
47            * the start then we can just grow it (but we must ensure that
48            * it doesn't get too large).
49            */
50           if (expand_vector->data.offset)
51             append_zeros (vectors->extra_bytes, padding);
52           else
53             g_assert (expand_vector->size < 8);
54
55           return padding;
56         }
57
58       /* If the last vector was a GBytes then fall through */
59     }
60
61   /* Otherwise, record a new vector pointing to the padding bytes at the
62    * start.
63    */
64   {
65     GVariantVector v;
66
67     v.gbytes = NULL;
68     v.data.offset = 0;
69     v.size = padding;
70
71     g_array_append_val (vectors->vectors, v);
72   }
73
74   return padding;
75 }
76
77 void
78 g_variant_vectors_append_copy (GVariantVectors *vectors,
79                                gconstpointer    data,
80                                gsize            size)
81 {
82   /* If the last vector that we stored was 'pad' or 'copy' then we will
83    * expand it instead of adding a new one.
84    */
85   if (vectors->vectors->len)
86     {
87       GVariantVector *expand_vector = &g_array_index (vectors->vectors, GVariantVector, vectors->vectors->len - 1);
88
89       if (expand_vector->gbytes == NULL)
90         {
91           /* If this was a padding vector then we must convert it to
92            * data first.
93            */
94           if (expand_vector->data.offset == 0)
95             {
96               expand_vector->data.offset = vectors->extra_bytes->len;
97               append_zeros (vectors->extra_bytes, expand_vector->size);
98             }
99
100           /* We have a vector pointing to data at the end of the
101            * extra_bytes array, so just append there and grow the
102            * vector.
103            */
104           g_byte_array_append (vectors->extra_bytes, data, size);
105           expand_vector->size += size;
106           return;
107         }
108
109       /* If the last vector was a GBytes then fall through */
110     }
111
112   /* Otherwise, copy the data and record a new vector. */
113   {
114     GVariantVector v;
115
116     v.gbytes = NULL;
117     v.data.offset = vectors->extra_bytes->len;
118     v.size = size;
119
120     g_byte_array_append (vectors->extra_bytes, data, size);
121     g_array_append_val (vectors->vectors, v);
122   }
123 }
124
125 void
126 g_variant_vectors_append_gbytes (GVariantVectors *vectors,
127                                  GBytes          *gbytes,
128                                  gconstpointer    data,
129                                  gsize            size)
130 {
131   GVariantVector v;
132
133   /* Some very rough profiling has indicated that the trade-off for
134    * overhead on the atomic operations involved in the ref/unref on the
135    * GBytes is larger than the cost of the copy at ~128 bytes.
136    */
137   if (size < 128)
138     {
139       g_variant_vectors_append_copy (vectors, data, size);
140       return;
141     }
142
143   v.gbytes = g_bytes_ref (gbytes);
144   v.data.pointer = data;
145   v.size = size;
146
147   g_array_append_val (vectors->vectors, v);
148 }
149
150 typedef void (* WriteFunction) (gpointer base, gsize offset, gsize value);
151 static void write_1 (gpointer base, gsize offset, gsize value) { ((guint8 *) base)[offset] = value; }
152 static void write_2 (gpointer base, gsize offset, gsize value) { ((guint16 *) base)[offset] = GUINT16_TO_LE (value); }
153 static void write_4 (gpointer base, gsize offset, gsize value) { ((guint32 *) base)[offset] = GUINT32_TO_LE (value); }
154 static void write_8 (gpointer base, gsize offset, gsize value) { ((guint64 *) base)[offset] = GUINT64_TO_LE (value); }
155
156 typedef struct
157 {
158   gsize         size;
159   WriteFunction func;
160 } OffsetsHeader;
161
162 gsize
163 g_variant_vectors_reserve_offsets (GVariantVectors *vectors,
164                                    guint            n_offsets,
165                                    guint            offset_size)
166 {
167   OffsetsHeader *header;
168   gsize total_size;
169   gsize add_size;
170   guint key;
171
172   total_size = n_offsets * offset_size;
173
174   /* Add room for the metadata and round up to multiple of 8 */
175   add_size = (sizeof (OffsetsHeader) + total_size + 7) & ~7ull;
176   key = vectors->offsets->len;
177   g_byte_array_set_size (vectors->offsets, key + add_size);
178   header = (OffsetsHeader *) (vectors->offsets->data + key);
179   key += sizeof (OffsetsHeader);
180   header->size = total_size;
181
182   switch (offset_size)
183     {
184     case 1:
185       header->func = write_1;
186       break;
187
188     case 2:
189       header->func = write_2;
190       break;
191
192     case 4:
193       header->func = write_4;
194       break;
195
196     case 8:
197       header->func = write_8;
198       break;
199
200     default:
201       g_assert_not_reached ();
202     }
203
204   return key;
205 }
206
207 void
208 g_variant_vectors_write_to_offsets (GVariantVectors *vectors,
209                                     gsize            offset,
210                                     gsize            value,
211                                     gsize            key)
212 {
213   OffsetsHeader *header;
214   guchar *offsets;
215
216   offsets = vectors->offsets->data + key;
217   header = (OffsetsHeader *) (offsets - sizeof (OffsetsHeader));
218
219   header->func (offsets, offset, value);
220 }
221
222 void
223 g_variant_vectors_commit_offsets (GVariantVectors *vectors,
224                                   gsize            key)
225 {
226   OffsetsHeader *header;
227   guchar *offsets;
228
229   offsets = vectors->offsets->data + key;
230   header = (OffsetsHeader *) (offsets - sizeof (OffsetsHeader));
231
232   g_variant_vectors_append_copy (vectors, offsets, header->size);
233   g_byte_array_set_size (vectors->offsets, key - sizeof (OffsetsHeader));
234 }