temp_serializer.c revision 299742
1/*
2 * svn_temp_serializer.c: implement the tempoary structure serialization API
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24#include <assert.h>
25#include "private/svn_temp_serializer.h"
26#include "svn_string.h"
27
28/* This is a very efficient serialization and especially efficient
29 * deserialization framework. The idea is just to concatenate all sub-
30 * structures and strings into a single buffer while preserving proper
31 * member alignment. Pointers will be replaced by the respective data
32 * offsets in the buffer when that target that it pointed to gets
33 * serialized, i.e. appended to the data buffer written so far.
34 *
35 * Hence, deserialization can be simply done by copying the buffer and
36 * adjusting the pointers. No fine-grained allocation and copying is
37 * necessary.
38 */
39
40/* An element in the structure stack. It contains a pointer to the source
41 * structure so that the relative offset of sub-structure or string
42 * references can be determined properly. It also contains the corresponding
43 * position within the serialized data. Thus, pointers can be serialized
44 * as offsets within the target buffer.
45 */
46typedef struct source_stack_t
47{
48  /* the source structure passed in to *_init or *_push */
49  const void *source_struct;
50
51  /* offset within the target buffer to where the structure got copied */
52  apr_size_t target_offset;
53
54  /* parent stack entry. Will be NULL for the root entry.
55   * Items in the svn_temp_serializer__context_t recycler will use this
56   * to link to the next unused item. */
57  struct source_stack_t *upper;
58} source_stack_t;
59
60/* Serialization context info. It basically consists of the buffer holding
61 * the serialized result and the stack of source structure information.
62 */
63struct svn_temp_serializer__context_t
64{
65  /* allocations are made from this pool */
66  apr_pool_t *pool;
67
68  /* the buffer holding all serialized data */
69  svn_stringbuf_t *buffer;
70
71  /* the stack of structures being serialized. If NULL, the serialization
72   * process has been finished. However, it is not necessarily NULL when
73   * the application end serialization. */
74  source_stack_t *source;
75
76  /* unused stack elements will be put here for later reuse. */
77  source_stack_t *recycler;
78};
79
80/* Make sure the serialized data len is a multiple of the default alignment,
81 * i.e. structures may be appended without violating member alignment
82 * guarantees.
83 */
84static void
85align_buffer_end(svn_temp_serializer__context_t *context)
86{
87  apr_size_t current_len = context->buffer->len;
88  apr_size_t aligned_len = APR_ALIGN_DEFAULT(current_len);
89
90  if (aligned_len + 1 > context->buffer->blocksize)
91    svn_stringbuf_ensure(context->buffer, aligned_len);
92
93   context->buffer->len = aligned_len;
94}
95
96/* Begin the serialization process for the SOURCE_STRUCT and all objects
97 * referenced from it. STRUCT_SIZE must match the result of sizeof() of
98 * the actual structure. You may suggest a larger initial buffer size
99 * in SUGGESTED_BUFFER_SIZE to minimize the number of internal buffer
100 * re-allocations during the serialization process. All allocations will
101 * be made from POOL.
102 */
103svn_temp_serializer__context_t *
104svn_temp_serializer__init(const void *source_struct,
105                          apr_size_t struct_size,
106                          apr_size_t suggested_buffer_size,
107                          apr_pool_t *pool)
108{
109  /* select a meaningful initial memory buffer capacity */
110  apr_size_t init_size = suggested_buffer_size < struct_size
111                       ? struct_size
112                       : suggested_buffer_size;
113
114  /* create the serialization context and initialize it */
115  svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context));
116  context->pool = pool;
117  context->buffer = svn_stringbuf_create_ensure(init_size, pool);
118  context->recycler = NULL;
119
120  /* If a source struct has been given, make it the root struct. */
121  if (source_struct)
122    {
123      context->source = apr_palloc(pool, sizeof(*context->source));
124      context->source->source_struct = source_struct;
125      context->source->target_offset = 0;
126      context->source->upper = NULL;
127
128      /* serialize, i.e. append, the content of the first structure */
129      svn_stringbuf_appendbytes(context->buffer, source_struct, struct_size);
130    }
131    else
132    {
133      /* The root struct will be set with the first push() op, or not at all
134       * (in case of a plain string). */
135      context->source = NULL;
136    }
137
138  /* done */
139  return context;
140}
141
142/* Continue the serialization process of the SOURCE_STRUCT that has already
143 * been serialized to BUFFER but contains references to new objects yet to
144 * serialize. The current size of the serialized data is given in
145 * CURRENTLY_USED. If the allocated data buffer is actually larger, you may
146 * specifiy that in CURRENTLY_ALLOCATED to prevent unnecessary allocations.
147 * Otherwise, set it to 0. All allocations will be made from POOl.
148 */
149svn_temp_serializer__context_t *
150svn_temp_serializer__init_append(void *buffer,
151                                 void *source_struct,
152                                 apr_size_t currently_used,
153                                 apr_size_t currently_allocated,
154                                 apr_pool_t *pool)
155{
156  /* determine the current memory buffer capacity */
157  apr_size_t init_size = currently_allocated < currently_used
158                       ? currently_used
159                       : currently_allocated;
160
161  /* create the serialization context and initialize it */
162  svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context));
163  context->pool = pool;
164
165  /* use BUFFER as serialization target */
166  context->buffer = svn_stringbuf_create_ensure(0, pool);
167  context->buffer->data = buffer;
168  context->buffer->len = currently_used;
169  context->buffer->blocksize = init_size;
170
171  /* SOURCE_STRUCT is our serialization root */
172  context->source = apr_palloc(pool, sizeof(*context->source));
173  context->source->source_struct = source_struct;
174  context->source->target_offset = (char *)source_struct - (char *)buffer;
175  context->source->upper = NULL;
176
177  /* initialize the RECYCLER */
178  context->recycler = NULL;
179
180  /* done */
181  return context;
182}
183
184/* Utility function replacing the serialized pointer corresponding to
185 * *SOURCE_POINTER with the offset that it will be put when being append
186 * right after this function call.
187 */
188static void
189store_current_end_pointer(svn_temp_serializer__context_t *context,
190                          const void * const * source_pointer)
191{
192  apr_size_t ptr_offset;
193  apr_size_t *target_ptr;
194
195  /* if *source_pointer is the root struct, there will be no parent structure
196   * to relate it to */
197  if (context->source == NULL)
198    return;
199
200  /* position of the serialized pointer relative to the begin of the buffer */
201  ptr_offset = (const char *)source_pointer
202             - (const char *)context->source->source_struct
203             + context->source->target_offset;
204
205  /* the offset must be within the serialized data. Otherwise, you forgot
206   * to serialize the respective sub-struct. */
207  assert(context->buffer->len > ptr_offset);
208
209  /* use the serialized pointer as a storage for the offset */
210  target_ptr = (apr_size_t*)(context->buffer->data + ptr_offset);
211
212  /* store the current buffer length because that's where we will append
213   * the serialized data of the sub-struct or string */
214  *target_ptr = *source_pointer == NULL
215              ? 0
216              : context->buffer->len - context->source->target_offset;
217}
218
219/* Begin serialization of a referenced sub-structure within the
220 * serialization CONTEXT. SOURCE_STRUCT must be a reference to the pointer
221 * in the original parent structure so that the correspondence in the
222 * serialized structure can be established. STRUCT_SIZE must match the
223 * result of sizeof() of the actual structure.
224 */
225void
226svn_temp_serializer__push(svn_temp_serializer__context_t *context,
227                          const void * const * source_struct,
228                          apr_size_t struct_size)
229{
230  const void *source = *source_struct;
231  source_stack_t *new;
232
233  /* recycle an old entry or create a new one for the structure stack */
234  if (context->recycler)
235    {
236      new = context->recycler;
237      context->recycler = new->upper;
238    }
239  else
240    new = apr_palloc(context->pool, sizeof(*new));
241
242  /* the serialized structure must be properly aligned */
243  if (source)
244    align_buffer_end(context);
245
246  /* Store the offset at which the struct data that will the appended.
247   * Write 0 for NULL pointers. */
248  store_current_end_pointer(context, source_struct);
249
250  /* store source and target information */
251  new->source_struct = source;
252  new->target_offset = context->buffer->len;
253
254  /* put the new entry onto the stack*/
255  new->upper = context->source;
256  context->source = new;
257
258  /* finally, actually append the new struct
259   * (so we can now manipulate pointers within it) */
260  if (*source_struct)
261    svn_stringbuf_appendbytes(context->buffer, source, struct_size);
262}
263
264/* Remove the lastest structure from the stack.
265 */
266void
267svn_temp_serializer__pop(svn_temp_serializer__context_t *context)
268{
269  source_stack_t *old = context->source;
270
271  /* we may pop the original struct but not further */
272  assert(context->source);
273
274  /* one level up the structure stack */
275  context->source = context->source->upper;
276
277  /* put the old stack element into the recycler for later reuse */
278  old->upper = context->recycler;
279  context->recycler = old;
280}
281
282void
283svn_temp_serializer__add_leaf(svn_temp_serializer__context_t *context,
284                              const void * const * source_struct,
285                              apr_size_t struct_size)
286{
287  const void *source = *source_struct;
288
289  /* the serialized structure must be properly aligned */
290  if (source)
291    align_buffer_end(context);
292
293  /* Store the offset at which the struct data that will the appended.
294   * Write 0 for NULL pointers. */
295  store_current_end_pointer(context, source_struct);
296
297  /* finally, actually append the struct contents */
298  if (*source_struct)
299    svn_stringbuf_appendbytes(context->buffer, source, struct_size);
300}
301
302/* Serialize a string referenced from the current structure within the
303 * serialization CONTEXT. S must be a reference to the char* pointer in
304 * the original structure so that the correspondence in the serialized
305 * structure can be established.
306 */
307void
308svn_temp_serializer__add_string(svn_temp_serializer__context_t *context,
309                                const char * const * s)
310{
311  const char *string = *s;
312
313  /* Store the offset at which the string data that will the appended.
314   * Write 0 for NULL pointers. Strings don't need special alignment. */
315  store_current_end_pointer(context, (const void *const *)s);
316
317  /* append the string data */
318  if (string)
319    svn_stringbuf_appendbytes(context->buffer, string, strlen(string) + 1);
320}
321
322/* Set the serialized representation of the pointer PTR inside the current
323 * structure within the serialization CONTEXT to NULL. This is particularly
324 * useful if the pointer is not NULL in the source structure.
325 */
326void
327svn_temp_serializer__set_null(svn_temp_serializer__context_t *context,
328                              const void * const * ptr)
329{
330  apr_size_t offset;
331
332  /* there must be a parent structure */
333  assert(context->source);
334
335  /* position of the serialized pointer relative to the begin of the buffer */
336  offset = (const char *)ptr
337         - (const char *)context->source->source_struct
338         + context->source->target_offset;
339
340  /* the offset must be within the serialized data. Otherwise, you forgot
341   * to serialize the respective sub-struct. */
342  assert(context->buffer->len > offset);
343
344  /* use the serialized pointer as a storage for the offset */
345  *(apr_size_t*)(context->buffer->data + offset) = 0;
346}
347
348/* Return the number of bytes currently used in the serialization buffer
349 * of the given serialization CONTEXT.*/
350apr_size_t
351svn_temp_serializer__get_length(svn_temp_serializer__context_t *context)
352{
353  return context->buffer->len;
354}
355
356/* Return the data buffer that receives the serialized data from
357 * the given serialization CONTEXT.
358 */
359svn_stringbuf_t *
360svn_temp_serializer__get(svn_temp_serializer__context_t *context)
361{
362  return context->buffer;
363}
364
365/* Replace the deserialized pointer value at PTR inside BUFFER with a
366 * proper pointer value.
367 */
368void
369svn_temp_deserializer__resolve(void *buffer, void **ptr)
370{
371  /* All pointers are stored as offsets to the buffer start
372   * (of the respective serialized sub-struct). */
373  apr_size_t ptr_offset = *(apr_size_t *)ptr;
374  if (ptr_offset)
375    {
376      /* Reconstruct the original pointer value */
377      const char *target = (const char *)buffer + ptr_offset;
378
379      /* All sub-structs are written _after_ their respective parent.
380       * Thus, all offsets are > 0. If the following assertion is not met,
381       * the data is either corrupt or you tried to resolve the pointer
382       * more than once. */
383      assert(target > (const char *)buffer);
384
385      /* replace the PTR_OFFSET in *ptr with the pointer to TARGET */
386      (*(const char **)ptr) = target;
387    }
388  else
389    {
390      /* NULL pointers are stored as 0 which might have a different
391       * binary representation. */
392      *ptr = NULL;
393    }
394}
395
396const void *
397svn_temp_deserializer__ptr(const void *buffer, const void *const *ptr)
398{
399  return (apr_size_t)*ptr == 0
400      ? NULL
401      : (const char*)buffer + (apr_size_t)*ptr;
402}
403