HelenOS sources

root/uspace/lib/bithenge/src/transform.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. bithenge_init_transform
  2. transform_indestructible
  3. bithenge_transform_apply
  4. bithenge_transform_prefix_length
  5. bithenge_transform_prefix_apply
  6. bithenge_scope_new
  7. bithenge_scope_dec_ref
  8. bithenge_scope_outer
  9. bithenge_scope_get_error
  10. bithenge_scope_error
  11. bithenge_scope_get_current_node
  12. bithenge_scope_set_current_node
  13. bithenge_scope_in_node
  14. bithenge_scope_set_in_node
  15. bithenge_scope_set_barrier
  16. bithenge_scope_is_barrier
  17. bithenge_scope_alloc_params
  18. bithenge_scope_set_param
  19. bithenge_scope_get_param
  20. transform_as_barrier
  21. barrier_as_transform
  22. barrier_transform_apply
  23. barrier_transform_prefix_length
  24. barrier_transform_prefix_apply
  25. barrier_transform_destroy
  26. bithenge_barrier_transform_set_subtransform
  27. bithenge_new_barrier_transform
  28. ascii_apply
  29. bit_prefix_apply
  30. blob_as_bits_xe
  31. bits_xe_as_blob
  32. bits_xe_size
  33. reverse_byte
  34. bits_xe_read_bits
  35. bits_xe_destroy
  36. bits_xe_apply
  37. invalid_apply
  38. known_length_apply
  39. known_length_prefix_length
  40. nonzero_boolean_apply
  41. prefix_length_1
  42. prefix_length_2
  43. prefix_length_4
  44. prefix_length_8
  45. uint8_t_identity
  46. uint_xe_prefix_apply
  47. zero_terminated_apply
  48. zero_terminated_prefix_length

/*
 * 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 <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <bithenge/blob.h>
#include <bithenge/print.h>
#include <bithenge/transform.h>
#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;

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub