2 * svn_temp_serializer.c: implement the tempoary structure serialization API
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
25 #include "private/svn_temp_serializer.h"
26 #include "svn_string.h"
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.
35 * Hence, deserialization can be simply done by copying the buffer and
36 * adjusting the pointers. No fine-grained allocation and copying is
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.
46 typedef struct source_stack_t
48 /* the source structure passed in to *_init or *_push */
49 const void *source_struct;
51 /* offset within the target buffer to where the structure got copied */
52 apr_size_t target_offset;
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;
60 /* Serialization context info. It basically consists of the buffer holding
61 * the serialized result and the stack of source structure information.
63 struct svn_temp_serializer__context_t
65 /* allocations are made from this pool */
68 /* the buffer holding all serialized data */
69 svn_stringbuf_t *buffer;
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;
76 /* unused stack elements will be put here for later reuse. */
77 source_stack_t *recycler;
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
85 align_buffer_end(svn_temp_serializer__context_t *context)
87 apr_size_t current_len = context->buffer->len;
88 apr_size_t aligned_len = APR_ALIGN_DEFAULT(current_len);
90 if (aligned_len + 1 > context->buffer->blocksize)
91 svn_stringbuf_ensure(context->buffer, aligned_len);
93 context->buffer->len = aligned_len;
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
103 svn_temp_serializer__context_t *
104 svn_temp_serializer__init(const void *source_struct,
105 apr_size_t struct_size,
106 apr_size_t suggested_buffer_size,
109 /* select a meaningful initial memory buffer capacity */
110 apr_size_t init_size = suggested_buffer_size < struct_size
112 : suggested_buffer_size;
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;
120 /* If a source struct has been given, make it the root struct. */
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;
128 /* serialize, i.e. append, the content of the first structure */
129 svn_stringbuf_appendbytes(context->buffer, source_struct, struct_size);
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;
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.
149 svn_temp_serializer__context_t *
150 svn_temp_serializer__init_append(void *buffer,
152 apr_size_t currently_used,
153 apr_size_t currently_allocated,
156 /* determine the current memory buffer capacity */
157 apr_size_t init_size = currently_allocated < currently_used
159 : currently_allocated;
161 /* create the serialization context and initialize it */
162 svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context));
163 context->pool = pool;
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;
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;
177 /* initialize the RECYCLER */
178 context->recycler = NULL;
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.
189 store_current_end_pointer(svn_temp_serializer__context_t *context,
190 const void * const * source_pointer)
192 apr_size_t ptr_offset;
193 apr_size_t *target_ptr;
195 /* if *source_pointer is the root struct, there will be no parent structure
197 if (context->source == NULL)
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;
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);
209 /* use the serialized pointer as a storage for the offset */
210 target_ptr = (apr_size_t*)(context->buffer->data + ptr_offset);
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
216 : context->buffer->len - context->source->target_offset;
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.
226 svn_temp_serializer__push(svn_temp_serializer__context_t *context,
227 const void * const * source_struct,
228 apr_size_t struct_size)
230 const void *source = *source_struct;
233 /* recycle an old entry or create a new one for the structure stack */
234 if (context->recycler)
236 new = context->recycler;
237 context->recycler = new->upper;
240 new = apr_palloc(context->pool, sizeof(*new));
242 /* the serialized structure must be properly aligned */
244 align_buffer_end(context);
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);
250 /* store source and target information */
251 new->source_struct = source;
252 new->target_offset = context->buffer->len;
254 /* put the new entry onto the stack*/
255 new->upper = context->source;
256 context->source = new;
258 /* finally, actually append the new struct
259 * (so we can now manipulate pointers within it) */
261 svn_stringbuf_appendbytes(context->buffer, source, struct_size);
264 /* Remove the lastest structure from the stack.
267 svn_temp_serializer__pop(svn_temp_serializer__context_t *context)
269 source_stack_t *old = context->source;
271 /* we may pop the original struct but not further */
272 assert(context->source);
274 /* one level up the structure stack */
275 context->source = context->source->upper;
277 /* put the old stack element into the recycler for later reuse */
278 old->upper = context->recycler;
279 context->recycler = old;
282 /* Serialize a string referenced from the current structure within the
283 * serialization CONTEXT. S must be a reference to the char* pointer in
284 * the original structure so that the correspondence in the serialized
285 * structure can be established.
288 svn_temp_serializer__add_string(svn_temp_serializer__context_t *context,
289 const char * const * s)
291 const char *string = *s;
293 /* Store the offset at which the string data that will the appended.
294 * Write 0 for NULL pointers. Strings don't need special alignment. */
295 store_current_end_pointer(context, (const void **)s);
297 /* append the string data */
299 svn_stringbuf_appendbytes(context->buffer, string, strlen(string) + 1);
302 /* Set the serialized representation of the pointer PTR inside the current
303 * structure within the serialization CONTEXT to NULL. This is particularly
304 * useful if the pointer is not NULL in the source structure.
307 svn_temp_serializer__set_null(svn_temp_serializer__context_t *context,
308 const void * const * ptr)
312 /* there must be a parent structure */
313 assert(context->source);
315 /* position of the serialized pointer relative to the begin of the buffer */
316 offset = (const char *)ptr
317 - (const char *)context->source->source_struct
318 + context->source->target_offset;
320 /* the offset must be within the serialized data. Otherwise, you forgot
321 * to serialize the respective sub-struct. */
322 assert(context->buffer->len > offset);
324 /* use the serialized pointer as a storage for the offset */
325 *(apr_size_t*)(context->buffer->data + offset) = 0;
328 /* Return the number of bytes currently used in the serialization buffer
329 * of the given serialization CONTEXT.*/
331 svn_temp_serializer__get_length(svn_temp_serializer__context_t *context)
333 return context->buffer->len;
336 /* Return the data buffer that receives the serialized data from
337 * the given serialization CONTEXT.
340 svn_temp_serializer__get(svn_temp_serializer__context_t *context)
342 return context->buffer;
345 /* Replace the deserialized pointer value at PTR inside BUFFER with a
346 * proper pointer value.
349 svn_temp_deserializer__resolve(void *buffer, void **ptr)
351 /* All pointers are stored as offsets to the buffer start
352 * (of the respective serialized sub-struct). */
353 apr_size_t ptr_offset = *(apr_size_t *)ptr;
356 /* Reconstruct the original pointer value */
357 const char *target = (const char *)buffer + ptr_offset;
359 /* All sub-structs are written _after_ their respective parent.
360 * Thus, all offsets are > 0. If the following assertion is not met,
361 * the data is either corrupt or you tried to resolve the pointer
363 assert(target > (const char *)buffer);
365 /* replace the PTR_OFFSET in *ptr with the pointer to TARGET */
366 (*(const char **)ptr) = target;
370 /* NULL pointers are stored as 0 which might have a different
371 * binary representation. */
377 svn_temp_deserializer__ptr(const void *buffer, const void *const *ptr)
379 return (apr_size_t)*ptr == 0
381 : (const char*)buffer + (apr_size_t)*ptr;