Merge branch 'fastbinary'


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@674688 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configure.ac b/configure.ac
index df5c229..ef163f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,6 +61,12 @@
 fi
 AM_CONDITIONAL(WITH_PYTHON, [test -n "$PYTHON" -a "$PYTHON" != ":"])
 
+AX_THRIFT_LIB(ruby, [Ruby], yes)
+if test "$with_ruby" = "yes"; then
+  AC_PATH_PROG([RUBY], [ruby])
+fi
+AM_CONDITIONAL(ENABLE_RUBY, [test -n "$RUBY"])
+
 AC_C_CONST
 AC_C_INLINE
 AC_C_VOLATILE
@@ -165,10 +171,12 @@
   lib/csharp/Makefile
   lib/java/Makefile
   lib/py/Makefile
+  lib/rb/Makefile
   if/Makefile
   test/Makefile
   test/py/Makefile
   test/java/Makefile
+  test/rb/Makefile
 ])
 
 AC_OUTPUT
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 09236ce..0765f03 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -17,12 +17,14 @@
 SUBDIRS += erl
 endif
 
+if ENABLE_RUBY
+SUBDIRS += rb
+endif
+
 EXTRA_DIST = \
   cocoa \
   hs    \
   ocaml \
   perl  \
   php   \
-  rb    \
-  erl   \
   st
diff --git a/lib/rb/Makefile.am b/lib/rb/Makefile.am
new file mode 100644
index 0000000..43acfff
--- /dev/null
+++ b/lib/rb/Makefile.am
@@ -0,0 +1,13 @@
+EXTRA_DIST = setup.rb lib ext
+
+all-local:
+	$(RUBY) setup.rb config
+	$(RUBY) setup.rb setup
+
+install-exec-hook:
+	$(RUBY) setup.rb install
+
+clean-local:
+	$(RUBY) setup.rb clean
+
+check-local: all
diff --git a/lib/rb/Manifest b/lib/rb/Manifest
index b90a86d..1b408fc 100644
--- a/lib/rb/Manifest
+++ b/lib/rb/Manifest
@@ -8,11 +8,14 @@
 benchmark/thin_server.rb
 CHANGELOG
 COPYING
+ext/binaryprotocolaccelerated.c
+ext/extconf.rb
 lib/thrift/client.rb
 lib/thrift/deprecation.rb
 lib/thrift/exceptions.rb
 lib/thrift/processor.rb
 lib/thrift/protocol/binaryprotocol.rb
+lib/thrift/protocol/binaryprotocolaccelerated.rb
 lib/thrift/protocol/tbinaryprotocol.rb
 lib/thrift/protocol/tprotocol.rb
 lib/thrift/protocol.rb
@@ -34,9 +37,13 @@
 lib/thrift/types.rb
 lib/thrift.rb
 LICENSE
+Makefile
+Makefile.am
+Makefile.in
 Manifest
 Rakefile
 README
+setup.rb
 spec/backwards_compatibility_spec.rb
 spec/binaryprotocol_spec.rb
 spec/client_spec.rb
diff --git a/lib/rb/Rakefile b/lib/rb/Rakefile
index e88e984..6ddca52 100644
--- a/lib/rb/Rakefile
+++ b/lib/rb/Rakefile
@@ -23,7 +23,7 @@
   # ensure this is a full thrift checkout and not a tarball of the ruby libs
   cmd = 'head -1 ../../README 2>/dev/null | grep Thrift >/dev/null 2>/dev/null'
   system(cmd) or fail "rake test requires a full thrift checkout"
-  sh 'make', '-C', File.dirname(__FILE__) + "/../../test/rb"
+  sh 'make', '-C', File.dirname(__FILE__) + "/../../test/rb", "check"
 end
 
 desc 'Compile the .thrift files for the specs'
@@ -57,6 +57,15 @@
     p.url = "http://incubator.apache.org/thrift/"
     p.include_rakefile = true
   end
+
+  task :install => [:check_site_lib]
+
+  require 'rbconfig'
+  task :check_site_lib do
+    if File.exist?(File.join(Config::CONFIG['sitelibdir'], 'thrift.rb'))
+      fail "thrift is already installed in site_ruby"
+    end
+  end
 rescue LoadError
   [:install, :package].each do |t|
     desc "Stub for #{t}"
diff --git a/lib/rb/ext/binaryprotocolaccelerated.c b/lib/rb/ext/binaryprotocolaccelerated.c
new file mode 100644
index 0000000..2725bfc
--- /dev/null
+++ b/lib/rb/ext/binaryprotocolaccelerated.c
@@ -0,0 +1,1231 @@
+// Half of this file comes from contributions from Nitay Joffe (nitay@powerset.com)
+// Much of the rest (almost) directly ported (or pulled) from thrift-py's fastbinary.c
+// Everything else via Kevin Clark (kevin@powerset.com)
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <ruby.h>
+#include <st.h>
+#include <netinet/in.h>
+
+// #define __DEBUG__
+
+#ifndef HAVE_STRLCPY
+
+static
+size_t
+strlcpy (char *dst, const char *src, size_t dst_sz)
+{
+    size_t n;
+
+    for (n = 0; n < dst_sz; n++) {
+      if ((*dst++ = *src++) == '\0')
+        break;
+    }
+
+    if (n < dst_sz)
+      return n;
+    if (n > 0)
+      *(dst - 1) = '\0';
+    return n + strlen (src);
+}
+
+#endif
+
+#define dbg() fprintf(stderr, "%s:%d\n", __FUNCTION__, __LINE__)
+
+
+// TODO (kevinclark): This was here from the patch/python. Not sure
+// If it's actually that big a pain. Need to look into pulling
+// From the right place
+
+// Stolen out of TProtocol.h.
+// It would be a huge pain to have both get this from one place.
+
+enum TType {
+  T_STOP       = 0,
+  T_BOOL       = 2,
+  T_BYTE       = 3,
+  T_I16        = 6,
+  T_I32        = 8,
+  T_I64        = 10,
+  T_DBL        = 4,
+  T_STR        = 11,
+  T_STRCT      = 12,
+  T_MAP        = 13,
+  T_SET        = 14,
+  T_LIST       = 15
+  // T_VOID       = 1,
+  // T_I08        = 3,
+  // T_U64        = 9,
+  // T_UTF7       = 11,
+  // T_UTF8       = 16,
+  // T_UTF16      = 17
+};
+
+#define IS_CONTAINER(x) (x == T_MAP || x == T_SET || x == T_LIST)
+
+// Same comment as the enum.  Sorry.
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+
+#ifndef __BYTE_ORDER
+# if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
+#  define __BYTE_ORDER BYTE_ORDER
+#  define __LITTLE_ENDIAN LITTLE_ENDIAN
+#  define __BIG_ENDIAN BIG_ENDIAN
+# else
+#  error "Cannot determine endianness"
+# endif
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+# define ntohll(n) (n)
+# define htonll(n) (n)
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+# if defined(__GNUC__) && defined(__GLIBC__)
+#  include <byteswap.h>
+#  define ntohll(n) bswap_64(n)
+#  define htonll(n) bswap_64(n)
+# else /* GNUC & GLIBC */
+#  define ntohll(n) ( (((unsigned long long)ntohl(n)) << 32) + ntohl(n >> 32) )
+#  define htonll(n) ( (((unsigned long long)htonl(n)) << 32) + htonl(n >> 32) )
+# endif /* GNUC & GLIBC */
+#else /* __BYTE_ORDER */
+# error "Can't define htonll or ntohll!"
+#endif
+
+
+// -----------------------------------------------------------------------------
+// Cached interned strings and such
+// -----------------------------------------------------------------------------
+
+static VALUE class_tbpa;
+static VALUE m_thrift;
+static VALUE rb_cSet;
+static ID type_sym;
+static ID class_sym;
+static ID key_sym;
+static ID value_sym;
+static ID element_sym;
+static ID name_sym;
+static ID fields_id;
+static ID consume_bang_id;
+static ID string_buffer_id;
+static ID borrow_id;
+static ID keys_id;
+static ID entries_id;
+
+static const uint32_t VERSION_MASK = 0xffff0000;
+static const uint32_t VERSION_1 = 0x80010000;
+
+// -----------------------------------------------------------------------------
+// Structs so I don't have to keep calling rb_hash_aref
+// -----------------------------------------------------------------------------
+
+// { :type => field[:type],
+//   :class => field[:class],
+//   :key => field[:key],
+//   :value => field[:value],
+//   :element => field[:element] }
+
+struct _thrift_map;
+struct _field_spec;
+
+typedef union {
+  VALUE class;
+  struct _thrift_map* map;
+  struct _field_spec* element;
+} container_data;
+
+typedef struct _field_spec {
+  int type;
+  char* name;
+  container_data data;
+} field_spec;
+
+typedef struct _thrift_map {
+  field_spec* key;
+  field_spec* value; 
+} thrift_map;
+
+
+static void free_field_spec(field_spec* spec) {
+  switch(spec->type) {
+    case T_LIST:
+    case T_SET:
+      free_field_spec(spec->data.element);
+      break;
+    
+    case T_MAP:
+      free_field_spec(spec->data.map->key);
+      free_field_spec(spec->data.map->value);
+      free(spec->data.map);
+      break;
+  }
+  
+  free(spec);
+}
+
+// Parses a ruby field spec into a C struct
+//
+// Simple fields look like:
+// { :name => .., :type => .. }
+// Structs add the :class attribute
+// Maps adds :key and :value attributes, field specs
+// Lists and Sets add an :element, a field spec
+static field_spec* parse_field_spec(VALUE field_data) {
+  int type = NUM2INT(rb_hash_aref(field_data, type_sym));
+  VALUE name = rb_hash_aref(field_data, name_sym);
+  field_spec* spec = (field_spec *) malloc(sizeof(field_spec));
+
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+  bzero(spec, sizeof(field_spec));
+#endif
+
+  spec->type = type;
+  
+  if (Qnil != name) {
+    spec->name = StringValuePtr(name);
+  } else {
+    spec->name = NULL;
+  }
+  
+  switch(type) {
+    case T_STRCT: {
+      spec->data.class = rb_hash_aref(field_data, class_sym);
+      break;
+    }
+    
+    case T_MAP: {
+      VALUE key_fields = rb_hash_aref(field_data, key_sym);
+      VALUE value_fields = rb_hash_aref(field_data, value_sym);
+      thrift_map* map = (thrift_map *) malloc(sizeof(thrift_map));
+      
+      map->key = parse_field_spec(key_fields);
+      map->value = parse_field_spec(value_fields);
+      spec->data.map = map;
+      
+      break;
+    }
+    
+    case T_LIST: 
+    case T_SET:
+    {
+      VALUE list_fields = rb_hash_aref(field_data, element_sym);
+      spec->data.element = parse_field_spec(list_fields);
+      break;
+    }
+  }
+  
+  return spec;
+}
+
+
+// -----------------------------------------------------------------------------
+// Serialization routines
+// -----------------------------------------------------------------------------
+
+
+// write_*(VALUE buf, ...) takes a value and adds it to a Ruby string buffer,
+// in network order
+static void write_byte(VALUE buf, int8_t val) {
+  rb_str_buf_cat(buf, (char*)&val, sizeof(int8_t));
+}
+
+static void write_i16(VALUE buf, int16_t val) {
+  int16_t net = (int16_t)htons(val);
+  rb_str_buf_cat(buf, (char*)&net, sizeof(int16_t));
+}
+
+static void write_i32(VALUE buf, int32_t val) {
+  int32_t net = (int32_t)htonl(val);
+  rb_str_buf_cat(buf, (char*)&net, sizeof(int32_t));
+}
+
+static void write_i64(VALUE buf, int64_t val) {
+  int64_t net = (int64_t)htonll(val);
+  rb_str_buf_cat(buf, (char*)&net, sizeof(int64_t));
+}
+
+static void write_double(VALUE buf, double dub) {
+  // Unfortunately, bitwise_cast doesn't work in C.  Bad C!
+  union {
+    double f;
+    int64_t t;
+  } transfer;
+  transfer.f = dub;
+  write_i64(buf, transfer.t);
+}
+
+static void write_string(VALUE buf, char* str) {
+  int32_t len = strlen(str);
+  write_i32(buf, len);
+  rb_str_buf_cat2(buf, str);
+}
+
+// Some functions macro'd out because they're nops for the binary protocol
+// but placeholders were desired in case things change
+#define write_struct_begin(buf)
+#define write_struct_end(buf)
+
+static void write_field_begin(VALUE buf, char* name, int type, int fid) {
+#ifdef __DEBUG__
+  fprintf(stderr, "Writing field beginning: %s %d %d\n", name, type, fid);
+#endif
+
+  write_byte(buf, (int8_t)type);
+  write_i16(buf, (int16_t)fid);
+}
+
+#define write_field_end(buf)
+
+static void write_field_stop(VALUE buf) {
+  write_byte(buf, T_STOP);
+}
+
+static void write_map_begin(VALUE buf, int8_t ktype, int8_t vtype, int32_t sz) {
+  write_byte(buf, ktype);
+  write_byte(buf, vtype);
+  write_i32(buf, sz);
+}
+
+#define write_map_end(buf);
+
+static void write_list_begin(VALUE buf, int type, int sz) {
+  write_byte(buf, type);
+  write_i32(buf, sz);
+}
+
+#define write_list_end(buf)
+
+static void write_set_begin(VALUE buf, int type, int sz) {
+  write_byte(buf, type);
+  write_i32(buf, sz);
+}
+
+#define write_set_end(buf)
+
+static void binary_encoding(VALUE buf, VALUE obj, int type);
+
+// Handles container types: Map, Set, List
+static void write_container(VALUE buf, VALUE value, field_spec* spec) {
+  int sz, i;
+  
+  switch(spec->type) {
+    case T_MAP: {
+      VALUE keys;
+      VALUE key;
+      VALUE val;
+      
+      keys = rb_funcall(value, keys_id, 0);
+      
+      sz = RARRAY(keys)->len;
+      
+      write_map_begin(buf, spec->data.map->key->type, spec->data.map->value->type, sz);
+      
+      for (i = 0; i < sz; i++) {
+        key = rb_ary_entry(keys, i);
+        val = rb_hash_aref(value, key);
+        
+        if (IS_CONTAINER(spec->data.map->key->type)) {
+          write_container(buf, key, spec->data.map->key);
+        } else {
+          binary_encoding(buf, key, spec->data.map->key->type);
+        }
+        
+        if (IS_CONTAINER(spec->data.map->value->type)) {
+          write_container(buf, val, spec->data.map->value);
+        } else {
+          binary_encoding(buf, val, spec->data.map->value->type);
+        }
+      }
+      
+      write_map_end(buf);
+
+      break;
+    }
+    
+    case T_LIST: {
+      sz = RARRAY(value)->len;
+      
+      write_list_begin(buf, spec->data.element->type, sz);
+      for (i = 0; i < sz; ++i) {
+        if (IS_CONTAINER(spec->data.element->type)) {
+          write_container(buf, rb_ary_entry(value, i), spec->data.element);
+        } else {
+          binary_encoding(buf, rb_ary_entry(value, i), spec->data.element->type);
+        }
+      }
+      write_list_end(buf);
+      break;
+    }
+
+    case T_SET: {
+      VALUE items;
+
+      if (TYPE(value) == T_ARRAY) {
+        items = value;
+      } else {        
+        if (rb_cSet == CLASS_OF(value)) {
+          items = rb_funcall(value, entries_id, 0);
+        } else {
+          Check_Type(value, T_HASH);
+          items = rb_funcall(value, keys_id, 0);
+        }
+      }
+
+      sz = RARRAY(items)->len;
+      
+      write_set_begin(buf, spec->data.element->type, sz);
+      
+      for (i = 0; i < sz; i++) {
+        if (IS_CONTAINER(spec->data.element->type)) {
+          write_container(buf, rb_ary_entry(items, i), spec->data.element);
+        } else {
+          binary_encoding(buf, rb_ary_entry(items, i), spec->data.element->type);
+        }
+      }
+      
+      write_set_end(buf);
+      break;
+    }
+  }
+}
+
+// Takes the field id, data to be encoded, buffer and enclosing object
+// to be encoded. buf and obj passed as a ruby array for rb_hash_foreach.
+// TODO(kevinclark): See if they can be passed individually to avoid object
+// creation
+static int encode_field(VALUE fid, VALUE data, VALUE ary) {
+  field_spec *spec = parse_field_spec(data);
+  
+  VALUE buf = rb_ary_entry(ary, 0);
+  VALUE obj = rb_ary_entry(ary, 1);
+  char name_buf[128];
+  
+  name_buf[0] = '@';
+  strlcpy(&name_buf[1], spec->name, sizeof(name_buf) - 1);
+  
+  VALUE value = rb_ivar_get(obj, rb_intern(name_buf));
+  
+  if (Qnil == value) {
+    free_field_spec(spec);
+    return 0;
+  }
+     
+  write_field_begin(buf, spec->name, spec->type, NUM2INT(fid));
+  
+  if (IS_CONTAINER(spec->type)) {
+    write_container(buf, value, spec);
+  } else {
+    binary_encoding(buf, value, spec->type);
+  }
+  write_field_end(buf);
+  
+  free_field_spec(spec);
+  
+  return 0;
+}
+
+// -----------------------------------------------------------------------------
+// TFastBinaryProtocol's main encoding loop
+// -----------------------------------------------------------------------------
+
+static void binary_encoding(VALUE buf, VALUE obj, int type) {
+#ifdef __DEBUG__
+  rb_p(rb_str_new2("Encoding binary (buf, obj, type)"));
+  rb_p(rb_inspect(buf));
+  rb_p(rb_inspect(obj));
+  rb_p(rb_inspect(INT2FIX(type)));
+#endif
+
+  switch(type) {
+    case T_BOOL:
+      if RTEST(obj) {
+        write_byte(buf, 1);
+      }
+      else {
+        write_byte(buf, 0);
+      }
+      
+      break;
+    
+    case T_BYTE:
+      write_byte(buf, NUM2INT(obj));
+      break;
+    
+    case T_I16:
+      write_i16(buf, NUM2INT(obj));
+      break;
+    
+    case T_I32:
+      write_i32(buf, NUM2INT(obj));
+      break;
+    
+    case T_I64:
+      write_i64(buf, rb_num2ll(obj));
+      break;
+    
+    case T_DBL:
+      write_double(buf, NUM2DBL(obj));
+      break;
+
+    case T_STR:
+      write_string(buf, StringValuePtr(obj));
+      break;
+          
+    case T_STRCT: {
+      // rb_hash_foreach has to take args as a ruby array
+      VALUE args = rb_ary_new3(2, buf, obj);
+      VALUE fields = rb_const_get(CLASS_OF(obj), fields_id);
+      
+      write_struct_begin(buf);
+      
+      rb_hash_foreach(fields, encode_field, args);
+      
+      write_field_stop(buf);
+      write_struct_end(buf);
+      break;
+    }
+    
+    default: {
+      rb_raise(rb_eNotImpError, "Unknown type for binary_encoding: %d", type);
+    }
+  }
+}
+
+// obj is always going to be a TSTRCT
+static VALUE tbpa_encode_binary(VALUE self, VALUE obj) {
+  VALUE buf = rb_str_buf_new(1024);
+  binary_encoding(buf, obj, T_STRCT);
+  return buf;
+}
+
+
+// -----------------------------------------------------------------------------
+// Read stuff
+// -----------------------------------------------------------------------------
+
+typedef struct {
+  char* name;
+  int8_t type;
+  int16_t id;
+} field_header;
+
+typedef struct {
+  int8_t key_type;
+  int8_t val_type;
+  int num_entries;
+} map_header;
+
+typedef struct {
+  int8_t type;
+  int num_elements;
+} list_header;
+
+typedef list_header set_header;
+
+typedef struct {
+  char* data;
+  int pos;
+  int len;
+  VALUE trans;
+} decode_buffer;
+
+typedef struct {
+  char* ptr;
+  int len;
+} thrift_string;
+
+#define read_struct_begin(buf)
+#define read_struct_end(buf)
+
+// This prototype is required to be able to run a call through rb_protect
+// which rescues from ruby exceptions
+static VALUE protectable_consume(VALUE args) {
+  VALUE trans = rb_ary_entry(args, 0);
+  VALUE size = rb_ary_entry(args, 1);
+  
+  return rb_funcall(trans, consume_bang_id, 1, size);
+}
+
+// Clears size bytes from the transport's string buffer
+static bool consume(decode_buffer* buf, int32_t size) {
+  if (size != 0) {
+    VALUE ret;
+    VALUE args = rb_ary_new3(2, buf->trans, INT2FIX(size));
+    int status = 0;
+    
+    ret = rb_protect(protectable_consume, args, &status);
+    
+    if (status) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+  
+  // Nothing to consume, we're all good
+  return true;
+}
+
+// This prototype is required to be able to run a call through rb_protect
+// which rescues from ruby exceptions
+static VALUE protectable_borrow(VALUE args) {
+  VALUE trans = rb_ary_entry(args, 0);
+  
+  switch(RARRAY(args)->len) {
+    case 1:
+      return rb_funcall(trans, borrow_id, 0);
+    
+    case 2: {
+      VALUE size = rb_ary_entry(args, 1);
+      return rb_funcall(trans, borrow_id, 1, size);
+    }
+  }
+  
+  return Qnil;
+}
+
+// Calls into the transport to get the available string buffer
+static bool borrow(decode_buffer* buf, int32_t size, VALUE* dst) {
+  int status = 0;
+  VALUE args;
+  
+  if (size == 0) {
+    args = rb_ary_new3(1, buf->trans);
+  } else {
+    args = rb_ary_new3(2, buf->trans, INT2FIX(size));
+  }
+
+  *dst = rb_protect(protectable_borrow, args, &status);
+  
+  return (status == 0);
+}
+
+// Refills the buffer by calling borrow. If buf->pos is nonzero that number of bytes
+// is cleared through consume.
+//
+// returns: 0 on success, non-zero on failure. On error buf is unchanged.
+static int fill_buffer(decode_buffer* buf, int32_t req_len) {
+  VALUE refill;
+  
+  if (!consume(buf, buf->pos)) {
+    return -1;
+  }
+
+  if (!borrow(buf, req_len, &refill)) {
+    return -2;
+  }
+  
+  buf->data = StringValuePtr(refill);
+  buf->len = RSTRING(refill)->len;
+  buf->pos = 0;
+  
+  return 0;
+}
+
+
+// read_bytes pulls a number of bytes (size) from the buffer, refilling if needed,
+// and places them in dst. This should _always_ be used used when reading from the buffer
+// or buffered transports will be upset with you.
+static bool read_bytes(decode_buffer* buf, void* dst, size_t size) {
+  int avail = (buf->len - buf->pos);
+  
+  if (size <= avail) {
+    memcpy(dst, buf->data + buf->pos, size);
+    buf->pos += size;
+  } else {
+    
+    if (avail > 0) {
+      // Copy what we can
+      memcpy(dst, buf->data + buf->pos, avail);
+      buf->pos += avail;
+    }
+
+    if (fill_buffer(buf, size - avail) < 0) {
+      return false;
+    }
+    
+    memcpy(dst + avail, buf->data, size - avail);
+    buf->pos += size - avail;
+  }
+  
+  return true;
+}
+
+// -----------------------------------------------------------------------------
+// Helpers for grabbing specific types from the buffer
+// -----------------------------------------------------------------------------
+
+static bool read_byte(decode_buffer* buf, int8_t* data) {
+  return read_bytes(buf, data, sizeof(int8_t));
+}
+
+static bool read_int16(decode_buffer* buf, int16_t* data) {
+  bool success = read_bytes(buf, data, sizeof(int16_t));
+  *data = ntohs(*data);
+  
+  return success;
+}
+
+static bool read_int32(decode_buffer* buf, int32_t* data) {
+  bool success = read_bytes(buf, data, sizeof(int32_t));
+  *data = ntohl(*data);
+  
+  return success;
+}
+
+static bool read_int64(decode_buffer* buf, int64_t* data) {
+  bool success = read_bytes(buf, data, sizeof(int64_t));
+  *data = ntohll(*data);
+  
+  return success;
+}
+
+static bool read_double(decode_buffer* buf, double* data) {
+  return read_int64(buf, (int64_t*)data);
+}
+
+static bool read_string(decode_buffer* buf, VALUE* data) {
+  int len;
+  
+  if (!read_int32(buf, &len)) {
+    return false;
+  }
+  
+  if (buf->len - buf->pos >= len) {
+    *data = rb_str_new(buf->data + buf->pos, len);
+    buf->pos += len;
+  } 
+  else {
+    char* str;
+    
+    if ((str = (char*) malloc(len)) == NULL) {
+      return false;
+    }
+    
+    if (!read_bytes(buf, str, len)) {
+      free(str);
+      return false;
+    }
+    
+    *data = rb_str_new(str, len);
+    
+    free(str);
+  }
+  
+  return true;
+}
+
+static bool read_field_begin(decode_buffer* buf, field_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+  bzero(header, sizeof(field_header));
+#endif
+
+  header->name = NULL;
+  
+  if (!read_byte(buf, &header->type)) {
+    return false;
+  }
+  
+  if (header->type == T_STOP) {
+    header->id = 0;
+  } else {
+    if (!read_int16(buf, &header->id)) {
+      return false;
+    }
+  }
+  
+  return true;
+}
+
+#define read_field_end(buf)
+
+static bool read_map_begin(decode_buffer* buf, map_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+  bzero(header, sizeof(map_header));
+#endif
+  
+  return (read_byte(buf, &header->key_type) && 
+          read_byte(buf, &header->val_type) &&
+          read_int32(buf, &header->num_entries));
+}
+
+#define read_map_end(buf)
+
+static bool read_list_begin(decode_buffer* buf, list_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+  bzero(header, sizeof(list_header));
+#endif
+  
+  if (!read_byte(buf, &header->type) || !read_int32(buf, &header->num_elements)) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+#define read_list_end(buf)
+
+#define read_set_begin read_list_begin
+#define read_set_end read_list_end
+
+
+// High level reader function with ruby type coercion
+static bool read_type(int type, decode_buffer* buf, VALUE* dst) {
+  switch(type) {
+    case T_BOOL: {
+      int8_t byte;
+      
+      if (!read_byte(buf, &byte)) {
+        return false;
+      }
+      
+      if (0 == byte) {
+        *dst = Qfalse;
+      } else {
+        *dst = Qtrue;
+      }
+      
+      break;
+    }
+    
+    case T_BYTE: {
+      int8_t byte;
+
+      if (!read_byte(buf, &byte)) {
+        return false;
+      }
+
+      *dst = INT2FIX(byte);
+      break;
+    }
+    
+    case T_I16: {
+      int16_t i16;
+      
+      if (!read_int16(buf, &i16)) {
+        return false;
+      }
+      
+      *dst = INT2FIX(i16);
+      break;
+    }
+
+    case T_I32: {
+      int32_t i32;
+      
+      if (!read_int32(buf, &i32)) {
+        return false;
+      }
+      
+      *dst = INT2NUM(i32);
+      break;
+    }
+
+    case T_I64: {
+      int64_t i64;
+      
+      if (!read_int64(buf, &i64)) {
+        return false;
+      }
+      
+      *dst = rb_ll2inum(i64);
+      break;
+    }
+    
+    case T_DBL: {
+      double dbl;
+      
+      if (!read_double(buf, &dbl)) {
+        return false;
+      }
+      
+      *dst = rb_float_new(dbl);
+      break;
+    }
+
+    case T_STR: {
+      VALUE str;
+      
+      if (!read_string(buf, &str)) {
+        return false;
+      }
+      
+      *dst = str;
+      break;
+    }
+  }
+  
+  return true;
+}
+
+// TODO(kevinclark): Now that read_string does a malloc,
+// This maybe could be modified to avoid that, and the type coercion
+
+// Read the bytes but don't do anything with the value
+static bool skip_type(int type, decode_buffer* buf) {
+  switch (type) {
+    case T_STRCT:
+      read_struct_begin(buf);
+      while (true) {
+        field_header header;
+
+        if (!read_field_begin(buf, &header)) {
+          return false;
+        }
+
+        if (header.type == T_STOP) {
+          break;
+        }
+
+        if (!skip_type(header.type, buf)) {
+          return false;
+        }
+
+        read_field_end(buf);
+      }
+      read_struct_end(buf);
+
+      break;
+
+    case T_MAP: {
+      int i;
+      map_header header;
+
+      if (!read_map_begin(buf, &header)) {
+        return false;
+      }
+
+      for (i = 0; i < header.num_entries; ++i) {
+        if (!skip_type(header.key_type, buf)) {
+          return false;
+        }
+        if (!skip_type(header.val_type, buf)) {
+          return false;
+        }
+      }
+
+      read_map_end(buf);
+      break;
+    }
+
+    case T_SET: {
+      int i;
+      set_header header;
+
+      if (!read_set_begin(buf, &header)) {
+        return false;
+      }
+
+      for (i = 0; i < header.num_elements; ++i) {
+        if (!skip_type(header.type, buf)) {
+          return false;
+        }
+      }
+
+      read_set_end(buf);
+      break;
+    }
+
+    case T_LIST: {
+      int i;
+      list_header header;
+
+      if (!read_list_begin(buf, &header)) {
+        return false;
+      }
+
+      for (i = 0; i < header.num_elements; ++i) {
+        if (!skip_type(header.type, buf)) {
+          return false;
+        }
+      }
+
+      read_list_end(buf);
+      break;
+    }
+
+    default: {
+      VALUE v;
+      if (!read_type(type, buf, &v)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+
+static VALUE read_struct(VALUE obj, decode_buffer* buf);
+
+// Read the right thing from the buffer given the field spec
+// and return the ruby object
+static bool read_field(decode_buffer* buf, field_spec* spec, VALUE* dst) {
+  switch (spec->type) {
+    case T_STRCT: {
+      VALUE obj = rb_class_new_instance(0, NULL, spec->data.class);
+      
+      *dst = read_struct(obj, buf);
+      break;
+    }
+    
+    case T_MAP: {
+      map_header hdr;
+      VALUE hsh;
+      int i;
+      
+      read_map_begin(buf, &hdr); 
+      hsh = rb_hash_new();
+      
+      for (i = 0; i < hdr.num_entries; ++i) {
+        VALUE key, val;
+        
+        if (!read_field(buf, spec->data.map->key, &key)) {
+          return false;
+        }
+        
+        if (!read_field(buf, spec->data.map->value, &val)) {
+          return false;
+        }
+        
+        rb_hash_aset(hsh, key, val);
+      }
+      
+      read_map_end(buf);
+      
+      *dst = hsh;
+      break;
+    }
+    
+    case T_LIST: {
+      list_header hdr;
+      VALUE arr, element;
+      int i;
+      
+      read_list_begin(buf, &hdr);
+      arr = rb_ary_new2(hdr.num_elements);
+      
+      for (i = 0; i < hdr.num_elements; ++i) {
+        if (!read_field(buf, spec->data.element, &element)) {
+          return false;
+        }
+
+        rb_ary_push(arr, element);
+      }
+      
+      read_list_end(buf);
+      
+      *dst = arr;
+      break;
+    }
+    
+    case T_SET: {
+      VALUE items, item;
+      set_header hdr;
+      int i;
+      
+      read_set_begin(buf, &hdr);
+      items = rb_ary_new2(hdr.num_elements);
+      
+      for (i = 0; i < hdr.num_elements; ++i) {
+        if (!read_field(buf, spec->data.element, &item)) {
+          return false;
+        }
+        
+        rb_ary_push(items, item);
+      }
+      
+      *dst = rb_class_new_instance(1, &items, rb_cSet);
+      break;
+    }
+    
+    
+    default:
+      return read_type(spec->type, buf, dst);
+  }
+  
+  return true;
+}
+
+static void handle_read_error() {
+  // If it was an exception, reraise
+  if (!NIL_P(ruby_errinfo)) {
+    rb_exc_raise(ruby_errinfo);
+  } else {
+    // Something else went wrong, no idea what would call this yet
+    // So far, the only thing to cause failures underneath is ruby
+    // exceptions. Follow up on this regularly -- Kevin Clark (TODO)
+    rb_raise(rb_eStandardError, "[BUG] Something went wrong in the field reading, but not a ruby exception");
+  }
+}
+
+// Fill in the instance variables in an object (thrift struct)
+// from the decode buffer
+static VALUE read_struct(VALUE obj, decode_buffer* buf) {
+  VALUE field;
+  field_header f_header;
+  VALUE value = Qnil;
+  VALUE fields = rb_const_get(CLASS_OF(obj), fields_id);
+  field_spec* spec;
+  char name_buf[128];
+    
+  read_struct_begin(buf);
+  
+  while(true) {
+    if (!read_field_begin(buf, &f_header)) {
+      handle_read_error();
+    }
+    
+    if (T_STOP == f_header.type) {
+      break;
+    }
+    
+    field = rb_hash_aref(fields, INT2FIX(f_header.id));
+    
+    if (NIL_P(field)) {
+      if (!skip_type(f_header.type, buf)) {
+        handle_read_error();
+        return Qnil;
+      }
+    } 
+    else {
+      spec = parse_field_spec(field);
+
+      if (spec->type != f_header.type) {
+        if (!skip_type(spec->type, buf)) {
+          free_field_spec(spec);
+          handle_read_error();
+          return Qnil;
+        }
+      } else {
+        // Read busted somewhere (probably borrow/consume), bail
+        if (!read_field(buf, spec, &value)) {
+          free_field_spec(spec);
+          handle_read_error();
+          return Qnil;
+        }
+        
+        name_buf[0] = '@';
+        strlcpy(&name_buf[1], spec->name, sizeof(name_buf) - 1);
+        
+        rb_iv_set(obj, name_buf, value);
+      }
+      
+      free_field_spec(spec);
+    }
+    
+    read_field_end(buf);
+  }
+  
+  read_struct_end(buf);
+  
+  return obj;
+}
+
+
+// Takes an object and transport, and decodes the values in the transport's
+// buffer to fill the object.
+static VALUE tbpa_decode_binary(VALUE self, VALUE obj, VALUE transport) {
+  decode_buffer buf;
+  VALUE ret_val;
+
+  buf.pos = 0;    // This needs to be set so an arbitrary number of bytes isn't consumed
+  buf.trans = transport;       // We need to hold this so the buffer can be refilled
+  
+  if (fill_buffer(&buf, 0) < 0) {
+    handle_read_error();
+    return Qnil;
+  }
+
+#ifdef __DEBUG__
+  rb_p(rb_str_new2("Running decode binary with data:"));
+  rb_p(rb_inspect(rb_str_new2(buf.data)));
+#endif
+ 
+  ret_val = read_struct(obj, &buf);
+  
+  // Consume whatever was read
+  consume(&buf, buf.pos);
+  
+  return ret_val;
+}
+
+// -----------------------------------------------------------------------------
+// These methods are used by the thrift library and need to handled
+// seperately from encode and decode
+// -----------------------------------------------------------------------------
+
+// Read the message header and return it as a ruby array
+static VALUE tbpa_read_message_begin(VALUE self) {
+  decode_buffer buf;
+  int32_t version, seqid;
+  int8_t type;
+  VALUE name;
+  
+  VALUE trans = rb_iv_get(self, "@trans");
+  
+  buf.pos = 0;              // This needs to be set so fill_buffer doesn't consume
+  buf.trans = trans;        // We need to hold this so the buffer can be refilled
+  
+
+  if (fill_buffer(&buf, 0) < 0 || !read_int32(&buf, &version)) {
+    // Consume whatever was read
+    consume(&buf, buf.pos);
+    handle_read_error();
+    return Qnil;
+  }
+  
+  if ((version & VERSION_MASK) != VERSION_1) {
+    VALUE tprotocol_exception = rb_const_get(rb_cObject, rb_intern("TProtocolException"));
+    VALUE exception = rb_funcall(tprotocol_exception, rb_intern("new"), 2, rb_const_get(tprotocol_exception, rb_intern("BAD_VERSION")), rb_str_new2("Missing version identifier"));
+    rb_raise(exception, "");
+  }
+  
+  type = version & 0x000000ff;
+  
+  if (!read_string(&buf, &name) || !read_int32(&buf, &seqid)) {
+    // Consume whatever was read
+    consume(&buf, buf.pos);
+    handle_read_error();
+    return Qnil;
+  }
+  
+  // Consume whatever was read
+  if (consume(&buf, buf.pos) < 0) {
+    handle_read_error();
+    return Qnil;
+  }
+  
+  return rb_ary_new3(3, name, INT2FIX(type), INT2FIX(seqid));
+}
+
+void Init_binaryprotocolaccelerated()
+{
+  m_thrift = rb_const_get(rb_cObject, rb_intern("Thrift"));
+  VALUE class_tbinproto = rb_const_get(m_thrift, rb_intern("BinaryProtocol"));
+  class_tbpa = rb_define_class_under(m_thrift, "BinaryProtocolAccelerated", class_tbinproto);
+  type_sym = ID2SYM(rb_intern("type"));
+  class_sym = ID2SYM(rb_intern("class"));
+  key_sym = ID2SYM(rb_intern("key"));
+  value_sym = ID2SYM(rb_intern("value"));
+  name_sym = ID2SYM(rb_intern("name"));
+  fields_id = rb_intern("FIELDS");
+  element_sym = ID2SYM(rb_intern("element"));
+  consume_bang_id = rb_intern("consume!");
+  string_buffer_id = rb_intern("string_buffer");
+  borrow_id = rb_intern("borrow");
+  keys_id = rb_intern("keys");
+  entries_id = rb_intern("entries");
+  rb_cSet = rb_const_get(rb_cObject, rb_intern("Set"));
+  
+  // For fast access
+  rb_define_method(class_tbpa, "encode_binary", tbpa_encode_binary, 1);
+  rb_define_method(class_tbpa, "decode_binary", tbpa_decode_binary, 2);
+  rb_define_method(class_tbpa, "read_message_begin", tbpa_read_message_begin, 0);
+  
+}
diff --git a/lib/rb/ext/extconf.rb b/lib/rb/ext/extconf.rb
new file mode 100644
index 0000000..54ad5ed
--- /dev/null
+++ b/lib/rb/ext/extconf.rb
@@ -0,0 +1,7 @@
+require 'mkmf'
+
+$CFLAGS = "-g -O2 -Wall -Werror"
+
+have_func("strlcpy", "string.h")
+
+create_makefile 'binaryprotocolaccelerated'
diff --git a/lib/rb/lib/thrift/protocol/binaryprotocolaccelerated.rb b/lib/rb/lib/thrift/protocol/binaryprotocolaccelerated.rb
new file mode 100644
index 0000000..fae9819
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/binaryprotocolaccelerated.rb
@@ -0,0 +1,19 @@
+require 'thrift/protocol/binaryprotocol'
+require 'binaryprotocolaccelerated'
+
+=begin
+The only change required for a transport to support TBinaryProtocolAccelerated is to implement 2 methods:
+  * borrow(size), which takes an optional argument and returns atleast _size_ bytes from the transport, 
+                  or the default buffer size if no argument is given
+  * consume!(size), which removes size bytes from the front of the buffer
+
+See TMemoryBuffer and TBufferedTransport for examples.
+=end
+
+module Thrift
+  class BinaryProtocolAcceleratedFactory < ProtocolFactory
+    def get_protocol(trans)
+      BinaryProtocolAccelerated.new(trans)
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/server/nonblockingserver.rb b/lib/rb/lib/thrift/server/nonblockingserver.rb
index dc0f646..df199cc 100644
--- a/lib/rb/lib/thrift/server/nonblockingserver.rb
+++ b/lib/rb/lib/thrift/server/nonblockingserver.rb
@@ -72,6 +72,8 @@
     end
 
     class IOManager # :nodoc:
+      DEFAULT_BUFFER = 2**20
+      
       def initialize(processor, serverTransport, transportFactory, protocolFactory, num, logger)
         @processor = processor
         @serverTransport = serverTransport
@@ -116,7 +118,7 @@
       end
 
       private
-
+      
       def run
         spin_worker_threads
 
@@ -139,7 +141,7 @@
       end
 
       def read_connection(fd)
-        @buffers[fd] << fd.readpartial(1048576)
+        @buffers[fd] << fd.read(DEFAULT_BUFFER)
         frame = slice_frame!(@buffers[fd])
         if frame
           @logger.debug "#{self} is processing a frame"
diff --git a/lib/rb/lib/thrift/struct.rb b/lib/rb/lib/thrift/struct.rb
index b016f13..0535948 100644
--- a/lib/rb/lib/thrift/struct.rb
+++ b/lib/rb/lib/thrift/struct.rb
@@ -23,31 +23,41 @@
     end
 
     def read(iprot)
-      iprot.read_struct_begin
-      loop do
-        fname, ftype, fid = iprot.read_field_begin
-        break if (ftype == Types::STOP)
-        handle_message(iprot, fid, ftype)
-        iprot.read_field_end
+      # TODO(kevinclark): Make sure transport is C readable
+      if iprot.respond_to?(:decode_binary)
+        iprot.decode_binary(self, iprot.trans)
+      else
+        iprot.read_struct_begin
+        loop do
+          fname, ftype, fid = iprot.read_field_begin
+          break if (ftype == Types::STOP)
+          handle_message(iprot, fid, ftype)
+          iprot.read_field_end
+        end
+        iprot.read_struct_end
       end
-      iprot.read_struct_end
     end
 
     def write(oprot)
-      oprot.write_struct_begin(self.class.name)
-      each_field do |fid, type, name|
-        unless (value = instance_variable_get("@#{name}")).nil?
-          if is_container? type
-            oprot.write_field_begin(name, type, fid)
-            write_container(oprot, value, struct_fields[fid])
-            oprot.write_field_end
-          else
-            oprot.write_field(name, type, fid, value)
+      if oprot.respond_to?(:encode_binary)
+        # TODO(kevinclark): Clean this so I don't have to access the transport.
+        oprot.trans.write oprot.encode_binary(self)
+      else
+        oprot.write_struct_begin(self.class.name)
+        each_field do |fid, type, name|
+          unless (value = instance_variable_get("@#{name}")).nil?
+            if is_container? type
+              oprot.write_field_begin(name, type, fid)
+              write_container(oprot, value, struct_fields[fid])
+              oprot.write_field_end
+            else
+              oprot.write_field(name, type, fid, value)
+            end
           end
         end
+        oprot.write_field_stop
+        oprot.write_struct_end
       end
-      oprot.write_field_stop
-      oprot.write_struct_end
     end
 
     def ==(other)
diff --git a/lib/rb/lib/thrift/transport.rb b/lib/rb/lib/thrift/transport.rb
index 36492dd..d55adf0 100644
--- a/lib/rb/lib/thrift/transport.rb
+++ b/lib/rb/lib/thrift/transport.rb
@@ -76,9 +76,12 @@
   deprecate_class! :TTransportFactory => TransportFactory
 
   class BufferedTransport < Transport
+    DEFAULT_BUFFER = 4096
+    
     def initialize(transport)
       @transport = transport
       @wbuf = ''
+      @rbuf = ''
     end
 
     def open?
@@ -95,7 +98,13 @@
     end
 
     def read(sz)
-      return @transport.read(sz)
+      ret = @rbuf.slice!(0...sz) 
+      if ret.length == 0
+        @rbuf = @transport.read([sz, DEFAULT_BUFFER].max) 
+        @rbuf.slice!(0...sz) 
+      else 
+        ret 
+      end
     end
 
     def write(buf)
@@ -110,6 +119,25 @@
       
       @transport.flush
     end
+
+    def borrow(requested_length = 0)
+      # $stderr.puts "#{Time.now.to_f} Have #{@rbuf.length} asking for #{requested_length.inspect}"
+      return @rbuf if @rbuf.length > requested_length
+      
+      if @rbuf.length < DEFAULT_BUFFER
+        @rbuf << @transport.read([requested_length, DEFAULT_BUFFER].max)
+      end
+      
+      if @rbuf.length < requested_length
+        @rbuf << @transport.read_all(requested_length - @rbuf.length)
+      end
+    
+      @rbuf
+    end
+    
+    def consume!(size)
+      @rbuf.slice!(0...size)
+    end
   end
   deprecate_class! :TBufferedTransport => BufferedTransport
 
@@ -230,6 +258,23 @@
 
     def flush
     end
+
+    # For fast binary protocol access
+    def borrow(size = nil)
+      if size.nil?
+        @buf[0..-1]
+      else
+        if size > @buf.length
+          raise EOFError # Memory buffers only get one shot.
+        else
+          @buf[0..size]
+        end
+      end
+    end
+
+    def consume!(size)
+      @buf.slice!(0, size)
+    end
   end
   deprecate_class! :TMemoryBuffer => MemoryBuffer
 
diff --git a/lib/rb/lib/thrift/transport/socket.rb b/lib/rb/lib/thrift/transport/socket.rb
index e3981fe..c7ed521 100644
--- a/lib/rb/lib/thrift/transport/socket.rb
+++ b/lib/rb/lib/thrift/transport/socket.rb
@@ -43,17 +43,11 @@
       end
     end
 
-    def read(sz, partial=false)
+    def read(sz)
       raise IOError, "closed stream" unless open?
+
       begin
-        if partial
-          data = @handle.readpartial(sz)
-        else
-          data = @handle.read(sz)
-        end
-      rescue Errno::EAGAIN => e
-        # let our parent know that the nonblock read failed
-        raise e
+        data = @handle.readpartial(sz)
       rescue StandardError => e
         @handle.close unless @handle.closed?
         @handle = nil
@@ -65,10 +59,6 @@
       data
     end
 
-    def readpartial(sz)
-      read(sz, true)
-    end
-
     def close
       @handle.close unless @handle.nil? or @handle.closed?
       @handle = nil
diff --git a/lib/rb/setup.rb b/lib/rb/setup.rb
new file mode 100644
index 0000000..9f0c826
--- /dev/null
+++ b/lib/rb/setup.rb
@@ -0,0 +1,1585 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
+  module Enumerable
+    alias map collect
+  end
+end
+
+unless File.respond_to?(:read)   # Ruby 1.6
+  def File.read(fname)
+    open(fname) {|f|
+      return f.read
+    }
+  end
+end
+
+unless Errno.const_defined?(:ENOTEMPTY)   # Windows?
+  module Errno
+    class ENOTEMPTY
+      # We do not raise this exception, implementation is not needed.
+    end
+  end
+end
+
+def File.binread(fname)
+  open(fname, 'rb') {|f|
+    return f.read
+  }
+end
+
+# for corrupted Windows' stat(2)
+def File.dir?(path)
+  File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class ConfigTable
+
+  include Enumerable
+
+  def initialize(rbconfig)
+    @rbconfig = rbconfig
+    @items = []
+    @table = {}
+    # options
+    @install_prefix = nil
+    @config_opt = nil
+    @verbose = true
+    @no_harm = false
+  end
+
+  attr_accessor :install_prefix
+  attr_accessor :config_opt
+
+  attr_writer :verbose
+
+  def verbose?
+    @verbose
+  end
+
+  attr_writer :no_harm
+
+  def no_harm?
+    @no_harm
+  end
+
+  def [](key)
+    lookup(key).resolve(self)
+  end
+
+  def []=(key, val)
+    lookup(key).set val
+  end
+
+  def names
+    @items.map {|i| i.name }
+  end
+
+  def each(&block)
+    @items.each(&block)
+  end
+
+  def key?(name)
+    @table.key?(name)
+  end
+
+  def lookup(name)
+    @table[name] or setup_rb_error "no such config item: #{name}"
+  end
+
+  def add(item)
+    @items.push item
+    @table[item.name] = item
+  end
+
+  def remove(name)
+    item = lookup(name)
+    @items.delete_if {|i| i.name == name }
+    @table.delete_if {|name, i| i.name == name }
+    item
+  end
+
+  def load_script(path, inst = nil)
+    if File.file?(path)
+      MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
+    end
+  end
+
+  def savefile
+    '.config'
+  end
+
+  def load_savefile
+    begin
+      File.foreach(savefile()) do |line|
+        k, v = *line.split(/=/, 2)
+        self[k] = v.strip
+      end
+    rescue Errno::ENOENT
+      setup_rb_error $!.message + "\n#{File.basename($0)} config first"
+    end
+  end
+
+  def save
+    @items.each {|i| i.value }
+    File.open(savefile(), 'w') {|f|
+      @items.each do |i|
+        f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
+      end
+    }
+  end
+
+  def load_standard_entries
+    standard_entries(@rbconfig).each do |ent|
+      add ent
+    end
+  end
+
+  def standard_entries(rbconfig)
+    c = rbconfig
+
+    rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
+
+    major = c['MAJOR'].to_i
+    minor = c['MINOR'].to_i
+    teeny = c['TEENY'].to_i
+    version = "#{major}.#{minor}"
+
+    # ruby ver. >= 1.4.4?
+    newpath_p = ((major >= 2) or
+                 ((major == 1) and
+                  ((minor >= 5) or
+                   ((minor == 4) and (teeny >= 4)))))
+
+    if c['rubylibdir']
+      # V > 1.6.3
+      libruby         = "#{c['prefix']}/lib/ruby"
+      librubyver      = c['rubylibdir']
+      librubyverarch  = c['archdir']
+      siteruby        = c['sitedir']
+      siterubyver     = c['sitelibdir']
+      siterubyverarch = c['sitearchdir']
+    elsif newpath_p
+      # 1.4.4 <= V <= 1.6.3
+      libruby         = "#{c['prefix']}/lib/ruby"
+      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
+      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+      siteruby        = c['sitedir']
+      siterubyver     = "$siteruby/#{version}"
+      siterubyverarch = "$siterubyver/#{c['arch']}"
+    else
+      # V < 1.4.4
+      libruby         = "#{c['prefix']}/lib/ruby"
+      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
+      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+      siteruby        = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
+      siterubyver     = siteruby
+      siterubyverarch = "$siterubyver/#{c['arch']}"
+    end
+    parameterize = lambda {|path|
+      path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
+    }
+
+    if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+      makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+    else
+      makeprog = 'make'
+    end
+
+    [
+      ExecItem.new('installdirs', 'std/site/home',
+                   'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
+          {|val, table|
+            case val
+            when 'std'
+              table['rbdir'] = '$librubyver'
+              table['sodir'] = '$librubyverarch'
+            when 'site'
+              table['rbdir'] = '$siterubyver'
+              table['sodir'] = '$siterubyverarch'
+            when 'home'
+              setup_rb_error '$HOME was not set' unless ENV['HOME']
+              table['prefix'] = ENV['HOME']
+              table['rbdir'] = '$libdir/ruby'
+              table['sodir'] = '$libdir/ruby'
+            end
+          },
+      PathItem.new('prefix', 'path', c['prefix'],
+                   'path prefix of target environment'),
+      PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+                   'the directory for commands'),
+      PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
+                   'the directory for libraries'),
+      PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+                   'the directory for shared data'),
+      PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+                   'the directory for man pages'),
+      PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+                   'the directory for system configuration files'),
+      PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
+                   'the directory for local state data'),
+      PathItem.new('libruby', 'path', libruby,
+                   'the directory for ruby libraries'),
+      PathItem.new('librubyver', 'path', librubyver,
+                   'the directory for standard ruby libraries'),
+      PathItem.new('librubyverarch', 'path', librubyverarch,
+                   'the directory for standard ruby extensions'),
+      PathItem.new('siteruby', 'path', siteruby,
+          'the directory for version-independent aux ruby libraries'),
+      PathItem.new('siterubyver', 'path', siterubyver,
+                   'the directory for aux ruby libraries'),
+      PathItem.new('siterubyverarch', 'path', siterubyverarch,
+                   'the directory for aux ruby binaries'),
+      PathItem.new('rbdir', 'path', '$siterubyver',
+                   'the directory for ruby scripts'),
+      PathItem.new('sodir', 'path', '$siterubyverarch',
+                   'the directory for ruby extentions'),
+      PathItem.new('rubypath', 'path', rubypath,
+                   'the path to set to #! line'),
+      ProgramItem.new('rubyprog', 'name', rubypath,
+                      'the ruby program using for installation'),
+      ProgramItem.new('makeprog', 'name', makeprog,
+                      'the make program to compile ruby extentions'),
+      SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+                     'shebang line (#!) editing mode'),
+      BoolItem.new('without-ext', 'yes/no', 'no',
+                   'does not compile/install ruby extentions')
+    ]
+  end
+  private :standard_entries
+
+  def load_multipackage_entries
+    multipackage_entries().each do |ent|
+      add ent
+    end
+  end
+
+  def multipackage_entries
+    [
+      PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+                               'package names that you want to install'),
+      PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+                               'package names that you do not want to install')
+    ]
+  end
+  private :multipackage_entries
+
+  ALIASES = {
+    'std-ruby'         => 'librubyver',
+    'stdruby'          => 'librubyver',
+    'rubylibdir'       => 'librubyver',
+    'archdir'          => 'librubyverarch',
+    'site-ruby-common' => 'siteruby',     # For backward compatibility
+    'site-ruby'        => 'siterubyver',  # For backward compatibility
+    'bin-dir'          => 'bindir',
+    'bin-dir'          => 'bindir',
+    'rb-dir'           => 'rbdir',
+    'so-dir'           => 'sodir',
+    'data-dir'         => 'datadir',
+    'ruby-path'        => 'rubypath',
+    'ruby-prog'        => 'rubyprog',
+    'ruby'             => 'rubyprog',
+    'make-prog'        => 'makeprog',
+    'make'             => 'makeprog'
+  }
+
+  def fixup
+    ALIASES.each do |ali, name|
+      @table[ali] = @table[name]
+    end
+    @items.freeze
+    @table.freeze
+    @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
+  end
+
+  def parse_opt(opt)
+    m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
+    m.to_a[1,2]
+  end
+
+  def dllext
+    @rbconfig['DLEXT']
+  end
+
+  def value_config?(name)
+    lookup(name).value?
+  end
+
+  class Item
+    def initialize(name, template, default, desc)
+      @name = name.freeze
+      @template = template
+      @value = default
+      @default = default
+      @description = desc
+    end
+
+    attr_reader :name
+    attr_reader :description
+
+    attr_accessor :default
+    alias help_default default
+
+    def help_opt
+      "--#{@name}=#{@template}"
+    end
+
+    def value?
+      true
+    end
+
+    def value
+      @value
+    end
+
+    def resolve(table)
+      @value.gsub(%r<\$([^/]+)>) { table[$1] }
+    end
+
+    def set(val)
+      @value = check(val)
+    end
+
+    private
+
+    def check(val)
+      setup_rb_error "config: --#{name} requires argument" unless val
+      val
+    end
+  end
+
+  class BoolItem < Item
+    def config_type
+      'bool'
+    end
+
+    def help_opt
+      "--#{@name}"
+    end
+
+    private
+
+    def check(val)
+      return 'yes' unless val
+      case val
+      when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
+      when /\An(o)?\z/i, /\Af(alse)\z/i  then 'no'
+      else
+        setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+      end
+    end
+  end
+
+  class PathItem < Item
+    def config_type
+      'path'
+    end
+
+    private
+
+    def check(path)
+      setup_rb_error "config: --#{@name} requires argument"  unless path
+      path[0,1] == '$' ? path : File.expand_path(path)
+    end
+  end
+
+  class ProgramItem < Item
+    def config_type
+      'program'
+    end
+  end
+
+  class SelectItem < Item
+    def initialize(name, selection, default, desc)
+      super
+      @ok = selection.split('/')
+    end
+
+    def config_type
+      'select'
+    end
+
+    private
+
+    def check(val)
+      unless @ok.include?(val.strip)
+        setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+      end
+      val.strip
+    end
+  end
+
+  class ExecItem < Item
+    def initialize(name, selection, desc, &block)
+      super name, selection, nil, desc
+      @ok = selection.split('/')
+      @action = block
+    end
+
+    def config_type
+      'exec'
+    end
+
+    def value?
+      false
+    end
+
+    def resolve(table)
+      setup_rb_error "$#{name()} wrongly used as option value"
+    end
+
+    undef set
+
+    def evaluate(val, table)
+      v = val.strip.downcase
+      unless @ok.include?(v)
+        setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
+      end
+      @action.call v, table
+    end
+  end
+
+  class PackageSelectionItem < Item
+    def initialize(name, template, default, help_default, desc)
+      super name, template, default, desc
+      @help_default = help_default
+    end
+
+    attr_reader :help_default
+
+    def config_type
+      'package'
+    end
+
+    private
+
+    def check(val)
+      unless File.dir?("packages/#{val}")
+        setup_rb_error "config: no such package: #{val}"
+      end
+      val
+    end
+  end
+
+  class MetaConfigEnvironment
+    def initialize(config, installer)
+      @config = config
+      @installer = installer
+    end
+
+    def config_names
+      @config.names
+    end
+
+    def config?(name)
+      @config.key?(name)
+    end
+
+    def bool_config?(name)
+      @config.lookup(name).config_type == 'bool'
+    end
+
+    def path_config?(name)
+      @config.lookup(name).config_type == 'path'
+    end
+
+    def value_config?(name)
+      @config.lookup(name).config_type != 'exec'
+    end
+
+    def add_config(item)
+      @config.add item
+    end
+
+    def add_bool_config(name, default, desc)
+      @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+    end
+
+    def add_path_config(name, default, desc)
+      @config.add PathItem.new(name, 'path', default, desc)
+    end
+
+    def set_config_default(name, default)
+      @config.lookup(name).default = default
+    end
+
+    def remove_config(name)
+      @config.remove(name)
+    end
+
+    # For only multipackage
+    def packages
+      raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
+      @installer.packages
+    end
+
+    # For only multipackage
+    def declare_packages(list)
+      raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
+      @installer.packages = list
+    end
+  end
+
+end   # class ConfigTable
+
+
+# This module requires: #verbose?, #no_harm?
+module FileOperations
+
+  def mkdir_p(dirname, prefix = nil)
+    dirname = prefix + File.expand_path(dirname) if prefix
+    $stderr.puts "mkdir -p #{dirname}" if verbose?
+    return if no_harm?
+
+    # Does not check '/', it's too abnormal.
+    dirs = File.expand_path(dirname).split(%r<(?=/)>)
+    if /\A[a-z]:\z/i =~ dirs[0]
+      disk = dirs.shift
+      dirs[0] = disk + dirs[0]
+    end
+    dirs.each_index do |idx|
+      path = dirs[0..idx].join('')
+      Dir.mkdir path unless File.dir?(path)
+    end
+  end
+
+  def rm_f(path)
+    $stderr.puts "rm -f #{path}" if verbose?
+    return if no_harm?
+    force_remove_file path
+  end
+
+  def rm_rf(path)
+    $stderr.puts "rm -rf #{path}" if verbose?
+    return if no_harm?
+    remove_tree path
+  end
+
+  def remove_tree(path)
+    if File.symlink?(path)
+      remove_file path
+    elsif File.dir?(path)
+      remove_tree0 path
+    else
+      force_remove_file path
+    end
+  end
+
+  def remove_tree0(path)
+    Dir.foreach(path) do |ent|
+      next if ent == '.'
+      next if ent == '..'
+      entpath = "#{path}/#{ent}"
+      if File.symlink?(entpath)
+        remove_file entpath
+      elsif File.dir?(entpath)
+        remove_tree0 entpath
+      else
+        force_remove_file entpath
+      end
+    end
+    begin
+      Dir.rmdir path
+    rescue Errno::ENOTEMPTY
+      # directory may not be empty
+    end
+  end
+
+  def move_file(src, dest)
+    force_remove_file dest
+    begin
+      File.rename src, dest
+    rescue
+      File.open(dest, 'wb') {|f|
+        f.write File.binread(src)
+      }
+      File.chmod File.stat(src).mode, dest
+      File.unlink src
+    end
+  end
+
+  def force_remove_file(path)
+    begin
+      remove_file path
+    rescue
+    end
+  end
+
+  def remove_file(path)
+    File.chmod 0777, path
+    File.unlink path
+  end
+
+  def install(from, dest, mode, prefix = nil)
+    $stderr.puts "install #{from} #{dest}" if verbose?
+    return if no_harm?
+
+    realdest = prefix ? prefix + File.expand_path(dest) : dest
+    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+    str = File.binread(from)
+    if diff?(str, realdest)
+      verbose_off {
+        rm_f realdest if File.exist?(realdest)
+      }
+      File.open(realdest, 'wb') {|f|
+        f.write str
+      }
+      File.chmod mode, realdest
+
+      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+        if prefix
+          f.puts realdest.sub(prefix, '')
+        else
+          f.puts realdest
+        end
+      }
+    end
+  end
+
+  def diff?(new_content, path)
+    return true unless File.exist?(path)
+    new_content != File.binread(path)
+  end
+
+  def command(*args)
+    $stderr.puts args.join(' ') if verbose?
+    system(*args) or raise RuntimeError,
+        "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
+  end
+
+  def ruby(*args)
+    command config('rubyprog'), *args
+  end
+
+  def make(task = nil)
+    command(*[config('makeprog'), task].compact)
+  end
+
+  def extdir?(dir)
+    File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
+  end
+
+  def files_of(dir)
+    Dir.open(dir) {|d|
+      return d.select {|ent| File.file?("#{dir}/#{ent}") }
+    }
+  end
+
+  DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
+
+  def directories_of(dir)
+    Dir.open(dir) {|d|
+      return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
+    }
+  end
+
+end
+
+
+# This module requires: #srcdir_root, #objdir_root, #relpath
+module HookScriptAPI
+
+  def get_config(key)
+    @config[key]
+  end
+
+  alias config get_config
+
+  # obsolete: use metaconfig to change configuration
+  def set_config(key, val)
+    @config[key] = val
+  end
+
+  #
+  # srcdir/objdir (works only in the package directory)
+  #
+
+  def curr_srcdir
+    "#{srcdir_root()}/#{relpath()}"
+  end
+
+  def curr_objdir
+    "#{objdir_root()}/#{relpath()}"
+  end
+
+  def srcfile(path)
+    "#{curr_srcdir()}/#{path}"
+  end
+
+  def srcexist?(path)
+    File.exist?(srcfile(path))
+  end
+
+  def srcdirectory?(path)
+    File.dir?(srcfile(path))
+  end
+
+  def srcfile?(path)
+    File.file?(srcfile(path))
+  end
+
+  def srcentries(path = '.')
+    Dir.open("#{curr_srcdir()}/#{path}") {|d|
+      return d.to_a - %w(. ..)
+    }
+  end
+
+  def srcfiles(path = '.')
+    srcentries(path).select {|fname|
+      File.file?(File.join(curr_srcdir(), path, fname))
+    }
+  end
+
+  def srcdirectories(path = '.')
+    srcentries(path).select {|fname|
+      File.dir?(File.join(curr_srcdir(), path, fname))
+    }
+  end
+
+end
+
+
+class ToplevelInstaller
+
+  Version   = '3.4.1'
+  Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
+
+  TASKS = [
+    [ 'all',      'do config, setup, then install' ],
+    [ 'config',   'saves your configurations' ],
+    [ 'show',     'shows current configuration' ],
+    [ 'setup',    'compiles ruby extentions and others' ],
+    [ 'install',  'installs files' ],
+    [ 'test',     'run all tests in test/' ],
+    [ 'clean',    "does `make clean' for each extention" ],
+    [ 'distclean',"does `make distclean' for each extention" ]
+  ]
+
+  def ToplevelInstaller.invoke
+    config = ConfigTable.new(load_rbconfig())
+    config.load_standard_entries
+    config.load_multipackage_entries if multipackage?
+    config.fixup
+    klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
+    klass.new(File.dirname($0), config).invoke
+  end
+
+  def ToplevelInstaller.multipackage?
+    File.dir?(File.dirname($0) + '/packages')
+  end
+
+  def ToplevelInstaller.load_rbconfig
+    if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+      ARGV.delete(arg)
+      load File.expand_path(arg.split(/=/, 2)[1])
+      $".push 'rbconfig.rb'
+    else
+      require 'rbconfig'
+    end
+    ::Config::CONFIG
+  end
+
+  def initialize(ardir_root, config)
+    @ardir = File.expand_path(ardir_root)
+    @config = config
+    # cache
+    @valid_task_re = nil
+  end
+
+  def config(key)
+    @config[key]
+  end
+
+  def inspect
+    "#<#{self.class} #{__id__()}>"
+  end
+
+  def invoke
+    run_metaconfigs
+    case task = parsearg_global()
+    when nil, 'all'
+      parsearg_config
+      init_installers
+      exec_config
+      exec_setup
+      exec_install
+    else
+      case task
+      when 'config', 'test'
+        ;
+      when 'clean', 'distclean'
+        @config.load_savefile if File.exist?(@config.savefile)
+      else
+        @config.load_savefile
+      end
+      __send__ "parsearg_#{task}"
+      init_installers
+      __send__ "exec_#{task}"
+    end
+  end
+
+  def run_metaconfigs
+    @config.load_script "#{@ardir}/metaconfig"
+  end
+
+  def init_installers
+    @installer = Installer.new(@config, @ardir, File.expand_path('.'))
+  end
+
+  #
+  # Hook Script API bases
+  #
+
+  def srcdir_root
+    @ardir
+  end
+
+  def objdir_root
+    '.'
+  end
+
+  def relpath
+    '.'
+  end
+
+  #
+  # Option Parsing
+  #
+
+  def parsearg_global
+    while arg = ARGV.shift
+      case arg
+      when /\A\w+\z/
+        setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
+        return arg
+      when '-q', '--quiet'
+        @config.verbose = false
+      when '--verbose'
+        @config.verbose = true
+      when '--help'
+        print_usage $stdout
+        exit 0
+      when '--version'
+        puts "#{File.basename($0)} version #{Version}"
+        exit 0
+      when '--copyright'
+        puts Copyright
+        exit 0
+      else
+        setup_rb_error "unknown global option '#{arg}'"
+      end
+    end
+    nil
+  end
+
+  def valid_task?(t)
+    valid_task_re() =~ t
+  end
+
+  def valid_task_re
+    @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
+  end
+
+  def parsearg_no_options
+    unless ARGV.empty?
+      task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
+      setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
+    end
+  end
+
+  alias parsearg_show       parsearg_no_options
+  alias parsearg_setup      parsearg_no_options
+  alias parsearg_test       parsearg_no_options
+  alias parsearg_clean      parsearg_no_options
+  alias parsearg_distclean  parsearg_no_options
+
+  def parsearg_config
+    evalopt = []
+    set = []
+    @config.config_opt = []
+    while i = ARGV.shift
+      if /\A--?\z/ =~ i
+        @config.config_opt = ARGV.dup
+        break
+      end
+      name, value = *@config.parse_opt(i)
+      if @config.value_config?(name)
+        @config[name] = value
+      else
+        evalopt.push [name, value]
+      end
+      set.push name
+    end
+    evalopt.each do |name, value|
+      @config.lookup(name).evaluate value, @config
+    end
+    # Check if configuration is valid
+    set.each do |n|
+      @config[n] if @config.value_config?(n)
+    end
+  end
+
+  def parsearg_install
+    @config.no_harm = false
+    @config.install_prefix = ''
+    while a = ARGV.shift
+      case a
+      when '--no-harm'
+        @config.no_harm = true
+      when /\A--prefix=/
+        path = a.split(/=/, 2)[1]
+        path = File.expand_path(path) unless path[0,1] == '/'
+        @config.install_prefix = path
+      else
+        setup_rb_error "install: unknown option #{a}"
+      end
+    end
+  end
+
+  def print_usage(out)
+    out.puts 'Typical Installation Procedure:'
+    out.puts "  $ ruby #{File.basename $0} config"
+    out.puts "  $ ruby #{File.basename $0} setup"
+    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
+    out.puts
+    out.puts 'Detailed Usage:'
+    out.puts "  ruby #{File.basename $0} <global option>"
+    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+    fmt = "  %-24s %s\n"
+    out.puts
+    out.puts 'Global options:'
+    out.printf fmt, '-q,--quiet',   'suppress message outputs'
+    out.printf fmt, '   --verbose', 'output messages verbosely'
+    out.printf fmt, '   --help',    'print this message'
+    out.printf fmt, '   --version', 'print version and quit'
+    out.printf fmt, '   --copyright',  'print copyright and quit'
+    out.puts
+    out.puts 'Tasks:'
+    TASKS.each do |name, desc|
+      out.printf fmt, name, desc
+    end
+
+    fmt = "  %-24s %s [%s]\n"
+    out.puts
+    out.puts 'Options for CONFIG or ALL:'
+    @config.each do |item|
+      out.printf fmt, item.help_opt, item.description, item.help_default
+    end
+    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+    out.puts
+    out.puts 'Options for INSTALL:'
+    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+    out.printf fmt, '--prefix=path',  'install path prefix', ''
+    out.puts
+  end
+
+  #
+  # Task Handlers
+  #
+
+  def exec_config
+    @installer.exec_config
+    @config.save   # must be final
+  end
+
+  def exec_setup
+    @installer.exec_setup
+  end
+
+  def exec_install
+    @installer.exec_install
+  end
+
+  def exec_test
+    @installer.exec_test
+  end
+
+  def exec_show
+    @config.each do |i|
+      printf "%-20s %s\n", i.name, i.value if i.value?
+    end
+  end
+
+  def exec_clean
+    @installer.exec_clean
+  end
+
+  def exec_distclean
+    @installer.exec_distclean
+  end
+
+end   # class ToplevelInstaller
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+  include FileOperations
+
+  def initialize(ardir_root, config)
+    super
+    @packages = directories_of("#{@ardir}/packages")
+    raise 'no package exists' if @packages.empty?
+    @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
+  end
+
+  def run_metaconfigs
+    @config.load_script "#{@ardir}/metaconfig", self
+    @packages.each do |name|
+      @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
+    end
+  end
+
+  attr_reader :packages
+
+  def packages=(list)
+    raise 'package list is empty' if list.empty?
+    list.each do |name|
+      raise "directory packages/#{name} does not exist"\
+              unless File.dir?("#{@ardir}/packages/#{name}")
+    end
+    @packages = list
+  end
+
+  def init_installers
+    @installers = {}
+    @packages.each do |pack|
+      @installers[pack] = Installer.new(@config,
+                                       "#{@ardir}/packages/#{pack}",
+                                       "packages/#{pack}")
+    end
+    with    = extract_selection(config('with'))
+    without = extract_selection(config('without'))
+    @selected = @installers.keys.select {|name|
+                  (with.empty? or with.include?(name)) \
+                      and not without.include?(name)
+                }
+  end
+
+  def extract_selection(list)
+    a = list.split(/,/)
+    a.each do |name|
+      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
+    end
+    a
+  end
+
+  def print_usage(f)
+    super
+    f.puts 'Inluded packages:'
+    f.puts '  ' + @packages.sort.join(' ')
+    f.puts
+  end
+
+  #
+  # Task Handlers
+  #
+
+  def exec_config
+    run_hook 'pre-config'
+    each_selected_installers {|inst| inst.exec_config }
+    run_hook 'post-config'
+    @config.save   # must be final
+  end
+
+  def exec_setup
+    run_hook 'pre-setup'
+    each_selected_installers {|inst| inst.exec_setup }
+    run_hook 'post-setup'
+  end
+
+  def exec_install
+    run_hook 'pre-install'
+    each_selected_installers {|inst| inst.exec_install }
+    run_hook 'post-install'
+  end
+
+  def exec_test
+    run_hook 'pre-test'
+    each_selected_installers {|inst| inst.exec_test }
+    run_hook 'post-test'
+  end
+
+  def exec_clean
+    rm_f @config.savefile
+    run_hook 'pre-clean'
+    each_selected_installers {|inst| inst.exec_clean }
+    run_hook 'post-clean'
+  end
+
+  def exec_distclean
+    rm_f @config.savefile
+    run_hook 'pre-distclean'
+    each_selected_installers {|inst| inst.exec_distclean }
+    run_hook 'post-distclean'
+  end
+
+  #
+  # lib
+  #
+
+  def each_selected_installers
+    Dir.mkdir 'packages' unless File.dir?('packages')
+    @selected.each do |pack|
+      $stderr.puts "Processing the package `#{pack}' ..." if verbose?
+      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+      Dir.chdir "packages/#{pack}"
+      yield @installers[pack]
+      Dir.chdir '../..'
+    end
+  end
+
+  def run_hook(id)
+    @root_installer.run_hook id
+  end
+
+  # module FileOperations requires this
+  def verbose?
+    @config.verbose?
+  end
+
+  # module FileOperations requires this
+  def no_harm?
+    @config.no_harm?
+  end
+
+end   # class ToplevelInstallerMulti
+
+
+class Installer
+
+  FILETYPES = %w( bin lib ext data conf man )
+
+  include FileOperations
+  include HookScriptAPI
+
+  def initialize(config, srcroot, objroot)
+    @config = config
+    @srcdir = File.expand_path(srcroot)
+    @objdir = File.expand_path(objroot)
+    @currdir = '.'
+  end
+
+  def inspect
+    "#<#{self.class} #{File.basename(@srcdir)}>"
+  end
+
+  def noop(rel)
+  end
+
+  #
+  # Hook Script API base methods
+  #
+
+  def srcdir_root
+    @srcdir
+  end
+
+  def objdir_root
+    @objdir
+  end
+
+  def relpath
+    @currdir
+  end
+
+  #
+  # Config Access
+  #
+
+  # module FileOperations requires this
+  def verbose?
+    @config.verbose?
+  end
+
+  # module FileOperations requires this
+  def no_harm?
+    @config.no_harm?
+  end
+
+  def verbose_off
+    begin
+      save, @config.verbose = @config.verbose?, false
+      yield
+    ensure
+      @config.verbose = save
+    end
+  end
+
+  #
+  # TASK config
+  #
+
+  def exec_config
+    exec_task_traverse 'config'
+  end
+
+  alias config_dir_bin noop
+  alias config_dir_lib noop
+
+  def config_dir_ext(rel)
+    extconf if extdir?(curr_srcdir())
+  end
+
+  alias config_dir_data noop
+  alias config_dir_conf noop
+  alias config_dir_man noop
+
+  def extconf
+    ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
+  end
+
+  #
+  # TASK setup
+  #
+
+  def exec_setup
+    exec_task_traverse 'setup'
+  end
+
+  def setup_dir_bin(rel)
+    files_of(curr_srcdir()).each do |fname|
+      update_shebang_line "#{curr_srcdir()}/#{fname}"
+    end
+  end
+
+  alias setup_dir_lib noop
+
+  def setup_dir_ext(rel)
+    make if extdir?(curr_srcdir())
+  end
+
+  alias setup_dir_data noop
+  alias setup_dir_conf noop
+  alias setup_dir_man noop
+
+  def update_shebang_line(path)
+    return if no_harm?
+    return if config('shebang') == 'never'
+    old = Shebang.load(path)
+    if old
+      $stderr.puts "warning: #{path}: Shebang line includes too many args.  It is not portable and your program may not work." if old.args.size > 1
+      new = new_shebang(old)
+      return if new.to_s == old.to_s
+    else
+      return unless config('shebang') == 'all'
+      new = Shebang.new(config('rubypath'))
+    end
+    $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
+    open_atomic_writer(path) {|output|
+      File.open(path, 'rb') {|f|
+        f.gets if old   # discard
+        output.puts new.to_s
+        output.print f.read
+      }
+    }
+  end
+
+  def new_shebang(old)
+    if /\Aruby/ =~ File.basename(old.cmd)
+      Shebang.new(config('rubypath'), old.args)
+    elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
+      Shebang.new(config('rubypath'), old.args[1..-1])
+    else
+      return old unless config('shebang') == 'all'
+      Shebang.new(config('rubypath'))
+    end
+  end
+
+  def open_atomic_writer(path, &block)
+    tmpfile = File.basename(path) + '.tmp'
+    begin
+      File.open(tmpfile, 'wb', &block)
+      File.rename tmpfile, File.basename(path)
+    ensure
+      File.unlink tmpfile if File.exist?(tmpfile)
+    end
+  end
+
+  class Shebang
+    def Shebang.load(path)
+      line = nil
+      File.open(path) {|f|
+        line = f.gets
+      }
+      return nil unless /\A#!/ =~ line
+      parse(line)
+    end
+
+    def Shebang.parse(line)
+      cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
+      new(cmd, args)
+    end
+
+    def initialize(cmd, args = [])
+      @cmd = cmd
+      @args = args
+    end
+
+    attr_reader :cmd
+    attr_reader :args
+
+    def to_s
+      "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
+    end
+  end
+
+  #
+  # TASK install
+  #
+
+  def exec_install
+    rm_f 'InstalledFiles'
+    exec_task_traverse 'install'
+  end
+
+  def install_dir_bin(rel)
+    install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
+  end
+
+  def install_dir_lib(rel)
+    install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
+  end
+
+  def install_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    install_files rubyextentions('.'),
+                  "#{config('sodir')}/#{File.dirname(rel)}",
+                  0555
+  end
+
+  def install_dir_data(rel)
+    install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
+  end
+
+  def install_dir_conf(rel)
+    # FIXME: should not remove current config files
+    # (rename previous file to .old/.org)
+    install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
+  end
+
+  def install_dir_man(rel)
+    install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
+  end
+
+  def install_files(list, dest, mode)
+    mkdir_p dest, @config.install_prefix
+    list.each do |fname|
+      install fname, dest, mode, @config.install_prefix
+    end
+  end
+
+  def libfiles
+    glob_reject(%w(*.y *.output), targetfiles())
+  end
+
+  def rubyextentions(dir)
+    ents = glob_select("*.#{@config.dllext}", targetfiles())
+    if ents.empty?
+      setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+    end
+    ents
+  end
+
+  def targetfiles
+    mapdir(existfiles() - hookfiles())
+  end
+
+  def mapdir(ents)
+    ents.map {|ent|
+      if File.exist?(ent)
+      then ent                         # objdir
+      else "#{curr_srcdir()}/#{ent}"   # srcdir
+      end
+    }
+  end
+
+  # picked up many entries from cvs-1.11.1/src/ignore.c
+  JUNK_FILES = %w(
+    core RCSLOG tags TAGS .make.state
+    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+    *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+    *.org *.in .*
+  )
+
+  def existfiles
+    glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
+  end
+
+  def hookfiles
+    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+      %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+    }.flatten
+  end
+
+  def glob_select(pat, ents)
+    re = globs2re([pat])
+    ents.select {|ent| re =~ ent }
+  end
+
+  def glob_reject(pats, ents)
+    re = globs2re(pats)
+    ents.reject {|ent| re =~ ent }
+  end
+
+  GLOB2REGEX = {
+    '.' => '\.',
+    '$' => '\$',
+    '#' => '\#',
+    '*' => '.*'
+  }
+
+  def globs2re(pats)
+    /\A(?:#{
+      pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
+    })\z/
+  end
+
+  #
+  # TASK test
+  #
+
+  TESTDIR = 'test'
+
+  def exec_test
+    unless File.directory?('test')
+      $stderr.puts 'no test in this package' if verbose?
+      return
+    end
+    $stderr.puts 'Running tests...' if verbose?
+    begin
+      require 'test/unit'
+    rescue LoadError
+      setup_rb_error 'test/unit cannot loaded.  You need Ruby 1.8 or later to invoke this task.'
+    end
+    runner = Test::Unit::AutoRunner.new(true)
+    runner.to_run << TESTDIR
+    runner.run
+  end
+
+  #
+  # TASK clean
+  #
+
+  def exec_clean
+    exec_task_traverse 'clean'
+    rm_f @config.savefile
+    rm_f 'InstalledFiles'
+  end
+
+  alias clean_dir_bin noop
+  alias clean_dir_lib noop
+  alias clean_dir_data noop
+  alias clean_dir_conf noop
+  alias clean_dir_man noop
+
+  def clean_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    make 'clean' if File.file?('Makefile')
+  end
+
+  #
+  # TASK distclean
+  #
+
+  def exec_distclean
+    exec_task_traverse 'distclean'
+    rm_f @config.savefile
+    rm_f 'InstalledFiles'
+  end
+
+  alias distclean_dir_bin noop
+  alias distclean_dir_lib noop
+
+  def distclean_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    make 'distclean' if File.file?('Makefile')
+  end
+
+  alias distclean_dir_data noop
+  alias distclean_dir_conf noop
+  alias distclean_dir_man noop
+
+  #
+  # Traversing
+  #
+
+  def exec_task_traverse(task)
+    run_hook "pre-#{task}"
+    FILETYPES.each do |type|
+      if type == 'ext' and config('without-ext') == 'yes'
+        $stderr.puts 'skipping ext/* by user option' if verbose?
+        next
+      end
+      traverse task, type, "#{task}_dir_#{type}"
+    end
+    run_hook "post-#{task}"
+  end
+
+  def traverse(task, rel, mid)
+    dive_into(rel) {
+      run_hook "pre-#{task}"
+      __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+      directories_of(curr_srcdir()).each do |d|
+        traverse task, "#{rel}/#{d}", mid
+      end
+      run_hook "post-#{task}"
+    }
+  end
+
+  def dive_into(rel)
+    return unless File.dir?("#{@srcdir}/#{rel}")
+
+    dir = File.basename(rel)
+    Dir.mkdir dir unless File.dir?(dir)
+    prevdir = Dir.pwd
+    Dir.chdir dir
+    $stderr.puts '---> ' + rel if verbose?
+    @currdir = rel
+    yield
+    Dir.chdir prevdir
+    $stderr.puts '<--- ' + rel if verbose?
+    @currdir = File.dirname(rel)
+  end
+
+  def run_hook(id)
+    path = [ "#{curr_srcdir()}/#{id}",
+             "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
+    return unless path
+    begin
+      instance_eval File.read(path), path, 1
+    rescue
+      raise if $DEBUG
+      setup_rb_error "hook #{path} failed:\n" + $!.message
+    end
+  end
+
+end   # class Installer
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+  raise SetupError, msg
+end
+
+if $0 == __FILE__
+  begin
+    ToplevelInstaller.invoke
+  rescue SetupError
+    raise if $DEBUG
+    $stderr.puts $!.message
+    $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+    exit 1
+  end
+end
diff --git a/lib/rb/spec/socket_spec_shared.rb b/lib/rb/spec/socket_spec_shared.rb
index a0092e1..b32ab44 100644
--- a/lib/rb/spec/socket_spec_shared.rb
+++ b/lib/rb/spec/socket_spec_shared.rb
@@ -26,19 +26,13 @@
 
   it "should raise an error when it cannot read from the handle" do
     @socket.open
-    @handle.should_receive(:read).with(17).and_raise(StandardError)
+    @handle.should_receive(:readpartial).with(17).and_raise(StandardError)
     lambda { @socket.read(17) }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
   end
 
-  it "should raise an error when it reads no data from the handle" do
-    @socket.open
-    @handle.should_receive(:read).with(17).and_return("")
-    lambda { @socket.read(17) }.should raise_error(Thrift::TransportException, "Socket: Could not read 17 bytes from #{@socket.instance_variable_get("@desc")}")
-  end
-
   it "should return the data read when reading from the handle works" do
     @socket.open
-    @handle.should_receive(:read).with(17).and_return("test data")
+    @handle.should_receive(:readpartial).with(17).and_return("test data")
     @socket.read(17).should == "test data"
   end
 
diff --git a/lib/rb/spec/transport_spec.rb b/lib/rb/spec/transport_spec.rb
index 44d0508..1fe8600 100644
--- a/lib/rb/spec/transport_spec.rb
+++ b/lib/rb/spec/transport_spec.rb
@@ -48,18 +48,26 @@
   end
 
   describe BufferedTransport do
-    it "should pass through everything but write/flush" do
+    it "should pass through everything but write/flush/read" do
       trans = mock("Transport")
       trans.should_receive(:open?).ordered.and_return("+ open?")
       trans.should_receive(:open).ordered.and_return("+ open")
       trans.should_receive(:flush).ordered # from the close
       trans.should_receive(:close).ordered.and_return("+ close")
-      trans.should_receive(:read).with(217).ordered.and_return("+ read")
       btrans = BufferedTransport.new(trans)
       btrans.open?.should == "+ open?"
       btrans.open.should == "+ open"
       btrans.close.should == "+ close"
-      btrans.read(217).should == "+ read"
+    end
+    
+    it "should buffer reads in chunks of #{BufferedTransport::DEFAULT_BUFFER}" do
+      trans = mock("Transport")
+      trans.should_receive(:read).with(BufferedTransport::DEFAULT_BUFFER).and_return("lorum ipsum dolor emet")
+      btrans = BufferedTransport.new(trans)
+      btrans.read(6).should == "lorum "
+      btrans.read(6).should == "ipsum "
+      btrans.read(6).should == "dolor "
+      btrans.read(6).should == "emet"
     end
 
     it "should buffer writes and send them on flush" do
diff --git a/test/Makefile.am b/test/Makefile.am
index 47e6b76..bac5b06 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,6 +8,10 @@
 SUBDIRS += py
 endif
 
+if ENABLE_RUBY
+SUBDIRS += rb
+endif
+
 noinst_LTLIBRARIES = libtestgencpp.la
 libtestgencpp_la_SOURCES = \
 	gen-cpp/DebugProtoTest_types.cpp \
diff --git a/test/SmallTest.thrift b/test/SmallTest.thrift
index 36ee349..dd5634d 100644
--- a/test/SmallTest.thrift
+++ b/test/SmallTest.thrift
@@ -11,6 +11,10 @@
   "ASDFLJASDF"
 }
 
+struct BoolPasser {
+  1: bool value = 1
+}
+
 struct Hello {
   1: i32 simple = 53,
   2: map<i32,i32> complex = {23:532, 6243:632, 2355:532},
diff --git a/test/rb/Makefile b/test/rb/Makefile
deleted file mode 100644
index 5c1e61a..0000000
--- a/test/rb/Makefile
+++ /dev/null
@@ -1,22 +0,0 @@
-# Makefile for Thrift test project.
-#
-# Author:
-#   Mark Slee <mcslee@facebook.com>
-
-# Default target is everything
-target: all
-
-# Tools
-THRIFT = ../../compiler/cpp/thrift
-
-all: stubs check
-
-stubs: ../ThriftTest.thrift ../SmallTest.thrift
-	$(THRIFT) --gen rb ../ThriftTest.thrift
-	$(THRIFT) --gen rb ../SmallTest.thrift
-
-check: stubs
-	ruby test_suite.rb
-
-clean:
-	$(RM) -r gen-rb
diff --git a/test/rb/Makefile.am b/test/rb/Makefile.am
new file mode 100644
index 0000000..b19fcde
--- /dev/null
+++ b/test/rb/Makefile.am
@@ -0,0 +1,9 @@
+THRIFT = $(top_srcdir)/compiler/cpp/thrift
+
+stubs: ../ThriftTest.thrift ../SmallTest.thrift
+	$(THRIFT) --gen rb ../ThriftTest.thrift
+	$(THRIFT) --gen rb ../SmallTest.thrift
+
+check: stubs
+	$(RUBY) test_suite.rb
+
diff --git a/test/rb/benchmarks/protocol_benchmark.rb b/test/rb/benchmarks/protocol_benchmark.rb
new file mode 100644
index 0000000..99f0760
--- /dev/null
+++ b/test/rb/benchmarks/protocol_benchmark.rb
@@ -0,0 +1,158 @@
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. .. .. lib rb lib])
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. .. .. lib rb ext])
+
+require 'thrift'
+require 'thrift/transport'
+require 'thrift/protocol/binaryprotocol'
+require 'thrift/protocol/binaryprotocolaccelerated'
+
+require 'benchmark'
+require 'rubygems'
+require 'set'
+require 'pp'
+
+# require 'ruby-debug'
+# require 'ruby-prof'
+
+require File.join(File.dirname(__FILE__), '../fixtures/structs')
+
+transport1 = Thrift::MemoryBuffer.new
+ruby_binary_protocol = Thrift::BinaryProtocol.new(transport1)
+
+transport2 = Thrift::MemoryBuffer.new
+c_fast_binary_protocol = Thrift::BinaryProtocolAccelerated.new(transport2)
+
+
+ooe = Fixtures::Structs::OneOfEach.new
+ooe.im_true   = true
+ooe.im_false  = false
+ooe.a_bite    = -42
+ooe.integer16 = 27000
+ooe.integer32 = 1<<24
+ooe.integer64 = 6000 * 1000 * 1000
+ooe.double_precision = Math::PI
+ooe.some_characters  = "Debug THIS!"
+ooe.zomg_unicode     = "\xd7\n\a\t"
+
+n1 = Fixtures::Structs::Nested1.new
+n1.a_list = []
+n1.a_list << ooe << ooe << ooe << ooe
+n1.i32_map = {}
+n1.i32_map[1234] = ooe
+n1.i32_map[46345] = ooe
+n1.i32_map[-34264] = ooe
+n1.i64_map = {}
+n1.i64_map[43534986783945] = ooe
+n1.i64_map[-32434639875122] = ooe
+n1.dbl_map = {}
+n1.dbl_map[324.65469834] = ooe
+n1.dbl_map[-9458672340.4986798345112] = ooe
+n1.str_map = {}
+n1.str_map['sdoperuix'] = ooe
+n1.str_map['pwoerxclmn'] = ooe
+
+n2 = Fixtures::Structs::Nested2.new
+n2.a_list = []
+n2.a_list << n1 << n1 << n1 << n1 << n1
+n2.i32_map = {}
+n2.i32_map[398345] = n1
+n2.i32_map[-2345] = n1
+n2.i32_map[12312] = n1
+n2.i64_map = {}
+n2.i64_map[2349843765934] = n1
+n2.i64_map[-123234985495] = n1
+n2.i64_map[0] = n1
+n2.dbl_map = {}
+n2.dbl_map[23345345.38927834] = n1
+n2.dbl_map[-1232349.5489345] = n1
+n2.dbl_map[-234984574.23498725] = n1
+n2.str_map = {}
+n2.str_map[''] = n1
+n2.str_map['sdflkertpioux'] = n1
+n2.str_map['sdfwepwdcjpoi'] = n1
+
+n3 = Fixtures::Structs::Nested3.new
+n3.a_list = []
+n3.a_list << n2 << n2 << n2 << n2 << n2
+n3.i32_map = {}
+n3.i32_map[398345] = n2
+n3.i32_map[-2345] = n2
+n3.i32_map[12312] = n2
+n3.i64_map = {}
+n3.i64_map[2349843765934] = n2
+n3.i64_map[-123234985495] = n2
+n3.i64_map[0] = n2
+n3.dbl_map = {}
+n3.dbl_map[23345345.38927834] = n2
+n3.dbl_map[-1232349.5489345] = n2
+n3.dbl_map[-234984574.23498725] = n2
+n3.str_map = {}
+n3.str_map[''] = n2
+n3.str_map['sdflkertpioux'] = n2
+n3.str_map['sdfwepwdcjpoi'] = n2
+
+n4 = Fixtures::Structs::Nested4.new
+n4.a_list = []
+n4.a_list << n3
+n4.i32_map = {}
+n4.i32_map[-2345] = n3
+n4.i64_map = {}
+n4.i64_map[2349843765934] = n3
+n4.dbl_map = {}
+n4.dbl_map[-1232349.5489345] = n3
+n4.str_map = {}
+n4.str_map[''] = n3
+
+
+# prof = RubyProf.profile do
+#   n4.write(c_fast_binary_protocol)
+#   Fixtures::Structs::Nested4.new.read(c_fast_binary_protocol)
+# end
+# 
+# printer = RubyProf::GraphHtmlPrinter.new(prof)
+# printer.print(STDOUT, :min_percent=>0)
+
+Benchmark.bmbm do |x|
+  x.report("ruby write large (1MB) structure once") do
+    n4.write(ruby_binary_protocol)
+  end
+  
+  x.report("ruby read large (1MB) structure once") do
+    Fixtures::Structs::Nested4.new.read(ruby_binary_protocol)
+  end
+  
+  x.report("c write large (1MB) structure once") do    
+    n4.write(c_fast_binary_protocol)
+  end
+  
+  x.report("c read large (1MB) structure once") do
+    Fixtures::Structs::Nested4.new.read(c_fast_binary_protocol)
+  end
+  
+  
+  
+  x.report("ruby write 10_000 small structures") do
+    10_000.times do
+      ooe.write(ruby_binary_protocol)
+    end
+  end
+  
+  x.report("ruby read 10_000 small structures") do
+    10_000.times do
+      Fixtures::Structs::OneOfEach.new.read(ruby_binary_protocol)
+    end
+  end
+  
+  x.report("c write 10_000 small structures") do
+    10_000.times do
+      ooe.write(c_fast_binary_protocol)
+    end
+  end
+  
+  x.report("c read 10_000 small structures") do
+    10_000.times do
+      Fixtures::Structs::OneOfEach.new.read(c_fast_binary_protocol)
+    end
+  end
+  
+end
diff --git a/test/rb/core/test_binary_protocol_accelerated.rb b/test/rb/core/test_binary_protocol_accelerated.rb
new file mode 100644
index 0000000..15c033e
--- /dev/null
+++ b/test/rb/core/test_binary_protocol_accelerated.rb
@@ -0,0 +1,275 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+require File.join(File.dirname(__FILE__), '../fixtures/structs')
+
+require 'thrift'
+require 'thrift/transport'
+require 'thrift/protocol/binaryprotocol'
+require 'thrift/protocol/binaryprotocolaccelerated'
+
+class BinaryProtocolAcceleratedTest < Test::Unit::TestCase
+  I8_MIN = -128
+  I8_MAX = 127
+  I16_MIN = -32768
+  I16_MAX = 32767
+  I32_MIN = -2147483648
+  I32_MAX = 2147483647
+  I64_MIN = -9223372036854775808
+  I64_MAX = 9223372036854775807
+  DBL_MIN = Float::MIN
+  DBL_MAX = Float::MAX
+
+  # booleans might be read back differently, so we supply a list [write_value, read_value]
+  BOOL_VALUES = [[0,true], [14,true], [-14,true], [true,true], [false,false], ["",true]]
+  BYTE_VALUES = [14, -14, I8_MIN, I8_MAX]
+  I16_VALUES = [400, 0, -234, I16_MIN, I16_MAX]
+  I32_VALUES = [325, 0, -1, -1073741825, -278, -4352388, I32_MIN, I32_MAX]
+  I64_VALUES = [15, 0, -33, I64_MIN, I64_MAX]
+  DBL_VALUES = [DBL_MIN, -33.8755, 0, 3658.1279, DBL_MAX]
+  STR_VALUES = ["", "welcome to my test"]
+  
+  def setup
+    @trans = Thrift::MemoryBuffer.new
+    @fast_proto = Thrift::BinaryProtocolAccelerated.new(@trans)
+    @slow_proto = Thrift::BinaryProtocol.new(@trans)
+  end
+  
+  def assert_encodes_struct(obj)
+    obj.write(@slow_proto)    
+    expected = @trans.read(@trans.available) # read it all baby
+    assert_equal expected, @fast_proto.encode_binary(obj)
+  end
+  
+  # Assumes encode works
+  def assert_decodes_struct(obj, eql_obj = nil)
+    data = @fast_proto.encode_binary(obj)
+    @trans.write data
+    assert_equal (eql_obj || obj), @fast_proto.decode_binary(obj.class.new, @trans)
+  end
+  
+  def test_encodes_and_decodes_bools
+    BOOL_VALUES.each do |(write_val, read_val)|
+      obj = Fixtures::Structs::OneBool.new(:bool => write_val)
+      assert_encodes_struct obj
+      assert_decodes_struct obj, Fixtures::Structs::OneBool.new(:bool => read_val)
+    end
+  end
+  
+  def test_encodes_and_decodes_bytes
+    BYTE_VALUES.each do |val|
+      obj = Fixtures::Structs::OneByte.new(:byte => val)
+      assert_encodes_struct obj
+      assert_decodes_struct obj
+    end
+  end
+  
+  def test_encodes_and_decodes_i16
+    I16_VALUES.each do |val|
+      obj = Fixtures::Structs::OneI16.new(:i16 => val)
+      assert_encodes_struct obj
+      assert_decodes_struct obj
+    end
+  end
+  
+  def test_encodes_and_decodes_i32
+    I32_VALUES.each do |val|
+      obj = Fixtures::Structs::OneI32.new(:i32 => val)
+      assert_encodes_struct obj
+      assert_decodes_struct obj
+    end
+  end
+  
+  def test_encodes_and_decodes_i64
+    I64_VALUES.each do |val|
+      obj = Fixtures::Structs::OneI64.new(:i64 => val)
+      assert_encodes_struct obj
+      assert_decodes_struct obj
+    end
+  end
+  
+  def test_encodes_and_decodes_double
+    DBL_VALUES.each do |val|
+      obj = Fixtures::Structs::OneDouble.new(:double => val)
+      assert_encodes_struct obj
+      assert_decodes_struct obj
+    end
+  end
+  
+  def test_encodes_strings
+    STR_VALUES.each do |val|
+      obj = Fixtures::Structs::OneString.new(:string => val)
+      assert_encodes_struct obj
+      assert_decodes_struct obj
+    end
+  end
+  
+  def test_encodes_maps
+    obj = Fixtures::Structs::OneMap.new(:map => {"a" => "b", "c" => "d"})
+    assert_encodes_struct obj
+    assert_decodes_struct obj
+  end
+  
+  def test_encodes_lists
+    obj = Fixtures::Structs::OneList.new(:list => ["a", "b", "c", "d"])
+    assert_encodes_struct obj
+    assert_decodes_struct obj
+  end
+  
+  def test_encodes_sets
+    expected_return = Fixtures::Structs::OneSet.new(:set => Set.new(["a", "b", "c"]))
+    # support hashes
+    obj = Fixtures::Structs::OneSet.new(:set => {"a" => true, "b" => true, "c" => true})
+    assert_encodes_struct obj
+    assert_decodes_struct obj, expected_return
+    
+    # We also support arrays as compatability bug from TBinaryProtocol....
+    obj = Fixtures::Structs::OneSet.new(:set => ["a", "b", "c"])
+    assert_encodes_struct obj
+    assert_decodes_struct obj, expected_return
+
+    # We also support sets
+    obj = Fixtures::Structs::OneSet.new(:set => Set.new(["a", "b", "c"]))
+    assert_encodes_struct obj
+    assert_decodes_struct obj, expected_return
+  end
+  
+  def test_encodes_maps_of_maps
+    obj = Fixtures::Structs::NestedMap.new(:map => { 1 => { 2 => 3 }})
+    assert_encodes_struct obj
+    assert_decodes_struct obj
+  end
+  
+  def test_encodes_list_of_lists
+    obj = Fixtures::Structs::NestedList.new(:list => [[1,2,3], [4,5,6]])
+    assert_encodes_struct obj
+    assert_decodes_struct obj
+  end
+
+  def test_encodes_set_of_sets
+    obj = Fixtures::Structs::NestedSet.new(:set => Set.new([Set.new('a')]))
+    assert_encodes_struct obj
+
+    # Nested hashes won't work with ==, so we do it by hand
+    data = @fast_proto.encode_binary(obj)
+    @trans.write data
+    decoded = @fast_proto.decode_binary(obj.class.new, @trans)
+    assert_equal decoded.set.entries, Set.new([Set.new('a')]).entries
+  end
+  
+  if ENV['MEM_TEST']
+    def test_for_memory_leaks_on_exceptions
+      ooe = Fixtures::Structs::OneOfEach.new
+      ooe.im_true   = true
+      ooe.im_false  = false
+      ooe.a_bite    = -42
+      ooe.integer16 = 27000
+      ooe.integer32 = 1<<24
+      ooe.integer64 = 6000 * 1000 * 1000
+      ooe.double_precision = Math::PI
+      ooe.some_characters  = "Debug THIS!"
+      ooe.zomg_unicode     = "\xd7\n\a\t"
+    
+      10_000.times do
+        data = @fast_proto.encode_binary(ooe)
+        bytes = []
+        data.each_byte do |b|
+          bytes << b
+          transport = TMemoryBuffer.new
+          transport.write(bytes.pack("c#{bytes.length}"))
+      
+          begin
+            @fast_proto.decode_binary(Fixtures::Structs::OneOfEach.new, transport)
+          rescue Exception
+          end
+        end
+      end
+    
+    end
+  end
+  
+  unless ENV['FAST_TEST']
+    def test_encodes_and_decodes_struct_of_structs
+      ooe = Fixtures::Structs::OneOfEach.new
+      ooe.im_true   = true
+      ooe.im_false  = false
+      ooe.a_bite    = -42
+      ooe.integer16 = 27000
+      ooe.integer32 = 1<<24
+      ooe.integer64 = 6000 * 1000 * 1000
+      ooe.double_precision = Math::PI
+      ooe.some_characters  = "Debug THIS!"
+      ooe.zomg_unicode     = "\xd7\n\a\t"
+  
+      n1 = Fixtures::Structs::Nested1.new
+      n1.a_list = []
+      n1.a_list << ooe << ooe << ooe << ooe
+      n1.i32_map = {}
+      n1.i32_map[1234] = ooe
+      n1.i32_map[46345] = ooe
+      n1.i32_map[-34264] = ooe
+      n1.i64_map = {}
+      n1.i64_map[43534986783945] = ooe
+      n1.i64_map[-32434639875122] = ooe
+      n1.dbl_map = {}
+      n1.dbl_map[324.65469834] = ooe
+      n1.dbl_map[-9458672340.4986798345112] = ooe
+      n1.str_map = {}
+      n1.str_map['sdoperuix'] = ooe
+      n1.str_map['pwoerxclmn'] = ooe
+    
+      n2 = Fixtures::Structs::Nested2.new
+      n2.a_list = []
+      n2.a_list << n1 << n1 << n1 << n1 << n1
+      n2.i32_map = {}
+      n2.i32_map[398345] = n1
+      n2.i32_map[-2345] = n1
+      n2.i32_map[12312] = n1
+      n2.i64_map = {}
+      n2.i64_map[2349843765934] = n1
+      n2.i64_map[-123234985495] = n1
+      n2.i64_map[0] = n1
+      n2.dbl_map = {}
+      n2.dbl_map[23345345.38927834] = n1
+      n2.dbl_map[-1232349.5489345] = n1
+      n2.dbl_map[-234984574.23498725] = n1
+      n2.str_map = {}
+      n2.str_map[''] = n1
+      n2.str_map['sdflkertpioux'] = n1
+      n2.str_map['sdfwepwdcjpoi'] = n1
+  
+      n3 = Fixtures::Structs::Nested3.new
+      n3.a_list = []
+      n3.a_list << n2 << n2 << n2 << n2 << n2
+      n3.i32_map = {}
+      n3.i32_map[398345] = n2
+      n3.i32_map[-2345] = n2
+      n3.i32_map[12312] = n2
+      n3.i64_map = {}
+      n3.i64_map[2349843765934] = n2
+      n3.i64_map[-123234985495] = n2
+      n3.i64_map[0] = n2
+      n3.dbl_map = {}
+      n3.dbl_map[23345345.38927834] = n2
+      n3.dbl_map[-1232349.5489345] = n2
+      n3.dbl_map[-234984574.23498725] = n2
+      n3.str_map = {}
+      n3.str_map[''] = n2
+      n3.str_map['sdflkertpioux'] = n2
+      n3.str_map['sdfwepwdcjpoi'] = n2
+  
+      n4 = Fixtures::Structs::Nested4.new
+      n4.a_list = []
+      n4.a_list << n3
+      n4.i32_map = {}
+      n4.i32_map[-2345] = n3
+      n4.i64_map = {}
+      n4.i64_map[2349843765934] = n3
+      n4.dbl_map = {}
+      n4.dbl_map[-1232349.5489345] = n3
+      n4.str_map = {}
+      n4.str_map[''] = n3
+    
+      assert_encodes_struct n4
+      assert_decodes_struct n4
+    end
+  end
+end
diff --git a/test/rb/fixtures/structs.rb b/test/rb/fixtures/structs.rb
new file mode 100644
index 0000000..82c291e
--- /dev/null
+++ b/test/rb/fixtures/structs.rb
@@ -0,0 +1,225 @@
+require 'thrift'
+
+module Fixtures
+  module Structs
+    class OneBool
+      include Thrift::Struct
+      attr_accessor :bool
+      FIELDS = {
+        1 => {:type => Thrift::Types::BOOL, :name => 'bool'}
+      }
+    end
+    
+    class OneByte
+      include Thrift::Struct
+      attr_accessor :byte
+      FIELDS = {
+        1 => {:type => Thrift::Types::BYTE, :name => 'byte'}
+      }
+    end
+    
+    class OneI16
+      include Thrift::Struct
+      attr_accessor :i16
+      FIELDS = {
+        1 => {:type => Thrift::Types::I16, :name => 'i16'}
+      }
+    end
+    
+    class OneI32
+      include Thrift::Struct
+      attr_accessor :i32
+      FIELDS = {
+        1 => {:type => Thrift::Types::I32, :name => 'i32'}
+      }
+    end
+    
+    class OneI64
+      include Thrift::Struct
+      attr_accessor :i64
+      FIELDS = {
+        1 => {:type => Thrift::Types::I64, :name => 'i64'}
+      }
+    end
+    
+    class OneDouble
+      include Thrift::Struct
+      attr_accessor :double
+      FIELDS = {
+        1 => {:type => Thrift::Types::DOUBLE, :name => 'double'}
+      }
+    end
+    
+    class OneString
+      include Thrift::Struct
+      attr_accessor :string
+      FIELDS = {
+        1 => {:type => Thrift::Types::STRING, :name => 'string'}
+      }
+    end
+    
+    class OneMap
+      include Thrift::Struct
+      attr_accessor :map
+      FIELDS = {
+        1 => {:type => Thrift::Types::MAP, :name => 'map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRING}}
+      }
+    end
+    
+    class NestedMap
+      include Thrift::Struct
+      attr_accessor :map
+      FIELDS = {
+        0 => {:type => Thrift::Types::MAP, :name => 'map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::MAP, :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::I32}}}
+      }
+    end
+    
+    class OneList
+      include Thrift::Struct
+      attr_accessor :list
+      FIELDS = {
+        1 => {:type => Thrift::Types::LIST, :name => 'list', :element => {:type => Thrift::Types::STRING}}
+      }
+    end
+    
+    class NestedList
+      include Thrift::Struct
+      attr_accessor :list
+      FIELDS = {
+        0 => {:type => Thrift::Types::LIST, :name => 'list', :element => {:type => Thrift::Types::LIST, :element => { :type => Thrift::Types::I32 } } }
+      }
+    end
+    
+    class OneSet
+      include Thrift::Struct
+      attr_accessor :set
+      FIELDS = {
+        1 => {:type => Thrift::Types::SET, :name => 'set', :element => {:type => Thrift::Types::STRING}}
+      }
+    end
+    
+    class NestedSet
+      include Thrift::Struct
+      attr_accessor :set
+      FIELDS = {
+        1 => {:type => Thrift::Types::SET, :name => 'set', :element => {:type => Thrift::Types::SET, :element => { :type => Thrift::Types::STRING } }}
+      }
+    end
+    
+    # struct OneOfEach {
+    #   1: bool im_true,
+    #   2: bool im_false,
+    #   3: byte a_bite,
+    #   4: i16 integer16,
+    #   5: i32 integer32,
+    #   6: i64 integer64,
+    #   7: double double_precision,
+    #   8: string some_characters,
+    #   9: string zomg_unicode,
+    #   10: bool what_who,
+    #   11: binary base64,
+    # }
+    class OneOfEach
+      include Thrift::Struct
+      attr_accessor :im_true, :im_false, :a_bite, :integer16, :integer32, :integer64, :double_precision, :some_characters, :zomg_unicode, :what_who, :base64
+      FIELDS = {
+        1 => {:type => Thrift::Types::BOOL, :name => 'im_true'},
+        2 => {:type => Thrift::Types::BOOL, :name => 'im_false'},
+        3 => {:type => Thrift::Types::BYTE, :name => 'a_bite'},
+        4 => {:type => Thrift::Types::I16, :name => 'integer16'},
+        5 => {:type => Thrift::Types::I32, :name => 'integer32'},
+        6 => {:type => Thrift::Types::I64, :name => 'integer64'},
+        7 => {:type => Thrift::Types::DOUBLE, :name => 'double_precision'},
+        8 => {:type => Thrift::Types::STRING, :name => 'some_characters'},
+        9 => {:type => Thrift::Types::STRING, :name => 'zomg_unicode'},
+        10 => {:type => Thrift::Types::BOOL, :name => 'what_who'},
+        11 => {:type => Thrift::Types::STRING, :name => 'base64'}
+      }
+
+      # Added for assert_equal
+      def ==(other)
+        [:im_true, :im_false, :a_bite, :integer16, :integer32, :integer64, :double_precision, :some_characters, :zomg_unicode, :what_who, :base64].each do |f|
+          var = "@#{f}"
+          return false if instance_variable_get(var) != other.instance_variable_get(var)
+        end
+        true
+      end
+    end
+
+    # struct Nested1 {
+    #   1: list<OneOfEach> a_list
+    #   2: map<i32, OneOfEach> i32_map
+    #   3: map<i64, OneOfEach> i64_map
+    #   4: map<double, OneOfEach> dbl_map
+    #   5: map<string, OneOfEach> str_map
+    # }
+    class Nested1
+      include Thrift::Struct
+      attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+      FIELDS = {
+        1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+        2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+        3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+        4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+        5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}}
+      }
+    end
+
+    # struct Nested2 {
+    #   1: list<Nested1> a_list
+    #   2: map<i32, Nested1> i32_map
+    #   3: map<i64, Nested1> i64_map
+    #   4: map<double, Nested1> dbl_map
+    #   5: map<string, Nested1> str_map
+    # }
+    class Nested2
+      include Thrift::Struct
+      attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+      FIELDS = {
+        1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+        2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+        3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+        4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+        5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}}
+      }
+    end
+
+    # struct Nested3 {
+    #   1: list<Nested2> a_list
+    #   2: map<i32, Nested2> i32_map
+    #   3: map<i64, Nested2> i64_map
+    #   4: map<double, Nested2> dbl_map
+    #   5: map<string, Nested2> str_map
+    # }
+    class Nested3
+      include Thrift::Struct
+      attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+      FIELDS = {
+        1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+        2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+        3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+        4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+        5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}}
+      }
+    end
+
+    # struct Nested4 {
+    #   1: list<Nested3> a_list
+    #   2: map<i32, Nested3> i32_map
+    #   3: map<i64, Nested3> i64_map
+    #   4: map<double, Nested3> dbl_map
+    #   5: map<string, Nested3> str_map
+    # }
+    class Nested4
+      include Thrift::Struct
+      attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+      FIELDS = {
+        1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+        2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+        3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+        4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+        5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}}
+      }
+    end
+  end
+end
diff --git a/test/rb/generation/test_struct.rb b/test/rb/generation/test_struct.rb
index 3af0df8..b4526ab 100644
--- a/test/rb/generation/test_struct.rb
+++ b/test/rb/generation/test_struct.rb
@@ -17,6 +17,9 @@
 
     assert_kind_of(Hash, hello.complex)
     assert_equal(hello.complex, { 6243 => 632, 2355 => 532, 23 => 532})
+    
+    bool_passer = TestNamespace::BoolPasser.new(:value => false)
+    assert_equal false, bool_passer.value
   end
 
   def test_goodbyez
diff --git a/test/rb/integration/accelerated_buffered_client.rb b/test/rb/integration/accelerated_buffered_client.rb
new file mode 100644
index 0000000..a27787c
--- /dev/null
+++ b/test/rb/integration/accelerated_buffered_client.rb
@@ -0,0 +1,145 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocolaccelerated'
+require 'ThriftTest'
+
+class AcceleratedBufferedClientTest < Test::Unit::TestCase
+  def setup
+    unless @socket
+      @socket   = Thrift::Socket.new('localhost', 9090)
+      @protocol = Thrift::BinaryProtocolAccelerated.new(Thrift::BufferedTransport.new(@socket))
+      @client   = Thrift::Test::ThriftTest::Client.new(@protocol)
+      @socket.open
+    end
+  end
+  
+  def test_string
+    assert_equal(@client.testString('string'), 'string')
+  end
+
+  def test_byte
+    val = 8
+    assert_equal(@client.testByte(val), val)
+    assert_equal(@client.testByte(-val), -val)
+  end
+
+  def test_i32
+    val = 32
+    assert_equal(@client.testI32(val), val)
+    assert_equal(@client.testI32(-val), -val)
+  end
+
+  def test_i64
+    val = 64
+    assert_equal(@client.testI64(val), val)
+    assert_equal(@client.testI64(-val), -val)
+  end
+
+  def test_double
+    val = 3.14
+    assert_equal(@client.testDouble(val), val)
+    assert_equal(@client.testDouble(-val), -val)
+    assert_kind_of(Float, @client.testDouble(val))
+  end
+
+  def test_map
+    val = {1 => 1, 2 => 2, 3 => 3}
+    assert_equal(@client.testMap(val), val)
+    assert_kind_of(Hash, @client.testMap(val))
+  end
+
+  def test_list
+    val = [1,2,3,4,5]
+    assert_equal(@client.testList(val), val)
+    assert_kind_of(Array, @client.testList(val))
+  end
+
+  def test_enum
+    val = Thrift::Test::Numberz::SIX
+    ret = @client.testEnum(val)
+
+    assert_equal(ret, 6)
+    assert_kind_of(Fixnum, ret)
+  end
+
+  def test_typedef
+    #UserId  testTypedef(1: UserId thing),
+    true
+  end
+
+  def test_set
+    val = Set.new([1,2,3])
+    assert_equal(@client.testSet(val), val)
+    assert_kind_of(Set, @client.testSet(val))
+  end
+
+  def get_struct
+    Thrift::Test::Xtruct.new({'string_thing' => 'hi!', 'i32_thing' => 4 })
+  end
+
+  def test_struct
+    ret = @client.testStruct(get_struct)
+
+    assert_nil(ret.byte_thing, nil)
+    assert_nil(ret.i64_thing, nil)
+    assert_equal(ret.string_thing, 'hi!')
+    assert_equal(ret.i32_thing, 4)
+    assert_kind_of(Thrift::Test::Xtruct, ret)
+  end
+
+  def test_nest
+    struct2 = Thrift::Test::Xtruct2.new({'struct_thing' => get_struct, 'i32_thing' => 10})
+
+    ret = @client.testNest(struct2)
+
+    assert_nil(ret.struct_thing.byte_thing, nil)
+    assert_nil(ret.struct_thing.i64_thing, nil)
+    assert_equal(ret.struct_thing.string_thing, 'hi!')
+    assert_equal(ret.struct_thing.i32_thing, 4)
+    assert_equal(ret.i32_thing, 10)
+
+    assert_kind_of(Thrift::Test::Xtruct, ret.struct_thing)
+    assert_kind_of(Thrift::Test::Xtruct2, ret)
+  end
+
+  def test_insane
+    insane = Thrift::Test::Insanity.new({
+      'userMap' => { Thrift::Test::Numberz::ONE => 44 },
+      'xtructs' => [get_struct,
+        Thrift::Test::Xtruct.new({
+          'string_thing' => 'hi again',
+          'i32_thing' => 12
+        })
+      ]
+    })
+
+    ret = @client.testInsanity(insane)
+
+    assert_not_nil(ret[44])
+    assert_not_nil(ret[44][1])
+
+    struct = ret[44][1]
+
+    assert_equal(struct.userMap[Thrift::Test::Numberz::ONE], 44)
+    assert_equal(struct.xtructs[1].string_thing, 'hi again')
+    assert_equal(struct.xtructs[1].i32_thing, 12)
+
+    assert_kind_of(Hash, struct.userMap)
+    assert_kind_of(Array, struct.xtructs)
+    assert_kind_of(Thrift::Test::Insanity, struct)
+  end
+
+  def test_map_map
+    ret = @client.testMapMap(4)
+    assert_kind_of(Hash, ret)
+    assert_equal(ret, { 4 => { 4 => 4}})
+  end
+
+  def test_exception
+    assert_raise Thrift::Test::Xception do
+      @client.testException('foo')
+    end
+  end
+end
+
diff --git a/test/rb/integration/accelerated_buffered_server.rb b/test/rb/integration/accelerated_buffered_server.rb
new file mode 100644
index 0000000..d505e9f
--- /dev/null
+++ b/test/rb/integration/accelerated_buffered_server.rb
@@ -0,0 +1,47 @@
+$:.push File.dirname(__FILE__) + '/../gen-rb'
+$:.push File.join(File.dirname(__FILE__), '../../../lib/rb/lib')
+$:.push File.join(File.dirname(__FILE__), '../../../lib/rb/ext')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocolaccelerated'
+require 'ThriftTest'
+
+class SimpleHandler
+  [:testString, :testByte, :testI32, :testI64, :testDouble,
+   :testStruct, :testMap, :testSet, :testList, :testNest,
+   :testEnum, :testTypedef].each do |meth|
+
+    define_method(meth) do |thing|
+      thing
+    end
+
+  end
+
+  def testInsanity(thing)
+    num, uid = thing.userMap.find { true }
+    return {uid => {num => thing}}
+  end
+
+  def testMapMap(thing)
+    return {thing => {thing => thing}}
+  end
+
+  def testEnum(thing)
+    return thing
+  end
+
+  def testTypedef(thing)
+    return thing
+  end
+
+  def testException(thing)
+    raise Thrift::Test::Xception, :message => 'error'
+  end
+end
+
+@handler   = SimpleHandler.new
+@processor = Thrift::Test::ThriftTest::Processor.new(@handler)
+@transport = Thrift::ServerSocket.new(9090)
+@server    = Thrift::ThreadedServer.new(@processor, @transport, Thrift::BufferedTransportFactory.new, Thrift::BinaryProtocolAcceleratedFactory.new)
+
+@server.serve
diff --git a/test/rb/integration/buffered_client.rb b/test/rb/integration/buffered_client.rb
new file mode 100644
index 0000000..3548b18
--- /dev/null
+++ b/test/rb/integration/buffered_client.rb
@@ -0,0 +1,145 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocol'
+require 'ThriftTest'
+
+class BufferedClientTest < Test::Unit::TestCase
+  def setup
+    unless @socket
+      @socket   = Thrift::Socket.new('localhost', 9090)
+      @protocol = Thrift::BinaryProtocol.new(Thrift::BufferedTransport.new(@socket))
+      @client   = Thrift::Test::ThriftTest::Client.new(@protocol)
+      @socket.open
+    end
+  end
+  
+  def test_string
+    assert_equal(@client.testString('string'), 'string')
+  end
+
+  def test_byte
+    val = 8
+    assert_equal(@client.testByte(val), val)
+    assert_equal(@client.testByte(-val), -val)
+  end
+
+  def test_i32
+    val = 32
+    assert_equal(@client.testI32(val), val)
+    assert_equal(@client.testI32(-val), -val)
+  end
+
+  def test_i64
+    val = 64
+    assert_equal(@client.testI64(val), val)
+    assert_equal(@client.testI64(-val), -val)
+  end
+
+  def test_double
+    val = 3.14
+    assert_equal(@client.testDouble(val), val)
+    assert_equal(@client.testDouble(-val), -val)
+    assert_kind_of(Float, @client.testDouble(val))
+  end
+
+  def test_map
+    val = {1 => 1, 2 => 2, 3 => 3}
+    assert_equal(@client.testMap(val), val)
+    assert_kind_of(Hash, @client.testMap(val))
+  end
+
+  def test_list
+    val = [1,2,3,4,5]
+    assert_equal(@client.testList(val), val)
+    assert_kind_of(Array, @client.testList(val))
+  end
+
+  def test_enum
+    val = Thrift::Test::Numberz::SIX
+    ret = @client.testEnum(val)
+
+    assert_equal(ret, 6)
+    assert_kind_of(Fixnum, ret)
+  end
+
+  def test_typedef
+    #UserId  testTypedef(1: UserId thing),
+    true
+  end
+
+  def test_set
+    val = Set.new([1,2,3])
+    assert_equal(@client.testSet(val), val)
+    assert_kind_of(Set, @client.testSet(val))
+  end
+
+  def get_struct
+    Thrift::Test::Xtruct.new({'string_thing' => 'hi!', 'i32_thing' => 4 })
+  end
+
+  def test_struct
+    ret = @client.testStruct(get_struct)
+
+    assert_nil(ret.byte_thing, nil)
+    assert_nil(ret.i64_thing, nil)
+    assert_equal(ret.string_thing, 'hi!')
+    assert_equal(ret.i32_thing, 4)
+    assert_kind_of(Thrift::Test::Xtruct, ret)
+  end
+
+  def test_nest
+    struct2 = Thrift::Test::Xtruct2.new({'struct_thing' => get_struct, 'i32_thing' => 10})
+
+    ret = @client.testNest(struct2)
+
+    assert_nil(ret.struct_thing.byte_thing, nil)
+    assert_nil(ret.struct_thing.i64_thing, nil)
+    assert_equal(ret.struct_thing.string_thing, 'hi!')
+    assert_equal(ret.struct_thing.i32_thing, 4)
+    assert_equal(ret.i32_thing, 10)
+
+    assert_kind_of(Thrift::Test::Xtruct, ret.struct_thing)
+    assert_kind_of(Thrift::Test::Xtruct2, ret)
+  end
+
+  def test_insane
+    insane = Thrift::Test::Insanity.new({
+      'userMap' => { Thrift::Test::Numberz::ONE => 44 },
+      'xtructs' => [get_struct,
+        Thrift::Test::Xtruct.new({
+          'string_thing' => 'hi again',
+          'i32_thing' => 12
+        })
+      ]
+    })
+
+    ret = @client.testInsanity(insane)
+
+    assert_not_nil(ret[44])
+    assert_not_nil(ret[44][1])
+
+    struct = ret[44][1]
+
+    assert_equal(struct.userMap[Thrift::Test::Numberz::ONE], 44)
+    assert_equal(struct.xtructs[1].string_thing, 'hi again')
+    assert_equal(struct.xtructs[1].i32_thing, 12)
+
+    assert_kind_of(Hash, struct.userMap)
+    assert_kind_of(Array, struct.xtructs)
+    assert_kind_of(Thrift::Test::Insanity, struct)
+  end
+
+  def test_map_map
+    ret = @client.testMapMap(4)
+    assert_kind_of(Hash, ret)
+    assert_equal(ret, { 4 => { 4 => 4}})
+  end
+
+  def test_exception
+    assert_raise Thrift::Test::Xception do
+      @client.testException('foo')
+    end
+  end
+end
+
diff --git a/test/rb/integration/simple_client.rb b/test/rb/integration/simple_client.rb
new file mode 100644
index 0000000..fe2ed82
--- /dev/null
+++ b/test/rb/integration/simple_client.rb
@@ -0,0 +1,145 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocol'
+require 'ThriftTest'
+
+class SimpleClientTest < Test::Unit::TestCase
+  def setup    
+    unless @socket
+      @socket   = Thrift::Socket.new('localhost', 9090)
+      @protocol = Thrift::BinaryProtocol.new(@socket)
+      @client   = Thrift::Test::ThriftTest::Client.new(@protocol)
+      @socket.open
+    end
+  end
+  
+  def test_string
+    assert_equal(@client.testString('string'), 'string')
+  end
+
+  def test_byte
+    val = 8
+    assert_equal(@client.testByte(val), val)
+    assert_equal(@client.testByte(-val), -val)
+  end
+
+  def test_i32
+    val = 32
+    assert_equal(@client.testI32(val), val)
+    assert_equal(@client.testI32(-val), -val)
+  end
+
+  def test_i64
+    val = 64
+    assert_equal(@client.testI64(val), val)
+    assert_equal(@client.testI64(-val), -val)
+  end
+
+  def test_double
+    val = 3.14
+    assert_equal(@client.testDouble(val), val)
+    assert_equal(@client.testDouble(-val), -val)
+    assert_kind_of(Float, @client.testDouble(val))
+  end
+
+  def test_map
+    val = {1 => 1, 2 => 2, 3 => 3}
+    assert_equal(@client.testMap(val), val)
+    assert_kind_of(Hash, @client.testMap(val))
+  end
+
+  def test_list
+    val = [1,2,3,4,5]
+    assert_equal(@client.testList(val), val)
+    assert_kind_of(Array, @client.testList(val))
+  end
+
+  def test_enum
+    val = Thrift::Test::Numberz::SIX
+    ret = @client.testEnum(val)
+
+    assert_equal(ret, 6)
+    assert_kind_of(Fixnum, ret)
+  end
+
+  def test_typedef
+    #UserId  testTypedef(1: UserId thing),
+    true
+  end
+
+  def test_set
+    val = Set.new([1,2,3])
+    assert_equal(@client.testSet(val), val)
+    assert_kind_of(Set, @client.testSet(val))
+  end
+
+  def get_struct
+    Thrift::Test::Xtruct.new({'string_thing' => 'hi!', 'i32_thing' => 4 })
+  end
+
+  def test_struct
+    ret = @client.testStruct(get_struct)
+
+    assert_nil(ret.byte_thing, nil)
+    assert_nil(ret.i64_thing, nil)
+    assert_equal(ret.string_thing, 'hi!')
+    assert_equal(ret.i32_thing, 4)
+    assert_kind_of(Thrift::Test::Xtruct, ret)
+  end
+
+  def test_nest
+    struct2 = Thrift::Test::Xtruct2.new({'struct_thing' => get_struct, 'i32_thing' => 10})
+
+    ret = @client.testNest(struct2)
+
+    assert_nil(ret.struct_thing.byte_thing, nil)
+    assert_nil(ret.struct_thing.i64_thing, nil)
+    assert_equal(ret.struct_thing.string_thing, 'hi!')
+    assert_equal(ret.struct_thing.i32_thing, 4)
+    assert_equal(ret.i32_thing, 10)
+
+    assert_kind_of(Thrift::Test::Xtruct, ret.struct_thing)
+    assert_kind_of(Thrift::Test::Xtruct2, ret)
+  end
+
+  def test_insane
+    insane = Thrift::Test::Insanity.new({
+      'userMap' => { Thrift::Test::Numberz::ONE => 44 },
+      'xtructs' => [get_struct,
+        Thrift::Test::Xtruct.new({
+          'string_thing' => 'hi again',
+          'i32_thing' => 12
+        })
+      ]
+    })
+
+    ret = @client.testInsanity(insane)
+
+    assert_not_nil(ret[44])
+    assert_not_nil(ret[44][1])
+
+    struct = ret[44][1]
+
+    assert_equal(struct.userMap[Thrift::Test::Numberz::ONE], 44)
+    assert_equal(struct.xtructs[1].string_thing, 'hi again')
+    assert_equal(struct.xtructs[1].i32_thing, 12)
+
+    assert_kind_of(Hash, struct.userMap)
+    assert_kind_of(Array, struct.xtructs)
+    assert_kind_of(Thrift::Test::Insanity, struct)
+  end
+
+  def test_map_map
+    ret = @client.testMapMap(4)
+    assert_kind_of(Hash, ret)
+    assert_equal(ret, { 4 => { 4 => 4}})
+  end
+
+  def test_exception
+    assert_raise Thrift::Test::Xception do
+      @client.testException('foo')
+    end
+  end
+end
+
diff --git a/test/rb/integration/simple_server.rb b/test/rb/integration/simple_server.rb
new file mode 100644
index 0000000..6b029ba
--- /dev/null
+++ b/test/rb/integration/simple_server.rb
@@ -0,0 +1,46 @@
+$:.push File.dirname(__FILE__) + '/../gen-rb'
+$:.push File.join(File.dirname(__FILE__), '../../../lib/rb/lib')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocol'
+require 'ThriftTest'
+
+class SimpleHandler
+  [:testString, :testByte, :testI32, :testI64, :testDouble,
+   :testStruct, :testMap, :testSet, :testList, :testNest,
+   :testEnum, :testTypedef].each do |meth|
+
+    define_method(meth) do |thing|
+      thing
+    end
+
+  end
+
+  def testInsanity(thing)
+    num, uid = thing.userMap.find { true }
+    return {uid => {num => thing}}
+  end
+
+  def testMapMap(thing)
+    return {thing => {thing => thing}}
+  end
+
+  def testEnum(thing)
+    return thing
+  end
+
+  def testTypedef(thing)
+    return thing
+  end
+
+  def testException(thing)
+    raise Thrift::Test::Xception, :message => 'error'
+  end
+end
+
+@handler   = SimpleHandler.new
+@processor = Thrift::Test::ThriftTest::Processor.new(@handler)
+@transport = Thrift::ServerSocket.new(9090)
+@server    = Thrift::ThreadedServer.new(@processor, @transport)
+
+@server.serve
diff --git a/test/rb/test_helper.rb b/test/rb/test_helper.rb
index 125f52f..51f72b1 100644
--- a/test/rb/test_helper.rb
+++ b/test/rb/test_helper.rb
@@ -1,4 +1,16 @@
 $:.unshift File.dirname(__FILE__) + '/gen-rb'
 $:.unshift File.join(File.dirname(__FILE__), '../../lib/rb/lib')
+$:.unshift File.join(File.dirname(__FILE__), '../../lib/rb/ext')
 
 require 'test/unit'
+
+module Thrift
+  module Struct
+    def ==(other)
+      return false unless other.is_a? self.class
+      self.class.const_get(:FIELDS).collect {|fid, data| data[:name] }.all? do |field|
+        send(field) == other.send(field)
+      end
+    end
+  end
+end
diff --git a/test/rb/test_suite.rb b/test/rb/test_suite.rb
index af686a8..3611963 100644
--- a/test/rb/test_suite.rb
+++ b/test/rb/test_suite.rb
@@ -1 +1 @@
-Dir["{core,generation,integration}/**/*.rb"].each {|f| require f }
\ No newline at end of file
+Dir["{core,generation}/**/*.rb"].each {|f| require f }
\ No newline at end of file