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 130804ad589d36f0f864713b8f9802f0f3eeaf16..3bc7cedc44739424dd9ad7a9f9f7db6b5b0d51ad 100644
--- a/lib/allocator.h
+++ b/lib/allocator.h
@@ -5,7 +5,7 @@
 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
index 431c351c7b1a7e97c224436367d150bb130e6c0d..b1f91fe81b8fb2c30faab4c6922ab8b64e9f2535 100644
--- a/src/bitreader.c
+++ b/src/bitreader.c
@@ -1,14 +1,17 @@
 #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(parse_state_t* state, int count) {
+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...
@@ -45,5 +48,65 @@ long long read_bits(parse_state_t* state, int count) {
     }
     count -= segment_len;
   }
-  return out;
+  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 d6c73eeba73a435ce5439e5c0596809da8be6204..99b4578c11dcd13bba70b0933e0b87e95a8ec8ab 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -37,14 +37,21 @@
  */
 #define BYTE_BIG_ENDIAN 0x1
 #define BIT_BIG_ENDIAN 0x2
+#define BIT_LITTLE_ENDIAN 0x0
+#define BYTE_LITTLE_ENDIAN 0x0
 
-typedef struct parse_state {
+typedef struct input_stream {
+  // This should be considered to be a really big value type.
   const uint8_t *input;
-  GHashTable *cache; 
   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 {
diff --git a/src/internal.h b/src/internal.h
index 0657de0a08f269db8f856122f1153604e3dca92b..b678b563c0f31c4d97870fe2723f930b33004cc0 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -2,6 +2,9 @@
 #define HAMMER_INTERNAL__H
 #include "hammer.h"
 
-long long read_bits(parse_state_t* state, int count);
+#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