Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

zed-0xff/iostruct

Open more actions menu

Repository files navigation

IOStruct

A Ruby Struct that can read/write itself from/to IO-like objects. Perfect for parsing binary file formats, network protocols, and other structured binary data.

Installation

Add this line to your application's Gemfile:

gem 'iostruct'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install iostruct

Usage

Basic Usage with Pack Format

Define structs using Ruby's pack/unpack format strings:

require 'iostruct'

# Define a struct with two 32-bit unsigned integers
Point = IOStruct.new('LL', :x, :y)

# Read from binary data
data = [100, 200].pack('LL')
point = Point.read(data)
point.x  # => 100
point.y  # => 200

# Write back to binary
point.pack  # => "\x64\x00\x00\x00\xC8\x00\x00\x00"

Hash-Based Definition with C Types

For more readable code, define structs using C-style type names:

Point = IOStruct.new(
  struct_name: 'Point',
  fields: {
    x: 'int',
    y: 'int',
    z: 'int',
  }
)

point = Point.read(binary_data)
point.inspect  # => "<Point x=0x64 y=0xc8 z=0x0>"

Supported C Types

Type Aliases Size
uint8_t unsigned char, _BYTE 1
uint16_t unsigned short 2
uint32_t unsigned int, unsigned 4
uint64_t unsigned long long 8
int8_t char, signed char 1
int16_t short, signed short 2
int32_t int, signed int, signed 4
int64_t long long, signed long long 8
float 4
double 8

Reading from IO or String

Header = IOStruct.new('L S S', :magic, :version, :flags)

# Read from a File
File.open('binary_file', 'rb') do |f|
  header = Header.read(f)
  puts header.magic
end

# Read from a String
header = Header.read("\x7fELF\x01\x00\x00\x00")

# Track file position with __offset
io = StringIO.new(data)
record = MyStruct.read(io)
record.__offset  # => position where the record was read from

Explicit Field Offsets

Specify exact byte offsets for fields (useful for structs with padding or gaps):

MyStruct = IOStruct.new(
  fields: {
    magic: 'uint32_t',
    flags: { type: 'uint16_t', offset: 0x10 },  # starts at byte 16
    data:  { type: 'uint32_t', offset: 0x20 },  # starts at byte 32
  }
)

Arrays

Define fixed-size arrays within structs:

Matrix = IOStruct.new(
  fields: {
    rows: 'int',
    cols: 'int',
    data: { type: 'float', count: 16 },  # 16-element float array
  }
)

m = Matrix.read(binary_data)
m.data  # => [1.0, 2.0, 3.0, ...]
m.pack  # serializes back to binary

Nested Structs

Compose complex structures from simpler ones:

Point = IOStruct.new(fields: { x: 'int', y: 'int' })

Rect = IOStruct.new(
  struct_name: 'Rect',
  fields: {
    top_left: Point,
    bottom_right: Point,
  }
)

rect = Rect.read([0, 0, 100, 100].pack('i*'))
rect.top_left.x       # => 0
rect.bottom_right.x   # => 100
rect.pack             # serializes entire structure including nested structs

Inspect Modes

Choose between hexadecimal (default) or decimal display:

# Hex display (default)
HexStruct = IOStruct.new('L L', :a, :b, inspect: :hex)
HexStruct.new(a: 255, b: 256).inspect
# => "<struct a=0xff b=0x100>"

# Decimal display
DecStruct = IOStruct.new('L L', :a, :b, inspect: :dec)
DecStruct.new(a: 255, b: 256).inspect
# => "#<struct DecStruct a=255, b=256>"

# Table format for aligned output
struct.to_table
# => "<struct a=  ff b= 100>"

Auto-Generated Field Names

If you don't specify field names, they're generated based on byte offset:

s = IOStruct.new('C S L')
s.members  # => [:f0, :f1, :f3]  (offsets 0, 1, 3)

Field Renaming

Rename auto-generated or explicit field names:

# Rename auto-generated names
IOStruct.new('C S L', f0: :byte_val, f3: :long_val)

# Rename explicit names
IOStruct.new('C S L', :a, :b, :c, a: :first, c: :last)

API Reference

Class Methods

Method Description
IOStruct.new(fmt, *names, **options) Create a new struct class with pack format
IOStruct.new(fields:, **options) Create a new struct class with hash definition
IOStruct.get_type_size(typename) Get byte size for a C type name
MyStruct.read(io_or_string) Read and parse binary data
MyStruct.size Return struct size in bytes
MyStruct::SIZE Struct size constant
MyStruct::FORMAT Pack format string
MyStruct::FIELDS Hash of field names to FieldInfo

Instance Methods

Method Description
#pack Serialize to binary string
#empty? True if all fields are zero/nil/empty
#to_table Formatted string with aligned values
#__offset File position where struct was read (nil if from string)

Constructor Options

Option Description
struct_name: Custom name for inspect output
inspect: :hex (default) or :dec for display format
size: Override calculated struct size
fields: Hash defining fields (for hash-based definition)

Examples

Parsing a BMP File Header

BMPHeader = IOStruct.new(
  struct_name: 'BMPHeader',
  fields: {
    magic:      { type: 'uint16_t' },
    file_size:  { type: 'uint32_t' },
    reserved:   { type: 'uint32_t' },
    data_offset: { type: 'uint32_t' },
  }
)

File.open('image.bmp', 'rb') do |f|
  header = BMPHeader.read(f)
  puts "File size: #{header.file_size} bytes"
  puts "Pixel data starts at: #{header.data_offset}"
end

Network Protocol Packet

Packet = IOStruct.new('n n N', :src_port, :dst_port, :sequence,
  struct_name: 'TCPHeader'
)

# Big-endian format for network byte order
packet = Packet.read(socket.read(8))

Contributing

  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

License

MIT License - see LICENSE.txt for details.

About

A Struct that can read/write itself from/to IO-like objects

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.