Ruby-Binary-Parser

Ruby-Binary-Parser is Ruby Gem library providing DSL for parsing binary-data, such as Image files, Video files, etc. Without operating bytes and bits complicatedly, you can parse and read binary-data generically and abstractly.

Description

This library can parse all kind of binary-data structures including non-fixed length of structures and nested structures. For generic parsing, loop and condition(if) statement to define structures is provided in this library. Of course, values of neighbor binary-data can be used as the other binary-data's specification of length.

Furthermore, this library handles all binary-data under the lazy evaluation. So you can read required parts of a binary-data very quickly even if whole of the binary-data is too big,

Notice

Currently, this library supports only READ of binary-data. So you cannot WRITE binary-data directly with this library.

Usage

Look at following examples to quickly understand how to use this library.

Install

$ gem install binary_parser

Example 1

Consider the following (temporary) binary structures which describe Image data.

MyImage (non-fixed length)
Data Name Type Bit Length Number Of Replications
height UInt 8 1
width UInt 8 1
RGB color bit-map UInt 8 * 3 'height' * 'width'
has date? Flag 1 1
date MyDate 31 'has date?' is 1 => 1
else => 0
MyDate (31 bit)
Data Name Type Bit Length Number Of Replications
year UInt 13 1
month UInt 9 1
day UInt 9 1

You can define MyImage structure in ruby program as following code.

require 'binary_parser'

class MyDate < BinaryParser::TemplateBase
  require 'date'

  Def do
    data :year,  UInt, 13
    data :month, UInt, 9
    data :day,   UInt, 9
  end

  def to_date
    return Date.new(year.to_i, month.to_i, day.to_i)
  end
end

class MyImage < BinaryParser::TemplateBase
  Def do
    data :height, UInt, 8
    data :width,  UInt, 8

    # Loop statement
    TIMES var(:height), :i do
      TIMES var(:width), :j do
        data :R, UInt, 8
        data :G, UInt, 8
        data :B, UInt, 8
      end
    end

    data :has_date, Flag, 1

    # Condition statement
    # * If you want to check whether variable-name is valid, alternative expression
    #     IF cond(:has_date){|v| v.on?} do ~ end
    #   is also available.
    IF E{ has_date.on? } do
      data :date, MyDate, 31
    end
  end
end

And then you can parse and read binay-data of MyImage as follows.

File.open('my_image.bin', 'rb') do |f|
  image = MyImage.new(f.read)
  puts "Image size: #{image.height}x#{image.width}"
  ul = image.i[0].j[0]
  puts "RGB color at the first is (#{ul.R}, #{ul.G}, #{ul.B})"
  puts "Image date: #{image.date.to_date}"
end

If 'my_image.bin' is binary-data-file of [0x02, 0x02, 0xe7,0x39,0x62, 0x00,0x00,0x00, 0xe7,0x39,0x62, 0x00,0x00,0x00, 0x9f, 0x78, 0x08, 0x03], you can get output as follows.

Image size: 2x2
RGB color at the first is (231, 57, 98)
Image date: 2014-04-03

For your information, you can dump all binary-data's information as follows.

File.open('my_image.bin', 'rb') do |f|
  image = MyImage.new(f.read)
  image.show(true)
end

Example 2

You can also define other structures as follows.

class DefExample < BinaryParser::TemplateBase
  Def do
    data :loop_byte_length, UInt, 8

    # Loop until 'loop_byte_length' * 8 bits are parsed.
    SPEND var(:loop_byte_length) * 8, :list do

      data :length, UInt,   8

      # You can specify length by neigbor value.
      data :data,   Binary, var(:length) * 8
    end

    data :v1, UInt, 8
    data :v2, UInt, 8

    # Number of Condition variables is arbitary. 
    IF cond(:v1, :v2){|v1, v2| v1 == v2} do

      # +, -, *, / is available to specify length with variable.
      data :v3, UInt, 8 * (var(:v1) + var(:v2))
    end
  end
end

You can check this definition by giving some binary-data and calling show method as follows.

binary = [0x05, 0x01, 0xff, 0x02, 0xff, 0xff, 0x01, 0x01, 0x01, 0x01].pack("C*")
i = DefExample.new(binary)
i.show(true)

Output for above checking-code is shown below.

*--------------------------------------------------------------------------------
loop_byte_length  Pos:      0  Len:      8  Type:       UInt  Cont: 5
list              Pos:      8  Len:     40  Type:   LoopList  Cont: list with 2 elements
  [0]
    *--------------------------------------------------------------------------------
    length  Pos:      0  Len:      8  Type:       UInt  Cont: 1
    data    Pos:      8  Len:      8  Type:     Binary  Cont: [0xff]
  [1]
    *--------------------------------------------------------------------------------
    length  Pos:      0  Len:      8  Type:       UInt  Cont: 2
    data    Pos:      8  Len:     16  Type:     Binary  Cont: [0xff, 0xff]
v1                Pos:     48  Len:      8  Type:       UInt  Cont: 1
v2                Pos:     56  Len:      8  Type:       UInt  Cont: 1
v3                Pos:     64  Len:     16  Type:       UInt  Cont: 257

Example 3

If you want to operate Stream-data, StreamTemplateBase class is useful. Define stream as follows.

class StreamExample < BinaryParser::StreamTemplateBase
  # Stream which consists of 4bytes-binary-datas.
  Def(4) do
    data :data1, UInt,   8
    data :data2, Binary, 24
  end
end

And then, get structures from the stream as follows.

File.open('my_image.bin', 'rb') do |f|
  stream = StreamExample.new(f)
  packet = stream.get_next
  puts "data1: #{packet.data1}, data2: #{packet.data2}"
  stream.get_next.show(true)
end

StreamTemplateBase has many useful methods to choose structures from the stream. If you want to know detail of these methods, please read documentation or concerned source-files.

Documentation

I'm sorry, but only RDoc (auto-generated documentation) is now available. For example, you can read RDoc on web browser by following operations.

$ gem install binary_parser
$ gem server
Server started at http://0.0.0.0:8808

Access shown address by web browser.

Versions

1.0.0 April 6, 2014
1.2.0 November 7, 2014