Skip to content
Ruby implementation of the Sanford TCP communication protocol.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.

Sanford Protocol

Ruby implementation of Sanford TCP communication protocol.

The Protocol

Version: 2

Sanford communicates using binary encoded messages. Sanford messages are two headers and a body:

|------ 1B -------|------ 4B -------|---- (Body Size)B ----|
| (packed header) | (packed header) | (BSON binary string) |
|     Version     |    Body Size    |         Body         |


The first header represents the protocol version in use. It is a 1 byte unsigned integer and exists to ensure both the client and the server are talking the same protocol.

Body Size

The second header represents the size of the message's body. It is a 4 byte unsigned integer and tells the receiver how many bytes to read to receive the body.


The Body is the content of the message. It is a BSON encoded binary string that decodes to a ruby hash. Since the size of the body is encoded as a 4 byte (32 bit) unsigned integer, there is a size limit for body data ((2 ** 32) - 1 or 4,294,967,295 or ~4GB).


A request is made up of 2 required parts: the name, and the params.

  • name - (string) name of the requested API service.
  • params - (document) data for the service call - must be a BSON document (ruby Hash, python dict, Javascript Object).

Requests are encoded as BSON hashes when transmitted in messages.

{ 'name'   => 'some_service',
  'params' => { 'key' => 'value' }

request = Sanford::Protocol::Request.parse(a_bson_request_hash)     #=> "some_service"
request.params   #=> { 'key' => 'value' }
request.to_s     #=> "some_service"


A response is made up of 2 parts: the status and the data.

  • status - (tuple, required) A code and message describing the result of the service call.
  • data - (object, optional) Return value of the service call. This can be any BSON serializable object. Typically won't be set if the request is not successful.

Responses are encoded as BSON hashes when transmitted in messages.

{ 'status'  => [ 200, 'The request was successful.' ]
  'data'    => true

response = Sanford::Protocol::Response.parse(a_bson_response_hash)
response.status.code    #=> 200
response.status.to_i    #=> 200    #=> "OK"
response.status.message #=> "The request was successful."
response.status.to_s    #=> "[200, OK]"
response.code           #=> 200
response.to_s           #=> "[200, OK]"           #=> true

Status Codes

This is the list of defined status codes.

  • 200 - OK- The request was successful.
  • 400 - BAD REQUEST - The request couldn't be read. This is usually because it was not formed correctly.
  • 404 - NOT FOUND - The server couldn't find something requested.
  • 408 - TIMEOUT - A client connected but didn't write a request before the server timeod out waiting for one.
  • 422 - INVALID - The request was sent with invalid params.
  • 500 - ERROR - The server errored responding to the request.

In addition to these, a service can return custom status codes, but they should use a number greater than or equal to 600 to avoid collisions with Sanford's defined status codes.


The Sanford::Protocol module defines helper methods for encoding and decoding messages.

# encode a message
data = { 'something' => true }
msg_body = Sanford::Protocol.msg_body.encode(data)
msg_size = Sanford::Protocol.msg_size.encode(msg_body.bytesize)
msg = [Sanford::Protocol.msg_version, msg_size, msg_body].join


If you are sending and receiving messages using a tcp socket, use Sanford::Protocol::Connection.

connection =
incoming_data =

For incoming messages, it reads them off the socket, validates them, and returns the decoded body data. For outgoing messages, it encodes the message body from given data, adds the appropiate message headers, and writes the message to the socket.


When reading data from a connection, you can optionally pass a timeout value. If given, the connection will block and wait until data is ready to be read. If a timeout occurs, the connection will raise TimeoutError.

begin  # timeout after waiting on data for 10s
rescue Sanford::Protocol::TimeoutError => err
  puts "timeout - so sad :("

Requests And Responses

Request and response objects have helpers for sending and receiving data using a connection.

# For a server...
# use Request#parse to build an incoming request
data_hash =
incoming_request = Sanford::Protocol::Request.parse(data_hash)
# use Response#to_hash to send a response
outgoing_response =, data)

# For a client...
# use Request#to_hash to send a request
outgoing_request =, params)
# use Response#parse to build an incoming response
data_hash =
incoming_response = Sanford::Protocol::Response.parse(data_hash)

Test Helpers

A FakeSocket helper class and an associated TestHelpers module are provided to help test receiving and sending Sanford::Protocol messages without using real sockets.

# fake a socket with some incoming binary
socket =
connection =
msg_data =

# write some binary to that fake socket and verify it
puts socket.out # => msg binary string

# create a socket with an incoming request and verify the request
socket   = FakeSocket.with_request(*request_params)
msg_data =
request  = Sanford::Protocol::Request.parse(msg_data)


  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request
Something went wrong with that request. Please try again.