Skip to content
Snippets Groups Projects
Forked from Hammer / hammer
1356 commits behind the upstream repository.
bitwriter.c 3.79 KiB
#include <string.h>
#include <assert.h>
#include "hammer.h"
#include "internal.h"

// This file provides the logical inverse of bitreader.c
struct HBitWriter_ {
  uint8_t* buf;
  size_t index;
  size_t capacity;
  char bit_offset; // unlike in bit_reader, this is always the number
		   // of used bits in the current byte. i.e., 0 always
		   // means that 8 bits are available for use.
  char flags;
};

// h_bit_writer_
HBitWriter *h_bit_writer_new() {
  HBitWriter *writer = g_new0(HBitWriter, 1);
  writer->buf = g_malloc0(writer->capacity = 8);

  writer->flags = BYTE_BIG_ENDIAN | BIT_BIG_ENDIAN;

  return writer;
}

/**
 * Ensure there are at least [nbits] bits available at the end of the
 * buffer. If the buffer is expanded, the added bits should be zeroed.
 */
static void h_bit_writer_reserve(HBitWriter* w, size_t nbits) {
  // As is, this may overshoot by a byte, e.g., nbits=9, bit_offset=1.
  // This will assume that the last partial byte is full, and reserve
  // 2 bytes at the end, whereas only one is necessary.
  //
  // That said, this guarantees the postcondition that w->buf[w->index]
  // is valid.

  // Round up to bytes
  int nbytes = (nbits + 7) / 8 + ((w->bit_offset != 0) ? 1 : 0);
  size_t old_capacity = w->capacity;
  while (w->index + nbytes >= w->capacity) {
    w->buf = g_realloc(w->buf, w->capacity *= 2);
  }

  if (old_capacity != w->capacity)
    memset(w->buf + old_capacity, 0, w->capacity - old_capacity);
}


void h_bit_writer_put(HBitWriter* w, unsigned long long data, size_t nbits) {
  assert(nbits > 0); // Less than or equal to zero makes complete nonsense

  // expand size...
  h_bit_writer_reserve(w, nbits);

  while (nbits) {
    size_t count = MIN((size_t)(8 - w->bit_offset), nbits);

    // select the bits to be written from the source
    uint16_t bits;
    if (w->flags & BYTE_BIG_ENDIAN) {
      // read from the top few bits; masking will be done later.
      bits = data >> (nbits - count);
    } else {
      // just copy the bottom byte over.
      bits = data & 0xff;
      data >>= count; // remove the bits that have just been used.
    }
    // mask off the unnecessary bits.
    bits &= (1 << count) - 1;

    // Now, push those bits onto the current byte...
    if (w->flags & BIT_BIG_ENDIAN)
      w->buf[w->index] = (w->buf[w->index] << count) | bits;
    else
      w->buf[w->index] = (w->buf[w->index] | (bits << 8)) >> count;

    // update index and bit_offset.
    w->bit_offset += count;
    if (w->bit_offset == 8) {
      w->bit_offset = 0;
      w->index++;
    }
    nbits -= count;
  }

}


const uint8_t *h_bit_writer_get_buffer(HBitWriter* w, size_t *len) {
  assert (len != NULL);
  assert (w != NULL);
  // Not entirely sure how to handle a non-integral number of bytes... make it an error for now
  assert (w->bit_offset == 0); // BUG: change this to some sane behaviour

  *len = w->index;
  return w->buf;
}

void h_bit_writer_free(HBitWriter* w) {
  g_free(w->buf);
  g_free(w);
}

#ifdef INCLUDE_TESTS

static void test_bitwriter_ints(void) {

}

static void test_bitwriter_be(void) {

}

static void test_bitwriter_le(void) {

}

static void test_largebits_be(void) {

}

static void test_largebits_le(void) {

}

static void test_offset_largebits_be(void) {

}

static void test_offset_largebits_le(void) {

}

void register_bitwriter_tests(void) {
  g_test_add_func("/core/bitwriter/be", test_bitwriter_be);
  g_test_add_func("/core/bitwriter/le", test_bitwriter_le);
  g_test_add_func("/core/bitwriter/largebits-be", test_largebits_be);
  g_test_add_func("/core/bitwriter/largebits-le", test_largebits_le);
  g_test_add_func("/core/bitwriter/offset-largebits-be", test_offset_largebits_be);
  g_test_add_func("/core/bitwriter/offset-largebits-le", test_offset_largebits_le);
  g_test_add_func("/core/bitwriter/ints", test_bitwriter_ints);
}

#endif // #ifdef INCLUDE_TESTS