diff options
Diffstat (limited to 'subversion/libsvn_subr/temp_serializer.c')
-rw-r--r-- | subversion/libsvn_subr/temp_serializer.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/temp_serializer.c b/subversion/libsvn_subr/temp_serializer.c new file mode 100644 index 000000000000..261267a37d37 --- /dev/null +++ b/subversion/libsvn_subr/temp_serializer.c @@ -0,0 +1,382 @@ +/* + * svn_temp_serializer.c: implement the tempoary structure serialization API + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <assert.h> +#include "private/svn_temp_serializer.h" +#include "svn_string.h" + +/* This is a very efficient serialization and especially efficient + * deserialization framework. The idea is just to concatenate all sub- + * structures and strings into a single buffer while preserving proper + * member alignment. Pointers will be replaced by the respective data + * offsets in the buffer when that target that it pointed to gets + * serialized, i.e. appended to the data buffer written so far. + * + * Hence, deserialization can be simply done by copying the buffer and + * adjusting the pointers. No fine-grained allocation and copying is + * necessary. + */ + +/* An element in the structure stack. It contains a pointer to the source + * structure so that the relative offset of sub-structure or string + * references can be determined properly. It also contains the corresponding + * position within the serialized data. Thus, pointers can be serialized + * as offsets within the target buffer. + */ +typedef struct source_stack_t +{ + /* the source structure passed in to *_init or *_push */ + const void *source_struct; + + /* offset within the target buffer to where the structure got copied */ + apr_size_t target_offset; + + /* parent stack entry. Will be NULL for the root entry. + * Items in the svn_temp_serializer__context_t recycler will use this + * to link to the next unused item. */ + struct source_stack_t *upper; +} source_stack_t; + +/* Serialization context info. It basically consists of the buffer holding + * the serialized result and the stack of source structure information. + */ +struct svn_temp_serializer__context_t +{ + /* allocations are made from this pool */ + apr_pool_t *pool; + + /* the buffer holding all serialized data */ + svn_stringbuf_t *buffer; + + /* the stack of structures being serialized. If NULL, the serialization + * process has been finished. However, it is not necessarily NULL when + * the application end serialization. */ + source_stack_t *source; + + /* unused stack elements will be put here for later reuse. */ + source_stack_t *recycler; +}; + +/* Make sure the serialized data len is a multiple of the default alignment, + * i.e. structures may be appended without violating member alignment + * guarantees. + */ +static void +align_buffer_end(svn_temp_serializer__context_t *context) +{ + apr_size_t current_len = context->buffer->len; + apr_size_t aligned_len = APR_ALIGN_DEFAULT(current_len); + + if (aligned_len + 1 > context->buffer->blocksize) + svn_stringbuf_ensure(context->buffer, aligned_len); + + context->buffer->len = aligned_len; +} + +/* Begin the serialization process for the SOURCE_STRUCT and all objects + * referenced from it. STRUCT_SIZE must match the result of sizeof() of + * the actual structure. You may suggest a larger initial buffer size + * in SUGGESTED_BUFFER_SIZE to minimize the number of internal buffer + * re-allocations during the serialization process. All allocations will + * be made from POOL. + */ +svn_temp_serializer__context_t * +svn_temp_serializer__init(const void *source_struct, + apr_size_t struct_size, + apr_size_t suggested_buffer_size, + apr_pool_t *pool) +{ + /* select a meaningful initial memory buffer capacity */ + apr_size_t init_size = suggested_buffer_size < struct_size + ? struct_size + : suggested_buffer_size; + + /* create the serialization context and initialize it */ + svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context)); + context->pool = pool; + context->buffer = svn_stringbuf_create_ensure(init_size, pool); + context->recycler = NULL; + + /* If a source struct has been given, make it the root struct. */ + if (source_struct) + { + context->source = apr_palloc(pool, sizeof(*context->source)); + context->source->source_struct = source_struct; + context->source->target_offset = 0; + context->source->upper = NULL; + + /* serialize, i.e. append, the content of the first structure */ + svn_stringbuf_appendbytes(context->buffer, source_struct, struct_size); + } + else + { + /* The root struct will be set with the first push() op, or not at all + * (in case of a plain string). */ + context->source = NULL; + } + + /* done */ + return context; +} + +/* Continue the serialization process of the SOURCE_STRUCT that has already + * been serialized to BUFFER but contains references to new objects yet to + * serialize. The current size of the serialized data is given in + * CURRENTLY_USED. If the allocated data buffer is actually larger, you may + * specifiy that in CURRENTLY_ALLOCATED to prevent unnecessary allocations. + * Otherwise, set it to 0. All allocations will be made from POOl. + */ +svn_temp_serializer__context_t * +svn_temp_serializer__init_append(void *buffer, + void *source_struct, + apr_size_t currently_used, + apr_size_t currently_allocated, + apr_pool_t *pool) +{ + /* determine the current memory buffer capacity */ + apr_size_t init_size = currently_allocated < currently_used + ? currently_used + : currently_allocated; + + /* create the serialization context and initialize it */ + svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context)); + context->pool = pool; + + /* use BUFFER as serialization target */ + context->buffer = svn_stringbuf_create_ensure(0, pool); + context->buffer->data = buffer; + context->buffer->len = currently_used; + context->buffer->blocksize = init_size; + + /* SOURCE_STRUCT is our serialization root */ + context->source = apr_palloc(pool, sizeof(*context->source)); + context->source->source_struct = source_struct; + context->source->target_offset = (char *)source_struct - (char *)buffer; + context->source->upper = NULL; + + /* initialize the RECYCLER */ + context->recycler = NULL; + + /* done */ + return context; +} + +/* Utility function replacing the serialized pointer corresponding to + * *SOURCE_POINTER with the offset that it will be put when being append + * right after this function call. + */ +static void +store_current_end_pointer(svn_temp_serializer__context_t *context, + const void * const * source_pointer) +{ + apr_size_t ptr_offset; + apr_size_t *target_ptr; + + /* if *source_pointer is the root struct, there will be no parent structure + * to relate it to */ + if (context->source == NULL) + return; + + /* position of the serialized pointer relative to the begin of the buffer */ + ptr_offset = (const char *)source_pointer + - (const char *)context->source->source_struct + + context->source->target_offset; + + /* the offset must be within the serialized data. Otherwise, you forgot + * to serialize the respective sub-struct. */ + assert(context->buffer->len > ptr_offset); + + /* use the serialized pointer as a storage for the offset */ + target_ptr = (apr_size_t*)(context->buffer->data + ptr_offset); + + /* store the current buffer length because that's where we will append + * the serialized data of the sub-struct or string */ + *target_ptr = *source_pointer == NULL + ? 0 + : context->buffer->len - context->source->target_offset; +} + +/* Begin serialization of a referenced sub-structure within the + * serialization CONTEXT. SOURCE_STRUCT must be a reference to the pointer + * in the original parent structure so that the correspondence in the + * serialized structure can be established. STRUCT_SIZE must match the + * result of sizeof() of the actual structure. + */ +void +svn_temp_serializer__push(svn_temp_serializer__context_t *context, + const void * const * source_struct, + apr_size_t struct_size) +{ + const void *source = *source_struct; + source_stack_t *new; + + /* recycle an old entry or create a new one for the structure stack */ + if (context->recycler) + { + new = context->recycler; + context->recycler = new->upper; + } + else + new = apr_palloc(context->pool, sizeof(*new)); + + /* the serialized structure must be properly aligned */ + if (source) + align_buffer_end(context); + + /* Store the offset at which the struct data that will the appended. + * Write 0 for NULL pointers. */ + store_current_end_pointer(context, source_struct); + + /* store source and target information */ + new->source_struct = source; + new->target_offset = context->buffer->len; + + /* put the new entry onto the stack*/ + new->upper = context->source; + context->source = new; + + /* finally, actually append the new struct + * (so we can now manipulate pointers within it) */ + if (*source_struct) + svn_stringbuf_appendbytes(context->buffer, source, struct_size); +} + +/* Remove the lastest structure from the stack. + */ +void +svn_temp_serializer__pop(svn_temp_serializer__context_t *context) +{ + source_stack_t *old = context->source; + + /* we may pop the original struct but not further */ + assert(context->source); + + /* one level up the structure stack */ + context->source = context->source->upper; + + /* put the old stack element into the recycler for later reuse */ + old->upper = context->recycler; + context->recycler = old; +} + +/* Serialize a string referenced from the current structure within the + * serialization CONTEXT. S must be a reference to the char* pointer in + * the original structure so that the correspondence in the serialized + * structure can be established. + */ +void +svn_temp_serializer__add_string(svn_temp_serializer__context_t *context, + const char * const * s) +{ + const char *string = *s; + + /* Store the offset at which the string data that will the appended. + * Write 0 for NULL pointers. Strings don't need special alignment. */ + store_current_end_pointer(context, (const void **)s); + + /* append the string data */ + if (string) + svn_stringbuf_appendbytes(context->buffer, string, strlen(string) + 1); +} + +/* Set the serialized representation of the pointer PTR inside the current + * structure within the serialization CONTEXT to NULL. This is particularly + * useful if the pointer is not NULL in the source structure. + */ +void +svn_temp_serializer__set_null(svn_temp_serializer__context_t *context, + const void * const * ptr) +{ + apr_size_t offset; + + /* there must be a parent structure */ + assert(context->source); + + /* position of the serialized pointer relative to the begin of the buffer */ + offset = (const char *)ptr + - (const char *)context->source->source_struct + + context->source->target_offset; + + /* the offset must be within the serialized data. Otherwise, you forgot + * to serialize the respective sub-struct. */ + assert(context->buffer->len > offset); + + /* use the serialized pointer as a storage for the offset */ + *(apr_size_t*)(context->buffer->data + offset) = 0; +} + +/* Return the number of bytes currently used in the serialization buffer + * of the given serialization CONTEXT.*/ +apr_size_t +svn_temp_serializer__get_length(svn_temp_serializer__context_t *context) +{ + return context->buffer->len; +} + +/* Return the data buffer that receives the serialized data from + * the given serialization CONTEXT. + */ +svn_stringbuf_t * +svn_temp_serializer__get(svn_temp_serializer__context_t *context) +{ + return context->buffer; +} + +/* Replace the deserialized pointer value at PTR inside BUFFER with a + * proper pointer value. + */ +void +svn_temp_deserializer__resolve(void *buffer, void **ptr) +{ + /* All pointers are stored as offsets to the buffer start + * (of the respective serialized sub-struct). */ + apr_size_t ptr_offset = *(apr_size_t *)ptr; + if (ptr_offset) + { + /* Reconstruct the original pointer value */ + const char *target = (const char *)buffer + ptr_offset; + + /* All sub-structs are written _after_ their respective parent. + * Thus, all offsets are > 0. If the following assertion is not met, + * the data is either corrupt or you tried to resolve the pointer + * more than once. */ + assert(target > (const char *)buffer); + + /* replace the PTR_OFFSET in *ptr with the pointer to TARGET */ + (*(const char **)ptr) = target; + } + else + { + /* NULL pointers are stored as 0 which might have a different + * binary representation. */ + *ptr = NULL; + } +} + +const void * +svn_temp_deserializer__ptr(const void *buffer, const void *const *ptr) +{ + return (apr_size_t)*ptr == 0 + ? NULL + : (const char*)buffer + (apr_size_t)*ptr; +} |