/* * Copyright (c) 2012 Sean Bartell * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup bithenge * @{ */ /** * @file * Transforms. */ #include #include #include #include #include #include #include #include "common.h" /* * transform */ /** Initialize a new transform. * @param[out] self Transform to initialize. * @param[in] ops Operations provided by the transform. * @param num_params The number of parameters required. If this is nonzero, the * transform will get its own context with parameters, probably provided by a * param_wrapper. If this is zero, the existing outer context will be used with * whatever parameters it has, so they can be passed to any param_wrappers * within. * @return EOK or an error code from errno.h. */ errno_t bithenge_init_transform(bithenge_transform_t *self, const bithenge_transform_ops_t *ops, int num_params) { assert(ops); assert(ops->apply || ops->prefix_apply); assert(ops->destroy); if (bithenge_should_fail()) return ENOMEM; self->ops = ops; self->refs = 1; self->num_params = num_params; return EOK; } static void transform_indestructible(bithenge_transform_t *self) { assert(false); } /** Apply a transform. Takes ownership of nothing. * @memberof bithenge_transform_t * @param self The transform. * @param scope The scope. * @param in The input tree. * @param[out] out Where the output tree will be stored. * @return EOK on success or an error code from errno.h. */ errno_t bithenge_transform_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { assert(self); assert(self->ops); if (self->ops->apply) return self->ops->apply(self, scope, in, out); if (bithenge_node_type(in) != BITHENGE_NODE_BLOB) return EINVAL; aoff64_t self_size, whole_size; errno_t rc = bithenge_transform_prefix_apply(self, scope, bithenge_node_as_blob(in), out, &self_size); if (rc != EOK) return rc; rc = bithenge_blob_size(bithenge_node_as_blob(in), &whole_size); if (rc == EOK && whole_size != self_size) rc = EINVAL; if (rc != EOK) { bithenge_node_dec_ref(*out); return rc; } return EOK; } /** Find the length of the prefix of a blob this transform can use as input. In * other words, figure out how many bytes this transform will use up. This * method is optional and can return an error, but it must succeed for struct * subtransforms. Takes ownership of nothing. * @memberof bithenge_transform_t * @param self The transform. * @param scope The scope. * @param blob The blob. * @param[out] out Where the prefix length will be stored. * @return EOK on success, ENOTSUP if not supported, or another error code from * errno.h. */ errno_t bithenge_transform_prefix_length(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, aoff64_t *out) { assert(self); assert(self->ops); if (self->ops->prefix_length) return self->ops->prefix_length(self, scope, blob, out); if (!self->ops->prefix_apply) return ENOTSUP; bithenge_node_t *node; errno_t rc = bithenge_transform_prefix_apply(self, scope, blob, &node, out); if (rc != EOK) return rc; bithenge_node_dec_ref(node); return EOK; } /** Apply this transform to a prefix of a blob. In other words, feed as much of * the blob into this transform as possible. Takes ownership of nothing. * @memberof bithenge_transform_t * @param self The transform. * @param scope The scope. * @param blob The blob. * @param[out] out_node Holds the result of applying this transform to the * prefix. * @param[out] out_size Holds the size of the prefix. Can be null, in which * case the size is not determined. * @return EOK on success, ENOTSUP if not supported, or another error code from * errno.h. */ errno_t bithenge_transform_prefix_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, bithenge_node_t **out_node, aoff64_t *out_size) { assert(self); assert(self->ops); if (self->ops->prefix_apply) return self->ops->prefix_apply(self, scope, blob, out_node, out_size); if (!self->ops->prefix_length) return ENOTSUP; aoff64_t size; errno_t rc = bithenge_transform_prefix_length(self, scope, blob, &size); if (rc != EOK) return rc; bithenge_node_t *prefix_blob; bithenge_blob_inc_ref(blob); rc = bithenge_new_subblob(&prefix_blob, blob, 0, size); if (rc != EOK) return rc; rc = bithenge_transform_apply(self, scope, prefix_blob, out_node); bithenge_node_dec_ref(prefix_blob); if (out_size) *out_size = size; return rc; } /* * scope */ /** Create a transform scope. It must be dereferenced with @a * bithenge_scope_dec_ref after it is used. Takes ownership of nothing. * @memberof bithenge_scope_t * @param[out] out Holds the new scope. * @param outer The outer scope, or NULL. * @return EOK on success or an error code from errno.h. */ errno_t bithenge_scope_new(bithenge_scope_t **out, bithenge_scope_t *outer) { bithenge_scope_t *self = malloc(sizeof(*self)); if (!self) return ENOMEM; self->refs = 1; if (outer) bithenge_scope_inc_ref(outer); self->outer = outer; self->error = NULL; self->barrier = false; self->num_params = 0; self->params = NULL; self->current_node = NULL; self->in_node = NULL; *out = self; return EOK; } /** Dereference a transform scope. * @memberof bithenge_scope_t * @param self The scope to dereference, or NULL. */ void bithenge_scope_dec_ref(bithenge_scope_t *self) { if (!self) return; if (--self->refs) return; bithenge_node_dec_ref(self->current_node); for (int i = 0; i < self->num_params; i++) bithenge_node_dec_ref(self->params[i]); bithenge_scope_dec_ref(self->outer); free(self->params); free(self->error); free(self); } /** Get the outer scope of a scope, which may be NULL. * @memberof bithenge_scope_t * @param self The scope to examine. * @return The outer scope, which may be NULL. */ bithenge_scope_t *bithenge_scope_outer(bithenge_scope_t *self) { return self->outer; } /** Get the error message stored in the scope, which may be NULL. The error * message only exists as long as the scope does. * @memberof bithenge_scope_t * @param scope The scope to get the error message from. * @return The error message, or NULL. */ const char *bithenge_scope_get_error(bithenge_scope_t *scope) { return scope->error; } /** Set the error message for the scope. The error message is stored in the * outermost scope, but if any scope already has an error message this error * message is ignored. * @memberof bithenge_scope_t * @param scope The scope. * @param format The format string. * @return EINVAL normally, or another error code from errno.h. */ errno_t bithenge_scope_error(bithenge_scope_t *scope, const char *format, ...) { if (scope->error) return EINVAL; while (scope->outer) { scope = scope->outer; if (scope->error) return EINVAL; } size_t space_left = 256; scope->error = malloc(space_left); if (!scope->error) return ENOMEM; char *out = scope->error; va_list ap; va_start(ap, format); while (*format) { if (format[0] == '%' && format[1] == 't') { format += 2; errno_t rc = bithenge_print_node_to_string(&out, &space_left, BITHENGE_PRINT_PYTHON, va_arg(ap, bithenge_node_t *)); if (rc != EOK) { va_end(ap); return rc; } } else { const char *end = str_chr(format, '%'); if (!end) end = format + str_length(format); size_t size = min((size_t)(end - format), space_left - 1); memcpy(out, format, size); format = end; out += size; space_left -= size; } } *out = '\0'; va_end(ap); return EINVAL; } /** Get the current node being created, which may be NULL. * @memberof bithenge_scope_t * @param scope The scope to get the current node from. * @return The node being created, or NULL. */ bithenge_node_t *bithenge_scope_get_current_node(bithenge_scope_t *scope) { if (scope->current_node) bithenge_node_inc_ref(scope->current_node); return scope->current_node; } /** Set the current node being created. Takes a reference to @a node. * @memberof bithenge_scope_t * @param scope The scope to set the current node in. * @param node The current node being created, or NULL. */ void bithenge_scope_set_current_node(bithenge_scope_t *scope, bithenge_node_t *node) { bithenge_node_dec_ref(scope->current_node); scope->current_node = node; } /** Get the current input node, which may be NULL. * @memberof bithenge_scope_t * @param scope The scope to get the current input node from. * @return The input node, or NULL. */ bithenge_node_t *bithenge_scope_in_node(bithenge_scope_t *scope) { if (scope->in_node) bithenge_node_inc_ref(scope->in_node); return scope->in_node; } /** Set the current input node. Takes a reference to @a node. * @memberof bithenge_scope_t * @param scope The scope to set the input node in. * @param node The input node, or NULL. */ void bithenge_scope_set_in_node(bithenge_scope_t *scope, bithenge_node_t *node) { bithenge_node_dec_ref(scope->in_node); scope->in_node = node; } /** Set a scope as a barrier. * @memberof bithenge_scope_t * @param self The scope to change. */ void bithenge_scope_set_barrier(bithenge_scope_t *self) { self->barrier = true; } /** Check whether a scope is a barrier, meaning that variable lookup stops at * it. * @memberof bithenge_scope_t * @param self The scope to check. * @return Whether the scope is a barrier. */ bool bithenge_scope_is_barrier(bithenge_scope_t *self) { return self->barrier; } /** Allocate parameters. The parameters must then be set with @a * bithenge_scope_set_param. This must not be called on a scope that already * has parameters. * @memberof bithenge_scope_t * @param scope The scope in which to allocate parameters. * @param num_params The number of parameters to allocate. * @return EOK on success or an error code from errno.h. */ errno_t bithenge_scope_alloc_params(bithenge_scope_t *scope, int num_params) { scope->params = malloc(sizeof(*scope->params) * num_params); if (!scope->params) return ENOMEM; scope->num_params = num_params; for (int i = 0; i < num_params; i++) scope->params[i] = NULL; return EOK; } /** Set a parameter. Takes a reference to @a node. Note that range checking is * not done in release builds. * @memberof bithenge_scope_t * @param scope The scope in which to allocate parameters. * @param i The index of the parameter to set. * @param node The value to store in the parameter. * @return EOK on success or an error code from errno.h. */ errno_t bithenge_scope_set_param(bithenge_scope_t *scope, int i, bithenge_node_t *node) { assert(scope); assert(i >= 0 && i < scope->num_params); if (bithenge_should_fail()) { bithenge_node_dec_ref(node); return ENOMEM; } scope->params[i] = node; return EOK; } /** Get a parameter. Note that range checking is not done in release builds. * @memberof bithenge_scope_t * @param scope The scope to get the parameter from. * @param i The index of the parameter to set. * @param[out] out Stores a new reference to the parameter. * @return EOK on success or an error code from errno.h. */ errno_t bithenge_scope_get_param(bithenge_scope_t *scope, int i, bithenge_node_t **out) { assert(scope); if (scope->num_params) { assert(i >= 0 && i < scope->num_params); *out = scope->params[i]; bithenge_node_inc_ref(*out); return EOK; } else { return bithenge_scope_get_param(scope->outer, i, out); } } /* * barrier_transform */ typedef struct { bithenge_transform_t base; bithenge_transform_t *transform; } barrier_transform_t; static inline barrier_transform_t *transform_as_barrier( bithenge_transform_t *base) { return (barrier_transform_t *)base; } static inline bithenge_transform_t *barrier_as_transform( barrier_transform_t *self) { return &self->base; } static errno_t barrier_transform_apply(bithenge_transform_t *base, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { barrier_transform_t *self = transform_as_barrier(base); bithenge_scope_t *inner_scope; errno_t rc = bithenge_scope_new(&inner_scope, scope); if (rc != EOK) return rc; bithenge_scope_set_barrier(inner_scope); bithenge_scope_set_in_node(inner_scope, in); rc = bithenge_transform_apply(self->transform, inner_scope, in, out); bithenge_scope_dec_ref(inner_scope); return rc; } static errno_t barrier_transform_prefix_length(bithenge_transform_t *base, bithenge_scope_t *scope, bithenge_blob_t *in, aoff64_t *out) { barrier_transform_t *self = transform_as_barrier(base); bithenge_scope_t *inner_scope; errno_t rc = bithenge_scope_new(&inner_scope, scope); if (rc != EOK) return rc; bithenge_scope_set_barrier(inner_scope); bithenge_scope_set_in_node(inner_scope, bithenge_blob_as_node(in)); rc = bithenge_transform_prefix_length(self->transform, inner_scope, in, out); bithenge_scope_dec_ref(inner_scope); return rc; } static errno_t barrier_transform_prefix_apply(bithenge_transform_t *base, bithenge_scope_t *scope, bithenge_blob_t *in, bithenge_node_t **out_node, aoff64_t *out_length) { barrier_transform_t *self = transform_as_barrier(base); bithenge_scope_t *inner_scope; errno_t rc = bithenge_scope_new(&inner_scope, scope); if (rc != EOK) return rc; bithenge_scope_set_barrier(inner_scope); bithenge_scope_set_in_node(inner_scope, bithenge_blob_as_node(in)); rc = bithenge_transform_prefix_apply(self->transform, inner_scope, in, out_node, out_length); bithenge_scope_dec_ref(inner_scope); return rc; } static void barrier_transform_destroy(bithenge_transform_t *base) { barrier_transform_t *self = transform_as_barrier(base); bithenge_transform_dec_ref(self->transform); free(self); } static const bithenge_transform_ops_t barrier_transform_ops = { .apply = barrier_transform_apply, .prefix_length = barrier_transform_prefix_length, .prefix_apply = barrier_transform_prefix_apply, .destroy = barrier_transform_destroy, }; /** Set the subtransform of a barrier transform. This must be done before the * barrier transform is used. Takes a reference to @a transform. * @param base The barrier transform. * @param transform The subtransform to use for all operations. * @return EOK on success or an error code from errno.h. */ errno_t bithenge_barrier_transform_set_subtransform(bithenge_transform_t *base, bithenge_transform_t *transform) { assert(transform); assert(bithenge_transform_num_params(transform) == 0); if (bithenge_should_fail()) { bithenge_transform_dec_ref(transform); return ENOMEM; } barrier_transform_t *self = transform_as_barrier(base); assert(!self->transform); self->transform = transform; return EOK; } /** Create a wrapper transform that creates a new scope. This ensures nothing * from the outer scope is passed in, other than parameters. The wrapper may * have a different value for num_params. The subtransform must be set with @a * bithenge_barrier_transform_set_subtransform before the result is used. * @param[out] out Holds the created transform. * @param num_params The number of parameters to require, which may be 0. * @return EOK on success or an error code from errno.h. */ errno_t bithenge_new_barrier_transform(bithenge_transform_t **out, int num_params) { errno_t rc; barrier_transform_t *self = malloc(sizeof(*self)); if (!self) { rc = ENOMEM; goto error; } rc = bithenge_init_transform(barrier_as_transform(self), &barrier_transform_ops, num_params); if (rc != EOK) goto error; self->transform = NULL; *out = barrier_as_transform(self); return EOK; error: free(self); return rc; } /* * ascii */ static errno_t ascii_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { errno_t rc; if (bithenge_node_type(in) != BITHENGE_NODE_BLOB) return EINVAL; bithenge_blob_t *blob = bithenge_node_as_blob(in); aoff64_t size; rc = bithenge_blob_size(blob, &size); if (rc != EOK) return rc; char *buffer = malloc(size + 1); if (!buffer) return ENOMEM; aoff64_t size_read = size; rc = bithenge_blob_read(blob, 0, buffer, &size_read); if (rc != EOK) { free(buffer); return rc; } if (size_read != size) { free(buffer); return EINVAL; } buffer[size] = '\0'; /* TODO: what if the OS encoding is incompatible with ASCII? */ return bithenge_new_string_node(out, buffer, true); } static const bithenge_transform_ops_t ascii_ops = { .apply = ascii_apply, .destroy = transform_indestructible, }; /** The ASCII text transform. */ bithenge_transform_t bithenge_ascii_transform = { &ascii_ops, 1, 0 }; /* * bit */ static errno_t bit_prefix_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, bithenge_node_t **out_node, aoff64_t *out_size) { char buffer; aoff64_t size = 1; errno_t rc = bithenge_blob_read_bits(blob, 0, &buffer, &size, true); if (rc != EOK) return rc; if (size != 1) return EINVAL; if (out_size) *out_size = size; return bithenge_new_boolean_node(out_node, (buffer & 1) != 0); } static const bithenge_transform_ops_t bit_ops = { .prefix_apply = bit_prefix_apply, .destroy = transform_indestructible, }; /** A transform that decodes a bit as a boolean. */ bithenge_transform_t bithenge_bit_transform = { &bit_ops, 1, 0 }; /* * bits_be, bits_le */ typedef struct { bithenge_blob_t base; bithenge_blob_t *bytes; bool little_endian; } bits_xe_blob_t; static bits_xe_blob_t *blob_as_bits_xe(bithenge_blob_t *base) { return (bits_xe_blob_t *)base; } static bithenge_blob_t *bits_xe_as_blob(bits_xe_blob_t *self) { return &self->base; } static errno_t bits_xe_size(bithenge_blob_t *base, aoff64_t *out) { bits_xe_blob_t *self = blob_as_bits_xe(base); errno_t rc = bithenge_blob_size(self->bytes, out); *out *= 8; return rc; } static uint8_t reverse_byte(uint8_t val) { val = ((val & 0x0f) << 4) ^ ((val & 0xf0) >> 4); val = ((val & 0x33) << 2) ^ ((val & 0xcc) >> 2); val = ((val & 0x55) << 1) ^ ((val & 0xaa) >> 1); return val; } static errno_t bits_xe_read_bits(bithenge_blob_t *base, aoff64_t offset, char *buffer, aoff64_t *size, bool little_endian) { bits_xe_blob_t *self = blob_as_bits_xe(base); aoff64_t bytes_offset = offset / 8; aoff64_t bit_offset = offset % 8; aoff64_t output_num_bytes = (*size + 7) / 8; aoff64_t bytes_size = (*size + bit_offset + 7) / 8; bool separate_buffer = bit_offset != 0; uint8_t *bytes_buffer; if (separate_buffer) { /* Allocate an extra byte, to make sure byte1 can be read. */ bytes_buffer = malloc(bytes_size + 1); if (!bytes_buffer) return ENOMEM; } else bytes_buffer = (uint8_t *)buffer; errno_t rc = bithenge_blob_read(self->bytes, bytes_offset, (char *)bytes_buffer, &bytes_size); if (rc != EOK) goto end; *size = min(*size, bytes_size * 8 - bit_offset); if (little_endian != self->little_endian) for (aoff64_t i = 0; i < bytes_size; i++) bytes_buffer[i] = reverse_byte(bytes_buffer[i]); if (bit_offset || separate_buffer) { for (aoff64_t i = 0; i < output_num_bytes; i++) { uint8_t byte0 = bytes_buffer[i]; uint8_t byte1 = bytes_buffer[i + 1]; buffer[i] = little_endian ? (byte0 >> bit_offset) ^ (byte1 << (8 - bit_offset)) : (byte0 << bit_offset) ^ (byte1 >> (8 - bit_offset)); } } end: if (separate_buffer) free(bytes_buffer); return rc; } static void bits_xe_destroy(bithenge_blob_t *base) { bits_xe_blob_t *self = blob_as_bits_xe(base); bithenge_blob_dec_ref(self->bytes); free(self); } static const bithenge_random_access_blob_ops_t bits_xe_blob_ops = { .size = bits_xe_size, .read_bits = bits_xe_read_bits, .destroy = bits_xe_destroy, }; static errno_t bits_xe_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { if (bithenge_node_type(in) != BITHENGE_NODE_BLOB) return EINVAL; bits_xe_blob_t *blob = malloc(sizeof(*blob)); if (!blob) return ENOMEM; errno_t rc = bithenge_init_random_access_blob(bits_xe_as_blob(blob), &bits_xe_blob_ops); if (rc != EOK) { free(blob); return rc; } bithenge_node_inc_ref(in); blob->bytes = bithenge_node_as_blob(in); blob->little_endian = (self == &bithenge_bits_le_transform); *out = bithenge_blob_as_node(bits_xe_as_blob(blob)); return EOK; } static const bithenge_transform_ops_t bits_xe_ops = { .apply = bits_xe_apply, .destroy = transform_indestructible, }; /** A transform that converts a byte blob to a bit blob, most-significant bit * first. */ bithenge_transform_t bithenge_bits_be_transform = { &bits_xe_ops, 1, 0 }; /** A transform that converts a byte blob to a bit blob, least-significant bit * first. */ bithenge_transform_t bithenge_bits_le_transform = { &bits_xe_ops, 1, 0 }; /* * invalid */ static errno_t invalid_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { return EINVAL; } static const bithenge_transform_ops_t invalid_ops = { .apply = invalid_apply, .destroy = transform_indestructible, }; /** A transform that always raises an error. */ bithenge_transform_t bithenge_invalid_transform = { &invalid_ops, 1, 0 }; /* * known_length */ static errno_t known_length_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { bithenge_node_t *length_node; errno_t rc = bithenge_scope_get_param(scope, 0, &length_node); if (rc != EOK) return rc; if (bithenge_node_type(length_node) != BITHENGE_NODE_INTEGER) { bithenge_node_dec_ref(length_node); return EINVAL; } bithenge_int_t length = bithenge_integer_node_value(length_node); bithenge_node_dec_ref(length_node); if (bithenge_node_type(in) != BITHENGE_NODE_BLOB) return EINVAL; aoff64_t size; rc = bithenge_blob_size(bithenge_node_as_blob(in), &size); if (rc != EOK) return rc; if (length != (bithenge_int_t)size) return EINVAL; bithenge_node_inc_ref(in); *out = in; return EOK; } static errno_t known_length_prefix_length(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *in, aoff64_t *out) { bithenge_node_t *length_node; errno_t rc = bithenge_scope_get_param(scope, 0, &length_node); if (rc != EOK) return rc; if (bithenge_node_type(length_node) != BITHENGE_NODE_INTEGER) { bithenge_node_dec_ref(length_node); return EINVAL; } bithenge_int_t length = bithenge_integer_node_value(length_node); bithenge_node_dec_ref(length_node); *out = (aoff64_t)length; return EOK; } static const bithenge_transform_ops_t known_length_ops = { .apply = known_length_apply, .prefix_length = known_length_prefix_length, .destroy = transform_indestructible, }; /** Pass through a blob, but require its length to equal the first argument. */ bithenge_transform_t bithenge_known_length_transform = { &known_length_ops, 1, 1 }; static errno_t nonzero_boolean_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { if (bithenge_node_type(in) != BITHENGE_NODE_INTEGER) return EINVAL; bool value = bithenge_integer_node_value(in) != 0; return bithenge_new_boolean_node(out, value); } static const bithenge_transform_ops_t nonzero_boolean_ops = { .apply = nonzero_boolean_apply, .destroy = transform_indestructible, }; /** A transform that converts integers to booleans, true if nonzero. */ bithenge_transform_t bithenge_nonzero_boolean_transform = { &nonzero_boolean_ops, 1, 0 }; static errno_t prefix_length_1(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, aoff64_t *out) { *out = 1; return EOK; } static errno_t prefix_length_2(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, aoff64_t *out) { *out = 2; return EOK; } static errno_t prefix_length_4(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, aoff64_t *out) { *out = 4; return EOK; } static errno_t prefix_length_8(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, aoff64_t *out) { *out = 8; return EOK; } static uint8_t uint8_t_identity(uint8_t arg) { return arg; } /** @cond internal */ #define MAKE_UINT_TRANSFORM(NAME, TYPE, ENDIAN, PREFIX_LENGTH_FUNC) \ static errno_t NAME##_apply(bithenge_transform_t *self, \ bithenge_scope_t *scope, bithenge_node_t *in, \ bithenge_node_t **out) \ { \ errno_t rc; \ if (bithenge_node_type(in) != BITHENGE_NODE_BLOB) \ return EINVAL; \ bithenge_blob_t *blob = bithenge_node_as_blob(in); \ \ /* Read too many bytes; success means the blob is too long. */ \ TYPE val[2]; \ aoff64_t size = sizeof(val[0]) + 1; \ rc = bithenge_blob_read(blob, 0, (char *)val, &size); \ if (rc != EOK) \ return rc; \ if (size != sizeof(val[0])) \ return EINVAL; \ \ return bithenge_new_integer_node(out, ENDIAN(val[0])); \ } \ \ static const bithenge_transform_ops_t NAME##_ops = { \ .apply = NAME##_apply, \ .prefix_length = PREFIX_LENGTH_FUNC, \ .destroy = transform_indestructible, \ }; \ \ bithenge_transform_t bithenge_##NAME##_transform = { \ &NAME##_ops, 1, 0 \ } MAKE_UINT_TRANSFORM(uint8, uint8_t, uint8_t_identity, prefix_length_1); MAKE_UINT_TRANSFORM(uint16le, uint16_t, uint16_t_le2host, prefix_length_2); MAKE_UINT_TRANSFORM(uint16be, uint16_t, uint16_t_be2host, prefix_length_2); MAKE_UINT_TRANSFORM(uint32le, uint32_t, uint32_t_le2host, prefix_length_4); MAKE_UINT_TRANSFORM(uint32be, uint32_t, uint32_t_be2host, prefix_length_4); MAKE_UINT_TRANSFORM(uint64le, uint64_t, uint64_t_le2host, prefix_length_8); MAKE_UINT_TRANSFORM(uint64be, uint64_t, uint64_t_be2host, prefix_length_8); /** @endcond */ /* * uint_be, uint_le */ static errno_t uint_xe_prefix_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, bithenge_node_t **out_node, aoff64_t *out_size) { bool little_endian = (self == &bithenge_uint_le_transform); bithenge_node_t *num_bits_node; errno_t rc = bithenge_scope_get_param(scope, 0, &num_bits_node); if (rc != EOK) return rc; if (bithenge_node_type(num_bits_node) != BITHENGE_NODE_INTEGER) { bithenge_node_dec_ref(num_bits_node); return EINVAL; } bithenge_int_t num_bits = bithenge_integer_node_value(num_bits_node); bithenge_node_dec_ref(num_bits_node); if (num_bits < 0) return EINVAL; if ((size_t)num_bits > sizeof(bithenge_int_t) * 8 - 1) return EINVAL; aoff64_t size = num_bits; uint8_t buffer[sizeof(bithenge_int_t)]; rc = bithenge_blob_read_bits(blob, 0, (char *)buffer, &size, little_endian); if (rc != EOK) return rc; if (size != (aoff64_t)num_bits) return EINVAL; if (out_size) *out_size = size; bithenge_int_t result = 0; bithenge_int_t num_easy_bytes = num_bits / 8; if (little_endian) { for (bithenge_int_t i = 0; i < num_easy_bytes; i++) result += buffer[i] << 8 * i; if (num_bits % 8) result += (buffer[num_easy_bytes] & ((1 << num_bits % 8) - 1)) << 8 * num_easy_bytes; } else { for (bithenge_int_t i = 0; i < num_easy_bytes; i++) result += buffer[i] << (num_bits - 8 * (i + 1)); if (num_bits % 8) result += buffer[num_easy_bytes] >> (8 - num_bits % 8); } return bithenge_new_integer_node(out_node, result); } static const bithenge_transform_ops_t uint_xe_ops = { .prefix_apply = uint_xe_prefix_apply, .destroy = transform_indestructible, }; /** A transform that reads an unsigned integer from an arbitrary number of * bits, most-significant bit first. */ bithenge_transform_t bithenge_uint_be_transform = { &uint_xe_ops, 1, 1 }; /** A transform that reads an unsigned integer from an arbitrary number of * bits, least-significant bit first. */ bithenge_transform_t bithenge_uint_le_transform = { &uint_xe_ops, 1, 1 }; /* * zero_terminated */ static errno_t zero_terminated_apply(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_node_t *in, bithenge_node_t **out) { errno_t rc; if (bithenge_node_type(in) != BITHENGE_NODE_BLOB) return EINVAL; bithenge_blob_t *blob = bithenge_node_as_blob(in); aoff64_t size; rc = bithenge_blob_size(blob, &size); if (rc != EOK) return rc; if (size < 1) return EINVAL; char ch; aoff64_t size_read = 1; rc = bithenge_blob_read(blob, size - 1, &ch, &size_read); if (rc != EOK) return rc; if (size_read != 1 || ch != '\0') return EINVAL; bithenge_blob_inc_ref(blob); return bithenge_new_subblob(out, blob, 0, size - 1); } static errno_t zero_terminated_prefix_length(bithenge_transform_t *self, bithenge_scope_t *scope, bithenge_blob_t *blob, aoff64_t *out) { errno_t rc; char buffer[4096]; aoff64_t offset = 0, size_read = sizeof(buffer); do { rc = bithenge_blob_read(blob, offset, buffer, &size_read); if (rc != EOK) return rc; char *found = memchr(buffer, '\0', size_read); if (found) { *out = found - buffer + offset + 1; return EOK; } offset += size_read; } while (size_read == sizeof(buffer)); return EINVAL; } static const bithenge_transform_ops_t zero_terminated_ops = { .apply = zero_terminated_apply, .prefix_length = zero_terminated_prefix_length, .destroy = transform_indestructible, }; /** The zero-terminated data transform. */ bithenge_transform_t bithenge_zero_terminated_transform = { &zero_terminated_ops, 1, 0 }; static bithenge_named_transform_t primitive_transforms[] = { { "ascii", &bithenge_ascii_transform }, { "bit", &bithenge_bit_transform }, { "bits_be", &bithenge_bits_be_transform }, { "bits_le", &bithenge_bits_le_transform }, { "known_length", &bithenge_known_length_transform }, { "nonzero_boolean", &bithenge_nonzero_boolean_transform }, { "uint8", &bithenge_uint8_transform }, { "uint16be", &bithenge_uint16be_transform }, { "uint16le", &bithenge_uint16le_transform }, { "uint32be", &bithenge_uint32be_transform }, { "uint32le", &bithenge_uint32le_transform }, { "uint64be", &bithenge_uint64be_transform }, { "uint64le", &bithenge_uint64le_transform }, { "uint_be", &bithenge_uint_be_transform }, { "uint_le", &bithenge_uint_le_transform }, { "zero_terminated", &bithenge_zero_terminated_transform }, { NULL, NULL } }; /** An array of named built-in transforms. */ bithenge_named_transform_t *bithenge_primitive_transforms = primitive_transforms; /** @} */