diff --git a/.gitignore b/.gitignore
index a70237a6305fd373ddb12639d7b2eddd4f90a774..7bd36bf93b53402a42c0988343b536a38f63139e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 *.o
 *~
+*.a
+src/test_suite
diff --git a/common.mk b/common.mk
new file mode 100644
index 0000000000000000000000000000000000000000..c048330de19404513c3a033889ba4cf329374aa9
--- /dev/null
+++ b/common.mk
@@ -0,0 +1,14 @@
+CFLAGS := $(shell pkg-config --cflags glib-2.0)
+LDFLAGS := $(shell pkg-config --libs glib-2.0)
+CC := gcc
+
+CFLAGS += -DINCLUDE_TESTS
+
+.SUFFIX:
+
+%.a:
+	-rm -f $@
+	ar crv $@ $^
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c -o $@ $<
\ No newline at end of file
diff --git a/config.mk b/config.mk
deleted file mode 100644
index 16209be97db2ac81dff0c1f0e58884aa2398817b..0000000000000000000000000000000000000000
--- a/config.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-CFLAGS := $(shell pkg-config --cflags glib-2.0)
-LDFLAGS := $(shell pkg-config --libs glib-2.0)
\ No newline at end of file
diff --git a/lib/Makefile b/lib/Makefile
index de2a9af10396f3205f4bc30a75cf8eeb4278d850..e8c0b5137d1e72d25d0262cb0caf2a831c9b4c68 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,6 +1,4 @@
-include ../config.mk
+include ../common.mk
 
 all: allocator.o
 
-%.o: %.c
-	gcc $(CFLAGS) -c -o $@ $^
\ No newline at end of file
diff --git a/lib/allocator.h b/lib/allocator.h
index 3cea5906f1df279f0cfa6592515ac5d4c6d300f3..3bc7cedc44739424dd9ad7a9f9f7db6b5b0d51ad 100644
--- a/lib/allocator.h
+++ b/lib/allocator.h
@@ -1,11 +1,11 @@
-#ifndef LIB_ALLOCATOR__H__
-#define LIB_ALLOCATOR__H__
+#ifndef HAMMER_ALLOCATOR__H__
+#define HAMMER_ALLOCATOR__H__
 #include <sys/types.h>
 
 typedef struct arena* arena_t; // hidden implementation
 
 arena_t new_arena(size_t block_size); // pass 0 for default...
-void* arena_malloc(arena_t arena, size_t count); // TODO: needs the malloc attribute
+void* arena_malloc(arena_t arena, size_t count) __attribute__(( malloc, alloc_size(2) ));
 void delete_arena(arena_t arena);
 
 
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..319cc722be1306fae4feae99f44333d9fd60b829
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,10 @@
+-include ../common.mk
+
+all: libhammer.a test_suite
+
+test_suite: test_suite.o libhammer.a
+	$(CC) $(LDFLAGS) -o $@ $^
+
+libhammer.a: bitreader.o
+
+bitreader.o: test_suite.h
diff --git a/src/bitreader.c b/src/bitreader.c
new file mode 100644
index 0000000000000000000000000000000000000000..b1f91fe81b8fb2c30faab4c6922ab8b64e9f2535
--- /dev/null
+++ b/src/bitreader.c
@@ -0,0 +1,112 @@
+#include <stdint.h>
+#include <stdio.h>
+#include "internal.h"
+#include "hammer.h"
+#include "test_suite.h"
+
+#define LSB(range) (0:range)
+#define MSB(range) (1:range)
+#define LDB(range,i) (((i)>>LSB(range))&((1<<(MSB(range)-LSB(range)+1))-1))
+
+long long read_bits(input_stream_t* state, int count, char signed_p) {
+  long long out = 0;
+  int offset = 0;
+  long long msb = (!!signed_p) << (count - 1); // 0 if unsigned, else 1 << (nbits - 1)
+  while (count) {
+    int segment, segment_len;
+    // Read a segment...
+    if (state->endianness & BIT_BIG_ENDIAN) {
+      if (count >= state->bit_offset) {
+	segment_len = state->bit_offset;
+	state->bit_offset = 8;
+	segment = state->input[state->index] & ((1 << segment_len) - 1);
+	state->index++;
+      } else {
+	segment_len = count;
+	state->bit_offset -= count;
+	segment = (state->input[state->index] >> state->bit_offset) & ((1 << segment_len) - 1);
+      }
+    } else { // BIT_LITTLE_ENDIAN
+      if (count + state->bit_offset >= 8) {
+	segment_len = 8 - state->bit_offset;
+	segment = (state->input[state->index] >> state->bit_offset);
+	state->index++;
+	state->bit_offset = 0;
+      } else {
+	segment_len = count;
+	segment = (state->input[state->index] >> state->bit_offset) & ((1 << segment_len) - 1);
+	state->bit_offset += segment_len;
+      }
+    }
+
+    // have a valid segment; time to assemble the byte
+    if (state->endianness & BYTE_BIG_ENDIAN) {
+      out = out << segment_len | segment;
+    } else { // BYTE_LITTLE_ENDIAN
+      out |= segment << offset;
+      offset += segment_len;
+    }
+    count -= segment_len;
+  }
+  return (out ^ msb) - msb; // perform sign extension
+}
+
+#ifdef INCLUDE_TESTS
+
+#define MK_INPUT_STREAM(buf,len,endianness_)   \
+  {					      \
+    .input = buf,						\
+      .length = len,						\
+      .index = 0,						\
+      .bit_offset = (((endianness_) & BIT_BIG_ENDIAN) ? 8 : 0),	\
+      .endianness = endianness_					\
+      }
+
+static void test_bitreader_be(void) {
+  input_stream_t is = MK_INPUT_STREAM("\x6A\x5A", 2, BIT_BIG_ENDIAN | BYTE_BIG_ENDIAN);
+  g_check_cmpint(read_bits(&is, 3, false), ==, 0x03);
+  g_check_cmpint(read_bits(&is, 8, false), ==, 0x52);
+  g_check_cmpint(read_bits(&is, 5, false), ==, 0x1A);
+}
+static void test_bitreader_le(void) {
+  input_stream_t is = MK_INPUT_STREAM("\x6A\x5A", 2, BIT_LITTLE_ENDIAN | BYTE_LITTLE_ENDIAN);
+  g_check_cmpint(read_bits(&is, 3, false), ==, 0x02);
+  g_check_cmpint(read_bits(&is, 8, false), ==, 0x4D);
+  g_check_cmpint(read_bits(&is, 5, false), ==, 0x0B);
+}
+
+static void test_largebits_be(void) {
+  input_stream_t is = MK_INPUT_STREAM("\x6A\x5A", 2, BIT_BIG_ENDIAN | BYTE_BIG_ENDIAN);
+  g_check_cmpint(read_bits(&is, 11, false), ==, 0x352);
+  g_check_cmpint(read_bits(&is, 5, false), ==, 0x1A);
+}
+  
+static void test_largebits_le(void) {
+  input_stream_t is = MK_INPUT_STREAM("\x6A\x5A", 2, BIT_LITTLE_ENDIAN | BYTE_LITTLE_ENDIAN);
+  g_check_cmpint(read_bits(&is, 11, false), ==, 0x26A);
+  g_check_cmpint(read_bits(&is, 5, false), ==, 0x0B);
+}
+
+static void test_offset_largebits_be(void) {
+  input_stream_t is = MK_INPUT_STREAM("\x6A\x5A", 2, BIT_BIG_ENDIAN | BYTE_BIG_ENDIAN);
+  g_check_cmpint(read_bits(&is, 5, false), ==, 0xD);
+  g_check_cmpint(read_bits(&is, 11, false), ==, 0x25A);
+}
+  
+static void test_offset_largebits_le(void) {
+  input_stream_t is = MK_INPUT_STREAM("\x6A\x5A", 2, BIT_LITTLE_ENDIAN | BYTE_LITTLE_ENDIAN);
+  g_check_cmpint(read_bits(&is, 5, false), ==, 0xA);
+  g_check_cmpint(read_bits(&is, 11, false), ==, 0x2D3);
+}
+
+
+void register_bitreader_tests(void)  {
+  g_test_add_func("/core/bitreader/be", test_bitreader_be);
+  g_test_add_func("/core/bitreader/le", test_bitreader_le);
+  g_test_add_func("/core/bitreader/largebits-be", test_largebits_be);
+  g_test_add_func("/core/bitreader/largebits-le", test_largebits_le);
+  g_test_add_func("/core/bitreader/offset-largebits-be", test_offset_largebits_be);
+  g_test_add_func("/core/bitreader/offset-largebits-le", test_offset_largebits_le);
+}
+
+#endif // #ifdef INCLUDE_TESTS
diff --git a/src/hammer.h b/src/hammer.h
index bbacb13d416b7b3ed85d09ffec5a6a05f1967833..99b4578c11dcd13bba70b0933e0b87e95a8ec8ab 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -15,6 +15,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+#ifndef HAMMER_HAMMER__H
+#define HAMMER_HAMMER__H
 #include <glib.h>
 #include <stdint.h>
 
@@ -33,11 +35,23 @@
  *           at which it's been applied are memoized.
  *
  */
-typedef struct parse_state {
+#define BYTE_BIG_ENDIAN 0x1
+#define BIT_BIG_ENDIAN 0x2
+#define BIT_LITTLE_ENDIAN 0x0
+#define BYTE_LITTLE_ENDIAN 0x0
+
+typedef struct input_stream {
+  // This should be considered to be a really big value type.
   const uint8_t *input;
   size_t index;
   size_t length;
+  char bit_offset;
+  char endianness;
+} input_stream_t;
+  
+typedef struct parse_state {
   GHashTable *cache; 
+  input_stream_t input_stream;
 } parse_state_t;
 
 typedef struct parse_result {
@@ -81,5 +95,4 @@ parser_t* epsilon_p();
 parser_t* and(parser_t* p);
 parser_t* not(parser_t* p);
 
-
-
+#endif // #ifndef HAMMER_HAMMER__H
diff --git a/src/internal.h b/src/internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..b678b563c0f31c4d97870fe2723f930b33004cc0
--- /dev/null
+++ b/src/internal.h
@@ -0,0 +1,10 @@
+#ifndef HAMMER_INTERNAL__H
+#define HAMMER_INTERNAL__H
+#include "hammer.h"
+
+#define false 0
+#define true 1
+
+long long read_bits(input_stream_t* state, int count, char signed_p);
+
+#endif // #ifndef HAMMER_INTERNAL__H
diff --git a/src/test_suite.c b/src/test_suite.c
new file mode 100644
index 0000000000000000000000000000000000000000..f519245624d7920b0b3e0c8bdfca1ebf4f3138e4
--- /dev/null
+++ b/src/test_suite.c
@@ -0,0 +1,14 @@
+#include "hammer.h"
+#include "test_suite.h"
+
+extern void register_bitreader_tests();
+
+int main(int argc, char** argv) {
+  g_test_init(&argc, &argv, NULL);
+
+  // register various test suites...
+  register_bitreader_tests();
+
+  g_test_run();
+}
+
diff --git a/src/test_suite.h b/src/test_suite.h
new file mode 100644
index 0000000000000000000000000000000000000000..914f4c43b7517a834fba557fd85ec3c0d2b2aeb6
--- /dev/null
+++ b/src/test_suite.h
@@ -0,0 +1,17 @@
+#ifndef HAMMER_TEST_SUITE__H
+#define HAMMER_TEST_SUITE__H
+
+// Equivalent to g_assert_*, but not using g_assert...
+#define g_check_cmpint(n1, op, n2) {			\
+  typeof (n1) _n1 = (n1);				\
+  typeof (n2) _n2 = (n2);				\
+  if (!(_n1 op _n2)) {					\
+    g_test_message("Check failed: (%s): (%d %s %d)",	\
+		   #n1 " " #op " " #n2,			\
+		   _n1, #op, _n2);			\
+    g_test_fail();					\
+  }							\
+  }
+
+
+#endif // #ifndef HAMMER_TEST_SUITE__H