Thrift now a TLP - INFRA-3116

git-svn-id: https://svn.apache.org/repos/asf/thrift/branches/0.1.x@1028168 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/rb/CHANGELOG b/lib/rb/CHANGELOG
new file mode 100644
index 0000000..b5dce2a
--- /dev/null
+++ b/lib/rb/CHANGELOG
@@ -0,0 +1 @@
+v0.0.1. Initial release
diff --git a/lib/rb/Makefile.am b/lib/rb/Makefile.am
new file mode 100644
index 0000000..9cfffc7
--- /dev/null
+++ b/lib/rb/Makefile.am
@@ -0,0 +1,47 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+EXTRA_DIST = \
+	CHANGELOG \
+	Rakefile \
+	Manifest \
+	setup.rb \
+	lib \
+	ext \
+	benchmark \
+	script \
+	spec
+
+all-local:
+	$(RUBY) setup.rb config
+	$(RUBY) setup.rb setup
+
+install-exec-hook:
+	$(RUBY) setup.rb install
+
+# Make sure this doesn't fail if Ruby is not configured.
+clean-local:
+	RUBY=$(RUBY) ; if test -z "$$RUBY" ; then RUBY=: ; fi ; \
+	$$RUBY setup.rb clean
+
+check-local: all
+if HAVE_RSPEC
+	rake spec
+endif
+
diff --git a/lib/rb/Manifest b/lib/rb/Manifest
new file mode 100644
index 0000000..7b4503f
--- /dev/null
+++ b/lib/rb/Manifest
@@ -0,0 +1,81 @@
+CHANGELOG
+Manifest
+Rakefile
+README
+setup.rb
+benchmark/benchmark.rb
+benchmark/Benchmark.thrift
+benchmark/client.rb
+benchmark/server.rb
+benchmark/thin_server.rb
+ext/binary_protocol_accelerated.c
+ext/binary_protocol_accelerated.h
+ext/compact_protocol.c
+ext/compact_protocol.h
+ext/constants.h
+ext/extconf.rb
+ext/macros.h
+ext/memory_buffer.c
+ext/memory_buffer.h
+ext/protocol.c
+ext/protocol.h
+ext/struct.c
+ext/struct.h
+ext/thrift_native.c
+lib/thrift.rb
+lib/thrift/client.rb
+lib/thrift/core_ext.rb
+lib/thrift/exceptions.rb
+lib/thrift/processor.rb
+lib/thrift/struct.rb
+lib/thrift/thrift_native.rb
+lib/thrift/types.rb
+lib/thrift/core_ext/fixnum.rb
+lib/thrift/protocol/base_protocol.rb
+lib/thrift/protocol/binary_protocol.rb
+lib/thrift/protocol/binary_protocol_accelerated.rb
+lib/thrift/protocol/compact_protocol.rb
+lib/thrift/serializer/deserializer.rb
+lib/thrift/serializer/serializer.rb
+lib/thrift/server/base_server.rb
+lib/thrift/server/mongrel_http_server.rb
+lib/thrift/server/nonblocking_server.rb
+lib/thrift/server/simple_server.rb
+lib/thrift/server/thread_pool_server.rb
+lib/thrift/server/threaded_server.rb
+lib/thrift/transport/base_server_transport.rb
+lib/thrift/transport/base_transport.rb
+lib/thrift/transport/buffered_transport.rb
+lib/thrift/transport/framed_transport.rb
+lib/thrift/transport/http_client_transport.rb
+lib/thrift/transport/io_stream_transport.rb
+lib/thrift/transport/memory_buffer_transport.rb
+lib/thrift/transport/server_socket.rb
+lib/thrift/transport/socket.rb
+lib/thrift/transport/unix_server_socket.rb
+lib/thrift/transport/unix_socket.rb
+script/proto_benchmark.rb
+script/read_struct.rb
+script/write_struct.rb
+spec/base_protocol_spec.rb
+spec/base_transport_spec.rb
+spec/binary_protocol_accelerated_spec.rb
+spec/binary_protocol_spec.rb
+spec/binary_protocol_spec_shared.rb
+spec/client_spec.rb
+spec/compact_protocol_spec.rb
+spec/exception_spec.rb
+spec/http_client_spec.rb
+spec/mongrel_http_server_spec.rb
+spec/nonblocking_server_spec.rb
+spec/processor_spec.rb
+spec/serializer_spec.rb
+spec/server_socket_spec.rb
+spec/server_spec.rb
+spec/socket_spec.rb
+spec/socket_spec_shared.rb
+spec/spec_helper.rb
+spec/struct_spec.rb
+spec/ThriftSpec.thrift
+spec/types_spec.rb
+spec/unix_socket_spec.rb
diff --git a/lib/rb/README b/lib/rb/README
new file mode 100644
index 0000000..d78e352
--- /dev/null
+++ b/lib/rb/README
@@ -0,0 +1,43 @@
+Thrift Ruby Software Library
+    http://incubator.apache.org/thrift/
+
+== LICENSE:
+
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+
+== DESCRIPTION:
+
+Thrift is a strongly-typed language-agnostic RPC system.
+This library is the ruby implementation for both clients and servers.
+
+== INSTALL:
+
+  $ gem install thrift
+
+== CAVEATS:
+
+This library provides the client and server implementations of thrift.
+It does <em>not</em> provide the compiler for the .thrift files. To compile
+.thrift files into language-specific implementations, please download the full
+thrift software package.
+
+== USAGE:
+
+This section should get written by someone with the time and inclination.
+In the meantime, look at existing code, such as the benchmark or the tutorial
+in the full thrift distribution.
diff --git a/lib/rb/Rakefile b/lib/rb/Rakefile
new file mode 100644
index 0000000..1a9467a
--- /dev/null
+++ b/lib/rb/Rakefile
@@ -0,0 +1,103 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require 'rubygems'
+require 'rake'
+require 'spec/rake/spectask'
+
+THRIFT = '../../compiler/cpp/thrift'
+
+task :default => [:spec]
+
+task :spec => [:'gen-rb', :realspec]
+
+Spec::Rake::SpecTask.new(:realspec) do |t|
+  t.spec_files = FileList['spec/**/*_spec.rb']
+  t.spec_opts = ['--color']
+end
+
+Spec::Rake::SpecTask.new(:'spec:rcov') do |t|
+  t.spec_files = FileList['spec/**/*_spec.rb']
+  t.spec_opts = ['--color']
+  t.rcov = true
+  t.rcov_opts = ['--exclude', '^spec,/gems/']
+end
+
+desc 'Run the compiler tests (requires full thrift checkout)'
+task :test do
+  # 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", "check"
+end
+
+desc 'Compile the .thrift files for the specs'
+task :'gen-rb' => [:'gen-rb:spec', :'gen-rb:benchmark', :'gen-rb:debug_proto']
+
+namespace :'gen-rb' do
+  task :'spec' do
+    dir = File.dirname(__FILE__) + '/spec'
+    sh THRIFT, '--gen', 'rb', '-o', dir, "#{dir}/ThriftSpec.thrift"
+  end
+
+  task :'benchmark' do
+    dir = File.dirname(__FILE__) + '/benchmark'
+    sh THRIFT, '--gen', 'rb', '-o', dir, "#{dir}/Benchmark.thrift"
+  end
+  
+  task :'debug_proto' do
+    sh "mkdir", "-p", "debug_proto_test"
+    sh THRIFT, '--gen', 'rb', "-o", "debug_proto_test", "../../test/DebugProtoTest.thrift"
+  end
+end
+
+desc 'Run benchmarking of NonblockingServer'
+task :benchmark do
+  ruby 'benchmark/benchmark.rb'
+end
+
+
+begin
+  require 'echoe'
+
+  Echoe.new('thrift') do |p|
+    p.author = ['Kevin Ballard', 'Kevin Clark', 'Mark Slee']
+    p.email = ['kevin@sb.org', 'kevin.clark@gmail.com', 'mcslee@facebook.com']
+    p.summary = "Ruby libraries for Thrift (a language-agnostic RPC system)"
+    p.url = "http://incubator.apache.org/thrift/"
+    p.include_rakefile = true
+    p.version = "0.1.0"
+  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}"
+    task t do
+      fail "The Echoe gem is required for this task"
+    end
+  end
+end
diff --git a/lib/rb/benchmark/Benchmark.thrift b/lib/rb/benchmark/Benchmark.thrift
new file mode 100644
index 0000000..eb5ae38
--- /dev/null
+++ b/lib/rb/benchmark/Benchmark.thrift
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+namespace rb ThriftBenchmark
+
+service BenchmarkService {
+  i32 fibonacci(1:byte n)
+}
diff --git a/lib/rb/benchmark/benchmark.rb b/lib/rb/benchmark/benchmark.rb
new file mode 100644
index 0000000..3dc67dd
--- /dev/null
+++ b/lib/rb/benchmark/benchmark.rb
@@ -0,0 +1,271 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require 'rubygems'
+$:.unshift File.dirname(__FILE__) + '/../lib'
+require 'thrift'
+require 'stringio'
+
+HOST = '127.0.0.1'
+PORT = 42587
+
+###############
+## Server
+###############
+
+class Server
+  attr_accessor :serverclass
+  attr_accessor :interpreter
+  attr_accessor :host
+  attr_accessor :port
+
+  def initialize(opts)
+    @serverclass = opts.fetch(:class, Thrift::NonblockingServer)
+    @interpreter = opts.fetch(:interpreter, "ruby")
+    @host = opts.fetch(:host, ::HOST)
+    @port = opts.fetch(:port, ::PORT)
+  end
+
+  def start
+    return if @serverclass == Object
+    args = (File.basename(@interpreter) == "jruby" ? "-J-server" : "")
+    @pipe = IO.popen("#{@interpreter} #{args} #{File.dirname(__FILE__)}/server.rb #{@host} #{@port} #{@serverclass.name}", "r+")
+    Marshal.load(@pipe) # wait until the server has started
+    sleep 0.4 # give the server time to actually start spawning sockets
+  end
+
+  def shutdown
+    return unless @pipe
+    Marshal.dump(:shutdown, @pipe)
+    begin
+      @pipe.read(10) # block until the server shuts down
+    rescue EOFError
+    end
+    @pipe.close
+    @pipe = nil
+  end
+end
+
+class BenchmarkManager
+  def initialize(opts, server)
+    @socket = opts.fetch(:socket) do
+      @host = opts.fetch(:host, 'localhost')
+      @port = opts.fetch(:port)
+      nil
+    end
+    @num_processes = opts.fetch(:num_processes, 40)
+    @clients_per_process = opts.fetch(:clients_per_process, 10)
+    @calls_per_client = opts.fetch(:calls_per_client, 50)
+    @interpreter = opts.fetch(:interpreter, "ruby")
+    @server = server
+    @log_exceptions = opts.fetch(:log_exceptions, false)
+  end
+
+  def run
+    @pool = []
+    @benchmark_start = Time.now
+    puts "Spawning benchmark processes..."
+    @num_processes.times do
+      spawn
+      sleep 0.02 # space out spawns
+    end
+    collect_output
+    @benchmark_end = Time.now # we know the procs are done here
+    translate_output
+    analyze_output
+    report_output
+  end
+
+  def spawn
+    pipe = IO.popen("#{@interpreter} #{File.dirname(__FILE__)}/client.rb #{"-log-exceptions" if @log_exceptions} #{@host} #{@port} #{@clients_per_process} #{@calls_per_client}")
+    @pool << pipe
+  end
+
+  def socket_class
+    if @socket
+      Thrift::UNIXSocket
+    else
+      Thrift::Socket
+    end
+  end
+
+  def collect_output
+    puts "Collecting output..."
+    # read from @pool until all sockets are closed
+    @buffers = Hash.new { |h,k| h[k] = '' }
+    until @pool.empty?
+      rd, = select(@pool)
+      next if rd.nil?
+      rd.each do |fd|
+        begin
+          @buffers[fd] << fd.readpartial(4096)
+        rescue EOFError
+          @pool.delete fd
+        end
+      end
+    end
+  end
+
+  def translate_output
+    puts "Translating output..."
+    @output = []
+    @buffers.each do |fd, buffer|
+      strio = StringIO.new(buffer)
+      logs = []
+      begin
+        loop do
+          logs << Marshal.load(strio)
+        end
+      rescue EOFError
+        @output << logs
+      end
+    end
+  end
+
+  def analyze_output
+    puts "Analyzing output..."
+    call_times = []
+    client_times = []
+    connection_failures = []
+    connection_errors = []
+    shortest_call = 0
+    shortest_client = 0
+    longest_call = 0
+    longest_client = 0
+    @output.each do |logs|
+      cur_call, cur_client = nil
+      logs.each do |tok, time|
+        case tok
+        when :start
+          cur_client = time
+        when :call_start
+          cur_call = time
+        when :call_end
+          delta = time - cur_call
+          call_times << delta
+          longest_call = delta unless longest_call > delta
+          shortest_call = delta if shortest_call == 0 or delta < shortest_call
+          cur_call = nil
+        when :end
+          delta = time - cur_client
+          client_times << delta
+          longest_client = delta unless longest_client > delta
+          shortest_client = delta if shortest_client == 0 or delta < shortest_client
+          cur_client = nil
+        when :connection_failure
+          connection_failures << time
+        when :connection_error
+          connection_errors << time
+        end
+      end
+    end
+    @report = {}
+    @report[:total_calls] = call_times.inject(0.0) { |a,t| a += t }
+    @report[:avg_calls] = @report[:total_calls] / call_times.size
+    @report[:total_clients] = client_times.inject(0.0) { |a,t| a += t }
+    @report[:avg_clients] = @report[:total_clients] / client_times.size
+    @report[:connection_failures] = connection_failures.size
+    @report[:connection_errors] = connection_errors.size
+    @report[:shortest_call] = shortest_call
+    @report[:shortest_client] = shortest_client
+    @report[:longest_call] = longest_call
+    @report[:longest_client] = longest_client
+    @report[:total_benchmark_time] = @benchmark_end - @benchmark_start
+    @report[:fastthread] = $".include?('fastthread.bundle')
+  end
+
+  def report_output
+    fmt = "%.4f seconds"
+    puts
+    tabulate "%d",
+             [["Server class", "%s"], @server.serverclass == Object ? "" : @server.serverclass],
+             [["Server interpreter", "%s"], @server.interpreter],
+             [["Client interpreter", "%s"], @interpreter],
+             [["Socket class", "%s"], socket_class],
+             ["Number of processes", @num_processes],
+             ["Clients per process", @clients_per_process],
+             ["Calls per client", @calls_per_client],
+             [["Using fastthread", "%s"], @report[:fastthread] ? "yes" : "no"]
+    puts
+    failures = (@report[:connection_failures] > 0)
+    tabulate fmt,
+             [["Connection failures", "%d", [:red, :bold]], @report[:connection_failures]],
+             [["Connection errors", "%d", [:red, :bold]], @report[:connection_errors]],
+             ["Average time per call", @report[:avg_calls]],
+             ["Average time per client (%d calls)" % @calls_per_client, @report[:avg_clients]],
+             ["Total time for all calls", @report[:total_calls]],
+             ["Real time for benchmarking", @report[:total_benchmark_time]],
+             ["Shortest call time", @report[:shortest_call]],
+             ["Longest call time", @report[:longest_call]],
+             ["Shortest client time (%d calls)" % @calls_per_client, @report[:shortest_client]],
+             ["Longest client time (%d calls)" % @calls_per_client, @report[:longest_client]]
+  end
+
+  ANSI = {
+    :reset => 0,
+    :bold => 1,
+    :black => 30,
+    :red => 31,
+    :green => 32,
+    :yellow => 33,
+    :blue => 34,
+    :magenta => 35,
+    :cyan => 36,
+    :white => 37
+  }
+
+  def tabulate(fmt, *labels_and_values)
+    labels = labels_and_values.map { |l| Array === l ? l.first : l }
+    label_width = labels.inject(0) { |w,l| l.size > w ? l.size : w }
+    labels_and_values.each do |(l,v)|
+      f = fmt
+      l, f, c = l if Array === l
+      fmtstr = "%-#{label_width+1}s #{f}"
+      if STDOUT.tty? and c and v.to_i > 0
+        fmtstr = "\e[#{[*c].map { |x| ANSI[x] } * ";"}m" + fmtstr + "\e[#{ANSI[:reset]}m"
+      end
+      puts fmtstr % [l+":", v]
+    end
+  end
+end
+
+def resolve_const(const)
+  const and const.split('::').inject(Object) { |k,c| k.const_get(c) }
+end
+
+puts "Starting server..."
+args = {}
+args[:interpreter] = ENV['THRIFT_SERVER_INTERPRETER'] || ENV['THRIFT_INTERPRETER'] || "ruby"
+args[:class] = resolve_const(ENV['THRIFT_SERVER']) || Thrift::NonblockingServer
+args[:host] = ENV['THRIFT_HOST'] || HOST
+args[:port] = (ENV['THRIFT_PORT'] || PORT).to_i
+server = Server.new(args)
+server.start
+
+args = {}
+args[:host] = ENV['THRIFT_HOST'] || HOST
+args[:port] = (ENV['THRIFT_PORT'] || PORT).to_i
+args[:num_processes] = (ENV['THRIFT_NUM_PROCESSES'] || 40).to_i
+args[:clients_per_process] = (ENV['THRIFT_NUM_CLIENTS'] || 5).to_i
+args[:calls_per_client] = (ENV['THRIFT_NUM_CALLS'] || 50).to_i
+args[:interpreter] = ENV['THRIFT_CLIENT_INTERPRETER'] || ENV['THRIFT_INTERPRETER'] || "ruby"
+args[:log_exceptions] = !!ENV['THRIFT_LOG_EXCEPTIONS']
+BenchmarkManager.new(args, server).run
+
+server.shutdown
diff --git a/lib/rb/benchmark/client.rb b/lib/rb/benchmark/client.rb
new file mode 100644
index 0000000..703dc8f
--- /dev/null
+++ b/lib/rb/benchmark/client.rb
@@ -0,0 +1,74 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+$:.unshift File.dirname(__FILE__) + '/../lib'
+require 'thrift'
+$:.unshift File.dirname(__FILE__) + "/gen-rb"
+require 'benchmark_service'
+
+class Client
+  def initialize(host, port, clients_per_process, calls_per_client, log_exceptions)
+    @host = host
+    @port = port
+    @clients_per_process = clients_per_process
+    @calls_per_client = calls_per_client
+    @log_exceptions = log_exceptions
+  end
+
+  def run
+    @clients_per_process.times do
+      socket = Thrift::Socket.new(@host, @port)
+      transport = Thrift::FramedTransport.new(socket)
+      protocol = Thrift::BinaryProtocol.new(transport)
+      client = ThriftBenchmark::BenchmarkService::Client.new(protocol)
+      begin
+        start = Time.now
+        transport.open
+        Marshal.dump [:start, start], STDOUT
+      rescue => e
+        Marshal.dump [:connection_failure, Time.now], STDOUT
+        print_exception e if @log_exceptions
+      else
+        begin
+          @calls_per_client.times do
+            Marshal.dump [:call_start, Time.now], STDOUT
+            client.fibonacci(15)
+            Marshal.dump [:call_end, Time.now], STDOUT
+          end
+          transport.close
+          Marshal.dump [:end, Time.now], STDOUT
+        rescue Thrift::TransportException => e
+          Marshal.dump [:connection_error, Time.now], STDOUT
+          print_exception e if @log_exceptions
+        end
+      end
+    end
+  end
+
+  def print_exception(e)
+    STDERR.puts "ERROR: #{e.message}"
+    STDERR.puts "\t#{e.backtrace * "\n\t"}"
+  end
+end
+
+log_exceptions = true if ARGV[0] == '-log-exceptions' and ARGV.shift
+
+host, port, clients_per_process, calls_per_client = ARGV
+
+Client.new(host, port.to_i, clients_per_process.to_i, calls_per_client.to_i, log_exceptions).run
diff --git a/lib/rb/benchmark/server.rb b/lib/rb/benchmark/server.rb
new file mode 100644
index 0000000..74e13f4
--- /dev/null
+++ b/lib/rb/benchmark/server.rb
@@ -0,0 +1,82 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+$:.unshift File.dirname(__FILE__) + '/../lib'
+require 'thrift'
+$:.unshift File.dirname(__FILE__) + "/gen-rb"
+require 'benchmark_service'
+
+module Server
+  include Thrift
+
+  class BenchmarkHandler
+    # 1-based index into the fibonacci sequence
+    def fibonacci(n)
+      seq = [1, 1]
+      3.upto(n) do
+        seq << seq[-1] + seq[-2]
+      end
+      seq[n-1] # n is 1-based
+    end
+  end
+
+  def self.start_server(host, port, serverClass)
+    handler = BenchmarkHandler.new
+    processor = ThriftBenchmark::BenchmarkService::Processor.new(handler)
+    transport = ServerSocket.new(host, port)
+    transport_factory = FramedTransportFactory.new
+    args = [processor, transport, transport_factory, nil, 20]
+    if serverClass == NonblockingServer
+      logger = Logger.new(STDERR)
+      logger.level = Logger::WARN
+      args << logger
+    end
+    server = serverClass.new(*args)
+    @server_thread = Thread.new do
+      server.serve
+    end
+    @server = server
+  end
+
+  def self.shutdown
+    return if @server.nil?
+    if @server.respond_to? :shutdown
+      @server.shutdown
+    else
+      @server_thread.kill
+    end
+  end
+end
+
+def resolve_const(const)
+  const and const.split('::').inject(Object) { |k,c| k.const_get(c) }
+end
+
+host, port, serverklass = ARGV
+
+Server.start_server(host, port.to_i, resolve_const(serverklass))
+
+# let our host know that the interpreter has started
+# ideally we'd wait until the server was serving, but we don't have a hook for that
+Marshal.dump(:started, STDOUT)
+STDOUT.flush
+
+Marshal.load(STDIN) # wait until we're instructed to shut down
+
+Server.shutdown
diff --git a/lib/rb/benchmark/thin_server.rb b/lib/rb/benchmark/thin_server.rb
new file mode 100644
index 0000000..4de2eef
--- /dev/null
+++ b/lib/rb/benchmark/thin_server.rb
@@ -0,0 +1,44 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+$:.unshift File.dirname(__FILE__) + '/../lib'
+require 'thrift'
+$:.unshift File.dirname(__FILE__) + "/gen-rb"
+require 'benchmark_service'
+HOST = 'localhost'
+PORT = 42587
+
+class BenchmarkHandler
+  # 1-based index into the fibonacci sequence
+  def fibonacci(n)
+    seq = [1, 1]
+    3.upto(n) do
+      seq << seq[-1] + seq[-2]
+    end
+    seq[n-1] # n is 1-based
+  end
+end
+
+handler = BenchmarkHandler.new
+processor = ThriftBenchmark::BenchmarkService::Processor.new(handler)
+transport = Thrift::ServerSocket.new(HOST, PORT)
+transport_factory = Thrift::FramedTransportFactory.new
+logger = Logger.new(STDERR)
+logger.level = Logger::WARN
+Thrift::NonblockingServer.new(processor, transport, transport_factory, nil, 20, logger).serve
diff --git a/lib/rb/ext/binary_protocol_accelerated.c b/lib/rb/ext/binary_protocol_accelerated.c
new file mode 100644
index 0000000..728a057
--- /dev/null
+++ b/lib/rb/ext/binary_protocol_accelerated.c
@@ -0,0 +1,474 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <ruby.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <constants.h>
+#include <struct.h>
+#include "macros.h"
+
+VALUE rb_thrift_binary_proto_native_qmark(VALUE self) {
+  return Qtrue;
+}
+
+
+
+static int VERSION_1;
+static int VERSION_MASK;
+static int TYPE_MASK;
+static int BAD_VERSION;
+
+static void write_byte_direct(VALUE trans, int8_t b) {
+  WRITE(trans, (char*)&b, 1);
+}
+
+static void write_i16_direct(VALUE trans, int16_t value) {
+  char data[2];
+  
+  data[1] = value;
+  data[0] = (value >> 8);
+
+  WRITE(trans, data, 2);
+}
+
+static void write_i32_direct(VALUE trans, int32_t value) {
+  char data[4];
+
+  data[3] = value;
+  data[2] = (value >> 8);
+  data[1] = (value >> 16);
+  data[0] = (value >> 24);
+
+  WRITE(trans, data, 4);
+}
+
+
+static void write_i64_direct(VALUE trans, int64_t value) {
+  char data[8];
+
+  data[7] = value;
+  data[6] = (value >> 8);
+  data[5] = (value >> 16);
+  data[4] = (value >> 24);
+  data[3] = (value >> 32);
+  data[2] = (value >> 40);
+  data[1] = (value >> 48);
+  data[0] = (value >> 56);
+
+  WRITE(trans, data, 8);
+}
+
+static void write_string_direct(VALUE trans, VALUE str) {
+  write_i32_direct(trans, RSTRING_LEN(str));
+  rb_funcall(trans, write_method_id, 1, str);
+}
+
+//--------------------------------
+// interface writing methods
+//--------------------------------
+
+VALUE rb_thrift_binary_proto_write_message_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_struct_begin(VALUE self, VALUE name) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_struct_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_field_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_map_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_list_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_set_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_message_begin(VALUE self, VALUE name, VALUE type, VALUE seqid) {
+  VALUE trans = GET_TRANSPORT(self);
+  VALUE strict_write = GET_STRICT_WRITE(self);
+
+  if (strict_write == Qtrue) {
+    write_i32_direct(trans, VERSION_1 | FIX2INT(type));
+    write_string_direct(trans, name);
+    write_i32_direct(trans, FIX2INT(seqid));
+  } else {
+    write_string_direct(trans, name);
+    write_byte_direct(trans, FIX2INT(type));
+    write_i32_direct(trans, FIX2INT(seqid));
+  }
+  
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_field_begin(VALUE self, VALUE name, VALUE type, VALUE id) {
+  VALUE trans = GET_TRANSPORT(self);
+  write_byte_direct(trans, FIX2INT(type));
+  write_i16_direct(trans, FIX2INT(id));
+  
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_field_stop(VALUE self) {
+  write_byte_direct(GET_TRANSPORT(self), TTYPE_STOP);
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_map_begin(VALUE self, VALUE ktype, VALUE vtype, VALUE size) {
+  VALUE trans = GET_TRANSPORT(self);
+  write_byte_direct(trans, FIX2INT(ktype));
+  write_byte_direct(trans, FIX2INT(vtype));
+  write_i32_direct(trans, FIX2INT(size));
+  
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_list_begin(VALUE self, VALUE etype, VALUE size) {
+  VALUE trans = GET_TRANSPORT(self);
+  write_byte_direct(trans, FIX2INT(etype));
+  write_i32_direct(trans, FIX2INT(size));
+  
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_set_begin(VALUE self, VALUE etype, VALUE size) {
+  rb_thrift_binary_proto_write_list_begin(self, etype, size);
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_bool(VALUE self, VALUE b) {
+  write_byte_direct(GET_TRANSPORT(self), RTEST(b) ? 1 : 0);
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_byte(VALUE self, VALUE byte) {
+  CHECK_NIL(byte);
+  write_byte_direct(GET_TRANSPORT(self), NUM2INT(byte));
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_i16(VALUE self, VALUE i16) {
+  CHECK_NIL(i16);
+  write_i16_direct(GET_TRANSPORT(self), FIX2INT(i16));
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_i32(VALUE self, VALUE i32) {
+  CHECK_NIL(i32);
+  write_i32_direct(GET_TRANSPORT(self), NUM2INT(i32));
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_i64(VALUE self, VALUE i64) {
+  CHECK_NIL(i64);
+  write_i64_direct(GET_TRANSPORT(self), NUM2LL(i64));
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_double(VALUE self, VALUE dub) {
+  CHECK_NIL(dub);
+  // Unfortunately, bitwise_cast doesn't work in C.  Bad C!
+  union {
+    double f;
+    int64_t t;
+  } transfer;
+  transfer.f = RFLOAT_VALUE(rb_Float(dub));
+  write_i64_direct(GET_TRANSPORT(self), transfer.t);
+
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_write_string(VALUE self, VALUE str) {
+  CHECK_NIL(str);
+  VALUE trans = GET_TRANSPORT(self);
+  write_string_direct(trans, str);
+  return Qnil;
+}
+
+//---------------------------------------
+// interface reading methods
+//---------------------------------------
+
+VALUE rb_thrift_binary_proto_read_string(VALUE self);
+VALUE rb_thrift_binary_proto_read_byte(VALUE self);
+VALUE rb_thrift_binary_proto_read_i32(VALUE self);
+VALUE rb_thrift_binary_proto_read_i16(VALUE self);
+
+static char read_byte_direct(VALUE self) {
+  VALUE buf = READ(self, 1);
+  return RSTRING_PTR(buf)[0];
+}
+
+static int16_t read_i16_direct(VALUE self) {
+  VALUE buf = READ(self, 2);
+  return (int16_t)(((uint8_t)(RSTRING_PTR(buf)[1])) | ((uint16_t)((RSTRING_PTR(buf)[0]) << 8)));
+}
+
+static int32_t read_i32_direct(VALUE self) {
+  VALUE buf = READ(self, 4);
+  return ((uint8_t)(RSTRING_PTR(buf)[3])) | 
+    (((uint8_t)(RSTRING_PTR(buf)[2])) << 8) | 
+    (((uint8_t)(RSTRING_PTR(buf)[1])) << 16) | 
+    (((uint8_t)(RSTRING_PTR(buf)[0])) << 24);
+}
+
+static int64_t read_i64_direct(VALUE self) {
+  uint64_t hi = read_i32_direct(self);
+  uint32_t lo = read_i32_direct(self);
+  return (hi << 32) | lo;
+}
+
+static VALUE get_protocol_exception(VALUE code, VALUE message) {
+  VALUE args[2];
+  args[0] = code;
+  args[1] = message;
+  return rb_class_new_instance(2, (VALUE*)&args, protocol_exception_class);
+}
+
+VALUE rb_thrift_binary_proto_read_message_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_struct_begin(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_struct_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_field_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_map_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_list_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_binary_proto_read_set_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_binary_proto_read_message_begin(VALUE self) {
+  VALUE strict_read = GET_STRICT_READ(self);
+  VALUE name, seqid;
+  int type;
+  
+  int version = read_i32_direct(self);
+  
+  if (version < 0) {
+    if ((version & VERSION_MASK) != VERSION_1) {
+      rb_exc_raise(get_protocol_exception(INT2FIX(BAD_VERSION), rb_str_new2("Missing version identifier")));
+    }
+    type = version & TYPE_MASK;
+    name = rb_thrift_binary_proto_read_string(self);
+    seqid = rb_thrift_binary_proto_read_i32(self);
+  } else {
+    if (strict_read == Qtrue) {
+      rb_exc_raise(get_protocol_exception(INT2FIX(BAD_VERSION), rb_str_new2("No version identifier, old protocol client?")));
+    }
+    name = READ(self, version);
+    type = read_byte_direct(self);
+    seqid = rb_thrift_binary_proto_read_i32(self);
+  }
+  
+  return rb_ary_new3(3, name, INT2FIX(type), seqid);
+}
+
+VALUE rb_thrift_binary_proto_read_field_begin(VALUE self) {
+  int type = read_byte_direct(self);
+  if (type == TTYPE_STOP) {
+    return rb_ary_new3(3, Qnil, INT2FIX(type), INT2FIX(0));
+  } else {
+    VALUE id = rb_thrift_binary_proto_read_i16(self);
+    return rb_ary_new3(3, Qnil, INT2FIX(type), id);
+  }
+}
+
+VALUE rb_thrift_binary_proto_read_map_begin(VALUE self) {
+  VALUE ktype = rb_thrift_binary_proto_read_byte(self);
+  VALUE vtype = rb_thrift_binary_proto_read_byte(self);
+  VALUE size = rb_thrift_binary_proto_read_i32(self);
+  return rb_ary_new3(3, ktype, vtype, size);
+}
+
+VALUE rb_thrift_binary_proto_read_list_begin(VALUE self) {
+  VALUE etype = rb_thrift_binary_proto_read_byte(self);
+  VALUE size = rb_thrift_binary_proto_read_i32(self);
+  return rb_ary_new3(2, etype, size);
+}
+
+VALUE rb_thrift_binary_proto_read_set_begin(VALUE self) {
+  return rb_thrift_binary_proto_read_list_begin(self);
+}
+
+VALUE rb_thrift_binary_proto_read_bool(VALUE self) {
+  char byte = read_byte_direct(self);
+  return byte != 0 ? Qtrue : Qfalse;
+}
+
+VALUE rb_thrift_binary_proto_read_byte(VALUE self) {
+  return INT2FIX(read_byte_direct(self));
+}
+
+VALUE rb_thrift_binary_proto_read_i16(VALUE self) {
+  return INT2FIX(read_i16_direct(self));
+}
+
+VALUE rb_thrift_binary_proto_read_i32(VALUE self) {
+  return INT2NUM(read_i32_direct(self));
+}
+
+VALUE rb_thrift_binary_proto_read_i64(VALUE self) {
+  return LL2NUM(read_i64_direct(self));
+}
+
+VALUE rb_thrift_binary_proto_read_double(VALUE self) {
+  union {
+    double f;
+    int64_t t;
+  } transfer;
+  transfer.t = read_i64_direct(self);
+  return rb_float_new(transfer.f);
+}
+
+VALUE rb_thrift_binary_proto_read_string(VALUE self) {
+  int size = read_i32_direct(self);
+  return READ(self, size);
+}
+
+void Init_binary_protocol_accelerated() {
+  VALUE thrift_binary_protocol_class = rb_const_get(thrift_module, rb_intern("BinaryProtocol"));
+  
+  VERSION_1 = rb_num2ll(rb_const_get(thrift_binary_protocol_class, rb_intern("VERSION_1")));
+  VERSION_MASK = rb_num2ll(rb_const_get(thrift_binary_protocol_class, rb_intern("VERSION_MASK")));
+  TYPE_MASK = rb_num2ll(rb_const_get(thrift_binary_protocol_class, rb_intern("TYPE_MASK")));
+  
+  VALUE bpa_class = rb_define_class_under(thrift_module, "BinaryProtocolAccelerated", thrift_binary_protocol_class);
+  
+  rb_define_method(bpa_class, "native?", rb_thrift_binary_proto_native_qmark, 0);
+  
+  rb_define_method(bpa_class, "write_message_begin", rb_thrift_binary_proto_write_message_begin, 3);
+  rb_define_method(bpa_class, "write_field_begin",   rb_thrift_binary_proto_write_field_begin, 3);
+  rb_define_method(bpa_class, "write_field_stop",    rb_thrift_binary_proto_write_field_stop, 0);
+  rb_define_method(bpa_class, "write_map_begin",     rb_thrift_binary_proto_write_map_begin, 3);
+  rb_define_method(bpa_class, "write_list_begin",    rb_thrift_binary_proto_write_list_begin, 2);
+  rb_define_method(bpa_class, "write_set_begin",     rb_thrift_binary_proto_write_set_begin, 2);
+  rb_define_method(bpa_class, "write_byte",          rb_thrift_binary_proto_write_byte, 1);
+  rb_define_method(bpa_class, "write_bool",          rb_thrift_binary_proto_write_bool, 1);
+  rb_define_method(bpa_class, "write_i16",           rb_thrift_binary_proto_write_i16, 1);
+  rb_define_method(bpa_class, "write_i32",           rb_thrift_binary_proto_write_i32, 1);
+  rb_define_method(bpa_class, "write_i64",           rb_thrift_binary_proto_write_i64, 1);
+  rb_define_method(bpa_class, "write_double",        rb_thrift_binary_proto_write_double, 1);
+  rb_define_method(bpa_class, "write_string",        rb_thrift_binary_proto_write_string, 1);
+  // unused methods
+  rb_define_method(bpa_class, "write_message_end", rb_thrift_binary_proto_write_message_end, 0);
+  rb_define_method(bpa_class, "write_struct_begin", rb_thrift_binary_proto_write_struct_begin, 1);
+  rb_define_method(bpa_class, "write_struct_end", rb_thrift_binary_proto_write_struct_end, 0);
+  rb_define_method(bpa_class, "write_field_end", rb_thrift_binary_proto_write_field_end, 0);
+  rb_define_method(bpa_class, "write_map_end", rb_thrift_binary_proto_write_map_end, 0);
+  rb_define_method(bpa_class, "write_list_end", rb_thrift_binary_proto_write_list_end, 0);
+  rb_define_method(bpa_class, "write_set_end", rb_thrift_binary_proto_write_set_end, 0);
+  
+
+
+  rb_define_method(bpa_class, "read_message_begin",  rb_thrift_binary_proto_read_message_begin, 0);
+  rb_define_method(bpa_class, "read_field_begin",    rb_thrift_binary_proto_read_field_begin, 0);
+  rb_define_method(bpa_class, "read_map_begin",      rb_thrift_binary_proto_read_map_begin, 0);
+  rb_define_method(bpa_class, "read_list_begin",     rb_thrift_binary_proto_read_list_begin, 0);
+  rb_define_method(bpa_class, "read_set_begin",      rb_thrift_binary_proto_read_set_begin, 0);
+  rb_define_method(bpa_class, "read_byte",           rb_thrift_binary_proto_read_byte, 0);
+  rb_define_method(bpa_class, "read_bool",           rb_thrift_binary_proto_read_bool, 0);
+  rb_define_method(bpa_class, "read_i16",            rb_thrift_binary_proto_read_i16, 0);
+  rb_define_method(bpa_class, "read_i32",            rb_thrift_binary_proto_read_i32, 0);
+  rb_define_method(bpa_class, "read_i64",            rb_thrift_binary_proto_read_i64, 0);
+  rb_define_method(bpa_class, "read_double",         rb_thrift_binary_proto_read_double, 0);
+  rb_define_method(bpa_class, "read_string",         rb_thrift_binary_proto_read_string, 0);
+  // unused methods
+  rb_define_method(bpa_class, "read_message_end", rb_thrift_binary_proto_read_message_end, 0);
+  rb_define_method(bpa_class, "read_struct_begin", rb_thift_binary_proto_read_struct_begin, 0);
+  rb_define_method(bpa_class, "read_struct_end", rb_thift_binary_proto_read_struct_end, 0);
+  rb_define_method(bpa_class, "read_field_end", rb_thift_binary_proto_read_field_end, 0);
+  rb_define_method(bpa_class, "read_map_end", rb_thift_binary_proto_read_map_end, 0);
+  rb_define_method(bpa_class, "read_list_end", rb_thift_binary_proto_read_list_end, 0);
+  rb_define_method(bpa_class, "read_set_end", rb_thift_binary_proto_read_set_end, 0);
+
+  // set up native method table
+  native_proto_method_table *npmt;
+  npmt = ALLOC(native_proto_method_table);
+
+  npmt->write_field_begin = rb_thrift_binary_proto_write_field_begin;
+  npmt->write_field_stop = rb_thrift_binary_proto_write_field_stop;
+  npmt->write_map_begin = rb_thrift_binary_proto_write_map_begin;
+  npmt->write_list_begin = rb_thrift_binary_proto_write_list_begin;
+  npmt->write_set_begin = rb_thrift_binary_proto_write_set_begin;
+  npmt->write_byte = rb_thrift_binary_proto_write_byte;
+  npmt->write_bool = rb_thrift_binary_proto_write_bool;
+  npmt->write_i16 = rb_thrift_binary_proto_write_i16;
+  npmt->write_i32 = rb_thrift_binary_proto_write_i32;
+  npmt->write_i64 = rb_thrift_binary_proto_write_i64;
+  npmt->write_double = rb_thrift_binary_proto_write_double;
+  npmt->write_string = rb_thrift_binary_proto_write_string;
+  npmt->write_message_end = rb_thrift_binary_proto_write_message_end;
+  npmt->write_struct_begin = rb_thrift_binary_proto_write_struct_begin;
+  npmt->write_struct_end = rb_thrift_binary_proto_write_struct_end;
+  npmt->write_field_end = rb_thrift_binary_proto_write_field_end;
+  npmt->write_map_end = rb_thrift_binary_proto_write_map_end;
+  npmt->write_list_end = rb_thrift_binary_proto_write_list_end;
+  npmt->write_set_end = rb_thrift_binary_proto_write_set_end;
+
+  npmt->read_message_begin = rb_thrift_binary_proto_read_message_begin;
+  npmt->read_field_begin = rb_thrift_binary_proto_read_field_begin;
+  npmt->read_map_begin = rb_thrift_binary_proto_read_map_begin;
+  npmt->read_list_begin = rb_thrift_binary_proto_read_list_begin;
+  npmt->read_set_begin = rb_thrift_binary_proto_read_set_begin;
+  npmt->read_byte = rb_thrift_binary_proto_read_byte;
+  npmt->read_bool = rb_thrift_binary_proto_read_bool;
+  npmt->read_i16 = rb_thrift_binary_proto_read_i16;
+  npmt->read_i32 = rb_thrift_binary_proto_read_i32;
+  npmt->read_i64 = rb_thrift_binary_proto_read_i64;
+  npmt->read_double = rb_thrift_binary_proto_read_double;
+  npmt->read_string = rb_thrift_binary_proto_read_string;
+  npmt->read_message_end = rb_thrift_binary_proto_read_message_end;
+  npmt->read_struct_begin = rb_thift_binary_proto_read_struct_begin;
+  npmt->read_struct_end = rb_thift_binary_proto_read_struct_end;
+  npmt->read_field_end = rb_thift_binary_proto_read_field_end;
+  npmt->read_map_end = rb_thift_binary_proto_read_map_end;
+  npmt->read_list_end = rb_thift_binary_proto_read_list_end;
+  npmt->read_set_end = rb_thift_binary_proto_read_set_end;
+  
+  VALUE method_table_object = Data_Wrap_Struct(rb_cObject, 0, free, npmt);
+  rb_const_set(bpa_class, rb_intern("@native_method_table"), method_table_object);
+}
diff --git a/lib/rb/ext/binary_protocol_accelerated.h b/lib/rb/ext/binary_protocol_accelerated.h
new file mode 100644
index 0000000..37baf41
--- /dev/null
+++ b/lib/rb/ext/binary_protocol_accelerated.h
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+void Init_binary_protocol_accelerated();
diff --git a/lib/rb/ext/compact_protocol.c b/lib/rb/ext/compact_protocol.c
new file mode 100644
index 0000000..7966d3e
--- /dev/null
+++ b/lib/rb/ext/compact_protocol.c
@@ -0,0 +1,665 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <ruby.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <constants.h>
+#include <struct.h>
+#include "macros.h"
+
+#define LAST_ID(obj) FIX2INT(rb_ary_pop(rb_ivar_get(obj, last_field_id)))
+#define SET_LAST_ID(obj, val) rb_ary_push(rb_ivar_get(obj, last_field_id), val)
+
+VALUE rb_thrift_compact_proto_native_qmark(VALUE self) {
+  return Qtrue;
+}
+
+static ID last_field_id;
+static ID boolean_field_id;
+static ID bool_value_id;
+
+static int VERSION;
+static int VERSION_MASK;
+static int TYPE_MASK;
+static int TYPE_SHIFT_AMOUNT;
+static int PROTOCOL_ID;
+
+static VALUE thrift_compact_protocol_class;
+
+static int CTYPE_BOOLEAN_TRUE   = 0x01;
+static int CTYPE_BOOLEAN_FALSE  = 0x02;
+static int CTYPE_BYTE           = 0x03;
+static int CTYPE_I16            = 0x04;
+static int CTYPE_I32            = 0x05;
+static int CTYPE_I64            = 0x06;
+static int CTYPE_DOUBLE         = 0x07;
+static int CTYPE_BINARY         = 0x08;
+static int CTYPE_LIST           = 0x09;
+static int CTYPE_SET            = 0x0A;
+static int CTYPE_MAP            = 0x0B;
+static int CTYPE_STRUCT         = 0x0C;
+
+VALUE rb_thrift_compact_proto_write_i16(VALUE self, VALUE i16);
+
+// TODO: implement this
+static int get_compact_type(VALUE type_value) {
+  int type = FIX2INT(type_value);
+  if (type == TTYPE_BOOL) {
+    return CTYPE_BOOLEAN_TRUE;
+  } else if (type == TTYPE_BYTE) {
+    return CTYPE_BYTE;
+  } else if (type == TTYPE_I16) {
+    return CTYPE_I16;
+  } else if (type == TTYPE_I32) {
+    return CTYPE_I32;
+  } else if (type == TTYPE_I64) {
+    return CTYPE_I64;
+  } else if (type == TTYPE_DOUBLE) {
+    return CTYPE_DOUBLE;
+  } else if (type == TTYPE_STRING) {
+    return CTYPE_BINARY;
+  } else if (type == TTYPE_LIST) {
+    return CTYPE_LIST;
+  } else if (type == TTYPE_SET) {
+    return CTYPE_SET;
+  } else if (type == TTYPE_MAP) {
+    return CTYPE_MAP;
+  } else if (type == TTYPE_STRUCT) {
+    return CTYPE_STRUCT;
+  } else {
+    char str[50];
+    sprintf(str, "don't know what type: %d", type);
+    rb_raise(rb_eStandardError, str);
+    return 0;
+  }
+}
+
+static void write_byte_direct(VALUE transport, int8_t b) {
+  WRITE(transport, (char*)&b, 1);
+}
+
+static void write_field_begin_internal(VALUE self, VALUE type, VALUE id_value, VALUE type_override) {
+  int id = FIX2INT(id_value);
+  int last_id = LAST_ID(self);
+  VALUE transport = GET_TRANSPORT(self);
+  
+  // if there's a type override, use that.
+  int8_t type_to_write = RTEST(type_override) ? FIX2INT(type_override) : get_compact_type(type);
+  // check if we can use delta encoding for the field id
+  int diff = id - last_id;
+  if (diff > 0 && diff <= 15) {
+    // write them together
+    write_byte_direct(transport, diff << 4 | (type_to_write & 0x0f));
+  } else {
+    // write them separate
+    write_byte_direct(transport, type_to_write & 0x0f);
+    rb_thrift_compact_proto_write_i16(self, id_value);
+  }
+
+  SET_LAST_ID(self, id_value);
+}
+
+static int32_t int_to_zig_zag(int32_t n) {
+  return (n << 1) ^ (n >> 31);
+}
+
+static uint64_t ll_to_zig_zag(int64_t n) {
+  return (n << 1) ^ (n >> 63);
+}
+
+static void write_varint32(VALUE transport, uint32_t n) {
+  while (true) {
+    if ((n & ~0x7F) == 0) {
+      write_byte_direct(transport, n & 0x7f);
+      break;
+    } else {
+      write_byte_direct(transport, (n & 0x7F) | 0x80);
+      n = n >> 7;
+    }
+  }
+}
+
+static void write_varint64(VALUE transport, uint64_t n) {
+  while (true) {
+    if ((n & ~0x7F) == 0) {
+      write_byte_direct(transport, n & 0x7f);
+      break;
+    } else {
+      write_byte_direct(transport, (n & 0x7F) | 0x80);
+      n = n >> 7;
+    }
+  }
+}
+
+static void write_collection_begin(VALUE transport, VALUE elem_type, VALUE size_value) {
+  int size = FIX2INT(size_value);
+  if (size <= 14) {
+    write_byte_direct(transport, size << 4 | get_compact_type(elem_type));
+  } else {
+    write_byte_direct(transport, 0xf0 | get_compact_type(elem_type));
+    write_varint32(transport, size);
+  }
+}
+
+
+//--------------------------------
+// interface writing methods
+//--------------------------------
+
+VALUE rb_thrift_compact_proto_write_i32(VALUE self, VALUE i32);
+VALUE rb_thrift_compact_proto_write_string(VALUE self, VALUE str);
+
+VALUE rb_thrift_compact_proto_write_message_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_struct_begin(VALUE self, VALUE name) {
+  rb_ary_push(rb_ivar_get(self, last_field_id), INT2FIX(0));
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_struct_end(VALUE self) {
+  rb_ary_pop(rb_ivar_get(self, last_field_id));
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_field_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_map_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_list_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_set_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_message_begin(VALUE self, VALUE name, VALUE type, VALUE seqid) {
+  VALUE transport = GET_TRANSPORT(self);
+  write_byte_direct(transport, PROTOCOL_ID);
+  write_byte_direct(transport, (VERSION & VERSION_MASK) | ((FIX2INT(type) << TYPE_SHIFT_AMOUNT) & TYPE_MASK));
+  write_varint32(transport, FIX2INT(seqid));
+  rb_thrift_compact_proto_write_string(self, name);
+  
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_field_begin(VALUE self, VALUE name, VALUE type, VALUE id) {
+  if (FIX2INT(type) == TTYPE_BOOL) {
+    // we want to possibly include the value, so we'll wait.
+    rb_ivar_set(self, boolean_field_id, rb_ary_new3(2, type, id));
+  } else {
+    write_field_begin_internal(self, type, id, Qnil);
+  }
+
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_field_stop(VALUE self) {
+  write_byte_direct(GET_TRANSPORT(self), TTYPE_STOP);
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_map_begin(VALUE self, VALUE ktype, VALUE vtype, VALUE size_value) {
+  int size = FIX2INT(size_value);
+  VALUE transport = GET_TRANSPORT(self);
+  if (size == 0) {
+    write_byte_direct(transport, 0);
+  } else {
+    write_varint32(transport, size);
+    write_byte_direct(transport, get_compact_type(ktype) << 4 | get_compact_type(vtype));
+  }
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_list_begin(VALUE self, VALUE etype, VALUE size) {
+  write_collection_begin(GET_TRANSPORT(self), etype, size);
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_set_begin(VALUE self, VALUE etype, VALUE size) {
+  write_collection_begin(GET_TRANSPORT(self), etype, size);
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_bool(VALUE self, VALUE b) {
+  int8_t type = b == Qtrue ? CTYPE_BOOLEAN_TRUE : CTYPE_BOOLEAN_FALSE;
+  VALUE boolean_field = rb_ivar_get(self, boolean_field_id);
+  if (NIL_P(boolean_field)) {
+    // we're not part of a field, so just write the value.
+    write_byte_direct(GET_TRANSPORT(self), type);
+  } else {
+    // we haven't written the field header yet
+    write_field_begin_internal(self, rb_ary_entry(boolean_field, 0), rb_ary_entry(boolean_field, 1), INT2FIX(type));
+    rb_ivar_set(self, boolean_field_id, Qnil);
+  }
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_byte(VALUE self, VALUE byte) {
+  CHECK_NIL(byte);
+  write_byte_direct(GET_TRANSPORT(self), FIX2INT(byte));
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_i16(VALUE self, VALUE i16) {
+  rb_thrift_compact_proto_write_i32(self, i16);
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_i32(VALUE self, VALUE i32) {
+  CHECK_NIL(i32);
+  write_varint32(GET_TRANSPORT(self), int_to_zig_zag(NUM2INT(i32)));
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_i64(VALUE self, VALUE i64) {
+  CHECK_NIL(i64);
+  write_varint64(GET_TRANSPORT(self), ll_to_zig_zag(NUM2LL(i64)));
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_double(VALUE self, VALUE dub) {
+  CHECK_NIL(dub);
+  // Unfortunately, bitwise_cast doesn't work in C.  Bad C!
+  union {
+    double f;
+    int64_t l;
+  } transfer;
+  transfer.f = RFLOAT_VALUE(rb_Float(dub));
+  char buf[8];
+  buf[0] = transfer.l & 0xff;
+  buf[1] = (transfer.l >> 8) & 0xff;
+  buf[2] = (transfer.l >> 16) & 0xff;
+  buf[3] = (transfer.l >> 24) & 0xff;
+  buf[4] = (transfer.l >> 32) & 0xff;
+  buf[5] = (transfer.l >> 40) & 0xff;
+  buf[6] = (transfer.l >> 48) & 0xff;
+  buf[7] = (transfer.l >> 56) & 0xff;
+  WRITE(GET_TRANSPORT(self), buf, 8);
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_write_string(VALUE self, VALUE str) {
+  VALUE transport = GET_TRANSPORT(self);
+  write_varint32(transport, RSTRING_LEN(str));
+  WRITE(transport, RSTRING_PTR(str), RSTRING_LEN(str));
+  return Qnil;
+}
+
+//---------------------------------------
+// interface reading methods
+//---------------------------------------
+
+#define is_bool_type(ctype) (((ctype) & 0x0F) == CTYPE_BOOLEAN_TRUE || ((ctype) & 0x0F) == CTYPE_BOOLEAN_FALSE)
+
+VALUE rb_thrift_compact_proto_read_string(VALUE self);
+VALUE rb_thrift_compact_proto_read_byte(VALUE self);
+VALUE rb_thrift_compact_proto_read_i32(VALUE self);
+VALUE rb_thrift_compact_proto_read_i16(VALUE self);
+
+static int8_t get_ttype(int8_t ctype) {
+  if (ctype == TTYPE_STOP) {
+    return TTYPE_STOP;
+  } else if (ctype == CTYPE_BOOLEAN_TRUE || ctype == CTYPE_BOOLEAN_FALSE) {
+    return TTYPE_BOOL;
+  } else if (ctype == CTYPE_BYTE) {
+    return TTYPE_BYTE;
+  } else if (ctype == CTYPE_I16) {
+    return TTYPE_I16;
+  } else if (ctype == CTYPE_I32) {
+    return TTYPE_I32;
+  } else if (ctype == CTYPE_I64) {
+    return TTYPE_I64;
+  } else if (ctype == CTYPE_DOUBLE) {
+    return TTYPE_DOUBLE;
+  } else if (ctype == CTYPE_BINARY) {
+    return TTYPE_STRING;
+  } else if (ctype == CTYPE_LIST) {
+    return TTYPE_LIST;
+  } else if (ctype == CTYPE_SET) {
+    return TTYPE_SET;
+  } else if (ctype == CTYPE_MAP) {
+    return TTYPE_MAP;
+  } else if (ctype == CTYPE_STRUCT) {
+    return TTYPE_STRUCT;
+  } else {
+    char str[50];
+    sprintf(str, "don't know what type: %d", ctype);
+    rb_raise(rb_eStandardError, str);
+    return 0;
+  }
+}
+
+static char read_byte_direct(VALUE self) {
+  VALUE buf = READ(self, 1);
+  return RSTRING_PTR(buf)[0];
+}
+
+static int64_t zig_zag_to_ll(int64_t n) {
+  return (((uint64_t)n) >> 1) ^ -(n & 1);
+}
+
+static int32_t zig_zag_to_int(int32_t n) {
+  return (((uint32_t)n) >> 1) ^ -(n & 1);
+}
+
+static int64_t read_varint64(VALUE self) {
+  int shift = 0;
+  int64_t result = 0;
+  while (true) {
+    int8_t b = read_byte_direct(self);
+    result = result | ((uint64_t)(b & 0x7f) << shift);
+    if ((b & 0x80) != 0x80) {
+      break;
+    }
+    shift += 7;
+  }
+  return result;
+}
+
+static int16_t read_i16(VALUE self) {
+  return zig_zag_to_int((int32_t)read_varint64(self));
+}
+
+static VALUE get_protocol_exception(VALUE code, VALUE message) {
+  VALUE args[2];
+  args[0] = code;
+  args[1] = message;
+  return rb_class_new_instance(2, (VALUE*)&args, protocol_exception_class);
+}
+
+VALUE rb_thrift_compact_proto_read_message_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_read_struct_begin(VALUE self) {
+  rb_ary_push(rb_ivar_get(self, last_field_id), INT2FIX(0));
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_read_struct_end(VALUE self) {
+  rb_ary_pop(rb_ivar_get(self, last_field_id));
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_read_field_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_read_map_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_read_list_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_read_set_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_compact_proto_read_message_begin(VALUE self) {
+  int8_t protocol_id = read_byte_direct(self);
+  if (protocol_id != PROTOCOL_ID) {
+    char buf[100];
+    int len = sprintf(buf, "Expected protocol id %d but got %d", PROTOCOL_ID, protocol_id);
+    buf[len] = 0;
+    rb_exc_raise(get_protocol_exception(INT2FIX(-1), rb_str_new2(buf)));
+  }
+  
+  int8_t version_and_type = read_byte_direct(self);
+  int8_t version = version_and_type & VERSION_MASK;
+  if (version != VERSION) {
+    char buf[100];
+    int len = sprintf(buf, "Expected version id %d but got %d", version, VERSION);
+    buf[len] = 0;
+    rb_exc_raise(get_protocol_exception(INT2FIX(-1), rb_str_new2(buf)));
+  }
+  
+  int8_t type = (version_and_type >> TYPE_SHIFT_AMOUNT) & 0x03;
+  int32_t seqid = read_varint64(self);
+  VALUE messageName = rb_thrift_compact_proto_read_string(self);
+  return rb_ary_new3(3, messageName, INT2FIX(type), INT2NUM(seqid));
+}
+
+VALUE rb_thrift_compact_proto_read_field_begin(VALUE self) {
+  int8_t type = read_byte_direct(self);
+  // if it's a stop, then we can return immediately, as the struct is over.
+  if ((type & 0x0f) == TTYPE_STOP) {
+    return rb_ary_new3(3, Qnil, INT2FIX(0), INT2FIX(0));
+  } else {
+    int field_id = 0;
+
+    // mask off the 4 MSB of the type header. it could contain a field id delta.
+    uint8_t modifier = ((type & 0xf0) >> 4);
+    
+    if (modifier == 0) {
+      // not a delta. look ahead for the zigzag varint field id.
+      field_id = read_i16(self);
+    } else {
+      // has a delta. add the delta to the last read field id.
+      field_id = LAST_ID(self) + modifier;
+    }
+
+    // if this happens to be a boolean field, the value is encoded in the type
+    if (is_bool_type(type)) {
+      // save the boolean value in a special instance variable.
+      rb_ivar_set(self, bool_value_id, (type & 0x0f) == CTYPE_BOOLEAN_TRUE ? Qtrue : Qfalse);
+    }
+
+    // push the new field onto the field stack so we can keep the deltas going.
+    SET_LAST_ID(self, INT2FIX(field_id));
+    return rb_ary_new3(3, Qnil, INT2FIX(get_ttype(type & 0x0f)), INT2FIX(field_id));
+  }
+}
+
+VALUE rb_thrift_compact_proto_read_map_begin(VALUE self) {
+  int32_t size = read_varint64(self);
+  uint8_t key_and_value_type = size == 0 ? 0 : read_byte_direct(self);
+  return rb_ary_new3(3, INT2FIX(get_ttype(key_and_value_type >> 4)), INT2FIX(get_ttype(key_and_value_type & 0xf)), INT2FIX(size));
+}
+
+VALUE rb_thrift_compact_proto_read_list_begin(VALUE self) {
+  uint8_t size_and_type = read_byte_direct(self);
+  int32_t size = (size_and_type >> 4) & 0x0f;
+  if (size == 15) {
+    size = read_varint64(self);
+  }
+  uint8_t type = get_ttype(size_and_type & 0x0f);
+  return rb_ary_new3(2, INT2FIX(type), INT2FIX(size));
+}
+
+VALUE rb_thrift_compact_proto_read_set_begin(VALUE self) {
+  return rb_thrift_compact_proto_read_list_begin(self);
+}
+
+VALUE rb_thrift_compact_proto_read_bool(VALUE self) {
+  VALUE bool_value = rb_ivar_get(self, bool_value_id);
+  if (NIL_P(bool_value)) {
+    return read_byte_direct(self) == CTYPE_BOOLEAN_TRUE ? Qtrue : Qfalse;
+  } else {
+    rb_ivar_set(self, bool_value_id, Qnil);
+    return bool_value;
+  }
+}
+
+VALUE rb_thrift_compact_proto_read_byte(VALUE self) {
+  return INT2FIX(read_byte_direct(self));
+}
+
+VALUE rb_thrift_compact_proto_read_i16(VALUE self) {
+  return INT2FIX(read_i16(self));
+}
+
+VALUE rb_thrift_compact_proto_read_i32(VALUE self) {
+  return INT2NUM(zig_zag_to_int(read_varint64(self)));
+}
+
+VALUE rb_thrift_compact_proto_read_i64(VALUE self) {
+  return LL2NUM(zig_zag_to_ll(read_varint64(self)));
+}
+
+VALUE rb_thrift_compact_proto_read_double(VALUE self) {
+  union {
+    double f;
+    int64_t l;
+  } transfer;
+  VALUE bytes = READ(self, 8);
+  uint32_t lo = ((uint8_t)(RSTRING_PTR(bytes)[0]))
+    | (((uint8_t)(RSTRING_PTR(bytes)[1])) << 8)
+    | (((uint8_t)(RSTRING_PTR(bytes)[2])) << 16)
+    | (((uint8_t)(RSTRING_PTR(bytes)[3])) << 24);
+  uint64_t hi = (((uint8_t)(RSTRING_PTR(bytes)[4])))
+    | (((uint8_t)(RSTRING_PTR(bytes)[5])) << 8)
+    | (((uint8_t)(RSTRING_PTR(bytes)[6])) << 16)
+    | (((uint8_t)(RSTRING_PTR(bytes)[7])) << 24);
+  transfer.l = (hi << 32) | lo;
+
+  return rb_float_new(transfer.f);
+}
+
+VALUE rb_thrift_compact_proto_read_string(VALUE self) {
+  int64_t size = read_varint64(self);
+  return READ(self, size);
+}
+
+static void Init_constants() {
+  thrift_compact_protocol_class = rb_const_get(thrift_module, rb_intern("CompactProtocol"));
+  
+  VERSION = rb_num2ll(rb_const_get(thrift_compact_protocol_class, rb_intern("VERSION")));
+  VERSION_MASK = rb_num2ll(rb_const_get(thrift_compact_protocol_class, rb_intern("VERSION_MASK")));
+  TYPE_MASK = rb_num2ll(rb_const_get(thrift_compact_protocol_class, rb_intern("TYPE_MASK")));
+  TYPE_SHIFT_AMOUNT = FIX2INT(rb_const_get(thrift_compact_protocol_class, rb_intern("TYPE_SHIFT_AMOUNT")));
+  PROTOCOL_ID = FIX2INT(rb_const_get(thrift_compact_protocol_class, rb_intern("PROTOCOL_ID")));
+  
+  last_field_id = rb_intern("@last_field");
+  boolean_field_id = rb_intern("@boolean_field");
+  bool_value_id = rb_intern("@bool_value");
+}
+
+static void Init_rb_methods() {
+  rb_define_method(thrift_compact_protocol_class, "native?", rb_thrift_compact_proto_native_qmark, 0);
+
+  rb_define_method(thrift_compact_protocol_class, "write_message_begin", rb_thrift_compact_proto_write_message_begin, 3);
+  rb_define_method(thrift_compact_protocol_class, "write_field_begin",   rb_thrift_compact_proto_write_field_begin, 3);
+  rb_define_method(thrift_compact_protocol_class, "write_field_stop",    rb_thrift_compact_proto_write_field_stop, 0);
+  rb_define_method(thrift_compact_protocol_class, "write_map_begin",     rb_thrift_compact_proto_write_map_begin, 3);
+  rb_define_method(thrift_compact_protocol_class, "write_list_begin",    rb_thrift_compact_proto_write_list_begin, 2);
+  rb_define_method(thrift_compact_protocol_class, "write_set_begin",     rb_thrift_compact_proto_write_set_begin, 2);
+  rb_define_method(thrift_compact_protocol_class, "write_byte",          rb_thrift_compact_proto_write_byte, 1);
+  rb_define_method(thrift_compact_protocol_class, "write_bool",          rb_thrift_compact_proto_write_bool, 1);
+  rb_define_method(thrift_compact_protocol_class, "write_i16",           rb_thrift_compact_proto_write_i16, 1);
+  rb_define_method(thrift_compact_protocol_class, "write_i32",           rb_thrift_compact_proto_write_i32, 1);
+  rb_define_method(thrift_compact_protocol_class, "write_i64",           rb_thrift_compact_proto_write_i64, 1);
+  rb_define_method(thrift_compact_protocol_class, "write_double",        rb_thrift_compact_proto_write_double, 1);
+  rb_define_method(thrift_compact_protocol_class, "write_string",        rb_thrift_compact_proto_write_string, 1);
+
+  rb_define_method(thrift_compact_protocol_class, "write_message_end", rb_thrift_compact_proto_write_message_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "write_struct_begin", rb_thrift_compact_proto_write_struct_begin, 1);
+  rb_define_method(thrift_compact_protocol_class, "write_struct_end", rb_thrift_compact_proto_write_struct_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "write_field_end", rb_thrift_compact_proto_write_field_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "write_map_end", rb_thrift_compact_proto_write_map_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "write_list_end", rb_thrift_compact_proto_write_list_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "write_set_end", rb_thrift_compact_proto_write_set_end, 0);
+
+
+  rb_define_method(thrift_compact_protocol_class, "read_message_begin",  rb_thrift_compact_proto_read_message_begin, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_field_begin",    rb_thrift_compact_proto_read_field_begin, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_map_begin",      rb_thrift_compact_proto_read_map_begin, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_list_begin",     rb_thrift_compact_proto_read_list_begin, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_set_begin",      rb_thrift_compact_proto_read_set_begin, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_byte",           rb_thrift_compact_proto_read_byte, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_bool",           rb_thrift_compact_proto_read_bool, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_i16",            rb_thrift_compact_proto_read_i16, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_i32",            rb_thrift_compact_proto_read_i32, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_i64",            rb_thrift_compact_proto_read_i64, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_double",         rb_thrift_compact_proto_read_double, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_string",         rb_thrift_compact_proto_read_string, 0);
+
+  rb_define_method(thrift_compact_protocol_class, "read_message_end", rb_thrift_compact_proto_read_message_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_struct_begin",  rb_thrift_compact_proto_read_struct_begin, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_struct_end",    rb_thrift_compact_proto_read_struct_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_field_end",     rb_thrift_compact_proto_read_field_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_map_end",       rb_thrift_compact_proto_read_map_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_list_end",      rb_thrift_compact_proto_read_list_end, 0);
+  rb_define_method(thrift_compact_protocol_class, "read_set_end",       rb_thrift_compact_proto_read_set_end, 0);
+}
+
+static void Init_npmt() {
+  native_proto_method_table *npmt;
+  npmt = ALLOC(native_proto_method_table);
+  
+  npmt->write_field_begin   =  rb_thrift_compact_proto_write_field_begin;
+  npmt->write_field_stop    =  rb_thrift_compact_proto_write_field_stop;
+  npmt->write_map_begin     =  rb_thrift_compact_proto_write_map_begin;
+  npmt->write_list_begin    =  rb_thrift_compact_proto_write_list_begin;
+  npmt->write_set_begin     =  rb_thrift_compact_proto_write_set_begin;
+  npmt->write_byte          =  rb_thrift_compact_proto_write_byte;
+  npmt->write_bool          =  rb_thrift_compact_proto_write_bool;
+  npmt->write_i16           =  rb_thrift_compact_proto_write_i16;
+  npmt->write_i32           =  rb_thrift_compact_proto_write_i32;
+  npmt->write_i64           =  rb_thrift_compact_proto_write_i64;
+  npmt->write_double        =  rb_thrift_compact_proto_write_double;
+  npmt->write_string        =  rb_thrift_compact_proto_write_string;
+  npmt->write_message_end   =  rb_thrift_compact_proto_write_message_end;
+  npmt->write_struct_begin  =  rb_thrift_compact_proto_write_struct_begin;
+  npmt->write_struct_end    =  rb_thrift_compact_proto_write_struct_end;
+  npmt->write_field_end     =  rb_thrift_compact_proto_write_field_end;
+  npmt->write_map_end       =  rb_thrift_compact_proto_write_map_end;
+  npmt->write_list_end      =  rb_thrift_compact_proto_write_list_end;
+  npmt->write_set_end       =  rb_thrift_compact_proto_write_set_end;
+  
+  npmt->read_message_begin  =  rb_thrift_compact_proto_read_message_begin;
+  npmt->read_field_begin    =  rb_thrift_compact_proto_read_field_begin;
+  npmt->read_map_begin      =  rb_thrift_compact_proto_read_map_begin;
+  npmt->read_list_begin     =  rb_thrift_compact_proto_read_list_begin;
+  npmt->read_set_begin      =  rb_thrift_compact_proto_read_set_begin;
+  npmt->read_byte           =  rb_thrift_compact_proto_read_byte;
+  npmt->read_bool           =  rb_thrift_compact_proto_read_bool;
+  npmt->read_i16            =  rb_thrift_compact_proto_read_i16;
+  npmt->read_i32            =  rb_thrift_compact_proto_read_i32;
+  npmt->read_i64            =  rb_thrift_compact_proto_read_i64;
+  npmt->read_double         =  rb_thrift_compact_proto_read_double;
+  npmt->read_string         =  rb_thrift_compact_proto_read_string;
+  npmt->read_message_end    =  rb_thrift_compact_proto_read_message_end;
+  npmt->read_struct_begin   =  rb_thrift_compact_proto_read_struct_begin;
+  npmt->read_struct_end     =  rb_thrift_compact_proto_read_struct_end;
+  npmt->read_field_end      =  rb_thrift_compact_proto_read_field_end;
+  npmt->read_map_end        =  rb_thrift_compact_proto_read_map_end;
+  npmt->read_list_end       =  rb_thrift_compact_proto_read_list_end;
+  npmt->read_set_end        =  rb_thrift_compact_proto_read_set_end;
+  
+  VALUE method_table_object = Data_Wrap_Struct(rb_cObject, 0, free, npmt);
+  rb_const_set(thrift_compact_protocol_class, rb_intern("@native_method_table"), method_table_object);
+}
+
+
+
+void Init_compact_protocol() {
+  Init_constants();
+  Init_rb_methods();
+  Init_npmt();
+}
diff --git a/lib/rb/ext/compact_protocol.h b/lib/rb/ext/compact_protocol.h
new file mode 100644
index 0000000..163915e
--- /dev/null
+++ b/lib/rb/ext/compact_protocol.h
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+void Init_compact_protocol();
diff --git a/lib/rb/ext/constants.h b/lib/rb/ext/constants.h
new file mode 100644
index 0000000..57df544
--- /dev/null
+++ b/lib/rb/ext/constants.h
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+extern int TTYPE_STOP;
+extern int TTYPE_BOOL;
+extern int TTYPE_BYTE;
+extern int TTYPE_I16;
+extern int TTYPE_I32;
+extern int TTYPE_I64;
+extern int TTYPE_DOUBLE;
+extern int TTYPE_STRING;
+extern int TTYPE_MAP;
+extern int TTYPE_SET;
+extern int TTYPE_LIST;
+extern int TTYPE_STRUCT;
+
+extern ID validate_method_id;
+extern ID write_struct_begin_method_id;
+extern ID write_struct_end_method_id;
+extern ID write_field_begin_method_id;
+extern ID write_field_end_method_id;
+extern ID write_boolean_method_id;
+extern ID write_byte_method_id;
+extern ID write_i16_method_id;
+extern ID write_i32_method_id;
+extern ID write_i64_method_id;
+extern ID write_double_method_id;
+extern ID write_string_method_id;
+extern ID write_map_begin_method_id;
+extern ID write_map_end_method_id;
+extern ID write_list_begin_method_id;
+extern ID write_list_end_method_id;
+extern ID write_set_begin_method_id;
+extern ID write_set_end_method_id;
+extern ID size_method_id;
+extern ID read_bool_method_id;
+extern ID read_byte_method_id;
+extern ID read_i16_method_id;
+extern ID read_i32_method_id;
+extern ID read_i64_method_id;
+extern ID read_string_method_id;
+extern ID read_double_method_id;
+extern ID read_map_begin_method_id;
+extern ID read_map_end_method_id;
+extern ID read_list_begin_method_id;
+extern ID read_list_end_method_id;
+extern ID read_set_begin_method_id;
+extern ID read_set_end_method_id;
+extern ID read_struct_begin_method_id;
+extern ID read_struct_end_method_id;
+extern ID read_field_begin_method_id;
+extern ID read_field_end_method_id;
+extern ID keys_method_id;
+extern ID entries_method_id; 
+extern ID name_method_id; 
+extern ID sort_method_id;
+extern ID write_field_stop_method_id;
+extern ID skip_method_id;
+extern ID write_method_id;
+extern ID read_all_method_id;
+extern ID native_qmark_method_id;
+
+extern ID fields_const_id;
+extern ID transport_ivar_id;
+extern ID strict_read_ivar_id;
+extern ID strict_write_ivar_id;
+
+extern VALUE type_sym;
+extern VALUE name_sym;
+extern VALUE key_sym;
+extern VALUE value_sym;
+extern VALUE element_sym;
+extern VALUE class_sym;
+
+extern VALUE rb_cSet;
+extern VALUE thrift_module;
+extern VALUE thrift_types_module;
+extern VALUE class_thrift_protocol;
+extern VALUE protocol_exception_class;
diff --git a/lib/rb/ext/extconf.rb b/lib/rb/ext/extconf.rb
new file mode 100644
index 0000000..07558b8
--- /dev/null
+++ b/lib/rb/ext/extconf.rb
@@ -0,0 +1,26 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'mkmf'
+
+$CFLAGS = "-g -O2 -Wall -Werror"
+
+have_func("strlcpy", "string.h")
+
+create_makefile 'thrift_native'
diff --git a/lib/rb/ext/macros.h b/lib/rb/ext/macros.h
new file mode 100644
index 0000000..265f693
--- /dev/null
+++ b/lib/rb/ext/macros.h
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#define GET_TRANSPORT(obj) rb_ivar_get(obj, transport_ivar_id)
+#define GET_STRICT_READ(obj) rb_ivar_get(obj, strict_read_ivar_id)
+#define GET_STRICT_WRITE(obj) rb_ivar_get(obj, strict_write_ivar_id)
+#define WRITE(obj, data, length) rb_funcall(obj, write_method_id, 1, rb_str_new(data, length))
+#define CHECK_NIL(obj) if (NIL_P(obj)) { rb_raise(rb_eStandardError, "nil argument not allowed!");}
+#define READ(obj, length) rb_funcall(GET_TRANSPORT(obj), read_all_method_id, 1, INT2FIX(length))
+
+#ifndef RFLOAT_VALUE
+#  define RFLOAT_VALUE(v) RFLOAT(rb_Float(v))->value
+#endif
+
+#ifndef RSTRING_LEN
+#  define RSTRING_LEN(v) RSTRING(rb_String(v))->len
+#endif
+
+#ifndef RSTRING_PTR
+#  define RSTRING_PTR(v) RSTRING(rb_String(v))->ptr
+#endif
+
+#ifndef RARRAY_LEN
+#  define RARRAY_LEN(v) RARRAY(rb_Array(v))->len
+#endif
diff --git a/lib/rb/ext/memory_buffer.c b/lib/rb/ext/memory_buffer.c
new file mode 100644
index 0000000..624012d
--- /dev/null
+++ b/lib/rb/ext/memory_buffer.c
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <ruby.h>
+#include <constants.h>
+#include "macros.h"
+
+ID buf_ivar_id;
+ID index_ivar_id;
+
+ID slice_method_id;
+
+int GARBAGE_BUFFER_SIZE;
+
+#define GET_BUF(self) rb_ivar_get(self, buf_ivar_id)
+
+VALUE rb_thrift_memory_buffer_write(VALUE self, VALUE str) {
+  VALUE buf = GET_BUF(self);
+  rb_str_buf_cat(buf, RSTRING_PTR(str), RSTRING_LEN(str));
+  return Qnil;
+}
+
+VALUE rb_thrift_memory_buffer_read(VALUE self, VALUE length_value) {
+  int length = FIX2INT(length_value);
+  
+  VALUE index_value = rb_ivar_get(self, index_ivar_id);
+  int index = FIX2INT(index_value);
+  
+  VALUE buf = GET_BUF(self);
+  VALUE data = rb_funcall(buf, slice_method_id, 2, index_value, length_value);
+  
+  index += length;
+  if (index > RSTRING_LEN(buf)) {
+    index = RSTRING_LEN(buf);
+  }
+  if (index >= GARBAGE_BUFFER_SIZE) {
+    rb_ivar_set(self, buf_ivar_id, rb_funcall(buf, slice_method_id, 2, INT2FIX(index), INT2FIX(RSTRING_LEN(buf) - 1)));
+    index = 0;
+  }
+
+  rb_ivar_set(self, index_ivar_id, INT2FIX(index));
+  return data;
+}
+
+void Init_memory_buffer() {
+  VALUE thrift_memory_buffer_class = rb_const_get(thrift_module, rb_intern("MemoryBufferTransport"));
+  rb_define_method(thrift_memory_buffer_class, "write", rb_thrift_memory_buffer_write, 1);
+  rb_define_method(thrift_memory_buffer_class, "read", rb_thrift_memory_buffer_read, 1);
+  
+  buf_ivar_id = rb_intern("@buf");
+  index_ivar_id = rb_intern("@index");
+  
+  slice_method_id = rb_intern("slice");
+  
+  GARBAGE_BUFFER_SIZE = FIX2INT(rb_const_get(thrift_memory_buffer_class, rb_intern("GARBAGE_BUFFER_SIZE")));
+}
diff --git a/lib/rb/ext/memory_buffer.h b/lib/rb/ext/memory_buffer.h
new file mode 100644
index 0000000..b277fa6
--- /dev/null
+++ b/lib/rb/ext/memory_buffer.h
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+void Init_memory_buffer();
diff --git a/lib/rb/ext/protocol.c b/lib/rb/ext/protocol.c
new file mode 100644
index 0000000..c187654
--- /dev/null
+++ b/lib/rb/ext/protocol.c
@@ -0,0 +1,185 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <ruby.h>
+#include <protocol.h>
+#include <stdbool.h>
+#include <constants.h>
+#include <struct.h>
+
+static VALUE skip(VALUE self, int ttype) {
+  if (ttype == TTYPE_STOP) {
+    return Qnil;
+  } else if (ttype == TTYPE_BOOL) {
+    rb_funcall(self, read_bool_method_id, 0);
+  } else if (ttype == TTYPE_BYTE) {
+    rb_funcall(self, read_byte_method_id, 0);
+  } else if (ttype == TTYPE_I16) {
+    rb_funcall(self, read_i16_method_id, 0);
+  } else if (ttype == TTYPE_I32) {
+    rb_funcall(self, read_i32_method_id, 0);
+  } else if (ttype == TTYPE_I64) {
+    rb_funcall(self, read_i64_method_id, 0);
+  } else if (ttype == TTYPE_DOUBLE) {
+    rb_funcall(self, read_double_method_id, 0);
+  } else if (ttype == TTYPE_STRING) {
+    rb_funcall(self, read_string_method_id, 0);
+  } else if (ttype == TTYPE_STRUCT) {
+    rb_funcall(self, read_struct_begin_method_id, 0);
+    while (true) {
+      VALUE field_header = rb_funcall(self, read_field_begin_method_id, 0);
+      if (NIL_P(field_header) || FIX2INT(rb_ary_entry(field_header, 1)) == TTYPE_STOP ) {
+        break;
+      } 
+      skip(self, FIX2INT(rb_ary_entry(field_header, 1)));
+      rb_funcall(self, read_field_end_method_id, 0);
+    }
+    rb_funcall(self, read_struct_end_method_id, 0);
+  } else if (ttype == TTYPE_MAP) {
+    int i;
+    VALUE map_header = rb_funcall(self, read_map_begin_method_id, 0);
+    int ktype = FIX2INT(rb_ary_entry(map_header, 0));
+    int vtype = FIX2INT(rb_ary_entry(map_header, 1));
+    int size = FIX2INT(rb_ary_entry(map_header, 2));
+    
+    for (i = 0; i < size; i++) {
+      skip(self, ktype);
+      skip(self, vtype);
+    }
+    rb_funcall(self, read_map_end_method_id, 0);
+  } else if (ttype == TTYPE_LIST || ttype == TTYPE_SET) {
+    int i;
+    VALUE collection_header = rb_funcall(self, ttype == TTYPE_LIST ? read_list_begin_method_id : read_set_begin_method_id, 0);
+    int etype = FIX2INT(rb_ary_entry(collection_header, 0));
+    int size = FIX2INT(rb_ary_entry(collection_header, 1));
+    for (i = 0; i < size; i++) {
+      skip(self, etype);
+    }
+    rb_funcall(self, ttype == TTYPE_LIST ? read_list_end_method_id : read_set_end_method_id, 0);
+  } else {
+    rb_raise(rb_eNotImpError, "don't know how to skip type %d", ttype);
+  }
+
+  return Qnil;
+}
+
+VALUE rb_thrift_protocol_native_qmark(VALUE self) {
+  return Qfalse;
+}
+
+VALUE rb_thrift_protocol_skip(VALUE protocol, VALUE ttype) {
+  return skip(protocol, FIX2INT(ttype));
+}
+
+VALUE rb_thrift_write_message_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_write_struct_begin(VALUE self, VALUE name) {
+  return Qnil;
+}
+
+VALUE rb_thrift_write_struct_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_write_field_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_write_map_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_write_list_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_write_set_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thrift_read_message_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_read_struct_begin(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_read_struct_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_read_field_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_read_map_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_read_list_end(VALUE self) {
+  return Qnil;
+}
+
+VALUE rb_thift_read_set_end(VALUE self) {
+  return Qnil;
+}
+
+void Init_protocol() {
+  VALUE c_protocol = rb_const_get(thrift_module, rb_intern("BaseProtocol"));
+  
+  rb_define_method(c_protocol, "skip", rb_thrift_protocol_skip, 1);
+  rb_define_method(c_protocol, "write_message_end", rb_thrift_write_message_end, 0);
+  rb_define_method(c_protocol, "write_struct_begin", rb_thrift_write_struct_begin, 1);
+  rb_define_method(c_protocol, "write_struct_end", rb_thrift_write_struct_end, 0);
+  rb_define_method(c_protocol, "write_field_end", rb_thrift_write_field_end, 0);
+  rb_define_method(c_protocol, "write_map_end", rb_thrift_write_map_end, 0);
+  rb_define_method(c_protocol, "write_list_end", rb_thrift_write_list_end, 0);
+  rb_define_method(c_protocol, "write_set_end", rb_thrift_write_set_end, 0);
+  rb_define_method(c_protocol, "read_message_end", rb_thrift_read_message_end, 0);
+  rb_define_method(c_protocol, "read_struct_begin", rb_thift_read_struct_begin, 0);
+  rb_define_method(c_protocol, "read_struct_end", rb_thift_read_struct_end, 0);
+  rb_define_method(c_protocol, "read_field_end", rb_thift_read_field_end, 0);
+  rb_define_method(c_protocol, "read_map_end", rb_thift_read_map_end, 0);
+  rb_define_method(c_protocol, "read_list_end", rb_thift_read_list_end, 0);
+  rb_define_method(c_protocol, "read_set_end", rb_thift_read_set_end, 0);
+  rb_define_method(c_protocol, "native?", rb_thrift_protocol_native_qmark, 0);
+  
+  // native_proto_method_table *npmt;
+  // npmt = ALLOC(native_proto_method_table);
+  // npmt->write_message_end = rb_thrift_write_message_end;
+  // npmt->write_struct_begin = rb_thrift_write_struct_begin;
+  // npmt->write_struct_end = rb_thrift_write_struct_end;
+  // npmt->write_field_end = rb_thrift_write_field_end;
+  // npmt->write_map_end = rb_thrift_write_map_end;
+  // npmt->write_list_end = rb_thrift_write_list_end;
+  // npmt->write_set_end = rb_thrift_write_set_end;
+  // npmt->read_message_end = rb_thrift_read_message_end;
+  // npmt->read_struct_begin = rb_thift_read_struct_begin;
+  // npmt->read_struct_end = rb_thift_read_struct_end;
+  // npmt->read_field_end = rb_thift_read_field_end;
+  // npmt->read_map_end = rb_thift_read_map_end;
+  // npmt->read_list_end = rb_thift_read_list_end;
+  // npmt->read_set_end = rb_thift_read_set_end;
+  // 
+  // VALUE method_table_object = Data_Wrap_Struct(rb_cObject, 0, free, npmt);
+  // rb_const_set(c_protocol, rb_intern("@native_method_table"), method_table_object);
+}
diff --git a/lib/rb/ext/protocol.h b/lib/rb/ext/protocol.h
new file mode 100644
index 0000000..5369530
--- /dev/null
+++ b/lib/rb/ext/protocol.h
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+void Init_protocol();
diff --git a/lib/rb/ext/struct.c b/lib/rb/ext/struct.c
new file mode 100644
index 0000000..fee285e
--- /dev/null
+++ b/lib/rb/ext/struct.c
@@ -0,0 +1,605 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <struct.h>
+#include <constants.h>
+#include "macros.h"
+
+#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
+
+static native_proto_method_table *mt;
+static native_proto_method_table *default_mt;
+static VALUE last_proto_class = Qnil;
+
+#define IS_CONTAINER(ttype) ((ttype) == TTYPE_MAP || (ttype) == TTYPE_LIST || (ttype) == TTYPE_SET)
+#define STRUCT_FIELDS(obj) rb_const_get(CLASS_OF(obj), fields_const_id)
+
+static void set_native_proto_function_pointers(VALUE protocol) {
+  VALUE method_table_object = rb_const_get(CLASS_OF(protocol), rb_intern("@native_method_table"));
+  // TODO: check nil?
+  Data_Get_Struct(method_table_object, native_proto_method_table, mt);
+}
+
+static void check_native_proto_method_table(VALUE protocol) {
+  VALUE protoclass = CLASS_OF(protocol);
+  if (protoclass != last_proto_class) {
+    last_proto_class = protoclass;
+    if (rb_funcall(protocol, native_qmark_method_id, 0) == Qtrue) {
+      set_native_proto_function_pointers(protocol);
+    } else {
+      mt = default_mt;
+    }
+  }
+}
+
+//-------------------------------------------
+// Writing section
+//-------------------------------------------
+
+// default fn pointers for protocol stuff here
+
+VALUE default_write_bool(VALUE protocol, VALUE value) {
+  rb_funcall(protocol, write_boolean_method_id, 1, value);
+  return Qnil;
+}
+
+VALUE default_write_byte(VALUE protocol, VALUE value) {
+  rb_funcall(protocol, write_byte_method_id, 1, value);
+  return Qnil;
+}
+
+VALUE default_write_i16(VALUE protocol, VALUE value) {
+  rb_funcall(protocol, write_i16_method_id, 1, value);
+  return Qnil;
+}
+
+VALUE default_write_i32(VALUE protocol, VALUE value) {
+  rb_funcall(protocol, write_i32_method_id, 1, value);
+  return Qnil;
+}
+
+VALUE default_write_i64(VALUE protocol, VALUE value) {
+  rb_funcall(protocol, write_i64_method_id, 1, value);
+  return Qnil;
+}
+
+VALUE default_write_double(VALUE protocol, VALUE value) {
+  rb_funcall(protocol, write_double_method_id, 1, value);
+  return Qnil;
+}
+
+VALUE default_write_string(VALUE protocol, VALUE value) {
+  rb_funcall(protocol, write_string_method_id, 1, value);
+  return Qnil;
+}
+
+VALUE default_write_list_begin(VALUE protocol, VALUE etype, VALUE length) {
+  rb_funcall(protocol, write_list_begin_method_id, 2, etype, length);
+  return Qnil;
+}
+
+VALUE default_write_list_end(VALUE protocol) {
+  rb_funcall(protocol, write_list_end_method_id, 0);
+  return Qnil;
+}
+
+VALUE default_write_set_begin(VALUE protocol, VALUE etype, VALUE length) {
+  rb_funcall(protocol, write_set_begin_method_id, 2, etype, length);
+  return Qnil;
+}
+
+VALUE default_write_set_end(VALUE protocol) {
+  rb_funcall(protocol, write_set_end_method_id, 0);
+  return Qnil;
+}
+
+VALUE default_write_map_begin(VALUE protocol, VALUE ktype, VALUE vtype, VALUE length) {
+  rb_funcall(protocol, write_map_begin_method_id, 3, ktype, vtype, length);
+  return Qnil;
+}
+
+VALUE default_write_map_end(VALUE protocol) {
+  rb_funcall(protocol, write_map_end_method_id, 0);
+  return Qnil;
+}
+
+VALUE default_write_struct_begin(VALUE protocol, VALUE struct_name) {
+  rb_funcall(protocol, write_struct_begin_method_id, 1, struct_name);
+  return Qnil;
+}
+
+VALUE default_write_struct_end(VALUE protocol) {
+  rb_funcall(protocol, write_struct_end_method_id, 0);
+  return Qnil;
+}
+
+VALUE default_write_field_begin(VALUE protocol, VALUE name, VALUE type, VALUE id) {
+  rb_funcall(protocol, write_field_begin_method_id, 3, name, type, id);
+  return Qnil;
+}
+
+VALUE default_write_field_end(VALUE protocol) {
+  rb_funcall(protocol, write_field_end_method_id, 0);
+  return Qnil;
+}
+
+VALUE default_write_field_stop(VALUE protocol) {
+  rb_funcall(protocol, write_field_stop_method_id, 0);
+  return Qnil;
+}
+
+VALUE default_read_field_begin(VALUE protocol) {
+  return rb_funcall(protocol, read_field_begin_method_id, 0);
+}
+
+VALUE default_read_field_end(VALUE protocol) {
+  return rb_funcall(protocol, read_field_end_method_id, 0);
+}
+
+VALUE default_read_map_begin(VALUE protocol) {
+  return rb_funcall(protocol, read_map_begin_method_id, 0);
+}
+
+VALUE default_read_map_end(VALUE protocol) {
+  return rb_funcall(protocol, read_map_end_method_id, 0);
+}
+
+VALUE default_read_list_begin(VALUE protocol) {
+  return rb_funcall(protocol, read_list_begin_method_id, 0);
+}
+
+VALUE default_read_list_end(VALUE protocol) {
+  return rb_funcall(protocol, read_list_end_method_id, 0);
+}
+
+VALUE default_read_set_begin(VALUE protocol) {
+  return rb_funcall(protocol, read_set_begin_method_id, 0);
+}
+
+VALUE default_read_set_end(VALUE protocol) {
+  return rb_funcall(protocol, read_set_end_method_id, 0);
+}
+
+VALUE default_read_byte(VALUE protocol) {
+  return rb_funcall(protocol, read_byte_method_id, 0);
+}
+
+VALUE default_read_bool(VALUE protocol) {
+  return rb_funcall(protocol, read_bool_method_id, 0);
+}
+
+VALUE default_read_i16(VALUE protocol) {
+  return rb_funcall(protocol, read_i16_method_id, 0);
+}
+
+VALUE default_read_i32(VALUE protocol) {
+  return rb_funcall(protocol, read_i32_method_id, 0);
+}
+
+VALUE default_read_i64(VALUE protocol) {
+  return rb_funcall(protocol, read_i64_method_id, 0);
+}
+
+VALUE default_read_double(VALUE protocol) {
+  return rb_funcall(protocol, read_double_method_id, 0);
+}
+
+VALUE default_read_string(VALUE protocol) {
+  return rb_funcall(protocol, read_string_method_id, 0);
+}
+
+VALUE default_read_struct_begin(VALUE protocol) {
+  return rb_funcall(protocol, read_struct_begin_method_id, 0);
+}
+
+VALUE default_read_struct_end(VALUE protocol) {
+  return rb_funcall(protocol, read_struct_end_method_id, 0);
+}
+
+static void set_default_proto_function_pointers() {
+  default_mt = ALLOC(native_proto_method_table);
+
+  default_mt->write_field_begin = default_write_field_begin;
+  default_mt->write_field_stop = default_write_field_stop;
+  default_mt->write_map_begin = default_write_map_begin;
+  default_mt->write_map_end = default_write_map_end;
+  default_mt->write_list_begin = default_write_list_begin;
+  default_mt->write_list_end = default_write_list_end;
+  default_mt->write_set_begin = default_write_set_begin;
+  default_mt->write_set_end = default_write_set_end;
+  default_mt->write_byte = default_write_byte;
+  default_mt->write_bool = default_write_bool;
+  default_mt->write_i16 = default_write_i16;
+  default_mt->write_i32 = default_write_i32;
+  default_mt->write_i64 = default_write_i64;
+  default_mt->write_double = default_write_double;
+  default_mt->write_string = default_write_string;
+  default_mt->write_struct_begin = default_write_struct_begin;
+  default_mt->write_struct_end = default_write_struct_end;
+  default_mt->write_field_end = default_write_field_end;
+
+  default_mt->read_struct_begin = default_read_struct_begin;
+  default_mt->read_struct_end = default_read_struct_end;
+  default_mt->read_field_begin = default_read_field_begin;
+  default_mt->read_field_end = default_read_field_end;
+  default_mt->read_map_begin = default_read_map_begin;
+  default_mt->read_map_end = default_read_map_end;
+  default_mt->read_list_begin = default_read_list_begin;
+  default_mt->read_list_end = default_read_list_end;
+  default_mt->read_set_begin = default_read_set_begin;
+  default_mt->read_set_end = default_read_set_end;
+  default_mt->read_byte = default_read_byte;
+  default_mt->read_bool = default_read_bool;
+  default_mt->read_i16 = default_read_i16;
+  default_mt->read_i32 = default_read_i32;
+  default_mt->read_i64 = default_read_i64;
+  default_mt->read_double = default_read_double;
+  default_mt->read_string = default_read_string;
+}
+
+// end default protocol methods
+
+
+static VALUE rb_thrift_struct_write(VALUE self, VALUE protocol);
+static void write_anything(int ttype, VALUE value, VALUE protocol, VALUE field_info);
+
+VALUE get_field_value(VALUE obj, VALUE field_name) {
+  char name_buf[RSTRING_LEN(field_name) + 1];
+  
+  name_buf[0] = '@';
+  strlcpy(&name_buf[1], RSTRING_PTR(field_name), sizeof(name_buf));
+
+  VALUE value = rb_ivar_get(obj, rb_intern(name_buf));
+  
+  return value;
+}
+
+static void write_container(int ttype, VALUE field_info, VALUE value, VALUE protocol) {
+  int sz, i;
+  
+  if (ttype == TTYPE_MAP) {
+    VALUE keys;
+    VALUE key;
+    VALUE val;
+
+    Check_Type(value, T_HASH);
+    
+    VALUE key_info = rb_hash_aref(field_info, key_sym);
+    VALUE keytype_value = rb_hash_aref(key_info, type_sym);
+    int keytype = FIX2INT(keytype_value);
+    
+    VALUE value_info = rb_hash_aref(field_info, value_sym);
+    VALUE valuetype_value = rb_hash_aref(value_info, type_sym);
+    int valuetype = FIX2INT(valuetype_value);
+    
+    keys = rb_funcall(value, keys_method_id, 0);
+    
+    sz = RARRAY_LEN(keys);
+    
+    mt->write_map_begin(protocol, keytype_value, valuetype_value, INT2FIX(sz));
+    
+    for (i = 0; i < sz; i++) {
+      key = rb_ary_entry(keys, i);
+      val = rb_hash_aref(value, key);
+      
+      if (IS_CONTAINER(keytype)) {
+        write_container(keytype, key_info, key, protocol);
+      } else {
+        write_anything(keytype, key, protocol, key_info);
+      }
+      
+      if (IS_CONTAINER(valuetype)) {
+        write_container(valuetype, value_info, val, protocol);
+      } else {
+        write_anything(valuetype, val, protocol, value_info);
+      }
+    }
+    
+    mt->write_map_end(protocol);
+  } else if (ttype == TTYPE_LIST) {
+    Check_Type(value, T_ARRAY);
+
+    sz = RARRAY_LEN(value);
+
+    VALUE element_type_info = rb_hash_aref(field_info, element_sym);
+    VALUE element_type_value = rb_hash_aref(element_type_info, type_sym);
+    int element_type = FIX2INT(element_type_value);
+    
+    mt->write_list_begin(protocol, element_type_value, INT2FIX(sz));
+    for (i = 0; i < sz; ++i) {
+      VALUE val = rb_ary_entry(value, i);
+      if (IS_CONTAINER(element_type)) {
+        write_container(element_type, element_type_info, val, protocol);
+      } else {
+        write_anything(element_type, val, protocol, element_type_info);
+      }
+    }
+    mt->write_list_end(protocol);
+  } else if (ttype == TTYPE_SET) {
+    VALUE items;
+
+    if (TYPE(value) == T_ARRAY) {
+      items = value;
+    } else {        
+      if (rb_cSet == CLASS_OF(value)) {
+        items = rb_funcall(value, entries_method_id, 0);
+      } else {
+        Check_Type(value, T_HASH);
+        items = rb_funcall(value, keys_method_id, 0);
+      }
+    }
+
+    sz = RARRAY_LEN(items);
+
+    VALUE element_type_info = rb_hash_aref(field_info, element_sym);
+    VALUE element_type_value = rb_hash_aref(element_type_info, type_sym);
+    int element_type = FIX2INT(element_type_value);
+    
+    mt->write_set_begin(protocol, element_type_value, INT2FIX(sz));
+    
+    for (i = 0; i < sz; i++) {
+      VALUE val = rb_ary_entry(items, i);
+      if (IS_CONTAINER(element_type)) {
+        write_container(element_type, element_type_info, val, protocol);
+      } else {
+        write_anything(element_type, val, protocol, element_type_info);
+      }
+    }
+    
+    mt->write_set_end(protocol);
+  } else {
+    rb_raise(rb_eNotImpError, "can't write container of type: %d", ttype);
+  }
+}
+
+static void write_anything(int ttype, VALUE value, VALUE protocol, VALUE field_info) {
+  if (ttype == TTYPE_BOOL) {
+    mt->write_bool(protocol, value);
+  } else if (ttype == TTYPE_BYTE) {
+    mt->write_byte(protocol, value);
+  } else if (ttype == TTYPE_I16) {
+    mt->write_i16(protocol, value);
+  } else if (ttype == TTYPE_I32) {
+    mt->write_i32(protocol, value);
+  } else if (ttype == TTYPE_I64) {
+    mt->write_i64(protocol, value);
+  } else if (ttype == TTYPE_DOUBLE) {
+    mt->write_double(protocol, value);
+  } else if (ttype == TTYPE_STRING) {
+    mt->write_string(protocol, value);
+  } else if (IS_CONTAINER(ttype)) {
+    write_container(ttype, field_info, value, protocol);
+  } else if (ttype == TTYPE_STRUCT) {
+    rb_thrift_struct_write(value, protocol);
+  } else {
+    rb_raise(rb_eNotImpError, "Unknown type for binary_encoding: %d", ttype);
+  }
+}
+
+static VALUE rb_thrift_struct_write(VALUE self, VALUE protocol) {
+  // call validate
+  rb_funcall(self, validate_method_id, 0);
+
+  check_native_proto_method_table(protocol);
+  
+  // write struct begin
+  mt->write_struct_begin(protocol, rb_class_name(CLASS_OF(self)));
+  
+  // iterate through all the fields here
+  VALUE struct_fields = STRUCT_FIELDS(self);
+  VALUE struct_field_ids_unordered = rb_funcall(struct_fields, keys_method_id, 0);
+  VALUE struct_field_ids_ordered = rb_funcall(struct_field_ids_unordered, sort_method_id, 0);
+  
+  int i = 0;
+  for (i=0; i < RARRAY_LEN(struct_field_ids_ordered); i++) {
+    VALUE field_id = rb_ary_entry(struct_field_ids_ordered, i);
+    VALUE field_info = rb_hash_aref(struct_fields, field_id);
+
+    VALUE ttype_value = rb_hash_aref(field_info, type_sym);
+    int ttype = FIX2INT(ttype_value);
+    VALUE field_name = rb_hash_aref(field_info, name_sym);
+    VALUE field_value = get_field_value(self, field_name);
+
+    if (!NIL_P(field_value)) {
+      mt->write_field_begin(protocol, field_name, ttype_value, field_id);
+      
+      write_anything(ttype, field_value, protocol, field_info);
+      
+      mt->write_field_end(protocol);
+    }
+  }
+  
+  mt->write_field_stop(protocol);
+  
+  // write struct end
+  mt->write_struct_end(protocol);
+  
+  return Qnil;
+}
+
+//-------------------------------------------
+// Reading section
+//-------------------------------------------
+
+static VALUE rb_thrift_struct_read(VALUE self, VALUE protocol);
+
+static void set_field_value(VALUE obj, VALUE field_name, VALUE value) {
+  char name_buf[RSTRING_LEN(field_name) + 1];
+
+  name_buf[0] = '@';
+  strlcpy(&name_buf[1], RSTRING_PTR(field_name), sizeof(name_buf));
+
+  rb_ivar_set(obj, rb_intern(name_buf), value);
+}
+
+static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
+  VALUE result = Qnil;
+  
+  if (ttype == TTYPE_BOOL) {
+    result = mt->read_bool(protocol);
+  } else if (ttype == TTYPE_BYTE) {
+    result = mt->read_byte(protocol);
+  } else if (ttype == TTYPE_I16) {
+    result = mt->read_i16(protocol);
+  } else if (ttype == TTYPE_I32) {
+    result = mt->read_i32(protocol);
+  } else if (ttype == TTYPE_I64) {
+    result = mt->read_i64(protocol);
+  } else if (ttype == TTYPE_STRING) {
+    result = mt->read_string(protocol);
+  } else if (ttype == TTYPE_DOUBLE) {
+    result = mt->read_double(protocol);
+  } else if (ttype == TTYPE_STRUCT) {
+    VALUE klass = rb_hash_aref(field_info, class_sym);
+    result = rb_class_new_instance(0, NULL, klass);
+    rb_thrift_struct_read(result, protocol);
+  } else if (ttype == TTYPE_MAP) {
+    int i;
+
+    VALUE map_header = mt->read_map_begin(protocol);
+    int key_ttype = FIX2INT(rb_ary_entry(map_header, 0));
+    int value_ttype = FIX2INT(rb_ary_entry(map_header, 1));
+    int num_entries = FIX2INT(rb_ary_entry(map_header, 2));
+    
+    VALUE key_info = rb_hash_aref(field_info, key_sym);
+    VALUE value_info = rb_hash_aref(field_info, value_sym);
+
+    result = rb_hash_new();
+    
+    for (i = 0; i < num_entries; ++i) {
+      VALUE key, val;
+      
+      key = read_anything(protocol, key_ttype, key_info);
+      val = read_anything(protocol, value_ttype, value_info);
+      
+      rb_hash_aset(result, key, val);
+    }
+    
+    mt->read_map_end(protocol);
+  } else if (ttype == TTYPE_LIST) {
+    int i;
+
+    VALUE list_header = mt->read_list_begin(protocol);
+    int element_ttype = FIX2INT(rb_ary_entry(list_header, 0));
+    int num_elements = FIX2INT(rb_ary_entry(list_header, 1));
+    result = rb_ary_new2(num_elements);
+    
+    for (i = 0; i < num_elements; ++i) {
+      rb_ary_push(result, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym)));
+    }
+
+
+    mt->read_list_end(protocol);
+  } else if (ttype == TTYPE_SET) {
+    VALUE items;
+    int i;
+
+    VALUE set_header = mt->read_set_begin(protocol);
+    int element_ttype = FIX2INT(rb_ary_entry(set_header, 0));
+    int num_elements = FIX2INT(rb_ary_entry(set_header, 1));
+    items = rb_ary_new2(num_elements);
+    
+    for (i = 0; i < num_elements; ++i) {
+      rb_ary_push(items, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym)));
+    }
+    
+
+    mt->read_set_end(protocol);
+    
+    result = rb_class_new_instance(1, &items, rb_cSet);
+  } else {
+    rb_raise(rb_eNotImpError, "read_anything not implemented for type %d!", ttype);
+  }
+  
+  return result;
+}
+
+static VALUE rb_thrift_struct_read(VALUE self, VALUE protocol) {
+  check_native_proto_method_table(protocol);
+  
+  // read struct begin
+  mt->read_struct_begin(protocol);
+
+  VALUE struct_fields = STRUCT_FIELDS(self);
+  
+  // read each field
+  while (true) {
+    VALUE field_header = mt->read_field_begin(protocol);
+    VALUE field_type_value = rb_ary_entry(field_header, 1);
+    int field_type = FIX2INT(field_type_value);
+    
+    if (field_type == TTYPE_STOP) {
+      break;
+    }
+
+    // make sure we got a type we expected
+    VALUE field_info = rb_hash_aref(struct_fields, rb_ary_entry(field_header, 2));
+
+    if (!NIL_P(field_info)) {
+      int specified_type = FIX2INT(rb_hash_aref(field_info, type_sym));
+      if (field_type == specified_type) {
+        // read the value
+        VALUE name = rb_hash_aref(field_info, name_sym);
+        set_field_value(self, name, read_anything(protocol, field_type, field_info));
+      } else {
+        rb_funcall(protocol, skip_method_id, 1, field_type_value);
+      }
+    } else {
+      rb_funcall(protocol, skip_method_id, 1, field_type_value);
+    }
+    
+    // read field end
+    mt->read_field_end(protocol);
+  }
+  
+  // read struct end
+  mt->read_struct_end(protocol);
+  
+  return Qnil;
+}
+
+void Init_struct() {
+  VALUE struct_module = rb_const_get(thrift_module, rb_intern("Struct"));
+  
+  rb_define_method(struct_module, "write", rb_thrift_struct_write, 1);
+  rb_define_method(struct_module, "read", rb_thrift_struct_read, 1);
+  
+  set_default_proto_function_pointers();
+}
+
diff --git a/lib/rb/ext/struct.h b/lib/rb/ext/struct.h
new file mode 100644
index 0000000..37b1b35
--- /dev/null
+++ b/lib/rb/ext/struct.h
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <stdbool.h>
+#include <ruby.h>
+
+typedef struct native_proto_method_table {
+  VALUE (*write_bool)(VALUE, VALUE);
+  VALUE (*write_byte)(VALUE, VALUE);
+  VALUE (*write_i16)(VALUE, VALUE);
+  VALUE (*write_i32)(VALUE, VALUE);
+  VALUE (*write_i64)(VALUE, VALUE);
+  VALUE (*write_double)(VALUE, VALUE);
+  VALUE (*write_string)(VALUE, VALUE);
+  VALUE (*write_list_begin)(VALUE, VALUE, VALUE);
+  VALUE (*write_list_end)(VALUE);
+  VALUE (*write_set_begin)(VALUE, VALUE, VALUE);
+  VALUE (*write_set_end)(VALUE);
+  VALUE (*write_map_begin)(VALUE, VALUE, VALUE, VALUE);
+  VALUE (*write_map_end)(VALUE);
+  VALUE (*write_struct_begin)(VALUE, VALUE);
+  VALUE (*write_struct_end)(VALUE);
+  VALUE (*write_field_begin)(VALUE, VALUE, VALUE, VALUE);
+  VALUE (*write_field_end)(VALUE);
+  VALUE (*write_field_stop)(VALUE);
+  VALUE (*write_message_begin)(VALUE, VALUE, VALUE, VALUE);
+  VALUE (*write_message_end)(VALUE);
+  
+  VALUE (*read_message_begin)(VALUE);
+  VALUE (*read_message_end)(VALUE);
+  VALUE (*read_field_begin)(VALUE);
+  VALUE (*read_field_end)(VALUE);
+  VALUE (*read_map_begin)(VALUE);
+  VALUE (*read_map_end)(VALUE);
+  VALUE (*read_list_begin)(VALUE);
+  VALUE (*read_list_end)(VALUE);
+  VALUE (*read_set_begin)(VALUE);
+  VALUE (*read_set_end)(VALUE);
+  VALUE (*read_byte)(VALUE);
+  VALUE (*read_bool)(VALUE);
+  VALUE (*read_i16)(VALUE);
+  VALUE (*read_i32)(VALUE);
+  VALUE (*read_i64)(VALUE);
+  VALUE (*read_double)(VALUE);
+  VALUE (*read_string)(VALUE);
+  VALUE (*read_struct_begin)(VALUE);
+  VALUE (*read_struct_end)(VALUE);
+  
+} native_proto_method_table;
+
+void Init_struct();
diff --git a/lib/rb/ext/thrift_native.c b/lib/rb/ext/thrift_native.c
new file mode 100644
index 0000000..effa202
--- /dev/null
+++ b/lib/rb/ext/thrift_native.c
@@ -0,0 +1,194 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <ruby.h>
+#include <struct.h>
+#include <binary_protocol_accelerated.h>
+#include <compact_protocol.h>
+#include <protocol.h>
+#include <memory_buffer.h>
+
+// cached classes/modules
+VALUE rb_cSet;
+VALUE thrift_module;
+VALUE thrift_types_module;
+
+// TType constants
+int TTYPE_STOP;
+int TTYPE_BOOL;
+int TTYPE_BYTE;
+int TTYPE_I16;
+int TTYPE_I32;
+int TTYPE_I64;
+int TTYPE_DOUBLE;
+int TTYPE_STRING;
+int TTYPE_MAP;
+int TTYPE_SET;
+int TTYPE_LIST;
+int TTYPE_STRUCT;
+
+// method ids
+ID validate_method_id;
+ID write_struct_begin_method_id;
+ID write_struct_end_method_id;
+ID write_field_begin_method_id;
+ID write_field_end_method_id;
+ID write_boolean_method_id;
+ID write_byte_method_id;
+ID write_i16_method_id;
+ID write_i32_method_id;
+ID write_i64_method_id;
+ID write_double_method_id;
+ID write_string_method_id;
+ID write_map_begin_method_id;
+ID write_map_end_method_id;
+ID write_list_begin_method_id;
+ID write_list_end_method_id;
+ID write_set_begin_method_id;
+ID write_set_end_method_id;
+ID size_method_id;
+ID read_bool_method_id;
+ID read_byte_method_id;
+ID read_i16_method_id;
+ID read_i32_method_id;
+ID read_i64_method_id;
+ID read_string_method_id;
+ID read_double_method_id;
+ID read_map_begin_method_id;
+ID read_map_end_method_id;
+ID read_list_begin_method_id;
+ID read_list_end_method_id;
+ID read_set_begin_method_id;
+ID read_set_end_method_id;
+ID read_struct_begin_method_id;
+ID read_struct_end_method_id;
+ID read_field_begin_method_id;
+ID read_field_end_method_id;
+ID keys_method_id;
+ID entries_method_id; 
+ID name_method_id; 
+ID sort_method_id;
+ID write_field_stop_method_id;
+ID skip_method_id;
+ID write_method_id;
+ID read_all_method_id;
+ID native_qmark_method_id;
+
+// constant ids
+ID fields_const_id;
+ID transport_ivar_id;
+ID strict_read_ivar_id;
+ID strict_write_ivar_id;
+
+// cached symbols
+VALUE type_sym;
+VALUE name_sym;
+VALUE key_sym;
+VALUE value_sym;
+VALUE element_sym;
+VALUE class_sym;
+VALUE protocol_exception_class;
+
+void Init_thrift_native() {
+  // cached classes
+  thrift_module = rb_const_get(rb_cObject, rb_intern("Thrift"));
+  thrift_types_module = rb_const_get(thrift_module, rb_intern("Types"));
+  rb_cSet = rb_const_get(rb_cObject, rb_intern("Set"));
+  protocol_exception_class = rb_const_get(thrift_module, rb_intern("ProtocolException"));
+  
+  // Init ttype constants
+  TTYPE_BOOL = FIX2INT(rb_const_get(thrift_types_module, rb_intern("BOOL")));
+  TTYPE_BYTE = FIX2INT(rb_const_get(thrift_types_module, rb_intern("BYTE")));
+  TTYPE_I16 = FIX2INT(rb_const_get(thrift_types_module, rb_intern("I16")));
+  TTYPE_I32 = FIX2INT(rb_const_get(thrift_types_module, rb_intern("I32")));
+  TTYPE_I64 = FIX2INT(rb_const_get(thrift_types_module, rb_intern("I64")));
+  TTYPE_DOUBLE = FIX2INT(rb_const_get(thrift_types_module, rb_intern("DOUBLE")));
+  TTYPE_STRING = FIX2INT(rb_const_get(thrift_types_module, rb_intern("STRING")));
+  TTYPE_MAP = FIX2INT(rb_const_get(thrift_types_module, rb_intern("MAP")));
+  TTYPE_SET = FIX2INT(rb_const_get(thrift_types_module, rb_intern("SET")));
+  TTYPE_LIST = FIX2INT(rb_const_get(thrift_types_module, rb_intern("LIST")));
+  TTYPE_STRUCT = FIX2INT(rb_const_get(thrift_types_module, rb_intern("STRUCT")));
+
+  // method ids
+  validate_method_id = rb_intern("validate");
+  write_struct_begin_method_id = rb_intern("write_struct_begin");
+  write_struct_end_method_id = rb_intern("write_struct_end");
+  write_field_begin_method_id = rb_intern("write_field_begin");
+  write_field_end_method_id = rb_intern("write_field_end");
+  write_boolean_method_id = rb_intern("write_bool");
+  write_byte_method_id = rb_intern("write_byte");
+  write_i16_method_id = rb_intern("write_i16");
+  write_i32_method_id = rb_intern("write_i32");
+  write_i64_method_id = rb_intern("write_i64");
+  write_double_method_id = rb_intern("write_double");
+  write_string_method_id = rb_intern("write_string");
+  write_map_begin_method_id = rb_intern("write_map_begin");
+  write_map_end_method_id = rb_intern("write_map_end");
+  write_list_begin_method_id = rb_intern("write_list_begin");
+  write_list_end_method_id = rb_intern("write_list_end");
+  write_set_begin_method_id = rb_intern("write_set_begin");
+  write_set_end_method_id = rb_intern("write_set_end");
+  size_method_id = rb_intern("size");
+  read_bool_method_id = rb_intern("read_bool");
+  read_byte_method_id = rb_intern("read_byte");
+  read_i16_method_id = rb_intern("read_i16");
+  read_i32_method_id = rb_intern("read_i32");
+  read_i64_method_id = rb_intern("read_i64");
+  read_string_method_id = rb_intern("read_string");
+  read_double_method_id = rb_intern("read_double");
+  read_map_begin_method_id = rb_intern("read_map_begin");
+  read_map_end_method_id = rb_intern("read_map_end");  
+  read_list_begin_method_id = rb_intern("read_list_begin");
+  read_list_end_method_id = rb_intern("read_list_end");
+  read_set_begin_method_id = rb_intern("read_set_begin");
+  read_set_end_method_id = rb_intern("read_set_end");
+  read_struct_begin_method_id = rb_intern("read_struct_begin");
+  read_struct_end_method_id = rb_intern("read_struct_end");
+  read_field_begin_method_id = rb_intern("read_field_begin");
+  read_field_end_method_id = rb_intern("read_field_end");
+  keys_method_id = rb_intern("keys");
+  entries_method_id = rb_intern("entries");
+  name_method_id = rb_intern("name");
+  sort_method_id = rb_intern("sort");
+  write_field_stop_method_id = rb_intern("write_field_stop");
+  skip_method_id = rb_intern("skip");
+  write_method_id = rb_intern("write");
+  read_all_method_id = rb_intern("read_all");
+  native_qmark_method_id = rb_intern("native?");
+  
+  // constant ids
+  fields_const_id = rb_intern("FIELDS");
+  transport_ivar_id = rb_intern("@trans");
+  strict_read_ivar_id = rb_intern("@strict_read");
+  strict_write_ivar_id = rb_intern("@strict_write");  
+  
+  // cached symbols
+  type_sym = ID2SYM(rb_intern("type"));
+  name_sym = ID2SYM(rb_intern("name"));
+  key_sym = ID2SYM(rb_intern("key"));
+  value_sym = ID2SYM(rb_intern("value"));
+  element_sym = ID2SYM(rb_intern("element"));
+  class_sym = ID2SYM(rb_intern("class"));
+
+  Init_protocol();
+  Init_struct();
+  Init_binary_protocol_accelerated();
+  Init_compact_protocol();
+  Init_memory_buffer();
+}
diff --git a/lib/rb/lib/thrift.rb b/lib/rb/lib/thrift.rb
new file mode 100644
index 0000000..88562e1
--- /dev/null
+++ b/lib/rb/lib/thrift.rb
@@ -0,0 +1,59 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+$:.unshift File.dirname(__FILE__)
+
+require 'thrift/core_ext'
+require 'thrift/exceptions'
+require 'thrift/types'
+require 'thrift/processor'
+require 'thrift/client'
+require 'thrift/struct'
+
+# serializer
+require 'thrift/serializer/serializer'
+require 'thrift/serializer/deserializer'
+
+# protocol
+require 'thrift/protocol/base_protocol'
+require 'thrift/protocol/binary_protocol'
+require 'thrift/protocol/binary_protocol_accelerated'
+require 'thrift/protocol/compact_protocol'
+
+# transport
+require 'thrift/transport/base_transport'
+require 'thrift/transport/base_server_transport'
+require 'thrift/transport/socket'
+require 'thrift/transport/server_socket'
+require 'thrift/transport/unix_socket'
+require 'thrift/transport/unix_server_socket'
+require 'thrift/transport/buffered_transport'
+require 'thrift/transport/framed_transport'
+require 'thrift/transport/http_client_transport'
+require 'thrift/transport/io_stream_transport'
+require 'thrift/transport/memory_buffer_transport'
+
+# server
+require 'thrift/server/base_server'
+require 'thrift/server/nonblocking_server'
+require 'thrift/server/simple_server'
+require 'thrift/server/threaded_server'
+require 'thrift/server/thread_pool_server'
+
+require 'thrift/thrift_native'
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/client.rb b/lib/rb/lib/thrift/client.rb
new file mode 100644
index 0000000..5b30f01
--- /dev/null
+++ b/lib/rb/lib/thrift/client.rb
@@ -0,0 +1,62 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  module Client
+    def initialize(iprot, oprot=nil)
+      @iprot = iprot
+      @oprot = oprot || iprot
+      @seqid = 0
+    end
+
+    def send_message(name, args_class, args = {})
+      @oprot.write_message_begin(name, MessageTypes::CALL, @seqid)
+      data = args_class.new
+      args.each do |k, v|
+        data.send("#{k.to_s}=", v)
+      end
+      begin
+        data.write(@oprot)
+      rescue StandardError => e
+        @oprot.trans.close
+        raise e
+      end
+      @oprot.write_message_end
+      @oprot.trans.flush
+    end
+
+    def receive_message(result_klass)
+      fname, mtype, rseqid = @iprot.read_message_begin
+      handle_exception(mtype)
+      result = result_klass.new
+      result.read(@iprot)
+      @iprot.read_message_end
+      result
+    end
+
+    def handle_exception(mtype)
+      if mtype == MessageTypes::EXCEPTION
+        x = ApplicationException.new
+        x.read(@iprot)
+        @iprot.read_message_end
+        raise x
+      end
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/core_ext.rb b/lib/rb/lib/thrift/core_ext.rb
new file mode 100644
index 0000000..f763cd5
--- /dev/null
+++ b/lib/rb/lib/thrift/core_ext.rb
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each do |file|
+  name = File.basename(file, '.rb')
+  require "thrift/core_ext/#{name}"
+end
diff --git a/lib/rb/lib/thrift/core_ext/fixnum.rb b/lib/rb/lib/thrift/core_ext/fixnum.rb
new file mode 100644
index 0000000..b4fc90d
--- /dev/null
+++ b/lib/rb/lib/thrift/core_ext/fixnum.rb
@@ -0,0 +1,29 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Versions of ruby pre 1.8.7 do not have an .ord method available in the Fixnum
+# class.
+#
+if RUBY_VERSION < "1.8.7"
+  class Fixnum
+    def ord
+      self
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/exceptions.rb b/lib/rb/lib/thrift/exceptions.rb
new file mode 100644
index 0000000..dda7089
--- /dev/null
+++ b/lib/rb/lib/thrift/exceptions.rb
@@ -0,0 +1,82 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  class Exception < StandardError
+    def initialize(message)
+      super
+      @message = message
+    end
+
+    attr_reader :message
+  end
+
+  class ApplicationException < Exception
+
+    UNKNOWN = 0
+    UNKNOWN_METHOD = 1
+    INVALID_MESSAGE_TYPE = 2
+    WRONG_METHOD_NAME = 3
+    BAD_SEQUENCE_ID = 4
+    MISSING_RESULT = 5
+
+    attr_reader :type
+
+    def initialize(type=UNKNOWN, message=nil)
+      super(message)
+      @type = type
+    end
+
+    def read(iprot)
+      iprot.read_struct_begin
+      while true
+        fname, ftype, fid = iprot.read_field_begin
+        if ftype == Types::STOP
+          break
+        end
+        if fid == 1 and ftype == Types::STRING
+          @message = iprot.read_string
+        elsif fid == 2 and ftype == Types::I32
+          @type = iprot.read_i32
+        else
+          iprot.skip(ftype)
+        end
+        iprot.read_field_end
+      end
+      iprot.read_struct_end
+    end
+
+    def write(oprot)
+      oprot.write_struct_begin('Thrift::ApplicationException')
+      unless @message.nil?
+        oprot.write_field_begin('message', Types::STRING, 1)
+        oprot.write_string(@message)
+        oprot.write_field_end
+      end
+      unless @type.nil?
+        oprot.write_field_begin('type', Types::I32, 2)
+        oprot.write_i32(@type)
+        oprot.write_field_end
+      end
+      oprot.write_field_stop
+      oprot.write_struct_end
+    end
+
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/processor.rb b/lib/rb/lib/thrift/processor.rb
new file mode 100644
index 0000000..5d9e0a1
--- /dev/null
+++ b/lib/rb/lib/thrift/processor.rb
@@ -0,0 +1,57 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  module Processor
+    def initialize(handler)
+      @handler = handler
+    end
+
+    def process(iprot, oprot)
+      name, type, seqid  = iprot.read_message_begin
+      if respond_to?("process_#{name}")
+        send("process_#{name}", seqid, iprot, oprot)
+        true
+      else
+        iprot.skip(Types::STRUCT)
+        iprot.read_message_end
+        x = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, 'Unknown function '+name)
+        oprot.write_message_begin(name, MessageTypes::EXCEPTION, seqid)
+        x.write(oprot)
+        oprot.write_message_end
+        oprot.trans.flush
+        false
+      end
+    end
+
+    def read_args(iprot, args_class)
+      args = args_class.new
+      args.read(iprot)
+      iprot.read_message_end
+      args
+    end
+
+    def write_result(result, oprot, name, seqid)
+      oprot.write_message_begin(name, MessageTypes::REPLY, seqid)
+      result.write(oprot)
+      oprot.write_message_end
+      oprot.trans.flush
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/protocol/base_protocol.rb b/lib/rb/lib/thrift/protocol/base_protocol.rb
new file mode 100644
index 0000000..b19909d
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/base_protocol.rb
@@ -0,0 +1,290 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+# this require is to make generated struct definitions happy
+require 'set'
+
+module Thrift
+  class ProtocolException < Exception
+
+    UNKNOWN = 0
+    INVALID_DATA = 1
+    NEGATIVE_SIZE = 2
+    SIZE_LIMIT = 3
+    BAD_VERSION = 4
+
+    attr_reader :type
+
+    def initialize(type=UNKNOWN, message=nil)
+      super(message)
+      @type = type
+    end
+  end
+
+  class BaseProtocol
+
+    attr_reader :trans
+
+    def initialize(trans)
+      @trans = trans
+    end
+
+    def native?
+      puts "wrong method is being called!"
+      false
+    end
+
+    def write_message_begin(name, type, seqid)
+      raise NotImplementedError
+    end
+
+    def write_message_end; nil; end
+
+    def write_struct_begin(name)
+      raise NotImplementedError
+    end
+
+    def write_struct_end; nil; end
+
+    def write_field_begin(name, type, id)
+      raise NotImplementedError
+    end
+
+    def write_field_end; nil; end
+
+    def write_field_stop
+      raise NotImplementedError
+    end
+
+    def write_map_begin(ktype, vtype, size)
+      raise NotImplementedError
+    end
+
+    def write_map_end; nil; end
+
+    def write_list_begin(etype, size)
+      raise NotImplementedError
+    end
+
+    def write_list_end; nil; end
+
+    def write_set_begin(etype, size)
+      raise NotImplementedError
+    end
+
+    def write_set_end; nil; end
+
+    def write_bool(bool)
+      raise NotImplementedError
+    end
+
+    def write_byte(byte)
+      raise NotImplementedError
+    end
+
+    def write_i16(i16)
+      raise NotImplementedError
+    end
+
+    def write_i32(i32)
+      raise NotImplementedError
+    end
+
+    def write_i64(i64)
+      raise NotImplementedError
+    end
+
+    def write_double(dub)
+      raise NotImplementedError
+    end
+
+    def write_string(str)
+      raise NotImplementedError
+    end
+
+    def read_message_begin
+      raise NotImplementedError
+    end
+
+    def read_message_end; nil; end
+
+    def read_struct_begin
+      raise NotImplementedError
+    end
+
+    def read_struct_end; nil; end
+
+    def read_field_begin
+      raise NotImplementedError
+    end
+
+    def read_field_end; nil; end
+
+    def read_map_begin
+      raise NotImplementedError
+    end
+
+    def read_map_end; nil; end
+
+    def read_list_begin
+      raise NotImplementedError
+    end
+
+    def read_list_end; nil; end
+
+    def read_set_begin
+      raise NotImplementedError
+    end
+
+    def read_set_end; nil; end
+
+    def read_bool
+      raise NotImplementedError
+    end
+
+    def read_byte
+      raise NotImplementedError
+    end
+
+    def read_i16
+      raise NotImplementedError
+    end
+
+    def read_i32
+      raise NotImplementedError
+    end
+
+    def read_i64
+      raise NotImplementedError
+    end
+
+    def read_double
+      raise NotImplementedError
+    end
+
+    def read_string
+      raise NotImplementedError
+    end
+
+    def write_field(name, type, fid, value)
+      write_field_begin(name, type, fid)
+      write_type(type, value)
+      write_field_end
+    end
+
+    def write_type(type, value)
+      case type
+      when Types::BOOL
+        write_bool(value)
+      when Types::BYTE
+        write_byte(value)
+      when Types::DOUBLE
+        write_double(value)
+      when Types::I16
+        write_i16(value)
+      when Types::I32
+        write_i32(value)
+      when Types::I64
+        write_i64(value)
+      when Types::STRING
+        write_string(value)
+      when Types::STRUCT
+        value.write(self)
+      else
+        raise NotImplementedError
+      end
+    end
+
+    def read_type(type)
+      case type
+      when Types::BOOL
+        read_bool
+      when Types::BYTE
+        read_byte
+      when Types::DOUBLE
+        read_double
+      when Types::I16
+        read_i16
+      when Types::I32
+        read_i32
+      when Types::I64
+        read_i64
+      when Types::STRING
+        read_string
+      else
+        raise NotImplementedError
+      end
+    end
+
+    def skip(type)
+      case type
+      when Types::STOP
+        nil
+      when Types::BOOL
+        read_bool
+      when Types::BYTE
+        read_byte
+      when Types::I16
+        read_i16
+      when Types::I32
+        read_i32
+      when Types::I64
+        read_i64
+      when Types::DOUBLE
+        read_double
+      when Types::STRING
+        read_string
+      when Types::STRUCT
+        read_struct_begin
+        while true
+          name, type, id = read_field_begin
+          break if type == Types::STOP
+          skip(type)
+          read_field_end
+        end
+        read_struct_end
+      when Types::MAP
+        ktype, vtype, size = read_map_begin
+        size.times do
+          skip(ktype)
+          skip(vtype)
+        end
+        read_map_end
+      when Types::SET
+        etype, size = read_set_begin
+        size.times do
+          skip(etype)
+        end
+        read_set_end
+      when Types::LIST
+        etype, size = read_list_begin
+        size.times do
+          skip(etype)
+        end
+        read_list_end
+      end
+    end
+  end
+
+  class BaseProtocolFactory
+    def get_protocol(trans)
+      raise NotImplementedError
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/protocol/binary_protocol.rb b/lib/rb/lib/thrift/protocol/binary_protocol.rb
new file mode 100644
index 0000000..04d149a
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/binary_protocol.rb
@@ -0,0 +1,225 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  class BinaryProtocol < BaseProtocol
+    VERSION_MASK = 0xffff0000
+    VERSION_1 = 0x80010000
+    TYPE_MASK = 0x000000ff
+    
+    attr_reader :strict_read, :strict_write
+
+    def initialize(trans, strict_read=true, strict_write=true)
+      super(trans)
+      @strict_read = strict_read
+      @strict_write = strict_write
+    end
+
+    def write_message_begin(name, type, seqid)
+      # this is necessary because we added (needed) bounds checking to 
+      # write_i32, and 0x80010000 is too big for that.
+      if strict_write
+        write_i16(VERSION_1 >> 16)
+        write_i16(type)
+        write_string(name)
+        write_i32(seqid)
+      else
+        write_string(name)
+        write_byte(type)
+        write_i32(seqid)
+      end
+    end
+
+    def write_struct_begin(name); nil; end
+
+    def write_field_begin(name, type, id)
+      write_byte(type)
+      write_i16(id)
+    end
+
+    def write_field_stop
+      write_byte(Thrift::Types::STOP)
+    end
+
+    def write_map_begin(ktype, vtype, size)
+      write_byte(ktype)
+      write_byte(vtype)
+      write_i32(size)
+    end
+
+    def write_list_begin(etype, size)
+      write_byte(etype)
+      write_i32(size)
+    end
+
+    def write_set_begin(etype, size)
+      write_byte(etype)
+      write_i32(size)
+    end
+
+    def write_bool(bool)
+      write_byte(bool ? 1 : 0)
+    end
+
+    def write_byte(byte)
+      raise RangeError if byte < -2**31 || byte >= 2**32
+      trans.write([byte].pack('c'))
+    end
+
+    def write_i16(i16)
+      trans.write([i16].pack('n'))
+    end
+
+    def write_i32(i32)
+      raise RangeError if i32 < -2**31 || i32 >= 2**31
+      trans.write([i32].pack('N'))
+    end
+
+    def write_i64(i64)
+      raise RangeError if i64 < -2**63 || i64 >= 2**64
+      hi = i64 >> 32
+      lo = i64 & 0xffffffff
+      trans.write([hi, lo].pack('N2'))
+    end
+
+    def write_double(dub)
+      trans.write([dub].pack('G'))
+    end
+
+    def write_string(str)
+      write_i32(str.length)
+      trans.write(str)
+    end
+
+    def read_message_begin
+      version = read_i32
+      if version < 0
+        if (version & VERSION_MASK != VERSION_1)
+          raise ProtocolException.new(ProtocolException::BAD_VERSION, 'Missing version identifier')
+        end
+        type = version & TYPE_MASK
+        name = read_string
+        seqid = read_i32
+        [name, type, seqid]
+      else
+        if strict_read
+          raise ProtocolException.new(ProtocolException::BAD_VERSION, 'No version identifier, old protocol client?')
+        end
+        name = trans.read_all(version)
+        type = read_byte
+        seqid = read_i32
+        [name, type, seqid]
+      end
+    end
+
+    def read_struct_begin; nil; end
+
+    def read_field_begin
+      type = read_byte
+      if (type == Types::STOP)
+        [nil, type, 0]
+      else
+        id = read_i16
+        [nil, type, id]
+      end
+    end
+
+    def read_map_begin
+      ktype = read_byte
+      vtype = read_byte
+      size = read_i32
+      [ktype, vtype, size]
+    end
+
+    def read_list_begin
+      etype = read_byte
+      size = read_i32
+      [etype, size]
+    end
+
+    def read_set_begin
+      etype = read_byte
+      size = read_i32
+      [etype, size]
+    end
+
+    def read_bool
+      byte = read_byte
+      byte != 0
+    end
+
+    def read_byte
+      dat = trans.read_all(1)
+      val = dat[0].ord
+      if (val > 0x7f)
+        val = 0 - ((val - 1) ^ 0xff)
+      end
+      val
+    end
+
+    def read_i16
+      dat = trans.read_all(2)
+      val, = dat.unpack('n')
+      if (val > 0x7fff)
+        val = 0 - ((val - 1) ^ 0xffff)
+      end
+      val
+    end
+
+    def read_i32
+      dat = trans.read_all(4)
+      val, = dat.unpack('N')
+      if (val > 0x7fffffff)
+        val = 0 - ((val - 1) ^ 0xffffffff)
+      end
+      val
+    end
+
+    def read_i64
+      dat = trans.read_all(8)
+      hi, lo = dat.unpack('N2')
+      if (hi > 0x7fffffff)
+        hi ^= 0xffffffff
+        lo ^= 0xffffffff
+        0 - (hi << 32) - lo - 1
+      else
+        (hi << 32) + lo
+      end
+    end
+
+    def read_double
+      dat = trans.read_all(8)
+      val = dat.unpack('G').first
+      val
+    end
+
+    def read_string
+      sz = read_i32
+      dat = trans.read_all(sz)
+      dat
+    end
+
+  end
+
+  class BinaryProtocolFactory < BaseProtocolFactory
+    def get_protocol(trans)
+      return Thrift::BinaryProtocol.new(trans)
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb b/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb
new file mode 100644
index 0000000..eaf64f6
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/binary_protocol_accelerated.rb
@@ -0,0 +1,35 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+=begin
+The only change required for a transport to support BinaryProtocolAccelerated 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 MemoryBuffer and BufferedTransport for examples.
+=end
+
+module Thrift
+  class BinaryProtocolAcceleratedFactory < BaseProtocolFactory
+    def get_protocol(trans)
+      BinaryProtocolAccelerated.new(trans)
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/protocol/compact_protocol.rb b/lib/rb/lib/thrift/protocol/compact_protocol.rb
new file mode 100644
index 0000000..c8f4365
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/compact_protocol.rb
@@ -0,0 +1,422 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  class CompactProtocol < BaseProtocol
+
+    PROTOCOL_ID = [0x82].pack('c').unpack('c').first
+    VERSION = 1
+    VERSION_MASK = 0x1f
+    TYPE_MASK = 0xE0
+    TYPE_SHIFT_AMOUNT = 5
+
+    TSTOP = ["", Types::STOP, 0]
+
+    # 
+    # All of the on-wire type codes.
+    # 
+    class CompactTypes
+      BOOLEAN_TRUE   = 0x01
+      BOOLEAN_FALSE  = 0x02
+      BYTE           = 0x03
+      I16            = 0x04
+      I32            = 0x05
+      I64            = 0x06
+      DOUBLE         = 0x07
+      BINARY         = 0x08
+      LIST           = 0x09
+      SET            = 0x0A
+      MAP            = 0x0B
+      STRUCT         = 0x0C
+      
+      def self.is_bool_type?(b)
+        (b & 0x0f) == BOOLEAN_TRUE || (b & 0x0f) == BOOLEAN_FALSE
+      end
+      
+      COMPACT_TO_TTYPE = {
+        Types::STOP   => Types::STOP,
+        BOOLEAN_FALSE => Types::BOOL,
+        BOOLEAN_TRUE  => Types::BOOL,
+        BYTE          => Types::BYTE,
+        I16           => Types::I16,
+        I32           => Types::I32,
+        I64           => Types::I64,
+        DOUBLE        => Types::DOUBLE,
+        BINARY        => Types::STRING,
+        LIST          => Types::LIST,
+        SET           => Types::SET,
+        MAP           => Types::MAP,
+        STRUCT        => Types::STRUCT
+      }
+
+      TTYPE_TO_COMPACT = {
+        Types::STOP           => Types::STOP,
+        Types::BOOL           => BOOLEAN_TRUE,
+        Types::BYTE           => BYTE,
+        Types::I16            => I16,
+        Types::I32            => I32,
+        Types::I64            => I64,
+        Types::DOUBLE         => DOUBLE,
+        Types::STRING         => BINARY,
+        Types::LIST           => LIST,
+        Types::SET            => SET,
+        Types::MAP            => MAP,
+        Types::STRUCT         => STRUCT
+      }
+      
+      def self.get_ttype(compact_type)
+        val = COMPACT_TO_TTYPE[compact_type & 0x0f]
+        raise "don't know what type: #{compact_type & 0x0f}" unless val
+        val
+      end
+      
+      def self.get_compact_type(ttype)
+        val = TTYPE_TO_COMPACT[ttype]
+        raise "don't know what type: #{ttype & 0x0f}" unless val
+        val
+      end
+    end
+
+    def initialize(transport)
+      super(transport)
+
+      @last_field = [0]
+      @boolean_value = nil
+    end
+
+    def write_message_begin(name, type, seqid)
+      write_byte(PROTOCOL_ID)
+      write_byte((VERSION & VERSION_MASK) | ((type << TYPE_SHIFT_AMOUNT) & TYPE_MASK))
+      write_varint32(seqid)
+      write_string(name)
+      nil
+    end
+
+    def write_struct_begin(name)
+      @last_field.push(0)
+      nil
+    end
+
+    def write_struct_end
+      @last_field.pop
+      nil
+    end
+
+    def write_field_begin(name, type, id)
+      if type == Types::BOOL
+        # we want to possibly include the value, so we'll wait.
+        @boolean_field = [type, id]
+      else
+        write_field_begin_internal(type, id)
+      end
+      nil
+    end
+
+    # 
+    # The workhorse of writeFieldBegin. It has the option of doing a 
+    # 'type override' of the type header. This is used specifically in the 
+    # boolean field case.
+    # 
+    def write_field_begin_internal(type, id, type_override=nil)
+      last_id = @last_field.pop
+      
+      # if there's a type override, use that.
+      typeToWrite = type_override || CompactTypes.get_compact_type(type)
+
+      # check if we can use delta encoding for the field id
+      if id > last_id && id - last_id <= 15
+        # write them together
+        write_byte((id - last_id) << 4 | typeToWrite)
+      else
+        # write them separate
+        write_byte(typeToWrite)
+        write_i16(id)
+      end
+
+      @last_field.push(id)
+      nil
+    end
+
+    def write_field_stop
+      write_byte(Types::STOP)
+    end
+
+    def write_map_begin(ktype, vtype, size)
+      if (size == 0)
+        write_byte(0)
+      else
+        write_varint32(size)
+        write_byte(CompactTypes.get_compact_type(ktype) << 4 | CompactTypes.get_compact_type(vtype))
+      end
+    end
+
+    def write_list_begin(etype, size)
+      write_collection_begin(etype, size)
+    end
+
+    def write_set_begin(etype, size)
+      write_collection_begin(etype, size);
+    end
+
+    def write_bool(bool)
+      type = bool ? CompactTypes::BOOLEAN_TRUE : CompactTypes::BOOLEAN_FALSE
+      unless @boolean_field.nil?
+        # we haven't written the field header yet
+        write_field_begin_internal(@boolean_field.first, @boolean_field.last, type)
+        @boolean_field = nil
+      else
+        # we're not part of a field, so just write the value.
+        write_byte(type)
+      end
+    end
+
+    def write_byte(byte)
+      @trans.write([byte].pack('c'))
+    end
+
+    def write_i16(i16)
+      write_varint32(int_to_zig_zag(i16))
+    end
+
+    def write_i32(i32)
+      write_varint32(int_to_zig_zag(i32))
+    end
+
+    def write_i64(i64)
+      write_varint64(long_to_zig_zag(i64))
+    end
+
+    def write_double(dub)
+      @trans.write([dub].pack("G").reverse)
+    end
+
+    def write_string(str)
+      write_varint32(str.length)
+      @trans.write(str)
+    end
+
+    def read_message_begin
+      protocol_id = read_byte()
+      if protocol_id != PROTOCOL_ID
+        raise ProtocolException.new("Expected protocol id #{PROTOCOL_ID} but got #{protocol_id}")
+      end
+      
+      version_and_type = read_byte()
+      version = version_and_type & VERSION_MASK
+      if (version != VERSION)
+        raise ProtocolException.new("Expected version #{VERSION} but got #{version}");
+      end
+      
+      type = (version_and_type >> TYPE_SHIFT_AMOUNT) & 0x03
+      seqid = read_varint32()
+      messageName = read_string()
+      [messageName, type, seqid]
+    end
+
+    def read_struct_begin
+      @last_field.push(0)
+      ""
+    end
+
+    def read_struct_end
+      @last_field.pop()
+      nil
+    end
+
+    def read_field_begin
+      type = read_byte()
+
+      # if it's a stop, then we can return immediately, as the struct is over.
+      if (type & 0x0f) == Types::STOP
+        TSTOP
+      else
+        field_id = nil
+
+        # mask off the 4 MSB of the type header. it could contain a field id delta.
+        modifier = (type & 0xf0) >> 4
+        if modifier == 0
+          # not a delta. look ahead for the zigzag varint field id.
+          field_id = read_i16()
+        else
+          # has a delta. add the delta to the last read field id.
+          field_id = @last_field.pop + modifier
+        end
+
+        # if this happens to be a boolean field, the value is encoded in the type
+        if CompactTypes.is_bool_type?(type)
+          # save the boolean value in a special instance variable.
+          @bool_value = (type & 0x0f) == CompactTypes::BOOLEAN_TRUE
+        end
+
+        # push the new field onto the field stack so we can keep the deltas going.
+        @last_field.push(field_id)
+        ["", CompactTypes.get_ttype(type & 0x0f), field_id]
+      end
+    end
+
+    def read_map_begin
+      size = read_varint32()
+      key_and_value_type = size == 0 ? 0 : read_byte()
+      [CompactTypes.get_ttype(key_and_value_type >> 4), CompactTypes.get_ttype(key_and_value_type & 0xf), size]
+    end
+
+    def read_list_begin
+      size_and_type = read_byte()
+      size = (size_and_type >> 4) & 0x0f
+      if size == 15
+        size = read_varint32()
+      end
+      type = CompactTypes.get_ttype(size_and_type)
+      [type, size]
+    end
+
+    def read_set_begin
+      read_list_begin
+    end
+
+    def read_bool
+      unless @bool_value.nil?
+        bv = @bool_value
+        @bool_value = nil
+        bv
+      else
+        read_byte() == CompactTypes::BOOLEAN_TRUE
+      end
+    end
+
+    def read_byte
+      dat = trans.read_all(1)
+      val = dat[0]
+      if (val > 0x7f)
+        val = 0 - ((val - 1) ^ 0xff)
+      end
+      val
+    end
+
+    def read_i16
+      zig_zag_to_int(read_varint32())
+    end
+
+    def read_i32
+      zig_zag_to_int(read_varint32())
+    end
+
+    def read_i64
+      zig_zag_to_long(read_varint64())
+    end
+
+    def read_double
+      dat = trans.read_all(8)
+      val = dat.reverse.unpack('G').first
+      val
+    end
+
+    def read_string
+      size = read_varint32()
+      trans.read_all(size)
+    end
+    
+    
+    private
+    
+    # 
+    # Abstract method for writing the start of lists and sets. List and sets on 
+    # the wire differ only by the type indicator.
+    # 
+    def write_collection_begin(elem_type, size)
+      if size <= 14
+        write_byte(size << 4 | CompactTypes.get_compact_type(elem_type))
+      else
+        write_byte(0xf0 | CompactTypes.get_compact_type(elem_type))
+        write_varint32(size)
+      end
+    end
+
+    def write_varint32(n)
+      # int idx = 0;
+      while true
+        if (n & ~0x7F) == 0
+          # i32buf[idx++] = (byte)n;
+          write_byte(n)
+          break
+          # return;
+        else
+          # i32buf[idx++] = (byte)((n & 0x7F) | 0x80);
+          write_byte((n & 0x7F) | 0x80)
+          n = n >> 7
+        end
+      end
+      # trans_.write(i32buf, 0, idx);
+    end
+
+    SEVEN_BIT_MASK = 0x7F
+    EVERYTHING_ELSE_MASK = ~SEVEN_BIT_MASK
+
+    def write_varint64(n)
+      while true
+        if (n & EVERYTHING_ELSE_MASK) == 0 #TODO need to find a way to make this into a long...
+          write_byte(n)
+          break
+        else
+          write_byte((n & SEVEN_BIT_MASK) | 0x80)
+          n >>= 7
+        end
+      end
+    end
+    
+    def read_varint32()
+      read_varint64()
+    end
+    
+    def read_varint64()
+      shift = 0
+      result = 0
+      while true
+        b = read_byte()
+        result |= (b & 0x7f) << shift
+        break if (b & 0x80) != 0x80
+        shift += 7
+      end
+      result
+    end
+    
+    def int_to_zig_zag(n)
+      (n << 1) ^ (n >> 31)
+    end
+    
+    def long_to_zig_zag(l)
+      # puts "zz encoded #{l} to #{(l << 1) ^ (l >> 63)}"
+      (l << 1) ^ (l >> 63)
+    end
+    
+    def zig_zag_to_int(n)
+      (n >> 1) ^ -(n & 1)
+    end
+    
+    def zig_zag_to_long(n)
+      (n >> 1) ^ -(n & 1)
+    end
+  end
+
+  class CompactProtocolFactory < BaseProtocolFactory
+    def get_protocol(trans)
+      CompactProtocol.new(trans)
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/serializer/deserializer.rb b/lib/rb/lib/thrift/serializer/deserializer.rb
new file mode 100644
index 0000000..d2ee325
--- /dev/null
+++ b/lib/rb/lib/thrift/serializer/deserializer.rb
@@ -0,0 +1,33 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  class Deserializer
+    def initialize(protocol_factory = BinaryProtocolFactory.new)
+      @transport = MemoryBufferTransport.new
+      @protocol = protocol_factory.get_protocol(@transport)
+    end
+
+    def deserialize(base, buffer)
+      @transport.reset_buffer(buffer)
+      base.read(@protocol)
+      base
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/serializer/serializer.rb b/lib/rb/lib/thrift/serializer/serializer.rb
new file mode 100644
index 0000000..2231639
--- /dev/null
+++ b/lib/rb/lib/thrift/serializer/serializer.rb
@@ -0,0 +1,34 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  class Serializer
+    def initialize(protocol_factory = BinaryProtocolFactory.new)
+      @transport = MemoryBufferTransport.new
+      @protocol = protocol_factory.get_protocol(@transport)
+    end
+
+    def serialize(base)
+      @transport.reset_buffer
+      base.write(@protocol)
+      @transport.read(@transport.available)
+    end
+  end
+end
+
diff --git a/lib/rb/lib/thrift/server/base_server.rb b/lib/rb/lib/thrift/server/base_server.rb
new file mode 100644
index 0000000..1ee1213
--- /dev/null
+++ b/lib/rb/lib/thrift/server/base_server.rb
@@ -0,0 +1,31 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+module Thrift
+  class BaseServer
+    def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil)
+      @processor = processor
+      @server_transport = server_transport
+      @transport_factory = transport_factory ? transport_factory : Thrift::BaseTransportFactory.new
+      @protocol_factory = protocol_factory ? protocol_factory : Thrift::BinaryProtocolFactory.new
+    end
+
+    def serve; nil; end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/mongrel_http_server.rb b/lib/rb/lib/thrift/server/mongrel_http_server.rb
new file mode 100644
index 0000000..84eacf0
--- /dev/null
+++ b/lib/rb/lib/thrift/server/mongrel_http_server.rb
@@ -0,0 +1,58 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'mongrel'
+
+## Sticks a service on a URL, using mongrel to do the HTTP work
+module Thrift
+  class MongrelHTTPServer < BaseServer
+    class Handler < Mongrel::HttpHandler
+      def initialize(processor, protocol_factory)
+        @processor = processor
+        @protocol_factory = protocol_factory
+      end
+
+      def process(request, response)
+        if request.params["REQUEST_METHOD"] == "POST"
+          response.start(200) do |head, out|
+            head["Content-Type"] = "application/x-thrift"
+            transport = IOStreamTransport.new request.body, out
+            protocol = @protocol_factory.get_protocol transport
+            @processor.process protocol, protocol
+          end
+        else
+          response.start(404) { }
+        end
+      end
+    end
+
+    def initialize(processor, opts={})
+      port = opts[:port] || 80
+      ip = opts[:ip] || "0.0.0.0"
+      path = opts[:path] || ""
+      protocol_factory = opts[:protocol_factory] || BinaryProtocolFactory.new
+      @server = Mongrel::HttpServer.new ip, port
+      @server.register "/#{path}", Handler.new(processor, protocol_factory)
+    end
+
+    def serve
+      @server.run.join
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/server/nonblocking_server.rb b/lib/rb/lib/thrift/server/nonblocking_server.rb
new file mode 100644
index 0000000..5425f6d
--- /dev/null
+++ b/lib/rb/lib/thrift/server/nonblocking_server.rb
@@ -0,0 +1,296 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'logger'
+require 'thread'
+
+module Thrift
+  # this class expects to always use a FramedTransport for reading messages
+  class NonblockingServer < BaseServer
+    def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil, num=20, logger=nil)
+      super(processor, server_transport, transport_factory, protocol_factory)
+      @num_threads = num
+      if logger.nil?
+        @logger = Logger.new(STDERR)
+        @logger.level = Logger::WARN
+      else
+        @logger = logger
+      end
+      @shutdown_semaphore = Mutex.new
+      @transport_semaphore = Mutex.new
+    end
+
+    def serve
+      @logger.info "Starting #{self}"
+      @server_transport.listen
+      @io_manager = start_io_manager
+
+      begin
+        loop do
+          break if @server_transport.closed?
+          rd, = select([@server_transport], nil, nil, 0.1)
+          next if rd.nil?
+          socket = @server_transport.accept
+          @logger.debug "Accepted socket: #{socket.inspect}"
+          @io_manager.add_connection socket
+        end
+      rescue IOError => e
+      end
+      # we must be shutting down
+      @logger.info "#{self} is shutting down, goodbye"
+    ensure
+      @transport_semaphore.synchronize do
+        @server_transport.close
+      end
+      @io_manager.ensure_closed unless @io_manager.nil?
+    end
+
+    def shutdown(timeout = 0, block = true)
+      @shutdown_semaphore.synchronize do
+        return if @is_shutdown
+        @is_shutdown = true
+      end
+      # nonblocking is intended for calling from within a Handler
+      # but we can't change the order of operations here, so lets thread
+      shutdown_proc = lambda do
+        @io_manager.shutdown(timeout)
+        @transport_semaphore.synchronize do
+          @server_transport.close # this will break the accept loop
+        end
+      end
+      if block
+        shutdown_proc.call
+      else
+        Thread.new &shutdown_proc
+      end
+    end
+
+    private
+
+    def start_io_manager
+      iom = IOManager.new(@processor, @server_transport, @transport_factory, @protocol_factory, @num_threads, @logger)
+      iom.spawn
+      iom
+    end
+
+    class IOManager # :nodoc:
+      DEFAULT_BUFFER = 2**20
+      
+      def initialize(processor, server_transport, transport_factory, protocol_factory, num, logger)
+        @processor = processor
+        @server_transport = server_transport
+        @transport_factory = transport_factory
+        @protocol_factory = protocol_factory
+        @num_threads = num
+        @logger = logger
+        @connections = []
+        @buffers = Hash.new { |h,k| h[k] = '' }
+        @signal_queue = Queue.new
+        @signal_pipes = IO.pipe
+        @signal_pipes[1].sync = true
+        @worker_queue = Queue.new
+        @shutdown_queue = Queue.new
+      end
+
+      def add_connection(socket)
+        signal [:connection, socket]
+      end
+
+      def spawn
+        @iom_thread = Thread.new do
+          @logger.debug "Starting #{self}"
+          run
+        end
+      end
+
+      def shutdown(timeout = 0)
+        @logger.debug "#{self} is shutting down workers"
+        @worker_queue.clear
+        @num_threads.times { @worker_queue.push [:shutdown] }
+        signal [:shutdown, timeout]
+        @shutdown_queue.pop
+        @signal_pipes[0].close
+        @signal_pipes[1].close
+        @logger.debug "#{self} is shutting down, goodbye"
+      end
+
+      def ensure_closed
+        kill_worker_threads if @worker_threads
+        @iom_thread.kill
+      end
+
+      private
+      
+      def run
+        spin_worker_threads
+
+        loop do
+          rd, = select([@signal_pipes[0], *@connections])
+          if rd.delete @signal_pipes[0]
+            break if read_signals == :shutdown
+          end
+          rd.each do |fd|
+            if fd.handle.eof?
+              remove_connection fd
+            else
+              read_connection fd
+            end
+          end
+        end
+        join_worker_threads(@shutdown_timeout)
+      ensure
+        @shutdown_queue.push :shutdown
+      end
+
+      def read_connection(fd)
+        @buffers[fd] << fd.read(DEFAULT_BUFFER)
+        frame = slice_frame!(@buffers[fd])
+        if frame
+          @logger.debug "#{self} is processing a frame"
+          @worker_queue.push [:frame, fd, frame]
+        end
+      end
+
+      def spin_worker_threads
+        @logger.debug "#{self} is spinning up worker threads"
+        @worker_threads = []
+        @num_threads.times do
+          @worker_threads << spin_thread
+        end
+      end
+
+      def spin_thread
+        Worker.new(@processor, @transport_factory, @protocol_factory, @logger, @worker_queue).spawn
+      end
+
+      def signal(msg)
+        @signal_queue << msg
+        @signal_pipes[1].write " "
+      end
+
+      def read_signals
+        # clear the signal pipe
+        # note that since read_nonblock is broken in jruby,
+        # we can only read up to a set number of signals at once
+        sigstr = @signal_pipes[0].readpartial(1024)
+        # now read the signals
+        begin
+          sigstr.length.times do
+            signal, obj = @signal_queue.pop(true)
+            case signal
+            when :connection
+              @connections << obj
+            when :shutdown
+              @shutdown_timeout = obj
+              return :shutdown
+            end
+          end
+        rescue ThreadError
+          # out of signals
+          # note that in a perfect world this would never happen, since we're
+          # only reading the number of signals pushed on the pipe, but given the lack
+          # of locks, in theory we could clear the pipe/queue while a new signal is being
+          # placed on the pipe, at which point our next read_signals would hit this error
+        end
+      end
+
+      def remove_connection(fd)
+        # don't explicitly close it, a thread may still be writing to it
+        @connections.delete fd
+        @buffers.delete fd
+      end
+
+      def join_worker_threads(shutdown_timeout)
+        start = Time.now
+        @worker_threads.each do |t|
+          if shutdown_timeout > 0
+            timeout = (start + shutdown_timeout) - Time.now
+            break if timeout <= 0
+            t.join(timeout)
+          else
+            t.join
+          end
+        end
+        kill_worker_threads
+      end
+
+      def kill_worker_threads
+        @worker_threads.each do |t|
+          t.kill if t.status
+        end
+        @worker_threads.clear
+      end
+
+      def slice_frame!(buf)
+        if buf.length >= 4
+          size = buf.unpack('N').first
+          if buf.length >= size + 4
+            buf.slice!(0, size + 4)
+          else
+            nil
+          end
+        else
+          nil
+        end
+      end
+
+      class Worker # :nodoc:
+        def initialize(processor, transport_factory, protocol_factory, logger, queue)
+          @processor = processor
+          @transport_factory = transport_factory
+          @protocol_factory = protocol_factory
+          @logger = logger
+          @queue = queue
+        end
+
+        def spawn
+          Thread.new do
+            @logger.debug "#{self} is spawning"
+            run
+          end
+        end
+
+        private
+
+        def run
+          loop do
+            cmd, *args = @queue.pop
+            case cmd
+            when :shutdown
+              @logger.debug "#{self} is shutting down, goodbye"
+              break
+            when :frame
+              fd, frame = args
+              begin
+                otrans = @transport_factory.get_transport(fd)
+                oprot = @protocol_factory.get_protocol(otrans)
+                membuf = MemoryBufferTransport.new(frame)
+                itrans = @transport_factory.get_transport(membuf)
+                iprot = @protocol_factory.get_protocol(itrans)
+                @processor.process(iprot, oprot)
+              rescue => e
+                @logger.error "#{Thread.current.inspect} raised error: #{e.inspect}\n#{e.backtrace.join("\n")}"
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/simple_server.rb b/lib/rb/lib/thrift/server/simple_server.rb
new file mode 100644
index 0000000..21e8659
--- /dev/null
+++ b/lib/rb/lib/thrift/server/simple_server.rb
@@ -0,0 +1,43 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+module Thrift
+  class SimpleServer < BaseServer
+    def serve
+      begin
+        @server_transport.listen
+        loop do
+          client = @server_transport.accept
+          trans = @transport_factory.get_transport(client)
+          prot = @protocol_factory.get_protocol(trans)
+          begin
+            loop do
+              @processor.process(prot, prot)
+            end
+          rescue Thrift::TransportException, Thrift::ProtocolException
+          ensure
+            trans.close
+          end
+        end
+      ensure
+        @server_transport.close
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/thread_pool_server.rb b/lib/rb/lib/thrift/server/thread_pool_server.rb
new file mode 100644
index 0000000..8cec805
--- /dev/null
+++ b/lib/rb/lib/thrift/server/thread_pool_server.rb
@@ -0,0 +1,75 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'thread'
+
+module Thrift
+  class ThreadPoolServer < BaseServer
+    def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil, num=20)
+      super(processor, server_transport, transport_factory, protocol_factory)
+      @thread_q = SizedQueue.new(num)
+      @exception_q = Queue.new
+      @running = false
+    end
+
+    ## exceptions that happen in worker threads will be relayed here and
+    ## must be caught. 'retry' can be used to continue. (threads will
+    ## continue to run while the exception is being handled.)
+    def rescuable_serve
+      Thread.new { serve } unless @running
+      @running = true
+      raise @exception_q.pop
+    end
+
+    ## exceptions that happen in worker threads simply cause that thread
+    ## to die and another to be spawned in its place.
+    def serve
+      @server_transport.listen
+
+      begin
+        loop do
+          @thread_q.push(:token)
+          Thread.new do
+            begin
+              loop do
+                client = @server_transport.accept
+                trans = @transport_factory.get_transport(client)
+                prot = @protocol_factory.get_protocol(trans)
+                begin
+                  loop do
+                    @processor.process(prot, prot)
+                  end
+                rescue Thrift::TransportException, Thrift::ProtocolException => e
+                ensure
+                  trans.close
+                end
+              end
+            rescue => e
+              @exception_q.push(e)
+            ensure
+              @thread_q.pop # thread died!
+            end
+          end
+        end
+      ensure
+        @server_transport.close
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/server/threaded_server.rb b/lib/rb/lib/thrift/server/threaded_server.rb
new file mode 100644
index 0000000..a2c917c
--- /dev/null
+++ b/lib/rb/lib/thrift/server/threaded_server.rb
@@ -0,0 +1,47 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require 'thread'
+
+module Thrift
+  class ThreadedServer < BaseServer
+    def serve
+      begin
+        @server_transport.listen
+        loop do
+          client = @server_transport.accept
+          trans = @transport_factory.get_transport(client)
+          prot = @protocol_factory.get_protocol(trans)
+          Thread.new(prot, trans) do |p, t|
+            begin
+              loop do
+                @processor.process(p, p)
+              end
+            rescue Thrift::TransportException, Thrift::ProtocolException
+            ensure
+              t.close
+            end
+          end
+        end
+      ensure
+        @server_transport.close
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/struct.rb b/lib/rb/lib/thrift/struct.rb
new file mode 100644
index 0000000..01aae56
--- /dev/null
+++ b/lib/rb/lib/thrift/struct.rb
@@ -0,0 +1,294 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'set'
+
+module Thrift
+  module Struct
+    def initialize(d={})
+      # get a copy of the default values to work on, removing defaults in favor of arguments
+      fields_with_defaults = fields_with_default_values.dup
+      
+      # check if the defaults is empty, or if there are no parameters for this 
+      # instantiation, and if so, don't bother overriding defaults.
+      unless fields_with_defaults.empty? || d.empty?
+        d.each_key do |name|
+          fields_with_defaults.delete(name.to_s)
+        end
+      end
+      
+      # assign all the user-specified arguments
+      unless d.empty?
+        d.each do |name, value|
+          unless name_to_id(name.to_s)
+            raise Exception, "Unknown key given to #{self.class}.new: #{name}"
+          end
+          Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking
+          instance_variable_set("@#{name}", value)
+        end
+      end
+      
+      # assign all the default values
+      unless fields_with_defaults.empty?
+        fields_with_defaults.each do |name, default_value|
+          instance_variable_set("@#{name}", (default_value.dup rescue default_value))
+        end
+      end
+    end
+
+    def fields_with_default_values
+      fields_with_default_values = self.class.instance_variable_get("@fields_with_default_values")
+      unless fields_with_default_values
+        fields_with_default_values = {}
+        struct_fields.each do |fid, field_def|
+          unless field_def[:default].nil?
+            fields_with_default_values[field_def[:name]] = field_def[:default]
+          end
+        end
+        self.class.instance_variable_set("@fields_with_default_values", fields_with_default_values)
+      end
+      fields_with_default_values
+    end
+
+    def name_to_id(name)
+      names_to_ids = self.class.instance_variable_get("@names_to_ids")
+      unless names_to_ids
+        names_to_ids = {}
+        struct_fields.each do |fid, field_def|
+          names_to_ids[field_def[:name]] = fid
+        end
+        self.class.instance_variable_set("@names_to_ids", names_to_ids)
+      end
+      names_to_ids[name]
+    end
+
+    def each_field
+      struct_fields.keys.sort.each do |fid|
+        data = struct_fields[fid]
+        yield fid, data
+      end
+    end
+
+    def inspect(skip_optional_nulls = true)
+      fields = []
+      each_field do |fid, field_info|
+        name = field_info[:name]
+        value = instance_variable_get("@#{name}")
+        unless skip_optional_nulls && field_info[:optional] && value.nil?
+          fields << "#{name}:#{value.inspect}"
+        end
+      end
+      "<#{self.class} #{fields.join(", ")}>"
+    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
+      end
+      iprot.read_struct_end
+      validate
+    end
+
+    def write(oprot)
+      validate
+      oprot.write_struct_begin(self.class.name)
+      each_field do |fid, field_info|
+        name = field_info[:name]
+        type = field_info[:type]
+        if (value = instance_variable_get("@#{name}"))
+          if is_container? type
+            oprot.write_field_begin(name, type, fid)
+            write_container(oprot, value, field_info)
+            oprot.write_field_end
+          else
+            oprot.write_field(name, type, fid, value)
+          end
+        end
+      end
+      oprot.write_field_stop
+      oprot.write_struct_end
+    end
+
+    def ==(other)
+      each_field do |fid, field_info|
+        name = field_info[:name]
+        return false unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}")
+      end
+      true
+    end
+
+    def eql?(other)
+      self.class == other.class && self == other
+    end
+
+    # for the time being, we're ok with a naive hash. this could definitely be improved upon.
+    def hash
+      0
+    end
+
+    def differences(other)
+      diffs = []
+      unless other.is_a?(self.class)
+        diffs << "Different class!"
+      else
+        each_field do |fid, field_info|
+          name = field_info[:name]
+          diffs << "#{name} differs!" unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}")
+        end
+      end
+      diffs
+    end
+
+    def self.field_accessor(klass, *fields)
+      fields.each do |field|
+        klass.send :attr_reader, field
+        klass.send :define_method, "#{field}=" do |value|
+          Thrift.check_type(value, klass::FIELDS.values.find { |f| f[:name].to_s == field.to_s }, field) if Thrift.type_checking
+          instance_variable_set("@#{field}", value)
+        end
+      end
+    end
+
+    protected
+
+    def self.append_features(mod)
+      if mod.ancestors.include? ::Exception
+        mod.send :class_variable_set, :'@@__thrift_struct_real_initialize', mod.instance_method(:initialize)
+        super
+        # set up our custom initializer so `raise Xception, 'message'` works
+        mod.send :define_method, :struct_initialize, mod.instance_method(:initialize)
+        mod.send :define_method, :initialize, mod.instance_method(:exception_initialize)
+      else
+        super
+      end
+    end
+
+    def exception_initialize(*args, &block)
+      if args.size == 1 and args.first.is_a? Hash
+        # looks like it's a regular Struct initialize
+        method(:struct_initialize).call(args.first)
+      else
+        # call the Struct initializer first with no args
+        # this will set our field default values
+        method(:struct_initialize).call()
+        # now give it to the exception
+        self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind(self).call(*args, &block) if args.size > 0
+        # self.class.instance_method(:initialize).bind(self).call(*args, &block)
+      end
+    end
+
+    def handle_message(iprot, fid, ftype)
+      field = struct_fields[fid]
+      if field and field[:type] == ftype
+        value = read_field(iprot, field)
+        instance_variable_set("@#{field[:name]}", value)
+      else
+        iprot.skip(ftype)
+      end
+    end
+
+    def read_field(iprot, field = {})
+      case field[:type]
+      when Types::STRUCT
+        value = field[:class].new
+        value.read(iprot)
+      when Types::MAP
+        key_type, val_type, size = iprot.read_map_begin
+        value = {}
+        size.times do
+          k = read_field(iprot, field_info(field[:key]))
+          v = read_field(iprot, field_info(field[:value]))
+          value[k] = v
+        end
+        iprot.read_map_end
+      when Types::LIST
+        e_type, size = iprot.read_list_begin
+        value = Array.new(size) do |n|
+          read_field(iprot, field_info(field[:element]))
+        end
+        iprot.read_list_end
+      when Types::SET
+        e_type, size = iprot.read_set_begin
+        value = Set.new
+        size.times do
+          element = read_field(iprot, field_info(field[:element]))
+          value << element
+        end
+        iprot.read_set_end
+      else
+        value = iprot.read_type(field[:type])
+      end
+      value
+    end
+
+    def write_data(oprot, value, field)
+      if is_container? field[:type]
+        write_container(oprot, value, field)
+      else
+        oprot.write_type(field[:type], value)
+      end
+    end
+
+    def write_container(oprot, value, field = {})
+      case field[:type]
+      when Types::MAP
+        oprot.write_map_begin(field[:key][:type], field[:value][:type], value.size)
+        value.each do |k, v|
+          write_data(oprot, k, field[:key])
+          write_data(oprot, v, field[:value])
+        end
+        oprot.write_map_end
+      when Types::LIST
+        oprot.write_list_begin(field[:element][:type], value.size)
+        value.each do |elem|
+          write_data(oprot, elem, field[:element])
+        end
+        oprot.write_list_end
+      when Types::SET
+        oprot.write_set_begin(field[:element][:type], value.size)
+        value.each do |v,| # the , is to preserve compatibility with the old Hash-style sets
+          write_data(oprot, v, field[:element])
+        end
+        oprot.write_set_end
+      else
+        raise "Not a container type: #{field[:type]}"
+      end
+    end
+
+    CONTAINER_TYPES = []
+    CONTAINER_TYPES[Types::LIST] = true
+    CONTAINER_TYPES[Types::MAP] = true
+    CONTAINER_TYPES[Types::SET] = true
+    def is_container?(type)
+      CONTAINER_TYPES[type]
+    end
+
+    def field_info(field)
+      { :type => field[:type],
+        :class => field[:class],
+        :key => field[:key],
+        :value => field[:value],
+        :element => field[:element] }
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/thrift_native.rb b/lib/rb/lib/thrift/thrift_native.rb
new file mode 100644
index 0000000..4d8df61
--- /dev/null
+++ b/lib/rb/lib/thrift/thrift_native.rb
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+begin
+  require "thrift_native"
+rescue LoadError
+  puts "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/base_server_transport.rb b/lib/rb/lib/thrift/transport/base_server_transport.rb
new file mode 100644
index 0000000..68c5af0
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/base_server_transport.rb
@@ -0,0 +1,37 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  class BaseServerTransport
+    def listen
+      raise NotImplementedError
+    end
+
+    def accept
+      raise NotImplementedError
+    end
+      
+    def close; nil; end
+
+    def closed?
+      raise NotImplementedError
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/base_transport.rb b/lib/rb/lib/thrift/transport/base_transport.rb
new file mode 100644
index 0000000..08a71da
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/base_transport.rb
@@ -0,0 +1,70 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+module Thrift
+  class TransportException < Exception
+    UNKNOWN = 0
+    NOT_OPEN = 1
+    ALREADY_OPEN = 2
+    TIMED_OUT = 3
+    END_OF_FILE = 4
+
+    attr_reader :type
+
+    def initialize(type=UNKNOWN, message=nil)
+      super(message)
+      @type = type
+    end
+  end
+
+  class BaseTransport
+    def open?; end
+    
+    def open; end
+
+    def close; end
+
+    def read(sz)
+      raise NotImplementedError
+    end
+
+    def read_all(size)
+      buf = ''
+    
+      while (buf.length < size)
+        chunk = read(size - buf.length)
+        buf << chunk
+      end
+    
+      buf
+    end
+  
+    def write(buf); end
+    alias_method :<<, :write
+
+    def flush; end
+  end
+  
+  class BaseTransportFactory
+    def get_transport(trans)
+      return trans
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/buffered_transport.rb b/lib/rb/lib/thrift/transport/buffered_transport.rb
new file mode 100644
index 0000000..8dead4e
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/buffered_transport.rb
@@ -0,0 +1,77 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+module Thrift
+  class BufferedTransport < BaseTransport
+    DEFAULT_BUFFER = 4096
+    
+    def initialize(transport)
+      @transport = transport
+      @wbuf = ''
+      @rbuf = ''
+      @index = 0
+    end
+
+    def open?
+      return @transport.open?
+    end
+
+    def open
+      @transport.open
+    end
+
+    def close
+      flush
+      @transport.close
+    end
+
+    def read(sz)
+      @index += sz
+      ret = @rbuf.slice(@index - sz, sz) || ''
+
+      if ret.length == 0
+        @rbuf = @transport.read([sz, DEFAULT_BUFFER].max)
+        @index = sz
+        ret = @rbuf.slice(0, sz) || ''
+      end
+
+      ret
+    end
+
+    def write(buf)
+      @wbuf << buf
+    end
+
+    def flush
+      if @wbuf != ''
+        @transport.write(@wbuf)
+        @wbuf = ''
+      end
+      
+      @transport.flush
+    end
+  end
+
+  class BufferedTransportFactory < BaseTransportFactory
+    def get_transport(transport)
+      return BufferedTransport.new(transport)
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/framed_transport.rb b/lib/rb/lib/thrift/transport/framed_transport.rb
new file mode 100644
index 0000000..558af74
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/framed_transport.rb
@@ -0,0 +1,90 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+module Thrift
+  class FramedTransport < BaseTransport
+    def initialize(transport, read=true, write=true)
+      @transport = transport
+      @rbuf      = ''
+      @wbuf      = ''
+      @read      = read
+      @write     = write
+      @index      = 0
+    end
+
+    def open?
+      @transport.open?
+    end
+
+    def open
+      @transport.open
+    end
+
+    def close
+      @transport.close
+    end
+
+    def read(sz)
+      return @transport.read(sz) unless @read
+
+      return '' if sz <= 0
+
+      read_frame if @index >= @rbuf.length
+
+      @index += sz
+      @rbuf.slice(@index - sz, sz) || ''
+    end
+
+    def write(buf,sz=nil)
+      return @transport.write(buf) unless @write
+
+      @wbuf << (sz ? buf[0...sz] : buf)
+    end
+
+    #
+    # Writes the output buffer to the stream in the format of a 4-byte length
+    # followed by the actual data.
+    #
+    def flush
+      return @transport.flush unless @write
+
+      out = [@wbuf.length].pack('N')
+      out << @wbuf
+      @transport.write(out)
+      @transport.flush
+      @wbuf = ''
+    end
+
+    private
+
+    def read_frame
+      sz = @transport.read_all(4).unpack('N').first
+
+      @index = 0
+      @rbuf = @transport.read_all(sz)
+    end
+  end
+
+  class FramedTransportFactory < BaseTransportFactory
+    def get_transport(transport)
+      return FramedTransport.new(transport)
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/http_client_transport.rb b/lib/rb/lib/thrift/transport/http_client_transport.rb
new file mode 100644
index 0000000..a190a98
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/http_client_transport.rb
@@ -0,0 +1,45 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'net/http'
+require 'net/https'
+require 'uri'
+require 'stringio'
+
+module Thrift
+  class HTTPClientTransport < BaseTransport
+    def initialize(url)
+      @url = URI url
+      @outbuf = ""
+    end
+
+    def open?; true end
+    def read(sz); @inbuf.read sz end
+    def write(buf); @outbuf << buf end
+    def flush
+      http = Net::HTTP.new @url.host, @url.port
+      http.use_ssl = @url.scheme == "https"
+      headers = { 'Content-Type' => 'application/x-thrift' }
+      resp, data = http.post(@url.path, @outbuf, headers)
+      @inbuf = StringIO.new data
+      @outbuf = ""
+    end
+  end
+end
diff --git a/lib/rb/lib/thrift/transport/io_stream_transport.rb b/lib/rb/lib/thrift/transport/io_stream_transport.rb
new file mode 100644
index 0000000..be348aa
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/io_stream_transport.rb
@@ -0,0 +1,39 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Very very simple implementation of wrapping two objects, one with a #read
+# method and one with a #write method, into a transport for thrift.
+#
+# Assumes both objects are open, remain open, don't require flushing, etc.
+#
+module Thrift
+  class IOStreamTransport < BaseTransport
+    def initialize(input, output)
+      @input = input
+      @output = output
+    end
+
+    def open?; not @input.closed? or not @output.closed? end
+    def read(sz); @input.read(sz) end
+    def write(buf); @output.write(buf) end
+    def close; @input.close; @output.close end
+    def to_io; @input end # we're assuming this is used in a IO.select for reading
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/memory_buffer_transport.rb b/lib/rb/lib/thrift/transport/memory_buffer_transport.rb
new file mode 100644
index 0000000..33d732d
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/memory_buffer_transport.rb
@@ -0,0 +1,93 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+module Thrift
+  class MemoryBufferTransport < BaseTransport
+    GARBAGE_BUFFER_SIZE = 4*(2**10) # 4kB
+
+    # If you pass a string to this, you should #dup that string
+    # unless you want it to be modified by #read and #write
+    #--
+    # this behavior is no longer required. If you wish to change it
+    # go ahead, just make sure the specs pass
+    def initialize(buffer = nil)
+      @buf = buffer || ''
+      @index = 0
+    end
+
+    def open?
+      return true
+    end
+
+    def open
+    end
+
+    def close
+    end
+
+    def peek
+      @index < @buf.size
+    end
+
+    # this method does not use the passed object directly but copies it
+    def reset_buffer(new_buf = '')
+      @buf.replace new_buf
+      @index = 0
+    end
+
+    def available
+      @buf.length - @index
+    end
+
+    def read(len)
+      data = @buf.slice(@index, len)
+      @index += len
+      @index = @buf.size if @index > @buf.size
+      if @index >= GARBAGE_BUFFER_SIZE
+        @buf = @buf.slice(@index..-1)
+        @index = 0
+      end
+      data
+    end
+
+    def write(wbuf)
+      @buf << wbuf
+    end
+
+    def flush
+    end
+
+    def inspect_buffer
+      out = []
+      for idx in 0...(@buf.size)
+        # if idx != 0
+        #   out << " "
+        # end
+      
+        if idx == @index
+          out << ">"
+        end
+      
+        out << @buf[idx].to_s(16)
+      end
+      out.join(" ")
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/server_socket.rb b/lib/rb/lib/thrift/transport/server_socket.rb
new file mode 100644
index 0000000..7feb9ab
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/server_socket.rb
@@ -0,0 +1,63 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'socket'
+
+module Thrift
+  class ServerSocket < BaseServerTransport
+    # call-seq: initialize(host = nil, port)
+    def initialize(host_or_port, port = nil)
+      if port
+        @host = host_or_port
+        @port = port
+      else
+        @host = nil
+        @port = host_or_port
+      end
+      @handle = nil
+    end
+
+    attr_reader :handle
+
+    def listen
+      @handle = TCPServer.new(@host, @port)
+    end
+
+    def accept
+      unless @handle.nil?
+        sock = @handle.accept
+        trans = Socket.new
+        trans.handle = sock
+        trans
+      end
+    end
+
+    def close
+     @handle.close unless @handle.nil? or @handle.closed?
+     @handle = nil
+    end
+
+    def closed?
+      @handle.nil? or @handle.closed?
+    end
+
+    alias to_io handle
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/socket.rb b/lib/rb/lib/thrift/transport/socket.rb
new file mode 100644
index 0000000..06c937e
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/socket.rb
@@ -0,0 +1,136 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'socket'
+
+module Thrift
+  class Socket < BaseTransport
+    def initialize(host='localhost', port=9090, timeout=nil)
+      @host = host
+      @port = port
+      @timeout = timeout
+      @desc = "#{host}:#{port}"
+      @handle = nil
+    end
+
+    attr_accessor :handle, :timeout
+
+    def open
+      begin
+        addrinfo = ::Socket::getaddrinfo(@host, @port).first
+        @handle = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0)
+        sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3])
+        begin
+          @handle.connect_nonblock(sockaddr)
+        rescue Errno::EINPROGRESS
+          unless IO.select(nil, [ @handle ], nil, @timeout)
+            raise TransportException.new(TransportException::NOT_OPEN, "Connection timeout to #{@desc}")
+          end
+          begin
+            @handle.connect_nonblock(sockaddr)
+          rescue Errno::EISCONN
+          end
+        end
+        @handle
+      rescue StandardError => e
+        raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
+      end
+    end
+
+    def open?
+      !@handle.nil? and !@handle.closed?
+    end
+
+    def write(str)
+      raise IOError, "closed stream" unless open?
+      begin
+        if @timeout.nil? or @timeout == 0
+          @handle.write(str)
+        else
+          len = 0
+          start = Time.now
+          while Time.now - start < @timeout
+            rd, wr, = IO.select(nil, [@handle], nil, @timeout)
+            if wr and not wr.empty?
+              len += @handle.write_nonblock(str[len..-1])
+              break if len >= str.length
+            end
+          end
+          if len < str.length
+            raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out writing #{str.length} bytes to #{@desc}")
+          else
+            len
+          end
+        end
+      rescue TransportException => e
+        # pass this on
+        raise e
+      rescue StandardError => e
+        @handle.close
+        @handle = nil
+        raise TransportException.new(TransportException::NOT_OPEN, e.message)
+      end
+    end
+
+    def read(sz)
+      raise IOError, "closed stream" unless open?
+
+      begin
+        if @timeout.nil? or @timeout == 0
+          data = @handle.readpartial(sz)
+        else
+          # it's possible to interrupt select for something other than the timeout
+          # so we need to ensure we've waited long enough
+          start = Time.now
+          rd = nil # scoping
+          loop do
+            rd, = IO.select([@handle], nil, nil, @timeout)
+            break if (rd and not rd.empty?) or Time.now - start >= @timeout
+          end
+          if rd.nil? or rd.empty?
+            raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out reading #{sz} bytes from #{@desc}")
+          else
+            data = @handle.readpartial(sz)
+          end
+        end
+      rescue TransportException => e
+        # don't let this get caught by the StandardError handler
+        raise e
+      rescue StandardError => e
+        @handle.close unless @handle.closed?
+        @handle = nil
+        raise TransportException.new(TransportException::NOT_OPEN, e.message)
+      end
+      if (data.nil? or data.length == 0)
+        raise TransportException.new(TransportException::UNKNOWN, "Socket: Could not read #{sz} bytes from #{@desc}")
+      end
+      data
+    end
+
+    def close
+      @handle.close unless @handle.nil? or @handle.closed?
+      @handle = nil
+    end
+
+    def to_io
+      @handle
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/unix_server_socket.rb b/lib/rb/lib/thrift/transport/unix_server_socket.rb
new file mode 100644
index 0000000..a135d25
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/unix_server_socket.rb
@@ -0,0 +1,60 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'socket'
+
+module Thrift
+  class UNIXServerSocket < BaseServerTransport
+    def initialize(path)
+      @path = path
+      @handle = nil
+    end
+
+    attr_accessor :handle
+
+    def listen
+      @handle = ::UNIXServer.new(@path)
+    end
+
+    def accept
+      unless @handle.nil?
+        sock = @handle.accept
+        trans = UNIXSocket.new(nil)
+        trans.handle = sock
+        trans
+      end
+    end
+
+    def close
+      if @handle
+        @handle.close unless @handle.closed?
+        @handle = nil
+        # UNIXServer doesn't delete the socket file, so we have to do it ourselves
+        File.delete(@path)
+      end
+    end
+
+    def closed?
+      @handle.nil? or @handle.closed?
+    end
+
+    alias to_io handle
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/transport/unix_socket.rb b/lib/rb/lib/thrift/transport/unix_socket.rb
new file mode 100644
index 0000000..8f692e4
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/unix_socket.rb
@@ -0,0 +1,40 @@
+# encoding: ascii-8bit
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'socket'
+
+module Thrift
+  class UNIXSocket < Socket
+    def initialize(path, timeout=nil)
+      @path = path
+      @timeout = timeout
+      @desc = @path # for read()'s error
+      @handle = nil
+    end
+
+    def open
+      begin
+        @handle = ::UNIXSocket.new(@path)
+      rescue StandardError
+        raise TransportException.new(TransportException::NOT_OPEN, "Could not open UNIX socket at #{@path}")
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/lib/thrift/types.rb b/lib/rb/lib/thrift/types.rb
new file mode 100644
index 0000000..20e4ca2
--- /dev/null
+++ b/lib/rb/lib/thrift/types.rb
@@ -0,0 +1,101 @@
+# 
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# 
+
+require 'set'
+
+module Thrift
+  module Types
+    STOP = 0
+    VOID = 1
+    BOOL = 2
+    BYTE = 3
+    DOUBLE = 4
+    I16 = 6
+    I32 = 8
+    I64 = 10
+    STRING = 11
+    STRUCT = 12
+    MAP = 13
+    SET = 14
+    LIST = 15
+  end
+
+  class << self
+    attr_accessor :type_checking
+  end
+
+  class TypeError < Exception
+  end
+
+  def self.check_type(value, field, name, skip_nil=true)
+    return if value.nil? and skip_nil
+    klasses = case field[:type]
+              when Types::VOID
+                NilClass
+              when Types::BOOL
+                [TrueClass, FalseClass]
+              when Types::BYTE, Types::I16, Types::I32, Types::I64
+                Integer
+              when Types::DOUBLE
+                Float
+              when Types::STRING
+                String
+              when Types::STRUCT
+                Struct
+              when Types::MAP
+                Hash
+              when Types::SET
+                Set
+              when Types::LIST
+                Array
+              end
+    valid = klasses && [*klasses].any? { |klass| klass === value }
+    raise TypeError, "Expected #{type_name(field[:type])}, received #{value.class} for field #{name}" unless valid
+    # check elements now
+    case field[:type]
+    when Types::MAP
+      value.each_pair do |k,v|
+        check_type(k, field[:key], "#{name}.key", false)
+        check_type(v, field[:value], "#{name}.value", false)
+      end
+    when Types::SET, Types::LIST
+      value.each do |el|
+        check_type(el, field[:element], "#{name}.element", false)
+      end
+    when Types::STRUCT
+      raise TypeError, "Expected #{field[:class]}, received #{value.class} for field #{name}" unless field[:class] == value.class
+    end
+  end
+
+  def self.type_name(type)
+    Types.constants.each do |const|
+      return "Types::#{const}" if Types.const_get(const) == type
+    end
+    nil
+  end
+
+  module MessageTypes
+    CALL = 1
+    REPLY = 2
+    EXCEPTION = 3
+    ONEWAY = 4
+  end
+end
+
+Thrift.type_checking = false if Thrift.type_checking.nil?
diff --git a/lib/rb/script/proto_benchmark.rb b/lib/rb/script/proto_benchmark.rb
new file mode 100644
index 0000000..bb49e2e
--- /dev/null
+++ b/lib/rb/script/proto_benchmark.rb
@@ -0,0 +1,121 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + "/../spec/spec_helper.rb"
+
+require "benchmark"
+# require "ruby-prof"
+
+obj = Fixtures::COMPACT_PROTOCOL_TEST_STRUCT
+
+HOW_MANY = 1_000
+
+binser = Thrift::Serializer.new
+bin_data = binser.serialize(obj)
+bindeser = Thrift::Deserializer.new
+accel_bin_ser = Thrift::Serializer.new(Thrift::BinaryProtocolAcceleratedFactory.new)
+accel_bin_deser = Thrift::Deserializer.new(Thrift::BinaryProtocolAcceleratedFactory.new)
+
+compact_ser = Thrift::Serializer.new(Thrift::CompactProtocolFactory.new)
+compact_data = compact_ser.serialize(obj)
+compact_deser = Thrift::Deserializer.new(Thrift::CompactProtocolFactory.new)
+
+Benchmark.bm(60) do |reporter|
+  reporter.report("binary protocol, write") do
+    HOW_MANY.times do
+      binser.serialize(obj)
+    end
+  end
+  
+  reporter.report("accelerated binary protocol, write") do
+    HOW_MANY.times do
+      accel_bin_ser.serialize(obj)
+    end
+  end
+  
+  reporter.report("compact protocol, write") do
+    # RubyProf.start
+    HOW_MANY.times do
+      compact_ser.serialize(obj)
+    end
+    # result = RubyProf.stop
+    # printer = RubyProf::GraphHtmlPrinter.new(result)
+    # file = File.open("profile.html", "w+")
+    # printer.print(file, 0)
+    # file.close
+  end
+  
+  reporter.report("binary protocol, read") do
+    HOW_MANY.times do
+      bindeser.deserialize(obj, bin_data)
+    end
+  end
+  
+  reporter.report("accelerated binary protocol, read") do
+    HOW_MANY.times do
+      accel_bin_deser.deserialize(obj, bin_data)
+    end
+  end
+  
+  reporter.report("compact protocol, read") do
+    HOW_MANY.times do
+      compact_deser.deserialize(obj, compact_data)
+    end
+  end
+
+
+  # f = File.new("/tmp/testfile", "w")
+  # proto = Thrift::BinaryProtocolAccelerated.new(Thrift::IOStreamTransport.new(Thrift::MemoryBufferTransport.new, f))
+  # reporter.report("accelerated binary protocol, write (to disk)") do
+  #   HOW_MANY.times do
+  #     obj.write(proto)
+  #   end
+  #   f.flush
+  # end
+  # f.close
+  #   
+  # f = File.new("/tmp/testfile", "r")
+  # proto = Thrift::BinaryProtocolAccelerated.new(Thrift::IOStreamTransport.new(f, Thrift::MemoryBufferTransport.new))
+  # reporter.report("accelerated binary protocol, read (from disk)") do
+  #   HOW_MANY.times do
+  #     obj.read(proto)
+  #   end
+  # end
+  # f.close
+  # 
+  # f = File.new("/tmp/testfile", "w")
+  # reporter.report("compact protocol, write (to disk)") do
+  #   proto = Thrift::CompactProtocol.new(Thrift::IOStreamTransport.new(Thrift::MemoryBufferTransport.new, f))
+  #   HOW_MANY.times do
+  #     obj.write(proto)
+  #   end
+  #   f.flush
+  # end
+  # f.close
+  # 
+  # f = File.new("/tmp/testfile", "r")
+  # reporter.report("compact protocol, read (from disk)") do
+  #   proto = Thrift::CompactProtocol.new(Thrift::IOStreamTransport.new(f, Thrift::MemoryBufferTransport.new))
+  #   HOW_MANY.times do
+  #     obj.read(proto)
+  #   end
+  # end
+  # f.close
+
+end
diff --git a/lib/rb/script/read_struct.rb b/lib/rb/script/read_struct.rb
new file mode 100644
index 0000000..831fcec
--- /dev/null
+++ b/lib/rb/script/read_struct.rb
@@ -0,0 +1,43 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require "spec/spec_helper"
+
+path, factory_class = ARGV
+
+factory = eval(factory_class).new
+
+deser = Thrift::Deserializer.new(factory)
+
+cpts = CompactProtoTestStruct.new
+CompactProtoTestStruct.constants.each do |const|
+  cpts.instance_variable_set("@#{const}", nil)
+end
+
+data = File.read(path)
+
+deser.deserialize(cpts, data)
+
+if cpts == Fixtures::COMPACT_PROTOCOL_TEST_STRUCT
+  puts "Object verified successfully!"
+else
+  puts "Object failed verification! Expected #{Fixtures::COMPACT_PROTOCOL_TEST_STRUCT.inspect} but got #{cpts.inspect}"
+  
+  puts cpts.differences(Fixtures::COMPACT_PROTOCOL_TEST_STRUCT)
+end
diff --git a/lib/rb/script/write_struct.rb b/lib/rb/script/write_struct.rb
new file mode 100644
index 0000000..da14219
--- /dev/null
+++ b/lib/rb/script/write_struct.rb
@@ -0,0 +1,30 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require "spec/spec_helper"
+
+path, factory_class = ARGV
+
+factory = eval(factory_class).new
+
+ser = Thrift::Serializer.new(factory)
+
+File.open(path, "w") do |file|
+  file.write(ser.serialize(Fixtures::COMPACT_PROTOCOL_TEST_STRUCT))
+end
\ No newline at end of file
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/ThriftSpec.thrift b/lib/rb/spec/ThriftSpec.thrift
new file mode 100644
index 0000000..fe5a8aa
--- /dev/null
+++ b/lib/rb/spec/ThriftSpec.thrift
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+namespace rb SpecNamespace
+
+struct Hello {
+  1: string greeting = "hello world"
+}
+
+struct Foo {
+  1: i32 simple = 53,
+  2: string words = "words",
+  3: Hello hello = {'greeting' : "hello, world!"},
+  4: list<i32> ints = [1, 2, 2, 3],
+  5: map<i32, map<string, double>> complex,
+  6: set<i16> shorts = [5, 17, 239],
+  7: optional string opt_string
+}
+
+struct BoolStruct {
+  1: bool yesno = 1
+}
+
+struct SimpleList {
+  1: list<bool> bools,
+  2: list<byte> bytes,
+  3: list<i16> i16s,
+  4: list<i32> i32s,
+  5: list<i64> i64s,
+  6: list<double> doubles,
+  7: list<string> strings,
+  8: list<map<i16, i16>> maps,
+  9: list<list<i16>> lists,
+  10: list<set<i16>> sets,
+  11: list<Hello> hellos
+}
+
+exception Xception {
+  1: string message,
+  2: i32 code = 1
+}
+
+service NonblockingService {
+  Hello greeting(1:bool english)
+  bool block()
+  oneway void unblock(1:i32 n)
+  oneway void shutdown()
+  void sleep(1:double seconds)
+}
diff --git a/lib/rb/spec/base_protocol_spec.rb b/lib/rb/spec/base_protocol_spec.rb
new file mode 100644
index 0000000..efb16d8
--- /dev/null
+++ b/lib/rb/spec/base_protocol_spec.rb
@@ -0,0 +1,160 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+class ThriftBaseProtocolSpec < Spec::ExampleGroup
+  include Thrift
+
+  before(:each) do
+    @trans = mock("MockTransport")
+    @prot = BaseProtocol.new(@trans)
+  end
+
+  describe BaseProtocol do
+    # most of the methods are stubs, so we can ignore them
+
+    it "should make trans accessible" do
+      @prot.trans.should eql(@trans)
+    end
+
+    it "should write out a field nicely" do
+      @prot.should_receive(:write_field_begin).with('field', 'type', 'fid').ordered
+      @prot.should_receive(:write_type).with('type', 'value').ordered
+      @prot.should_receive(:write_field_end).ordered
+      @prot.write_field('field', 'type', 'fid', 'value')
+    end
+
+    it "should write out the different types" do
+      @prot.should_receive(:write_bool).with('bool').ordered
+      @prot.should_receive(:write_byte).with('byte').ordered
+      @prot.should_receive(:write_double).with('double').ordered
+      @prot.should_receive(:write_i16).with('i16').ordered
+      @prot.should_receive(:write_i32).with('i32').ordered
+      @prot.should_receive(:write_i64).with('i64').ordered
+      @prot.should_receive(:write_string).with('string').ordered
+      struct = mock('Struct')
+      struct.should_receive(:write).with(@prot).ordered
+      @prot.write_type(Types::BOOL, 'bool')
+      @prot.write_type(Types::BYTE, 'byte')
+      @prot.write_type(Types::DOUBLE, 'double')
+      @prot.write_type(Types::I16, 'i16')
+      @prot.write_type(Types::I32, 'i32')
+      @prot.write_type(Types::I64, 'i64')
+      @prot.write_type(Types::STRING, 'string')
+      @prot.write_type(Types::STRUCT, struct)
+      # all other types are not implemented
+      [Types::STOP, Types::VOID, Types::MAP, Types::SET, Types::LIST].each do |type|
+        lambda { @prot.write_type(type, type.to_s) }.should raise_error(NotImplementedError)
+      end
+    end
+
+    it "should read the different types" do
+      @prot.should_receive(:read_bool).ordered
+      @prot.should_receive(:read_byte).ordered
+      @prot.should_receive(:read_i16).ordered
+      @prot.should_receive(:read_i32).ordered
+      @prot.should_receive(:read_i64).ordered
+      @prot.should_receive(:read_double).ordered
+      @prot.should_receive(:read_string).ordered
+      @prot.read_type(Types::BOOL)
+      @prot.read_type(Types::BYTE)
+      @prot.read_type(Types::I16)
+      @prot.read_type(Types::I32)
+      @prot.read_type(Types::I64)
+      @prot.read_type(Types::DOUBLE)
+      @prot.read_type(Types::STRING)
+      # all other types are not implemented
+      [Types::STOP, Types::VOID, Types::MAP, Types::SET, Types::LIST].each do |type|
+        lambda { @prot.read_type(type) }.should raise_error(NotImplementedError)
+      end
+    end
+
+    it "should skip the basic types" do
+      @prot.should_receive(:read_bool).ordered
+      @prot.should_receive(:read_byte).ordered
+      @prot.should_receive(:read_i16).ordered
+      @prot.should_receive(:read_i32).ordered
+      @prot.should_receive(:read_i64).ordered
+      @prot.should_receive(:read_double).ordered
+      @prot.should_receive(:read_string).ordered
+      @prot.skip(Types::BOOL)
+      @prot.skip(Types::BYTE)
+      @prot.skip(Types::I16)
+      @prot.skip(Types::I32)
+      @prot.skip(Types::I64)
+      @prot.skip(Types::DOUBLE)
+      @prot.skip(Types::STRING)
+      @prot.skip(Types::STOP) # should do absolutely nothing
+    end
+
+    it "should skip structs" do
+      real_skip = @prot.method(:skip)
+      @prot.should_receive(:read_struct_begin).ordered
+      @prot.should_receive(:read_field_begin).exactly(4).times.and_return(
+        ['field 1', Types::STRING, 1],
+        ['field 2', Types::I32, 2],
+        ['field 3', Types::MAP, 3],
+        [nil, Types::STOP, 0]
+      )
+      @prot.should_receive(:read_field_end).exactly(3).times
+      @prot.should_receive(:read_string).exactly(3).times
+      @prot.should_receive(:read_i32).ordered
+      @prot.should_receive(:read_map_begin).ordered.and_return([Types::STRING, Types::STRING, 1])
+      # @prot.should_receive(:read_string).exactly(2).times
+      @prot.should_receive(:read_map_end).ordered
+      @prot.should_receive(:read_struct_end).ordered
+      real_skip.call(Types::STRUCT)
+    end
+
+    it "should skip maps" do
+      real_skip = @prot.method(:skip)
+      @prot.should_receive(:read_map_begin).ordered.and_return([Types::STRING, Types::STRUCT, 1])
+      @prot.should_receive(:read_string).ordered
+      @prot.should_receive(:read_struct_begin).ordered.and_return(["some_struct"])
+      @prot.should_receive(:read_field_begin).ordered.and_return([nil, Types::STOP, nil]);
+      @prot.should_receive(:read_struct_end).ordered
+      @prot.should_receive(:read_map_end).ordered
+      real_skip.call(Types::MAP)
+    end
+
+    it "should skip sets" do
+      real_skip = @prot.method(:skip)
+      @prot.should_receive(:read_set_begin).ordered.and_return([Types::I64, 9])
+      @prot.should_receive(:read_i64).ordered.exactly(9).times
+      @prot.should_receive(:read_set_end)
+      real_skip.call(Types::SET)
+    end
+
+    it "should skip lists" do
+      real_skip = @prot.method(:skip)
+      @prot.should_receive(:read_list_begin).ordered.and_return([Types::DOUBLE, 11])
+      @prot.should_receive(:read_double).ordered.exactly(11).times
+      @prot.should_receive(:read_list_end)
+      real_skip.call(Types::LIST)
+    end
+  end
+
+  describe BaseProtocolFactory do
+    it "should raise NotImplementedError" do
+      # returning nil since Protocol is just an abstract class
+      lambda {BaseProtocolFactory.new.get_protocol(mock("MockTransport"))}.should raise_error(NotImplementedError)
+    end
+  end
+end
diff --git a/lib/rb/spec/base_transport_spec.rb b/lib/rb/spec/base_transport_spec.rb
new file mode 100644
index 0000000..7189775
--- /dev/null
+++ b/lib/rb/spec/base_transport_spec.rb
@@ -0,0 +1,344 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+class ThriftBaseTransportSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe TransportException do
+    it "should make type accessible" do
+      exc = TransportException.new(TransportException::ALREADY_OPEN, "msg")
+      exc.type.should == TransportException::ALREADY_OPEN
+      exc.message.should == "msg"
+    end
+  end
+
+  describe BaseTransport do
+    it "should read the specified size" do
+      transport = BaseTransport.new
+      transport.should_receive(:read).with(40).ordered.and_return("10 letters")
+      transport.should_receive(:read).with(30).ordered.and_return("fifteen letters")
+      transport.should_receive(:read).with(15).ordered.and_return("more characters")
+      transport.read_all(40).should == "10 lettersfifteen lettersmore characters"
+    end
+
+    it "should stub out the rest of the methods" do
+      # can't test for stubbiness, so just make sure they're defined
+      [:open?, :open, :close, :read, :write, :flush].each do |sym|
+        BaseTransport.method_defined?(sym).should be_true
+      end
+    end
+
+    it "should alias << to write" do
+      BaseTransport.instance_method(:<<).should == BaseTransport.instance_method(:write)
+    end
+  end
+
+  describe BaseServerTransport do
+    it "should stub out its methods" do
+      [:listen, :accept, :close].each do |sym|
+        BaseServerTransport.method_defined?(sym).should be_true
+      end
+    end
+  end
+
+  describe BaseTransportFactory do
+    it "should return the transport it's given" do
+      transport = mock("Transport")
+      BaseTransportFactory.new.get_transport(transport).should eql(transport)
+    end
+  end
+
+  describe BufferedTransport 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")
+      btrans = BufferedTransport.new(trans)
+      btrans.open?.should == "+ open?"
+      btrans.open.should == "+ open"
+      btrans.close.should == "+ close"
+    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
+      trans = mock("Transport")
+      btrans = BufferedTransport.new(trans)
+      btrans.write("one/")
+      btrans.write("two/")
+      btrans.write("three/")
+      trans.should_receive(:write).with("one/two/three/").ordered
+      trans.should_receive(:flush).ordered
+      btrans.flush
+    end
+
+    it "should only send buffered data once" do
+      trans = mock("Transport")
+      btrans = BufferedTransport.new(trans)
+      btrans.write("one/")
+      btrans.write("two/")
+      btrans.write("three/")
+      trans.should_receive(:write).with("one/two/three/")
+      trans.stub!(:flush)
+      btrans.flush
+      # Nothing to flush with no data
+      btrans.flush
+    end
+    
+    it "should flush on close" do
+      trans = mock("Transport")
+      trans.should_receive(:close)
+      btrans = BufferedTransport.new(trans)
+      btrans.should_receive(:flush)
+      btrans.close
+    end
+    
+    it "should not write to socket if there's no data" do
+      trans = mock("Transport")
+      trans.should_receive(:flush)
+      btrans = BufferedTransport.new(trans)
+      btrans.flush
+    end
+  end
+
+  describe BufferedTransportFactory do
+    it "should wrap the given transport in a BufferedTransport" do
+      trans = mock("Transport")
+      btrans = mock("BufferedTransport")
+      BufferedTransport.should_receive(:new).with(trans).and_return(btrans)
+      BufferedTransportFactory.new.get_transport(trans).should == btrans
+    end
+  end
+
+  describe FramedTransport do
+    before(:each) do
+      @trans = mock("Transport")
+    end
+
+    it "should pass through open?/open/close" do
+      ftrans = FramedTransport.new(@trans)
+      @trans.should_receive(:open?).ordered.and_return("+ open?")
+      @trans.should_receive(:open).ordered.and_return("+ open")
+      @trans.should_receive(:close).ordered.and_return("+ close")
+      ftrans.open?.should == "+ open?"
+      ftrans.open.should == "+ open"
+      ftrans.close.should == "+ close"
+    end
+
+    it "should pass through read when read is turned off" do
+      ftrans = FramedTransport.new(@trans, false, true)
+      @trans.should_receive(:read).with(17).ordered.and_return("+ read")
+      ftrans.read(17).should == "+ read"
+    end
+
+    it "should pass through write/flush when write is turned off" do
+      ftrans = FramedTransport.new(@trans, true, false)
+      @trans.should_receive(:write).with("foo").ordered.and_return("+ write")
+      @trans.should_receive(:flush).ordered.and_return("+ flush")
+      ftrans.write("foo").should == "+ write"
+      ftrans.flush.should == "+ flush"
+    end
+
+    it "should return a full frame if asked for >= the frame's length" do
+      frame = "this is a frame"
+      @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017")
+      @trans.should_receive(:read_all).with(frame.length).and_return(frame)
+      FramedTransport.new(@trans).read(frame.length + 10).should == frame
+    end
+
+    it "should return slices of the frame when asked for < the frame's length" do
+      frame = "this is a frame"
+      @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017")
+      @trans.should_receive(:read_all).with(frame.length).and_return(frame)
+      ftrans = FramedTransport.new(@trans)
+      ftrans.read(4).should == "this"
+      ftrans.read(4).should == " is "
+      ftrans.read(16).should == "a frame"
+    end
+
+    it "should return nothing if asked for <= 0" do
+      FramedTransport.new(@trans).read(-2).should == ""
+    end
+
+    it "should pull a new frame when the first is exhausted" do
+      frame = "this is a frame"
+      frame2 = "yet another frame"
+      @trans.should_receive(:read_all).with(4).and_return("\000\000\000\017", "\000\000\000\021")
+      @trans.should_receive(:read_all).with(frame.length).and_return(frame)
+      @trans.should_receive(:read_all).with(frame2.length).and_return(frame2)
+      ftrans = FramedTransport.new(@trans)
+      ftrans.read(4).should == "this"
+      ftrans.read(8).should == " is a fr"
+      ftrans.read(6).should == "ame"
+      ftrans.read(4).should == "yet "
+      ftrans.read(16).should == "another frame"
+    end
+
+    it "should buffer writes" do
+      ftrans = FramedTransport.new(@trans)
+      @trans.should_not_receive(:write)
+      ftrans.write("foo")
+      ftrans.write("bar")
+      ftrans.write("this is a frame")
+    end
+
+    it "should write slices of the buffer" do
+      ftrans = FramedTransport.new(@trans)
+      ftrans.write("foobar", 3)
+      ftrans.write("barfoo", 1)
+      @trans.stub!(:flush)
+      @trans.should_receive(:write).with("\000\000\000\004foob")
+      ftrans.flush
+    end
+
+    it "should flush frames with a 4-byte header" do
+      ftrans = FramedTransport.new(@trans)
+      @trans.should_receive(:write).with("\000\000\000\035one/two/three/this is a frame").ordered
+      @trans.should_receive(:flush).ordered
+      ftrans.write("one/")
+      ftrans.write("two/")
+      ftrans.write("three/")
+      ftrans.write("this is a frame")
+      ftrans.flush
+    end
+
+    it "should not flush the same buffered data twice" do
+      ftrans = FramedTransport.new(@trans)
+      @trans.should_receive(:write).with("\000\000\000\007foo/bar")
+      @trans.stub!(:flush)
+      ftrans.write("foo")
+      ftrans.write("/bar")
+      ftrans.flush
+      @trans.should_receive(:write).with("\000\000\000\000")
+      ftrans.flush
+    end
+  end
+
+  describe FramedTransportFactory do
+    it "should wrap the given transport in a FramedTransport" do
+      trans = mock("Transport")
+      FramedTransport.should_receive(:new).with(trans)
+      FramedTransportFactory.new.get_transport(trans)
+    end
+  end
+
+  describe MemoryBufferTransport do
+    before(:each) do
+      @buffer = MemoryBufferTransport.new
+    end
+
+    it "should accept a buffer on input and use it directly" do
+      s = "this is a test"
+      @buffer = MemoryBufferTransport.new(s)
+      @buffer.read(4).should == "this"
+      s.slice!(-4..-1)
+      @buffer.read(@buffer.available).should == " is a "
+    end
+
+    it "should always remain open" do
+      @buffer.should be_open
+      @buffer.close
+      @buffer.should be_open
+    end
+
+    it "should respond to peek and available" do
+      @buffer.write "some data"
+      @buffer.peek.should be_true
+      @buffer.available.should == 9
+      @buffer.read(4)
+      @buffer.peek.should be_true
+      @buffer.available.should == 5
+      @buffer.read(16)
+      @buffer.peek.should be_false
+      @buffer.available.should == 0
+    end
+
+    it "should be able to reset the buffer" do
+      @buffer.write "test data"
+      @buffer.reset_buffer("foobar")
+      @buffer.available.should == 6
+      @buffer.read(10).should == "foobar"
+      @buffer.reset_buffer
+      @buffer.available.should == 0
+    end
+
+    it "should copy the given string whne resetting the buffer" do
+      s = "this is a test"
+      @buffer.reset_buffer(s)
+      @buffer.available.should == 14
+      @buffer.read(10)
+      @buffer.available.should == 4
+      s.should == "this is a test"
+    end
+
+    it "should return from read what was given in write" do
+      @buffer.write "test data"
+      @buffer.read(4).should == "test"
+      @buffer.read(10).should == " data"
+      @buffer.read(10).should == ""
+      @buffer.write "foo"
+      @buffer.write " bar"
+      @buffer.read(10).should == "foo bar"
+    end
+  end
+
+  describe IOStreamTransport do
+    before(:each) do
+      @input = mock("Input", :closed? => false)
+      @output = mock("Output", :closed? => false)
+      @trans = IOStreamTransport.new(@input, @output)
+    end
+
+    it "should be open as long as both input or output are open" do
+      @trans.should be_open
+      @input.stub!(:closed?).and_return(true)
+      @trans.should be_open
+      @input.stub!(:closed?).and_return(false)
+      @output.stub!(:closed?).and_return(true)
+      @trans.should be_open
+      @input.stub!(:closed?).and_return(true)
+      @trans.should_not be_open
+    end
+
+    it "should pass through read/write to input/output" do
+      @input.should_receive(:read).with(17).and_return("+ read")
+      @output.should_receive(:write).with("foobar").and_return("+ write")
+      @trans.read(17).should == "+ read"
+      @trans.write("foobar").should == "+ write"
+    end
+
+    it "should close both input and output when closed" do
+      @input.should_receive(:close)
+      @output.should_receive(:close)
+      @trans.close
+    end
+  end
+end
diff --git a/lib/rb/spec/binary_protocol_accelerated_spec.rb b/lib/rb/spec/binary_protocol_accelerated_spec.rb
new file mode 100644
index 0000000..0306cf5
--- /dev/null
+++ b/lib/rb/spec/binary_protocol_accelerated_spec.rb
@@ -0,0 +1,42 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + '/binary_protocol_spec_shared'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftBinaryProtocolAcceleratedSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe Thrift::BinaryProtocolAccelerated do
+    # since BinaryProtocolAccelerated should be directly equivalent to 
+    # BinaryProtocol, we don't need any custom specs!
+    it_should_behave_like 'a binary protocol'
+
+    def protocol_class
+      BinaryProtocolAccelerated
+    end
+  end
+
+  describe BinaryProtocolAcceleratedFactory do
+    it "should create a BinaryProtocolAccelerated" do
+      BinaryProtocolAcceleratedFactory.new.get_protocol(mock("MockTransport")).should be_instance_of(BinaryProtocolAccelerated)
+    end
+  end
+end
diff --git a/lib/rb/spec/binary_protocol_spec.rb b/lib/rb/spec/binary_protocol_spec.rb
new file mode 100644
index 0000000..0abccb8
--- /dev/null
+++ b/lib/rb/spec/binary_protocol_spec.rb
@@ -0,0 +1,63 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + '/binary_protocol_spec_shared'
+
+class ThriftBinaryProtocolSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe BinaryProtocol do
+    it_should_behave_like 'a binary protocol'
+
+    def protocol_class
+      BinaryProtocol
+    end
+
+    it "should read a message header" do
+      @trans.should_receive(:read_all).exactly(2).times.and_return(
+        [protocol_class.const_get(:VERSION_1) | Thrift::MessageTypes::REPLY].pack('N'),
+        [42].pack('N')
+      )      
+      @prot.should_receive(:read_string).and_return('testMessage')
+      @prot.read_message_begin.should == ['testMessage', Thrift::MessageTypes::REPLY, 42]
+    end
+
+    it "should raise an exception if the message header has the wrong version" do
+      @prot.should_receive(:read_i32).and_return(-1)
+      lambda { @prot.read_message_begin }.should raise_error(Thrift::ProtocolException, 'Missing version identifier') do |e|
+        e.type == Thrift::ProtocolException::BAD_VERSION
+      end
+    end
+
+    it "should raise an exception if the message header does not exist and strict_read is enabled" do
+      @prot.should_receive(:read_i32).and_return(42)
+      @prot.should_receive(:strict_read).and_return(true)
+      lambda { @prot.read_message_begin }.should raise_error(Thrift::ProtocolException, 'No version identifier, old protocol client?') do |e|        
+        e.type == Thrift::ProtocolException::BAD_VERSION
+      end
+    end
+  end
+
+  describe BinaryProtocolFactory do
+    it "should create a BinaryProtocol" do
+      BinaryProtocolFactory.new.get_protocol(mock("MockTransport")).should be_instance_of(BinaryProtocol)
+    end
+  end
+end
diff --git a/lib/rb/spec/binary_protocol_spec_shared.rb b/lib/rb/spec/binary_protocol_spec_shared.rb
new file mode 100644
index 0000000..c6608e0
--- /dev/null
+++ b/lib/rb/spec/binary_protocol_spec_shared.rb
@@ -0,0 +1,375 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+shared_examples_for 'a binary protocol' do
+  before(:each) do
+    @trans = Thrift::MemoryBufferTransport.new
+    @prot = protocol_class.new(@trans)
+  end
+
+  it "should define the proper VERSION_1, VERSION_MASK AND TYPE_MASK" do
+    protocol_class.const_get(:VERSION_MASK).should == 0xffff0000
+    protocol_class.const_get(:VERSION_1).should == 0x80010000
+    protocol_class.const_get(:TYPE_MASK).should == 0x000000ff
+  end
+
+  it "should make strict_read readable" do
+    @prot.strict_read.should eql(true)
+  end
+
+  it "should make strict_write readable" do
+    @prot.strict_write.should eql(true)
+  end    
+
+  it "should write the message header" do
+    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
+    @trans.read(1000).should == [protocol_class.const_get(:VERSION_1) | Thrift::MessageTypes::CALL, "testMessage".size, "testMessage", 17].pack("NNa11N")
+  end
+  
+  it "should write the message header without version when writes are not strict" do
+    @prot = protocol_class.new(@trans, true, false) # no strict write
+    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
+    @trans.read(1000).should == "\000\000\000\vtestMessage\001\000\000\000\021"
+  end
+  
+  it "should write the message header with a version when writes are strict" do
+    @prot = protocol_class.new(@trans) # strict write
+    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
+    @trans.read(1000).should == "\200\001\000\001\000\000\000\vtestMessage\000\000\000\021"
+  end
+  
+
+  # message footer is a noop
+
+  it "should write the field header" do
+    @prot.write_field_begin('foo', Thrift::Types::DOUBLE, 3)
+    @trans.read(1000).should == [Thrift::Types::DOUBLE, 3].pack("cn")
+  end
+  
+  # field footer is a noop
+  
+  it "should write the STOP field" do
+    @prot.write_field_stop
+    @trans.read(1).should == "\000"
+  end
+  
+  it "should write the map header" do
+    @prot.write_map_begin(Thrift::Types::STRING, Thrift::Types::LIST, 17)
+    @trans.read(1000).should == [Thrift::Types::STRING, Thrift::Types::LIST, 17].pack("ccN");
+  end
+   
+  # map footer is a noop
+  
+  it "should write the list header" do
+    @prot.write_list_begin(Thrift::Types::I16, 42)
+    @trans.read(1000).should == [Thrift::Types::I16, 42].pack("cN")
+  end
+  
+  # list footer is a noop
+  
+  it "should write the set header" do
+    @prot.write_set_begin(Thrift::Types::I16, 42)
+    @trans.read(1000).should == [Thrift::Types::I16, 42].pack("cN")
+  end
+  
+  it "should write a bool" do
+    @prot.write_bool(true)
+    @prot.write_bool(false)
+    @trans.read(1000).should == "\001\000"
+  end
+  
+  it "should treat a nil bool as false" do
+    @prot.write_bool(nil)
+    @trans.read(1).should == "\000"
+  end
+  
+  it "should write a byte" do
+    # byte is small enough, let's check -128..127
+    (-128..127).each do |i|
+      @prot.write_byte(i)
+      @trans.read(1).should == [i].pack('c')
+    end
+    # handing it numbers out of signed range should clip
+    @trans.rspec_verify
+    (128..255).each do |i|
+      @prot.write_byte(i)
+      @trans.read(1).should == [i].pack('c')
+    end
+    # and lastly, a Bignum is going to error out
+    lambda { @prot.write_byte(2**65) }.should raise_error(RangeError)
+  end
+  
+  it "should error gracefully when trying to write a nil byte" do
+    lambda { @prot.write_byte(nil) }.should raise_error
+  end
+  
+  it "should write an i16" do
+    # try a random scattering of values
+    # include the signed i16 minimum/maximum
+    [-2**15, -1024, 17, 0, -10000, 1723, 2**15-1].each do |i|
+      @prot.write_i16(i)
+    end
+    # and try something out of signed range, it should clip
+    @prot.write_i16(2**15 + 5)
+    
+    @trans.read(1000).should == "\200\000\374\000\000\021\000\000\330\360\006\273\177\377\200\005"
+    
+    # a Bignum should error
+    # lambda { @prot.write_i16(2**65) }.should raise_error(RangeError)
+  end
+  
+  it "should error gracefully when trying to write a nil i16" do
+    lambda { @prot.write_i16(nil) }.should raise_error
+  end
+  
+  it "should write an i32" do
+    # try a random scattering of values
+    # include the signed i32 minimum/maximum
+    [-2**31, -123123, -2532, -3, 0, 2351235, 12331, 2**31-1].each do |i|
+      @prot.write_i32(i)
+    end
+    # try something out of signed range, it should clip
+    @trans.read(1000).should == "\200\000\000\000" + "\377\376\037\r" + "\377\377\366\034" + "\377\377\377\375" + "\000\000\000\000" + "\000#\340\203" + "\000\0000+" + "\177\377\377\377"
+    [2 ** 31 + 5, 2 ** 65 + 5].each do |i|
+      lambda { @prot.write_i32(i) }.should raise_error(RangeError)  
+    end
+  end
+  
+  it "should error gracefully when trying to write a nil i32" do
+    lambda { @prot.write_i32(nil) }.should raise_error
+  end
+  
+  it "should write an i64" do
+    # try a random scattering of values
+    # try the signed i64 minimum/maximum
+    [-2**63, -12356123612323, -23512351, -234, 0, 1231, 2351236, 12361236213, 2**63-1].each do |i|
+      @prot.write_i64(i)
+    end
+    # try something out of signed range, it should clip
+    @trans.read(1000).should == ["\200\000\000\000\000\000\000\000",
+      "\377\377\364\303\035\244+]",
+      "\377\377\377\377\376\231:\341",
+      "\377\377\377\377\377\377\377\026",
+      "\000\000\000\000\000\000\000\000",
+      "\000\000\000\000\000\000\004\317",
+      "\000\000\000\000\000#\340\204",
+      "\000\000\000\002\340\311~\365",
+      "\177\377\377\377\377\377\377\377"].join("")
+    lambda { @prot.write_i64(2 ** 65 + 5) }.should raise_error(RangeError)
+  end
+  
+  it "should error gracefully when trying to write a nil i64" do
+    lambda { @prot.write_i64(nil) }.should raise_error
+  end
+  
+  it "should write a double" do
+    # try a random scattering of values, including min/max
+    values = [Float::MIN,-1231.15325, -123123.23, -23.23515123, 0, 12351.1325, 523.23, Float::MAX]
+    values.each do |f|
+      @prot.write_double(f)
+      @trans.read(1000).should == [f].pack("G")
+    end
+  end
+  
+  it "should error gracefully when trying to write a nil double" do
+    lambda { @prot.write_double(nil) }.should raise_error
+  end
+  
+  it "should write a string" do
+    str = "hello world"
+    @prot.write_string(str)
+    @trans.read(1000).should == [str.size].pack("N") + str
+  end
+  
+  it "should error gracefully when trying to write a nil string" do
+    lambda { @prot.write_string(nil) }.should raise_error
+  end
+  
+  it "should write the message header without version when writes are not strict" do
+    @prot = protocol_class.new(@trans, true, false) # no strict write
+    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
+    @trans.read(1000).should == "\000\000\000\vtestMessage\001\000\000\000\021"
+  end
+    
+  it "should write the message header with a version when writes are strict" do
+    @prot = protocol_class.new(@trans) # strict write
+    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
+    @trans.read(1000).should == "\200\001\000\001\000\000\000\vtestMessage\000\000\000\021"
+  end
+  
+  # message footer is a noop
+  
+  it "should read a field header" do
+    @trans.write([Thrift::Types::STRING, 3].pack("cn"))
+    @prot.read_field_begin.should == [nil, Thrift::Types::STRING, 3]
+  end
+  
+  # field footer is a noop
+  
+  it "should read a stop field" do
+    @trans.write([Thrift::Types::STOP].pack("c"));
+    @prot.read_field_begin.should == [nil, Thrift::Types::STOP, 0]
+  end
+
+  it "should read a map header" do
+    @trans.write([Thrift::Types::DOUBLE, Thrift::Types::I64, 42].pack("ccN"))
+    @prot.read_map_begin.should == [Thrift::Types::DOUBLE, Thrift::Types::I64, 42]
+  end
+  
+  # map footer is a noop
+  
+  it "should read a list header" do
+    @trans.write([Thrift::Types::STRING, 17].pack("cN"))
+    @prot.read_list_begin.should == [Thrift::Types::STRING, 17]
+  end
+  
+  # list footer is a noop
+  
+  it "should read a set header" do
+    @trans.write([Thrift::Types::STRING, 17].pack("cN"))
+    @prot.read_set_begin.should == [Thrift::Types::STRING, 17]
+  end
+  
+  # set footer is a noop
+  
+  it "should read a bool" do
+    @trans.write("\001\000");
+    @prot.read_bool.should == true
+    @prot.read_bool.should == false
+  end
+  
+  it "should read a byte" do
+    [-128, -57, -3, 0, 17, 24, 127].each do |i|
+      @trans.write([i].pack("c"))
+      @prot.read_byte.should == i
+    end
+  end
+  
+  it "should read an i16" do
+    # try a scattering of values, including min/max
+    [-2**15, -5237, -353, 0, 1527, 2234, 2**15-1].each do |i|
+      @trans.write([i].pack("n"));
+      @prot.read_i16.should == i
+    end
+  end
+  
+  it "should read an i32" do
+    # try a scattering of values, including min/max
+    [-2**31, -235125, -6236, 0, 2351, 123123, 2**31-1].each do |i|
+      @trans.write([i].pack("N"))
+      @prot.read_i32.should == i
+    end
+  end
+  
+  it "should read an i64" do
+    # try a scattering of values, including min/max
+    [-2**63, -123512312, -6346, 0, 32, 2346322323, 2**63-1].each do |i|
+      @trans.write([i >> 32, i & 0xFFFFFFFF].pack("NN"))
+      @prot.read_i64.should == i
+    end
+  end
+  
+  it "should read a double" do
+    # try a random scattering of values, including min/max
+    [Float::MIN, -231231.12351, -323.233513, 0, 123.2351235, 2351235.12351235, Float::MAX].each do |f|
+      @trans.write([f].pack("G"));
+      @prot.read_double.should == f
+    end
+  end
+  
+  it "should read a string" do
+    str = "hello world"
+    @trans.write([str.size].pack("N") + str)
+    @prot.read_string.should == str
+  end
+
+  it "should perform a complete rpc with no args or return" do
+    srv_test(
+      proc {|client| client.send_voidMethod()},
+      proc {|client| client.recv_voidMethod.should == nil}
+    )
+  end
+
+  it "should perform a complete rpc with a primitive return type" do
+    srv_test(
+      proc {|client| client.send_primitiveMethod()},
+      proc {|client| client.recv_primitiveMethod.should == 1}
+    )
+  end
+
+  it "should perform a complete rpc with a struct return type" do
+    srv_test(
+      proc {|client| client.send_structMethod()},
+      proc {|client|
+        result = client.recv_structMethod
+        result.set_byte_map = nil
+        result.map_byte_map = nil
+        result.should == Fixtures::COMPACT_PROTOCOL_TEST_STRUCT
+      }
+    )
+  end
+
+  def get_socket_connection
+    server = Thrift::ServerSocket.new("localhost", 9090)
+    server.listen
+    
+    clientside = Thrift::Socket.new("localhost", 9090)
+    clientside.open
+    serverside = server.accept
+    [clientside, serverside, server]
+  end
+
+  def srv_test(firstblock, secondblock)
+    clientside, serverside, server = get_socket_connection
+
+    clientproto = protocol_class.new(clientside)
+    serverproto = protocol_class.new(serverside)
+
+    processor = Srv::Processor.new(SrvHandler.new)
+
+    client = Srv::Client.new(clientproto, clientproto)
+
+    # first block
+    firstblock.call(client)
+    
+    processor.process(serverproto, serverproto)
+    
+    # second block
+    secondblock.call(client)
+  ensure
+    clientside.close
+    serverside.close
+    server.close
+  end
+
+  class SrvHandler 
+    def voidMethod()
+    end
+    
+    def primitiveMethod
+      1
+    end
+    
+    def structMethod
+      Fixtures::COMPACT_PROTOCOL_TEST_STRUCT
+    end
+  end
+end
diff --git a/lib/rb/spec/client_spec.rb b/lib/rb/spec/client_spec.rb
new file mode 100644
index 0000000..e707d81
--- /dev/null
+++ b/lib/rb/spec/client_spec.rb
@@ -0,0 +1,100 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+class ThriftClientSpec < Spec::ExampleGroup
+  include Thrift
+
+  class ClientSpec
+    include Thrift::Client
+  end
+
+  before(:each) do
+    @prot = mock("MockProtocol")
+    @client = ClientSpec.new(@prot)
+  end
+
+  describe Client do
+    it "should re-use iprot for oprot if not otherwise specified" do
+      @client.instance_variable_get(:'@iprot').should eql(@prot)
+      @client.instance_variable_get(:'@oprot').should eql(@prot)
+    end
+
+    it "should send a test message" do
+      @prot.should_receive(:write_message_begin).with('testMessage', MessageTypes::CALL, 0)
+      mock_args = mock('#<TestMessage_args:mock>')
+      mock_args.should_receive(:foo=).with('foo')
+      mock_args.should_receive(:bar=).with(42)
+      mock_args.should_receive(:write).with(@prot)
+      @prot.should_receive(:write_message_end)
+      @prot.should_receive(:trans) do
+        mock('trans').tee do |trans|
+          trans.should_receive(:flush)
+        end
+      end
+      klass = stub("TestMessage_args", :new => mock_args)
+      @client.send_message('testMessage', klass, :foo => 'foo', :bar => 42)
+    end
+
+    it "should increment the sequence id when sending messages" do
+      pending "it seems sequence ids are completely ignored right now" do
+        @prot.should_receive(:write_message_begin).with('testMessage',  MessageTypes::CALL, 0).ordered
+        @prot.should_receive(:write_message_begin).with('testMessage2', MessageTypes::CALL, 1).ordered
+        @prot.should_receive(:write_message_begin).with('testMessage3', MessageTypes::CALL, 2).ordered
+        @prot.stub!(:write_message_end)
+        @prot.stub!(:trans).and_return mock("trans").as_null_object
+        @client.send_message('testMessage', mock("args class").as_null_object)
+        @client.send_message('testMessage2', mock("args class").as_null_object)
+        @client.send_message('testMessage3', mock("args class").as_null_object)        
+      end
+    end
+
+    it "should receive a test message" do
+      @prot.should_receive(:read_message_begin).and_return [nil, MessageTypes::CALL, 0]
+      @prot.should_receive(:read_message_end)
+      mock_klass = mock("#<MockClass:mock>")
+      mock_klass.should_receive(:read).with(@prot)
+      @client.receive_message(stub("MockClass", :new => mock_klass))
+    end
+
+    it "should handle received exceptions" do
+      @prot.should_receive(:read_message_begin).and_return [nil, MessageTypes::EXCEPTION, 0]
+      @prot.should_receive(:read_message_end)
+      ApplicationException.should_receive(:new).and_return do
+        StandardError.new.tee do |mock_exc|
+          mock_exc.should_receive(:read).with(@prot)
+        end
+      end
+      lambda { @client.receive_message(nil) }.should raise_error(StandardError)
+    end
+
+    it "should close the transport if an error occurs while sending a message" do
+      @prot.stub!(:write_message_begin)
+      @prot.should_not_receive(:write_message_end)
+      mock_args = mock("#<TestMessage_args:mock>")
+      mock_args.should_receive(:write).with(@prot).and_raise(StandardError)
+      trans = mock("MockTransport")
+      @prot.stub!(:trans).and_return(trans)
+      trans.should_receive(:close)
+      klass = mock("TestMessage_args", :new => mock_args)
+      lambda { @client.send_message("testMessage", klass) }.should raise_error(StandardError)
+    end
+  end
+end
diff --git a/lib/rb/spec/compact_protocol_spec.rb b/lib/rb/spec/compact_protocol_spec.rb
new file mode 100644
index 0000000..b9a7981
--- /dev/null
+++ b/lib/rb/spec/compact_protocol_spec.rb
@@ -0,0 +1,117 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe Thrift::CompactProtocol do
+  TESTS = {
+    :byte => (-127..127).to_a,
+    :i16 => (0..14).map {|shift| [1 << shift, -(1 << shift)]}.flatten.sort,
+    :i32 => (0..30).map {|shift| [1 << shift, -(1 << shift)]}.flatten.sort,
+    :i64 => (0..62).map {|shift| [1 << shift, -(1 << shift)]}.flatten.sort,
+    :string => ["", "1", "short", "fourteen123456", "fifteen12345678", "1" * 127, "1" * 3000],
+    :binary => ["", "\001", "\001" * 5, "\001" * 14, "\001" * 15, "\001" * 127, "\001" * 3000],
+    :double => [0.0, 1.0, -1.0, 1.1, -1.1, 10000000.1, 1.0/0.0, -1.0/0.0],
+    :bool => [true, false]
+  }
+  
+  it "should encode and decode naked primitives correctly" do
+    TESTS.each_pair do |primitive_type, test_values|
+      test_values.each do |value|
+        # puts "testing #{value}" if primitive_type == :i64
+        trans = Thrift::MemoryBufferTransport.new
+        proto = Thrift::CompactProtocol.new(trans)
+        
+        proto.send(writer(primitive_type), value)
+        # puts "buf: #{trans.inspect_buffer}" if primitive_type == :i64
+        read_back = proto.send(reader(primitive_type))
+        read_back.should == value
+      end
+    end
+  end
+  
+  it "should encode and decode primitives in fields correctly" do
+    TESTS.each_pair do |primitive_type, test_values|
+      final_primitive_type = primitive_type == :binary ? :string : primitive_type
+      thrift_type = Thrift::Types.const_get(final_primitive_type.to_s.upcase)
+      # puts primitive_type
+      test_values.each do |value|
+        trans = Thrift::MemoryBufferTransport.new
+        proto = Thrift::CompactProtocol.new(trans)
+
+        proto.write_field_begin(nil, thrift_type, 15)
+        proto.send(writer(primitive_type), value)
+        proto.write_field_end
+
+        proto = Thrift::CompactProtocol.new(trans)
+        name, type, id = proto.read_field_begin
+        type.should == thrift_type
+        id.should == 15
+        read_back = proto.send(reader(primitive_type))
+        read_back.should == value
+        proto.read_field_end
+      end
+    end
+  end
+
+  it "should encode and decode a monster struct correctly" do
+    trans = Thrift::MemoryBufferTransport.new
+    proto = Thrift::CompactProtocol.new(trans)
+
+    struct = CompactProtoTestStruct.new
+    # sets and maps don't hash well... not sure what to do here.
+    struct.write(proto)
+
+    struct2 = CompactProtoTestStruct.new
+    struct2.read(proto)    
+    struct2.should == struct
+  end
+
+  it "should make method calls correctly" do
+    client_out_trans = Thrift::MemoryBufferTransport.new
+    client_out_proto = Thrift::CompactProtocol.new(client_out_trans)
+    
+    client_in_trans = Thrift::MemoryBufferTransport.new
+    client_in_proto = Thrift::CompactProtocol.new(client_in_trans)
+    
+    processor = Srv::Processor.new(JankyHandler.new)
+    
+    client = Srv::Client.new(client_in_proto, client_out_proto)
+    client.send_Janky(1)
+    # puts client_out_trans.inspect_buffer
+    processor.process(client_out_proto, client_in_proto)
+    client.recv_Janky.should == 2
+  end
+  
+  class JankyHandler
+    def Janky(i32arg)
+      i32arg * 2
+    end
+  end
+  
+  def writer(sym)
+    sym = sym == :binary ? :string : sym
+    "write_#{sym.to_s}"
+  end
+  
+  def reader(sym)
+    sym = sym == :binary ? :string : sym
+    "read_#{sym.to_s}"
+  end
+end
\ No newline at end of file
diff --git a/lib/rb/spec/exception_spec.rb b/lib/rb/spec/exception_spec.rb
new file mode 100644
index 0000000..fc32137
--- /dev/null
+++ b/lib/rb/spec/exception_spec.rb
@@ -0,0 +1,142 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+class ThriftExceptionSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe Exception do
+    it "should have an accessible message" do
+      e = Exception.new("test message")
+      e.message.should == "test message"
+    end
+  end
+
+  describe ApplicationException do
+    it "should inherit from Thrift::Exception" do
+      ApplicationException.superclass.should == Exception
+    end
+
+    it "should have an accessible type and message" do
+      e = ApplicationException.new
+      e.type.should == ApplicationException::UNKNOWN
+      e.message.should be_nil
+      e = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, "test message")
+      e.type.should == ApplicationException::UNKNOWN_METHOD
+      e.message.should == "test message"
+    end
+
+    it "should read a struct off of a protocol" do
+      prot = mock("MockProtocol")
+      prot.should_receive(:read_struct_begin).ordered
+      prot.should_receive(:read_field_begin).exactly(3).times.and_return(
+        ["message", Types::STRING, 1],
+        ["type", Types::I32, 2],
+        [nil, Types::STOP, 0]
+      )
+      prot.should_receive(:read_string).ordered.and_return "test message"
+      prot.should_receive(:read_i32).ordered.and_return ApplicationException::BAD_SEQUENCE_ID
+      prot.should_receive(:read_field_end).exactly(2).times
+      prot.should_receive(:read_struct_end).ordered
+
+      e = ApplicationException.new
+      e.read(prot)
+      e.message.should == "test message"
+      e.type.should == ApplicationException::BAD_SEQUENCE_ID
+    end
+
+    it "should skip bad fields when reading a struct" do
+      prot = mock("MockProtocol")
+      prot.should_receive(:read_struct_begin).ordered
+      prot.should_receive(:read_field_begin).exactly(5).times.and_return(
+        ["type", Types::I32, 2],
+        ["type", Types::STRING, 2],
+        ["message", Types::MAP, 1],
+        ["message", Types::STRING, 3],
+        [nil, Types::STOP, 0]
+      )
+      prot.should_receive(:read_i32).and_return ApplicationException::INVALID_MESSAGE_TYPE
+      prot.should_receive(:skip).with(Types::STRING).twice
+      prot.should_receive(:skip).with(Types::MAP)
+      prot.should_receive(:read_field_end).exactly(4).times
+      prot.should_receive(:read_struct_end).ordered
+
+      e = ApplicationException.new
+      e.read(prot)
+      e.message.should be_nil
+      e.type.should == ApplicationException::INVALID_MESSAGE_TYPE
+    end
+
+    it "should write a Thrift::ApplicationException struct to the oprot" do
+      prot = mock("MockProtocol")
+      prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+      prot.should_receive(:write_field_begin).with("message", Types::STRING, 1).ordered
+      prot.should_receive(:write_string).with("test message").ordered
+      prot.should_receive(:write_field_begin).with("type", Types::I32, 2).ordered
+      prot.should_receive(:write_i32).with(ApplicationException::UNKNOWN_METHOD).ordered
+      prot.should_receive(:write_field_end).twice
+      prot.should_receive(:write_field_stop).ordered
+      prot.should_receive(:write_struct_end).ordered
+
+      e = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, "test message")
+      e.write(prot)
+    end
+
+    it "should skip nil fields when writing to the oprot" do
+      prot = mock("MockProtocol")
+      prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+      prot.should_receive(:write_field_begin).with("message", Types::STRING, 1).ordered
+      prot.should_receive(:write_string).with("test message").ordered
+      prot.should_receive(:write_field_end).ordered
+      prot.should_receive(:write_field_stop).ordered
+      prot.should_receive(:write_struct_end).ordered
+
+      e = ApplicationException.new(nil, "test message")
+      e.write(prot)
+
+      prot = mock("MockProtocol")
+      prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+      prot.should_receive(:write_field_begin).with("type", Types::I32, 2).ordered
+      prot.should_receive(:write_i32).with(ApplicationException::BAD_SEQUENCE_ID).ordered
+      prot.should_receive(:write_field_end).ordered
+      prot.should_receive(:write_field_stop).ordered
+      prot.should_receive(:write_struct_end).ordered
+
+      e = ApplicationException.new(ApplicationException::BAD_SEQUENCE_ID)
+      e.write(prot)
+
+      prot = mock("MockProtocol")
+      prot.should_receive(:write_struct_begin).with("Thrift::ApplicationException").ordered
+      prot.should_receive(:write_field_stop).ordered
+      prot.should_receive(:write_struct_end).ordered
+
+      e = ApplicationException.new(nil)
+      e.write(prot)
+    end
+  end
+
+  describe ProtocolException do
+    it "should have an accessible type" do
+      prot = ProtocolException.new(ProtocolException::SIZE_LIMIT, "message")
+      prot.type.should == ProtocolException::SIZE_LIMIT
+      prot.message.should == "message"
+    end
+  end
+end
diff --git a/lib/rb/spec/http_client_spec.rb b/lib/rb/spec/http_client_spec.rb
new file mode 100644
index 0000000..94526de
--- /dev/null
+++ b/lib/rb/spec/http_client_spec.rb
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+class ThriftHTTPClientTransportSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe HTTPClientTransport do
+    before(:each) do
+      @client = HTTPClientTransport.new("http://my.domain.com/path/to/service")
+    end
+
+    it "should always be open" do
+      @client.should be_open
+      @client.close
+      @client.should be_open
+    end
+
+    it "should post via HTTP and return the results" do
+      @client.write "a test"
+      @client.write " frame"
+      Net::HTTP.should_receive(:new).with("my.domain.com", 80).and_return do
+        mock("Net::HTTP").tee do |http|
+          http.should_receive(:use_ssl=).with(false)
+          http.should_receive(:post).with("/path/to/service", "a test frame", {"Content-Type"=>"application/x-thrift"}).and_return([nil, "data"])
+        end
+      end
+      @client.flush
+      @client.read(10).should == "data"
+    end
+  end
+end
diff --git a/lib/rb/spec/mongrel_http_server_spec.rb b/lib/rb/spec/mongrel_http_server_spec.rb
new file mode 100644
index 0000000..c994491
--- /dev/null
+++ b/lib/rb/spec/mongrel_http_server_spec.rb
@@ -0,0 +1,117 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require 'thrift/server/mongrel_http_server'
+
+class ThriftHTTPServerSpec < Spec::ExampleGroup
+  include Thrift
+
+  Handler = MongrelHTTPServer::Handler
+
+  describe MongrelHTTPServer do
+    it "should have appropriate defaults" do
+      mock_factory = mock("BinaryProtocolFactory")
+      mock_proc = mock("Processor")
+      BinaryProtocolFactory.should_receive(:new).and_return(mock_factory)
+      Mongrel::HttpServer.should_receive(:new).with("0.0.0.0", 80).and_return do
+        mock("Mongrel::HttpServer").tee do |mock|
+          handler = mock("Handler")
+          Handler.should_receive(:new).with(mock_proc, mock_factory).and_return(handler)
+          mock.should_receive(:register).with("/", handler)
+        end
+      end
+      MongrelHTTPServer.new(mock_proc)
+    end
+
+    it "should understand :ip, :port, :path, and :protocol_factory" do
+      mock_proc = mock("Processor")
+      mock_factory = mock("ProtocolFactory")
+      Mongrel::HttpServer.should_receive(:new).with("1.2.3.4", 1234).and_return do
+        mock("Mongrel::HttpServer").tee do |mock|
+          handler = mock("Handler")
+          Handler.should_receive(:new).with(mock_proc, mock_factory).and_return(handler)
+          mock.should_receive(:register).with("/foo", handler)
+        end
+      end
+      MongrelHTTPServer.new(mock_proc, :ip => "1.2.3.4", :port => 1234, :path => "foo",
+                                             :protocol_factory => mock_factory)
+    end
+
+    it "should serve using Mongrel::HttpServer" do
+      BinaryProtocolFactory.stub!(:new)
+      Mongrel::HttpServer.should_receive(:new).and_return do
+        mock("Mongrel::HttpServer").tee do |mock|
+          Handler.stub!(:new)
+          mock.stub!(:register)
+          mock.should_receive(:run).and_return do
+            mock("Mongrel::HttpServer.run").tee do |runner|
+              runner.should_receive(:join)
+            end
+          end
+        end
+      end
+      MongrelHTTPServer.new(nil).serve
+    end
+  end
+
+  describe MongrelHTTPServer::Handler do
+    before(:each) do
+      @processor = mock("Processor")
+      @factory = mock("ProtocolFactory")
+      @handler = Handler.new(@processor, @factory)
+    end
+
+    it "should return 404 for non-POST requests" do
+      request = mock("request", :params => {"REQUEST_METHOD" => "GET"})
+      response = mock("response")
+      response.should_receive(:start).with(404)
+      response.should_not_receive(:start).with(200)
+      @handler.process(request, response)
+    end
+
+    it "should serve using application/x-thrift" do
+      request = mock("request", :params => {"REQUEST_METHOD" => "POST"}, :body => nil)
+      response = mock("response")
+      head = mock("head")
+      head.should_receive(:[]=).with("Content-Type", "application/x-thrift")
+      IOStreamTransport.stub!(:new)
+      @factory.stub!(:get_protocol)
+      @processor.stub!(:process)
+      response.should_receive(:start).with(200).and_yield(head, nil)
+      @handler.process(request, response)
+    end
+
+    it "should use the IOStreamTransport" do
+      body = mock("body")
+      request = mock("request", :params => {"REQUEST_METHOD" => "POST"}, :body => body)
+      response = mock("response")
+      head = mock("head")
+      head.stub!(:[]=)
+      out = mock("out")
+      protocol = mock("protocol")
+      transport = mock("transport")
+      IOStreamTransport.should_receive(:new).with(body, out).and_return(transport)
+      @factory.should_receive(:get_protocol).with(transport).and_return(protocol)
+      @processor.should_receive(:process).with(protocol, protocol)
+      response.should_receive(:start).with(200).and_yield(head, out)
+      @handler.process(request, response)
+    end
+  end
+end
diff --git a/lib/rb/spec/nonblocking_server_spec.rb b/lib/rb/spec/nonblocking_server_spec.rb
new file mode 100644
index 0000000..a0e86cf
--- /dev/null
+++ b/lib/rb/spec/nonblocking_server_spec.rb
@@ -0,0 +1,266 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + '/gen-rb/nonblocking_service'
+
+class ThriftNonblockingServerSpec < Spec::ExampleGroup
+  include Thrift
+  include SpecNamespace
+
+  class Handler
+    def initialize
+      @queue = Queue.new
+    end
+
+    attr_accessor :server
+
+    def greeting(english)
+      if english
+        SpecNamespace::Hello.new
+      else
+        SpecNamespace::Hello.new(:greeting => "Aloha!")
+      end
+    end
+
+    def block
+      @queue.pop
+    end
+
+    def unblock(n)
+      n.times { @queue.push true }
+    end
+
+    def sleep(time)
+      Kernel.sleep time
+    end
+
+    def shutdown
+      @server.shutdown(0, false)
+    end
+  end
+
+  class SpecTransport < BaseTransport
+    def initialize(transport, queue)
+      @transport = transport
+      @queue = queue
+      @flushed = false
+    end
+
+    def open?
+      @transport.open?
+    end
+
+    def open
+      @transport.open
+    end
+
+    def close
+      @transport.close
+    end
+
+    def read(sz)
+      @transport.read(sz)
+    end
+
+    def write(buf,sz=nil)
+      @transport.write(buf, sz)
+    end
+
+    def flush
+      @queue.push :flushed unless @flushed or @queue.nil?
+      @flushed = true
+      @transport.flush
+    end
+  end
+
+  class SpecServerSocket < ServerSocket
+    def initialize(host, port, queue)
+      super(host, port)
+      @queue = queue
+    end
+
+    def listen
+      super
+      @queue.push :listen
+    end
+  end
+
+  describe Thrift::NonblockingServer do
+    before(:each) do
+      @port = 43251
+      handler = Handler.new
+      processor = NonblockingService::Processor.new(handler)
+      queue = Queue.new
+      @transport = SpecServerSocket.new('localhost', @port, queue)
+      transport_factory = FramedTransportFactory.new
+      logger = Logger.new(STDERR)
+      logger.level = Logger::WARN
+      @server = NonblockingServer.new(processor, @transport, transport_factory, nil, 5, logger)
+      handler.server = @server
+      @server_thread = Thread.new(Thread.current) do |master_thread|
+        begin
+          @server.serve
+        rescue => e
+          p e
+          puts e.backtrace * "\n"
+          master_thread.raise e
+        end
+      end
+      queue.pop
+
+      @clients = []
+      @catch_exceptions = false
+    end
+
+    after(:each) do
+      @clients.each { |client, trans| trans.close }
+      # @server.shutdown(1)
+      @server_thread.kill
+      @transport.close
+    end
+
+    def setup_client(queue = nil)
+      transport = SpecTransport.new(FramedTransport.new(Socket.new('localhost', @port)), queue)
+      protocol = BinaryProtocol.new(transport)
+      client = NonblockingService::Client.new(protocol)
+      transport.open
+      @clients << [client, transport]
+      client
+    end
+
+    def setup_client_thread(result)
+      queue = Queue.new
+      Thread.new do
+        begin
+          client = setup_client
+          while (cmd = queue.pop)
+            msg, *args = cmd
+            case msg
+            when :block
+              result << client.block
+            when :unblock
+              client.unblock(args.first)
+            when :hello
+              result << client.greeting(true) # ignore result
+            when :sleep
+              client.sleep(args[0] || 0.5)
+              result << :slept
+            when :shutdown
+              client.shutdown
+            when :exit
+              result << :done
+              break
+            end
+          end
+          @clients.each { |c,t| t.close and break if c == client } #close the transport
+        rescue => e
+          raise e unless @catch_exceptions
+        end
+      end
+      queue
+    end
+
+    it "should handle basic message passing" do
+      client = setup_client
+      client.greeting(true).should == Hello.new
+      client.greeting(false).should == Hello.new(:greeting => 'Aloha!')
+      @server.shutdown
+    end
+
+    it "should handle concurrent clients" do
+      queue = Queue.new
+      trans_queue = Queue.new
+      4.times do
+        Thread.new(Thread.current) do |main_thread|
+          begin
+            queue.push setup_client(trans_queue).block
+          rescue => e
+            main_thread.raise e
+          end
+        end
+      end
+      4.times { trans_queue.pop }
+      setup_client.unblock(4)
+      4.times { queue.pop.should be_true }
+      @server.shutdown
+    end
+
+    it "should handle messages from more than 5 long-lived connections" do
+      queues = []
+      result = Queue.new
+      7.times do |i|
+        queues << setup_client_thread(result)
+        Thread.pass if i == 4 # give the server time to accept connections
+      end
+      client = setup_client
+      # block 4 connections
+      4.times { |i| queues[i] << :block }
+      queues[4] << :hello
+      queues[5] << :hello
+      queues[6] << :hello
+      3.times { result.pop.should == Hello.new }
+      client.greeting(true).should == Hello.new
+      queues[5] << [:unblock, 4]
+      4.times { result.pop.should be_true }
+      queues[2] << :hello
+      result.pop.should == Hello.new
+      client.greeting(false).should == Hello.new(:greeting => 'Aloha!')
+      7.times { queues.shift << :exit }
+      client.greeting(true).should == Hello.new
+      @server.shutdown
+    end
+
+    it "should shut down when asked" do
+      # connect first to ensure it's running
+      client = setup_client
+      client.greeting(false) # force a message pass
+      @server.shutdown
+      @server_thread.join(2).should be_an_instance_of(Thread)
+    end
+
+    it "should continue processing active messages when shutting down" do
+      result = Queue.new
+      client = setup_client_thread(result)
+      client << :sleep
+      sleep 0.1 # give the server time to start processing the client's message
+      @server.shutdown
+      @server_thread.join(2).should be_an_instance_of(Thread)
+      result.pop.should == :slept
+    end
+
+    it "should kill active messages when they don't expire while shutting down" do
+      result = Queue.new
+      client = setup_client_thread(result)
+      client << [:sleep, 10]
+      sleep 0.1 # start processing the client's message
+      @server.shutdown(1)
+      @catch_exceptions = true
+      @server_thread.join(3).should_not be_nil
+      result.should be_empty
+    end
+
+    it "should allow shutting down in response to a message" do
+      client = setup_client
+      client.greeting(true).should == Hello.new
+      client.shutdown
+      @server_thread.join(2).should_not be_nil
+    end
+  end
+end
diff --git a/lib/rb/spec/processor_spec.rb b/lib/rb/spec/processor_spec.rb
new file mode 100644
index 0000000..d35f652
--- /dev/null
+++ b/lib/rb/spec/processor_spec.rb
@@ -0,0 +1,83 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+class ThriftProcessorSpec < Spec::ExampleGroup
+  include Thrift
+
+  class ProcessorSpec
+    include Thrift::Processor
+  end
+
+  describe "Processor" do
+    before(:each) do
+      @processor = ProcessorSpec.new(mock("MockHandler"))
+      @prot = mock("MockProtocol")
+    end
+
+    def mock_trans(obj)
+      obj.should_receive(:trans).ordered.and_return do
+        mock("trans").tee do |trans|
+          trans.should_receive(:flush).ordered
+        end
+      end
+    end
+
+    it "should call process_<message> when it receives that message" do
+      @prot.should_receive(:read_message_begin).ordered.and_return ['testMessage', MessageTypes::CALL, 17]
+      @processor.should_receive(:process_testMessage).with(17, @prot, @prot).ordered
+      @processor.process(@prot, @prot).should == true
+    end
+
+    it "should raise an ApplicationException when the received message cannot be processed" do
+      @prot.should_receive(:read_message_begin).ordered.and_return ['testMessage', MessageTypes::CALL, 4]
+      @prot.should_receive(:skip).with(Types::STRUCT).ordered
+      @prot.should_receive(:read_message_end).ordered
+      @prot.should_receive(:write_message_begin).with('testMessage', MessageTypes::EXCEPTION, 4).ordered
+      ApplicationException.should_receive(:new).with(ApplicationException::UNKNOWN_METHOD, "Unknown function testMessage").and_return do
+        mock(ApplicationException).tee do |e|
+          e.should_receive(:write).with(@prot).ordered
+        end
+      end
+      @prot.should_receive(:write_message_end).ordered
+      mock_trans(@prot)
+      @processor.process(@prot, @prot)
+    end
+
+    it "should pass args off to the args class" do
+      args_class = mock("MockArgsClass")
+      args = mock("#<MockArgsClass:mock>").tee do |args|
+        args.should_receive(:read).with(@prot).ordered
+      end
+      args_class.should_receive(:new).and_return args
+      @prot.should_receive(:read_message_end).ordered
+      @processor.read_args(@prot, args_class).should eql(args)
+    end
+
+    it "should write out a reply when asked" do
+      @prot.should_receive(:write_message_begin).with('testMessage', MessageTypes::REPLY, 23).ordered
+      result = mock("MockResult")
+      result.should_receive(:write).with(@prot).ordered
+      @prot.should_receive(:write_message_end).ordered
+      mock_trans(@prot)
+      @processor.write_result(result, @prot, 'testMessage', 23)
+    end
+  end
+end
diff --git a/lib/rb/spec/serializer_spec.rb b/lib/rb/spec/serializer_spec.rb
new file mode 100644
index 0000000..82f374b
--- /dev/null
+++ b/lib/rb/spec/serializer_spec.rb
@@ -0,0 +1,70 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftSerializerSpec < Spec::ExampleGroup
+  include Thrift
+  include SpecNamespace
+
+  describe Serializer do
+    it "should serialize structs to binary by default" do
+      serializer = Serializer.new(Thrift::BinaryProtocolAcceleratedFactory.new)
+      data = serializer.serialize(Hello.new(:greeting => "'Ello guv'nor!"))
+      data.should == "\x0B\x00\x01\x00\x00\x00\x0E'Ello guv'nor!\x00"
+    end
+
+    it "should serialize structs to the given protocol" do
+      protocol = BaseProtocol.new(mock("transport"))
+      protocol.should_receive(:write_struct_begin).with("SpecNamespace::Hello")
+      protocol.should_receive(:write_field_begin).with("greeting", Types::STRING, 1)
+      protocol.should_receive(:write_string).with("Good day")
+      protocol.should_receive(:write_field_end)
+      protocol.should_receive(:write_field_stop)
+      protocol.should_receive(:write_struct_end)
+      protocol_factory = mock("ProtocolFactory")
+      protocol_factory.stub!(:get_protocol).and_return(protocol)
+      serializer = Serializer.new(protocol_factory)
+      serializer.serialize(Hello.new(:greeting => "Good day"))
+    end
+  end
+
+  describe Deserializer do
+    it "should deserialize structs from binary by default" do
+      deserializer = Deserializer.new
+      data = "\x0B\x00\x01\x00\x00\x00\x0E'Ello guv'nor!\x00"
+      deserializer.deserialize(Hello.new, data).should == Hello.new(:greeting => "'Ello guv'nor!")
+    end
+
+    it "should deserialize structs from the given protocol" do
+      protocol = BaseProtocol.new(mock("transport"))
+      protocol.should_receive(:read_struct_begin).and_return("SpecNamespace::Hello")
+      protocol.should_receive(:read_field_begin).and_return(["greeting", Types::STRING, 1],
+                                                            [nil, Types::STOP, 0])
+      protocol.should_receive(:read_string).and_return("Good day")
+      protocol.should_receive(:read_field_end)
+      protocol.should_receive(:read_struct_end)
+      protocol_factory = mock("ProtocolFactory")
+      protocol_factory.stub!(:get_protocol).and_return(protocol)
+      deserializer = Deserializer.new(protocol_factory)
+      deserializer.deserialize(Hello.new, "").should == Hello.new(:greeting => "Good day")
+    end
+  end
+end
diff --git a/lib/rb/spec/server_socket_spec.rb b/lib/rb/spec/server_socket_spec.rb
new file mode 100644
index 0000000..fce5013
--- /dev/null
+++ b/lib/rb/spec/server_socket_spec.rb
@@ -0,0 +1,80 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + "/socket_spec_shared"
+
+class ThriftServerSocketSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe ServerSocket do
+    before(:each) do
+      @socket = ServerSocket.new(1234)
+    end
+
+    it "should create a handle when calling listen" do
+      TCPServer.should_receive(:new).with(nil, 1234)
+      @socket.listen
+    end
+
+    it "should accept an optional host argument" do
+      @socket = ServerSocket.new('localhost', 1234)
+      TCPServer.should_receive(:new).with('localhost', 1234)
+      @socket.listen
+    end
+
+    it "should create a Thrift::Socket to wrap accepted sockets" do
+      handle = mock("TCPServer")
+      TCPServer.should_receive(:new).with(nil, 1234).and_return(handle)
+      @socket.listen
+      sock = mock("sock")
+      handle.should_receive(:accept).and_return(sock)
+      trans = mock("Socket")
+     Socket.should_receive(:new).and_return(trans)
+      trans.should_receive(:handle=).with(sock)
+      @socket.accept.should == trans
+    end
+
+    it "should close the handle when closed" do
+      handle = mock("TCPServer", :closed? => false)
+      TCPServer.should_receive(:new).with(nil, 1234).and_return(handle)
+      @socket.listen
+      handle.should_receive(:close)
+      @socket.close
+    end
+
+    it "should return nil when accepting if there is no handle" do
+      @socket.accept.should be_nil
+    end
+
+    it "should return true for closed? when appropriate" do
+      handle = mock("TCPServer", :closed? => false)
+      TCPServer.stub!(:new).and_return(handle)
+      @socket.listen
+      @socket.should_not be_closed
+      handle.stub!(:close)
+      @socket.close
+      @socket.should be_closed
+      @socket.listen
+      @socket.should_not be_closed
+      handle.stub!(:closed?).and_return(true)
+      @socket.should be_closed
+    end
+  end
+end
diff --git a/lib/rb/spec/server_spec.rb b/lib/rb/spec/server_spec.rb
new file mode 100644
index 0000000..ffe9bff
--- /dev/null
+++ b/lib/rb/spec/server_spec.rb
@@ -0,0 +1,160 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+class ThriftServerSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe BaseServer do
+    it "should default to BaseTransportFactory and BinaryProtocolFactory when not specified" do
+      server = BaseServer.new(mock("Processor"), mock("BaseServerTransport"))
+      server.instance_variable_get(:'@transport_factory').should be_an_instance_of(BaseTransportFactory)
+      server.instance_variable_get(:'@protocol_factory').should be_an_instance_of(BinaryProtocolFactory)
+    end
+
+    # serve is a noop, so can't test that
+  end
+
+  shared_examples_for "servers" do
+    before(:each) do
+      @processor = mock("Processor")
+      @serverTrans = mock("ServerTransport")
+      @trans = mock("BaseTransport")
+      @prot = mock("BaseProtocol")
+      @client = mock("Client")
+      @server = server_type.new(@processor, @serverTrans, @trans, @prot)
+    end
+  end
+
+  describe SimpleServer do
+    it_should_behave_like "servers"
+
+    def server_type
+      SimpleServer
+    end
+
+    it "should serve in the main thread" do
+      @serverTrans.should_receive(:listen).ordered
+      @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
+      @trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
+      @prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
+      x = 0
+      @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
+        case (x += 1)
+        when 1 then raise Thrift::TransportException
+        when 2 then raise Thrift::ProtocolException
+        when 3 then throw :stop
+        end
+      end
+      @trans.should_receive(:close).exactly(3).times
+      @serverTrans.should_receive(:close).ordered
+      lambda { @server.serve }.should throw_symbol(:stop)
+    end
+  end
+
+  describe ThreadedServer do
+    it_should_behave_like "servers"
+
+    def server_type
+      ThreadedServer
+    end
+
+    it "should serve using threads" do
+      @serverTrans.should_receive(:listen).ordered
+      @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
+      @trans.should_receive(:get_transport).exactly(3).times.with(@client).and_return(@trans)
+      @prot.should_receive(:get_protocol).exactly(3).times.with(@trans).and_return(@prot)
+      Thread.should_receive(:new).with(@prot, @trans).exactly(3).times.and_yield(@prot, @trans)
+      x = 0
+      @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
+        case (x += 1)
+        when 1 then raise Thrift::TransportException
+        when 2 then raise Thrift::ProtocolException
+        when 3 then throw :stop
+        end
+      end
+      @trans.should_receive(:close).exactly(3).times
+      @serverTrans.should_receive(:close).ordered
+      lambda { @server.serve }.should throw_symbol(:stop)
+    end
+  end
+
+  describe ThreadPoolServer do
+    it_should_behave_like "servers"
+
+    def server_type
+      # put this stuff here so it runs before the server is created
+      @threadQ = mock("SizedQueue")
+      SizedQueue.should_receive(:new).with(20).and_return(@threadQ)
+      @excQ = mock("Queue")
+      Queue.should_receive(:new).and_return(@excQ)
+      ThreadPoolServer
+    end
+
+    it "should set up the queues" do
+      @server.instance_variable_get(:'@thread_q').should be(@threadQ)
+      @server.instance_variable_get(:'@exception_q').should be(@excQ)
+    end
+
+    it "should serve inside a thread" do
+      Thread.should_receive(:new).and_return do |block|
+        @server.should_receive(:serve)
+        block.call
+        @server.rspec_verify
+      end
+      @excQ.should_receive(:pop).and_throw(:popped)
+      lambda { @server.rescuable_serve }.should throw_symbol(:popped)
+    end
+
+    it "should avoid running the server twice when retrying rescuable_serve" do
+      Thread.should_receive(:new).and_return do |block|
+        @server.should_receive(:serve)
+        block.call
+        @server.rspec_verify
+      end
+      @excQ.should_receive(:pop).twice.and_throw(:popped)
+      lambda { @server.rescuable_serve }.should throw_symbol(:popped)
+      lambda { @server.rescuable_serve }.should throw_symbol(:popped)
+    end
+
+    it "should serve using a thread pool" do
+      @serverTrans.should_receive(:listen).ordered
+      @threadQ.should_receive(:push).with(:token)
+      @threadQ.should_receive(:pop)
+      Thread.should_receive(:new).and_yield
+      @serverTrans.should_receive(:accept).exactly(3).times.and_return(@client)
+      @trans.should_receive(:get_transport).exactly(3).times.and_return(@trans)
+      @prot.should_receive(:get_protocol).exactly(3).times.and_return(@prot)
+      x = 0
+      error = RuntimeError.new("Stopped")
+      @processor.should_receive(:process).exactly(3).times.with(@prot, @prot).and_return do
+        case (x += 1)
+        when 1 then raise Thrift::TransportException
+        when 2 then raise Thrift::ProtocolException
+        when 3 then raise error
+        end
+      end
+      @trans.should_receive(:close).exactly(3).times
+      @excQ.should_receive(:push).with(error).and_throw(:stop)
+      @serverTrans.should_receive(:close)
+      lambda { @server.serve }.should throw_symbol(:stop)
+    end
+  end
+end
diff --git a/lib/rb/spec/socket_spec.rb b/lib/rb/spec/socket_spec.rb
new file mode 100644
index 0000000..dd8b0f9
--- /dev/null
+++ b/lib/rb/spec/socket_spec.rb
@@ -0,0 +1,61 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + "/socket_spec_shared"
+
+class ThriftSocketSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe Socket do
+    before(:each) do
+      @socket = Socket.new
+      @handle = mock("Handle", :closed? => false)
+      @handle.stub!(:close)
+      @handle.stub!(:connect_nonblock)
+      ::Socket.stub!(:new).and_return(@handle)
+    end
+
+    it_should_behave_like "a socket"
+
+    it "should raise a TransportException when it cannot open a socket" do
+      ::Socket.should_receive(:new).and_raise(StandardError)
+      lambda { @socket.open }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
+    end
+
+    it "should open a ::Socket with default args" do
+      ::Socket.should_receive(:new).and_return(mock("Handle", :connect_nonblock => true))
+      ::Socket.should_receive(:getaddrinfo).with("localhost", 9090).and_return([[]])
+      ::Socket.should_receive(:sockaddr_in)
+      @socket.open
+    end
+
+    it "should accept host/port options" do
+      ::Socket.should_receive(:new).and_return(mock("Handle", :connect_nonblock => true))
+      ::Socket.should_receive(:getaddrinfo).with("my.domain", 1234).and_return([[]])
+      ::Socket.should_receive(:sockaddr_in)
+      Socket.new('my.domain', 1234).open
+    end
+
+    it "should accept an optional timeout" do
+      ::Socket.stub!(:new)
+      Socket.new('localhost', 8080, 5).timeout.should == 5
+    end
+  end
+end
diff --git a/lib/rb/spec/socket_spec_shared.rb b/lib/rb/spec/socket_spec_shared.rb
new file mode 100644
index 0000000..96b433b
--- /dev/null
+++ b/lib/rb/spec/socket_spec_shared.rb
@@ -0,0 +1,104 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+
+shared_examples_for "a socket" do
+  it "should open a socket" do
+    @socket.open.should == @handle
+  end
+
+  it "should be open whenever it has a handle" do
+    @socket.should_not be_open
+    @socket.open
+    @socket.should be_open
+    @socket.handle = nil
+    @socket.should_not be_open
+    @socket.handle = @handle
+    @socket.close
+    @socket.should_not be_open
+  end
+
+  it "should write data to the handle" do
+    @socket.open
+    @handle.should_receive(:write).with("foobar")
+    @socket.write("foobar")
+    @handle.should_receive(:write).with("fail").and_raise(StandardError)
+    lambda { @socket.write("fail") }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
+  end
+
+  it "should raise an error when it cannot read from the handle" do
+    @socket.open
+    @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 return the data read when reading from the handle works" do
+    @socket.open
+    @handle.should_receive(:readpartial).with(17).and_return("test data")
+    @socket.read(17).should == "test data"
+  end
+
+  it "should declare itself as closed when it has an error" do
+    @socket.open
+    @handle.should_receive(:write).with("fail").and_raise(StandardError)
+    @socket.should be_open
+    lambda { @socket.write("fail") }.should raise_error
+    @socket.should_not be_open
+  end
+
+  it "should raise an error when the stream is closed" do
+    @socket.open
+    @handle.stub!(:closed?).and_return(true)
+    @socket.should_not be_open
+    lambda { @socket.write("fail") }.should raise_error(IOError, "closed stream")
+    lambda { @socket.read(10) }.should raise_error(IOError, "closed stream")
+  end
+
+  it "should support the timeout accessor for read" do
+    @socket.timeout = 3
+    @socket.open
+    IO.should_receive(:select).with([@handle], nil, nil, 3).and_return([[@handle], [], []])
+    @handle.should_receive(:readpartial).with(17).and_return("test data")
+    @socket.read(17).should == "test data"
+  end
+
+  it "should support the timeout accessor for write" do
+    @socket.timeout = 3
+    @socket.open
+    IO.should_receive(:select).with(nil, [@handle], nil, 3).twice.and_return([[], [@handle], []])
+    @handle.should_receive(:write_nonblock).with("test data").and_return(4)
+    @handle.should_receive(:write_nonblock).with(" data").and_return(5)
+    @socket.write("test data").should == 9
+  end
+
+  it "should raise an error when read times out" do
+    @socket.timeout = 0.5
+    @socket.open
+    IO.should_receive(:select).with([@handle], nil, nil, 0.5).at_least(1).times.and_return(nil)
+    lambda { @socket.read(17) }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::TIMED_OUT }
+  end
+
+  it "should raise an error when write times out" do
+    @socket.timeout = 0.5
+    @socket.open
+    IO.should_receive(:select).with(nil, [@handle], nil, 0.5).any_number_of_times.and_return(nil)
+    lambda { @socket.write("test data") }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::TIMED_OUT }
+  end
+end
diff --git a/lib/rb/spec/spec_helper.rb b/lib/rb/spec/spec_helper.rb
new file mode 100644
index 0000000..3c20bf9
--- /dev/null
+++ b/lib/rb/spec/spec_helper.rb
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require 'rubygems'
+# require at least 1.1.4 to fix a bug with describing Modules
+gem 'rspec', '>= 1.1.4'
+require 'spec'
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext])
+
+# pretend we already loaded fastthread, otherwise the nonblocking_server_spec
+# will get screwed up
+# $" << 'fastthread.bundle'
+
+require File.dirname(__FILE__) + '/../lib/thrift'
+
+class Object
+  # tee is a useful method, so let's let our tests have it
+  def tee(&block)
+    block.call(self)
+    self
+  end
+end
+
+Spec::Runner.configure do |configuration|
+  configuration.before(:each) do
+    Thrift.type_checking = true
+  end
+end
+
+require File.dirname(__FILE__) + "/../debug_proto_test/gen-rb/Srv"
+require File.dirname(__FILE__) + "/../debug_proto_test/gen-rb/debug_proto_test_constants"
+
+module Fixtures
+  COMPACT_PROTOCOL_TEST_STRUCT = COMPACT_TEST.dup
+  COMPACT_PROTOCOL_TEST_STRUCT.a_binary = [0,1,2,3,4,5,6,7,8].pack('c*')
+  COMPACT_PROTOCOL_TEST_STRUCT.set_byte_map = nil
+  COMPACT_PROTOCOL_TEST_STRUCT.map_byte_map = nil
+end
\ No newline at end of file
diff --git a/lib/rb/spec/struct_spec.rb b/lib/rb/spec/struct_spec.rb
new file mode 100644
index 0000000..23a701e
--- /dev/null
+++ b/lib/rb/spec/struct_spec.rb
@@ -0,0 +1,253 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftStructSpec < Spec::ExampleGroup
+  include Thrift
+  include SpecNamespace
+
+  describe Struct do
+    it "should iterate over all fields properly" do
+      fields = {}
+      Foo.new.each_field { |fid,field_info| fields[fid] = field_info }
+      fields.should == Foo::FIELDS
+    end
+
+    it "should initialize all fields to defaults" do
+      struct = Foo.new
+      struct.simple.should == 53
+      struct.words.should == "words"
+      struct.hello.should == Hello.new(:greeting => 'hello, world!')
+      struct.ints.should == [1, 2, 2, 3]
+      struct.complex.should be_nil
+      struct.shorts.should == Set.new([5, 17, 239])
+    end
+
+    it "should not share default values between instances" do
+      begin
+        struct = Foo.new
+        struct.ints << 17
+        Foo.new.ints.should == [1,2,2,3]
+      ensure
+        # ensure no leakage to other tests
+        Foo::FIELDS[4][:default] = [1,2,2,3]
+      end
+    end
+
+    it "should properly initialize boolean values" do
+      struct = BoolStruct.new(:yesno => false)
+      struct.yesno.should be_false
+    end
+
+    it "should have proper == semantics" do
+      Foo.new.should_not == Hello.new
+      Foo.new.should == Foo.new
+      Foo.new(:simple => 52).should_not == Foo.new
+    end
+
+    it "should read itself off the wire" do
+      struct = Foo.new
+      prot = BaseProtocol.new(mock("transport"))
+      prot.should_receive(:read_struct_begin).twice
+      prot.should_receive(:read_struct_end).twice
+      prot.should_receive(:read_field_begin).and_return(
+        ['complex', Types::MAP, 5], # Foo
+        ['words', Types::STRING, 2], # Foo
+        ['hello', Types::STRUCT, 3], # Foo
+          ['greeting', Types::STRING, 1], # Hello
+          [nil, Types::STOP, 0], # Hello
+        ['simple', Types::I32, 1], # Foo
+        ['ints', Types::LIST, 4], # Foo
+        ['shorts', Types::SET, 6], # Foo
+        [nil, Types::STOP, 0] # Hello
+      )
+      prot.should_receive(:read_field_end).exactly(7).times
+      prot.should_receive(:read_map_begin).and_return(
+        [Types::I32, Types::MAP, 2], # complex
+          [Types::STRING, Types::DOUBLE, 2], # complex/1/value
+          [Types::STRING, Types::DOUBLE, 1] # complex/2/value
+      )
+      prot.should_receive(:read_map_end).exactly(3).times
+      prot.should_receive(:read_list_begin).and_return([Types::I32, 4])
+      prot.should_receive(:read_list_end)
+      prot.should_receive(:read_set_begin).and_return([Types::I16, 2])
+      prot.should_receive(:read_set_end)
+      prot.should_receive(:read_i32).and_return(
+        1, 14,        # complex keys
+        42,           # simple
+        4, 23, 4, 29  # ints
+      )
+      prot.should_receive(:read_string).and_return("pi", "e", "feigenbaum", "apple banana", "what's up?")
+      prot.should_receive(:read_double).and_return(Math::PI, Math::E, 4.669201609)
+      prot.should_receive(:read_i16).and_return(2, 3)
+      prot.should_not_receive(:skip)
+      struct.read(prot)
+
+      struct.simple.should == 42
+      struct.complex.should == {1 => {"pi" => Math::PI, "e" => Math::E}, 14 => {"feigenbaum" => 4.669201609}}
+      struct.hello.should == Hello.new(:greeting => "what's up?")
+      struct.words.should == "apple banana"
+      struct.ints.should == [4, 23, 4, 29]
+      struct.shorts.should == Set.new([3, 2])
+    end
+
+    it "should skip unexpected fields in structs and use default values" do
+      struct = Foo.new
+      prot = BaseProtocol.new(mock("transport"))
+      prot.should_receive(:read_struct_begin)
+      prot.should_receive(:read_struct_end)
+      prot.should_receive(:read_field_begin).and_return(
+        ['simple', Types::I32, 1],
+        ['complex', Types::STRUCT, 5],
+        ['thinz', Types::MAP, 7],
+        ['foobar', Types::I32, 3],
+        ['words', Types::STRING, 2],
+        [nil, Types::STOP, 0]
+      )
+      prot.should_receive(:read_field_end).exactly(5).times
+      prot.should_receive(:read_i32).and_return(42)
+      prot.should_receive(:read_string).and_return("foobar")
+      prot.should_receive(:skip).with(Types::STRUCT)
+      prot.should_receive(:skip).with(Types::MAP)
+      # prot.should_receive(:read_map_begin).and_return([Types::I32, Types::I32, 0])
+      # prot.should_receive(:read_map_end)
+      prot.should_receive(:skip).with(Types::I32)
+      struct.read(prot)
+
+      struct.simple.should == 42
+      struct.complex.should be_nil
+      struct.words.should == "foobar"
+      struct.hello.should == Hello.new(:greeting => 'hello, world!')
+      struct.ints.should == [1, 2, 2, 3]
+      struct.shorts.should == Set.new([5, 17, 239])
+    end
+
+    it "should write itself to the wire" do
+      prot = BaseProtocol.new(mock("transport")) #mock("Protocol")
+      prot.should_receive(:write_struct_begin).with("SpecNamespace::Foo")
+      prot.should_receive(:write_struct_begin).with("SpecNamespace::Hello")
+      prot.should_receive(:write_struct_end).twice
+      prot.should_receive(:write_field_begin).with('ints', Types::LIST, 4)
+      prot.should_receive(:write_i32).with(1)
+      prot.should_receive(:write_i32).with(2).twice
+      prot.should_receive(:write_i32).with(3)
+      prot.should_receive(:write_field_begin).with('complex', Types::MAP, 5)
+      prot.should_receive(:write_i32).with(5)
+      prot.should_receive(:write_string).with('foo')
+      prot.should_receive(:write_double).with(1.23)
+      prot.should_receive(:write_field_begin).with('shorts', Types::SET, 6)
+      prot.should_receive(:write_i16).with(5)
+      prot.should_receive(:write_i16).with(17)
+      prot.should_receive(:write_i16).with(239)
+      prot.should_receive(:write_field_stop).twice
+      prot.should_receive(:write_field_end).exactly(6).times
+      prot.should_receive(:write_field_begin).with('simple', Types::I32, 1)
+      prot.should_receive(:write_i32).with(53)
+      prot.should_receive(:write_field_begin).with('hello', Types::STRUCT, 3)
+      prot.should_receive(:write_field_begin).with('greeting', Types::STRING, 1)
+      prot.should_receive(:write_string).with('hello, world!')
+      prot.should_receive(:write_map_begin).with(Types::I32, Types::MAP, 1)
+      prot.should_receive(:write_map_begin).with(Types::STRING, Types::DOUBLE, 1)
+      prot.should_receive(:write_map_end).twice
+      prot.should_receive(:write_list_begin).with(Types::I32, 4)
+      prot.should_receive(:write_list_end)
+      prot.should_receive(:write_set_begin).with(Types::I16, 3)
+      prot.should_receive(:write_set_end)
+
+      struct = Foo.new
+      struct.words = nil
+      struct.complex = {5 => {"foo" => 1.23}}
+      struct.write(prot)
+    end
+
+    it "should raise an exception if presented with an unknown container" do
+      # yeah this is silly, but I'm going for code coverage here
+      struct = Foo.new
+      lambda { struct.send :write_container, nil, nil, {:type => "foo"} }.should raise_error(StandardError, "Not a container type: foo")
+    end
+
+    it "should support optional type-checking in Thrift::Struct.new" do
+      Thrift.type_checking = true
+      begin
+        lambda { Hello.new(:greeting => 3) }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
+      ensure
+        Thrift.type_checking = false
+      end
+      lambda { Hello.new(:greeting => 3) }.should_not raise_error(TypeError)
+    end
+
+    it "should support optional type-checking in field accessors" do
+      Thrift.type_checking = true
+      begin
+        hello = Hello.new
+        lambda { hello.greeting = 3 }.should raise_error(TypeError, "Expected Types::STRING, received Fixnum for field greeting")
+      ensure
+        Thrift.type_checking = false
+      end
+      lambda { hello.greeting = 3 }.should_not raise_error(TypeError)
+    end
+
+    it "should raise an exception when unknown types are given to Thrift::Struct.new" do
+      lambda { Hello.new(:fish => 'salmon') }.should raise_error(Exception, "Unknown key given to SpecNamespace::Hello.new: fish")
+    end
+
+    it "should support `raise Xception, 'message'` for Exception structs" do
+      begin
+        raise Xception, "something happened"
+      rescue Thrift::Exception => e
+        e.message.should == "something happened"
+        e.code.should == 1
+        # ensure it gets serialized properly, this is the really important part
+        prot = BaseProtocol.new(mock("trans"))
+        prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
+        prot.should_receive(:write_struct_end)
+        prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)#, "something happened")
+        prot.should_receive(:write_string).with("something happened")
+        prot.should_receive(:write_field_begin).with('code', Types::I32, 2)#, 1)
+        prot.should_receive(:write_i32).with(1)
+        prot.should_receive(:write_field_stop)
+        prot.should_receive(:write_field_end).twice
+
+        e.write(prot)
+      end
+    end
+
+    it "should support the regular initializer for exception structs" do
+      begin
+        raise Xception, :message => "something happened", :code => 5
+      rescue Thrift::Exception => e
+        e.message.should == "something happened"
+        e.code.should == 5
+        prot = BaseProtocol.new(mock("trans"))
+        prot.should_receive(:write_struct_begin).with("SpecNamespace::Xception")
+        prot.should_receive(:write_struct_end)
+        prot.should_receive(:write_field_begin).with('message', Types::STRING, 1)
+        prot.should_receive(:write_string).with("something happened")
+        prot.should_receive(:write_field_begin).with('code', Types::I32, 2)
+        prot.should_receive(:write_i32).with(5)
+        prot.should_receive(:write_field_stop)
+        prot.should_receive(:write_field_end).twice
+
+        e.write(prot)
+      end
+    end
+  end
+end
diff --git a/lib/rb/spec/types_spec.rb b/lib/rb/spec/types_spec.rb
new file mode 100644
index 0000000..d979cfb
--- /dev/null
+++ b/lib/rb/spec/types_spec.rb
@@ -0,0 +1,117 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + '/gen-rb/thrift_spec_types'
+
+class ThriftTypesSpec < Spec::ExampleGroup
+  include Thrift
+
+  before(:each) do
+    Thrift.type_checking = true
+  end
+
+  after(:each) do
+    Thrift.type_checking = false
+  end
+
+  describe "Type checking" do
+    it "should return the proper name for each type" do
+      Thrift.type_name(Types::I16).should == "Types::I16"
+      Thrift.type_name(Types::VOID).should == "Types::VOID"
+      Thrift.type_name(Types::LIST).should == "Types::LIST"
+      Thrift.type_name(42).should be_nil
+    end
+
+    it "should check types properly" do
+      # lambda { Thrift.check_type(nil, Types::STOP) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(3,              {:type => Types::STOP},   :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil,            {:type => Types::VOID},   :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(3,              {:type => Types::VOID},   :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(true,           {:type => Types::BOOL},   :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(3,              {:type => Types::BOOL},   :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(42,             {:type => Types::BYTE},   :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(42,             {:type => Types::I16},    :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(42,             {:type => Types::I32},    :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(42,             {:type => Types::I64},    :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(3.14,           {:type => Types::I32},    :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(3.14,           {:type => Types::DOUBLE}, :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(3,              {:type => Types::DOUBLE}, :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type("3",            {:type => Types::STRING}, :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(3,              {:type => Types::STRING}, :foo) }.should raise_error(TypeError)
+      hello = SpecNamespace::Hello.new
+      lambda { Thrift.check_type(hello,          {:type => Types::STRUCT, :class => SpecNamespace::Hello}, :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type("foo",          {:type => Types::STRUCT}, :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type({:foo => 1},    {:type => Types::MAP},    :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type([1],            {:type => Types::MAP},    :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type([1],            {:type => Types::LIST},   :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type({:foo => 1},    {:type => Types::LIST},   :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(Set.new([1,2]), {:type => Types::SET},    :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type([1,2],          {:type => Types::SET},    :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type({:foo => true}, {:type => Types::SET},    :foo) }.should raise_error(TypeError)
+    end
+
+    it "should error out if nil is passed and skip_types is false" do
+      lambda { Thrift.check_type(nil, {:type => Types::BOOL},   :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::BYTE},   :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::I16},    :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::I32},    :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::I64},    :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::DOUBLE}, :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::STRING}, :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::STRUCT}, :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::LIST},   :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::SET},    :foo, false) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(nil, {:type => Types::MAP},    :foo, false) }.should raise_error(TypeError)
+    end
+
+    it "should check element types on containers" do
+      field = {:type => Types::LIST, :element => {:type => Types::I32}}
+      lambda { Thrift.check_type([1, 2], field, :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type([1, nil, 2], field, :foo) }.should raise_error(TypeError)
+      field = {:type => Types::MAP, :key => {:type => Types::I32}, :value => {:type => Types::STRING}}
+      lambda { Thrift.check_type({1 => "one", 2 => "two"}, field, :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type({1 => "one", nil => "nil"}, field, :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type({1 => nil, 2 => "two"}, field, :foo) }.should raise_error(TypeError)
+      field = {:type => Types::SET, :element => {:type => Types::I32}}
+      lambda { Thrift.check_type(Set.new([1, 2]), field, :foo) }.should_not raise_error(TypeError)
+      lambda { Thrift.check_type(Set.new([1, nil, 2]), field, :foo) }.should raise_error(TypeError)
+      lambda { Thrift.check_type(Set.new([1, 2.3, 2]), field, :foo) }.should raise_error(TypeError)
+      
+      field = {:type => Types::STRUCT, :class => SpecNamespace::Hello}
+      lambda { Thrift.check_type(SpecNamespace::BoolStruct, field, :foo) }.should raise_error(TypeError)
+    end
+
+    it "should give the TypeError a readable message" do
+      msg = "Expected Types::STRING, received Fixnum for field foo"
+      lambda { Thrift.check_type(3, {:type => Types::STRING}, :foo) }.should raise_error(TypeError, msg)
+      msg = "Expected Types::STRING, received Fixnum for field foo.element"
+      field = {:type => Types::LIST, :element => {:type => Types::STRING}}
+      lambda { Thrift.check_type([3], field, :foo) }.should raise_error(TypeError, msg)
+      msg = "Expected Types::I32, received NilClass for field foo.element.key"
+      field = {:type => Types::LIST,
+               :element => {:type => Types::MAP,
+                            :key => {:type => Types::I32},
+                            :value => {:type => Types::I32}}}
+      lambda { Thrift.check_type([{nil => 3}], field, :foo) }.should raise_error(TypeError, msg)
+      msg = "Expected Types::I32, received NilClass for field foo.element.value"
+      lambda { Thrift.check_type([{1 => nil}], field, :foo) }.should raise_error(TypeError, msg)
+    end
+  end
+end
diff --git a/lib/rb/spec/unix_socket_spec.rb b/lib/rb/spec/unix_socket_spec.rb
new file mode 100644
index 0000000..df239d7
--- /dev/null
+++ b/lib/rb/spec/unix_socket_spec.rb
@@ -0,0 +1,108 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+require File.dirname(__FILE__) + '/spec_helper'
+require File.dirname(__FILE__) + "/socket_spec_shared"
+
+class ThriftUNIXSocketSpec < Spec::ExampleGroup
+  include Thrift
+
+  describe UNIXSocket do
+    before(:each) do
+      @path = '/tmp/thrift_spec_socket'
+      @socket = UNIXSocket.new(@path)
+      @handle = mock("Handle", :closed? => false)
+      @handle.stub!(:close)
+      ::UNIXSocket.stub!(:new).and_return(@handle)
+    end
+
+    it_should_behave_like "a socket"
+
+    it "should raise a TransportException when it cannot open a socket" do
+      ::UNIXSocket.should_receive(:new).and_raise(StandardError)
+      lambda { @socket.open }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
+    end
+
+    it "should accept an optional timeout" do
+      ::UNIXSocket.stub!(:new)
+      UNIXSocket.new(@path, 5).timeout.should == 5
+    end
+  end
+
+  describe UNIXServerSocket do
+    before(:each) do
+      @path = '/tmp/thrift_spec_socket'
+      @socket = UNIXServerSocket.new(@path)
+    end
+
+    it "should create a handle when calling listen" do
+      UNIXServer.should_receive(:new).with(@path)
+      @socket.listen
+    end
+
+    it "should create a Thrift::UNIXSocket to wrap accepted sockets" do
+      handle = mock("UNIXServer")
+      UNIXServer.should_receive(:new).with(@path).and_return(handle)
+      @socket.listen
+      sock = mock("sock")
+      handle.should_receive(:accept).and_return(sock)
+      trans = mock("UNIXSocket")
+      UNIXSocket.should_receive(:new).and_return(trans)
+      trans.should_receive(:handle=).with(sock)
+      @socket.accept.should == trans
+    end
+
+    it "should close the handle when closed" do
+      handle = mock("UNIXServer", :closed? => false)
+      UNIXServer.should_receive(:new).with(@path).and_return(handle)
+      @socket.listen
+      handle.should_receive(:close)
+      File.stub!(:delete)
+      @socket.close
+    end
+
+    it "should delete the socket when closed" do
+      handle = mock("UNIXServer", :closed? => false)
+      UNIXServer.should_receive(:new).with(@path).and_return(handle)
+      @socket.listen
+      handle.stub!(:close)
+      File.should_receive(:delete).with(@path)
+      @socket.close
+    end
+
+    it "should return nil when accepting if there is no handle" do
+      @socket.accept.should be_nil
+    end
+
+    it "should return true for closed? when appropriate" do
+      handle = mock("UNIXServer", :closed? => false)
+      UNIXServer.stub!(:new).and_return(handle)
+      File.stub!(:delete)
+      @socket.listen
+      @socket.should_not be_closed
+      handle.stub!(:close)
+      @socket.close
+      @socket.should be_closed
+      @socket.listen
+      @socket.should_not be_closed
+      handle.stub!(:closed?).and_return(true)
+      @socket.should be_closed
+    end
+  end
+end