class Ronin::Support::Binary::Struct

Generic Binary Struct class.

@note This class provides lazy memory mapped access to an underlying buffer. This means values are decoded/encoded each time they are read or written to.

## Examples

### Defining Members

class Point < Ronin::Support::Binary::Struct

  member :x, :int32
  member :y, :int32

end

### Initializing Structs

From a Hash:

point = Point.new(x: 1, y: 2)

From a buffer:

point = Point.new("\x01\x00\x00\x00\xFF\xFF\xFF\xFF")

### Reading Fields

point = Point.new("\x01\x00\x00\x00\xFF\xFF\xFF\xFF")
point[:x]
# => 1
point[:y]
# => -1

point.x
# => 1
point.y
# => -1

### Packing Structs

class Point < Ronin::Support::Binary::Struct

  member :x, :int32
  member :y, :int32

end

point = Point.new(x: 10, y: -1)
point.pack
# => "\n\x00\x00\x00\xFF\xFF\xFF\xFF"

### Unpacking Structs

class Point < Ronin::Support::Binary::Struct

  member :x, :int32
  member :y, :int32

end

point = Point.unpack("\x00\x00\x00\x01\xFF\xFF\xFF\xFF")
point.x
# => 1
point.y
# => -1

### Inheriting Structs

class Point < Ronin::Support::Binary::Struct

  member :x, :int32
  member :y, :int32

end

class Point3D < Point

  member :z, :int32

end

point   = Point.new(x: 100, y: 42)
point3d = Point3D.new(x: 100, y: 42, z: -1)

### Array Fields

class MyStruct < Ronin::Support::Binary::Struct

  member :x,    :uint32
  member :nums, [:uint8, 10]

end

struct = MyStruct.new
struct.nums = [0x01, 0x02, 0x03, 0x04]
struct.pack
# => "\x00\x00\x00\x00\x01\x02\x03\x04\x00\x00\x00\x00\x00\x00"

### Unbounded Array Fields

class MyStruct < Ronin::Support::Binary::Struct

  member :length,  :uint32
  member :payload, (:uint8..)

end

struct = MyStruct.new
struct.payload = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
struct.pack
# => "\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\a\b"

### Struct Endianness

class MyStruct < Ronin::Support::Binary::Struct

  platform endian: :big

  member :x, :uint32
  member :y, :uint32

end

struct = MyStruct.new
struct.x = 0xAABB
struct.y = 0xCCDD
struct.pack
# => "\x00\x00\xAA\xBB\x00\x00\xCC\xDD"

### Struct Architecture

class MyStruct < Ronin::Support::Binary::Struct

  platform arch: :arm64_be

  member :x, :int
  member :y, :int
  member :f, :double

end

struct = MyStruct.new
struct.x = 100
struct.y = -100
struct.f = (90 / Math::PI)
struct.pack
# => "\x00\x00\x00d\xFF\xFF\xFF\x9C@<\xA5\xDC\x1Ac\xC1\xF8"

### Struct Operating System (OS)

class MyStruct < Ronin::Support::Binary::Struct

  platform arch: :x86_64, os: :windows

  member :x, :long
  member :y, :long

end

struct = MyStruct.new
struct.x = 255
struct.y = -1
struct.pack
# => "\xFF\x00\x00\x00\xFF\xFF\xFF\xFF"

### Struct Alignment

class Pixel < Ronin::Support::Binary::Struct

  align 4

  member :r, :uint8
  member :g, :uint8
  member :b, :uint8

end

class PixelBuf < Ronin::Support::Binary::Struct

  member :count, :uint8
  member :pixels, [Pixel, 255]

end

pixelbuf = PixelBuf.new
pixelbuf.count = 2
pixelbuf.pixels[0].r = 0xAA
pixelbuf.pixels[0].g = 0xBB
pixelbuf.pixels[0].b = 0xCC
pixelbuf.pixels[1].r = 0xAA
pixelbuf.pixels[1].g = 0xBB
pixelbuf.pixels[1].b = 0xCC
pixelbuf.pack
# => "\x02\x00\x00\x00\xAA\xBB\xCC\xAA\xBB\xCC..."

### Disable Struct Padding

class MyStruct < Ronin::Support::Binary::Struct

  padding false

  member :c, :char
  member :i, :int32

end

struct = MyStruct.new
struct.c = 'A'
struct.i = -1
struct.pack
# => "A\xFF\xFF\xFF\xFF"

@api public

Attributes

type[R]

The type system used by the struct.

@return [CTypes::StructType]

@api private

Public Class Methods

align(new_alignment) click to toggle source

Sets the alignment of the struct.

@param [Integer] new_alignment

The new alignment for the struct.

@api public

# File lib/ronin/support/binary/struct.rb, line 705
def self.align(new_alignment)
  @alignment = new_alignment
end
alignment() click to toggle source

The alignment of the struct.

@return [Integer]

The alignment, in bytes, for the struct.

@api public

# File lib/ronin/support/binary/struct.rb, line 691
def self.alignment
  @alignment ||= if superclass < Struct
                   superclass.alignment
                 end
end
has_member?(name) click to toggle source

Determines if the struct has the given member.

@param [Symbol] name

The member name.

@return [Boolean]

Specifies that the member exists in the struct.

@api semipublic

# File lib/ronin/support/binary/struct.rb, line 400
def self.has_member?(name)
  members.has_key?(name.to_sym)
end
member(name,type_signature,**kwargs) click to toggle source

Defines a member in the struct.

@param [Symbol] name

The name of the member.

@param [Symbol, (Symbol, Integer), Range(Symbol)] type_signature

The type of the member.

@example Defining a member:

class MyStruct < Ronin::Support::Binary::Struct
  member :x, :uint32
end

struct = MyStruct.new
struct.x = 0x11223344

@example Defining an Array member:

class MyStruct < Ronin::Support::Binary::Struct
  member :nums, [:uint32, 10]
end

struct = MyStruct.new
struct.x = [0x11111111, 0x22222222, 0x33333333, 0x44444444]

@example Defining an unbounded Array member:

class MyStruct < Ronin::Support::Binary::Struct
  member :payload, :uint8..
end

struct = MyStruct.new
struct.payloads = [0x1, 0x2, 0x3, 0x4]

@api public

# File lib/ronin/support/binary/struct.rb, line 799
def self.member(name,type_signature,**kwargs)
  self.members[name] = Member.new(name,type_signature,**kwargs)

  define_method(name)       { self[name] }
  define_method("#{name}=") { |value| self[name] = value }
end
members() click to toggle source

The members in the struct.

@return [Hash{Symbol => Member}]

The member names and type information.

@api semipublic

# File lib/ronin/support/binary/struct.rb, line 381
def self.members
  @members ||= if superclass < Struct
                 superclass.members.dup
               else
                 {}
               end
end
new(buffer_or_values={}) click to toggle source

Initializes the struct.

@param [Hash{Symbol => Object}, String, ByteSlice] buffer_or_values

Optional values to initialize the members of the struct.

@example Initiializing a new struct from a buffer:

class MyStruct < Ronin::Support::Binary::Struct

  member :x,    :uint32
  member :f,    :double
  member :nums, [:uint8, 10]

end

struct = MyStruct.new("\x01\x00\x00\x00\x00\x00\x00\x00333333\xD3?\x01\x02\x03\x00\x00\x00\x00\x00\x00\x00")
struct.x
# => 1
struct.f
# => 0.3
struct.nums.to_a
# => [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]

@example Initializing a new struct with values:

struct = MyStruct.new(x: 1, f: 0.3, nums: [1,2,3])
struct.x
# => 1
struct.f
# => 0.3
struct.nums.to_a
# => [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]

@api public

Calls superclass method Ronin::Support::Binary::Memory::new
# File lib/ronin/support/binary/struct.rb, line 288
def initialize(buffer_or_values={})
  @type_system = self.class.type_system
  @type        = self.class.type

  @cache = {}

  case buffer_or_values
  when Hash
    super(@type.size)

    values = buffer_or_values
    values.each do |name,value|
      self[name] = value
    end
  when String, ByteSlice
    buffer = buffer_or_values

    super(buffer)
  else
    raise(ArgumentError,"first argument of #{self.class}.new must be either a Hash of values or a String or #{ByteSlice}")
  end
end
pack(values) click to toggle source

Initializes the struct and then packs it.

@param [Hash{Symbol => Object}] values

The values to initialize the struct with.

@return [String]

The packed struct.

@example

class MyStruct < Ronin::Support::Binary::Struct

  member :x,    :uint32
  member :f,    :double
  member :nums, [:uint8, 10]

end

MyStruct.pack(x: 1, f: 0.3, nums: [1,2,3])
# => "\x01\x00\x00\x00\x00\x00\x00\x00333333\xD3?\x01\x02\x03\x00\x00\x00\x00\x00\x00\x00"

@api public

# File lib/ronin/support/binary/struct.rb, line 427
def self.pack(values)
  new(values).to_s
end
padding(new_padding=nil) click to toggle source

Gets or sets whether the struct’s members will have padding.

@param [Boolean, nil] new_padding

The optional new value for {padding}.

@return [Boolean]

Specifies whether the struct's members will be padded.

@api public

# File lib/ronin/support/binary/struct.rb, line 720
def self.padding(new_padding=nil)
  unless new_padding.nil?
    @padding = new_padding
  else
    if @padding.nil?
      @padding = if superclass < Struct
                   superclass.padding
                 else
                   true
                 end
    else
      @padding
    end
  end
end
platform(endian: nil, arch: nil, os: nil) click to toggle source

Gets or sets the struct’s target platform.

@param [:little, :big, :net, nil] endian

The desired endianness for the struct.

@param [:x86, :x86_64,

      :ppc, :ppc64,
      :mips, :mips_le, :mips_be,
      :mips64, :mips64_le, :mips64_be,
      :arm, :arm_le, :arm_be,
      :arm64, :arm64_le, :arm64_be] arch
The desired architecture for the struct.

@param [:linux, :macos, :windows,

      :android, :apple_ios, :bsd,
      :freebsd, :openbsd, :netbsd] os
The Operating System (OS) for the struct.

@return [Hash{Symbol => Object}]

The platform configuration Hash.

@api public

# File lib/ronin/support/binary/struct.rb, line 670
def self.platform(endian: nil, arch: nil, os: nil)
  if endian || arch || os
    @type_system = CTypes.platform(endian: endian, arch: arch, os: os)
    @platform    = {endian: endian, arch: arch, os: os}
  else
    @platform ||= if superclass < Struct
                    superclass.platform
                  else
                    {}
                  end
  end
end
read_from(io) click to toggle source

Reads the struct from the IO stream.

@param [IO] io

The IO object to read from.

@return [Struct]

The read struct.

@example

class MyStruct < Ronin::Support::Binary::Struct

  member :x,    :uint32
  member :f,    :double
  member :nums, [:uint8, 10]

end

file   = File.new('binary.dat','b')
struct = MyStruct.read_from(file)

@see read_from

@api public

# File lib/ronin/support/binary/struct.rb, line 482
def self.read_from(io)
  new.read_from(io)
end
size() click to toggle source

The size of the struct.

@return [Integer]

The size of the struct in bytes.

@example

class MyStruct < Ronin::Support::Binary::Struct

  member :x, :uint32
  member :y, :uint32

end

MyStruct.size
# => 8

@api public

# File lib/ronin/support/binary/struct.rb, line 342
def self.size
  type.size
end
translate(endian: nil, arch: nil) click to toggle source

Translates the struct class using the given type system into a new struct class.

@param [:little, :big, :net, nil] endian

The desired endian-ness.

@param [:x86, :x86_64,

      :ppc, :ppc64,
      :mips, :mips_le, :mips_be,
      :mips64, :mips64_le, :mips64_be,
      :arm, :arm_le, :arm_be,
      :arm64, :arm64_le, :arm64_be, nil] arch
The new architecture for the struct.

@return [Class<Struct>]

# File lib/ronin/support/binary/struct.rb, line 363
def self.translate(endian: nil, arch: nil)
  struct_class = Class.new(self)

  if    arch   then struct_class.arch(arch)
  elsif endian then struct_class.endian(endian)
  end

  struct_class
end
type() click to toggle source

The type for the struct class.

@return [CTypes::StructObjectType]

The underlying type.

@api semipublic

# File lib/ronin/support/binary/struct.rb, line 319
def self.type
  @type ||= type_resolver.resolve(self)
end
type_resolver() click to toggle source

The type resolver using {type_system}.

@return [CTypes::TypeResolver]

@api semipublic

# File lib/ronin/support/binary/struct.rb, line 760
def self.type_resolver
  @type_resolver ||= CTypes::TypeResolver.new(type_system)
end
type_system() click to toggle source

Gets or sets the struct’s type system.

@return [Types, CTypes::LittleEndian,

CTypes::BigEndian,
CTypes::Network]

@api semipublic

# File lib/ronin/support/binary/struct.rb, line 745
def self.type_system
  @type_system ||= if superclass < Struct
                     superclass.type_system
                   else
                     CTypes
                   end
end
unpack(data) click to toggle source

Unpacks data into the struct.

@param [String] data

The data to unpack.

@return [Struct]

The newly unpacked struct.

@example

class MyStruct < Ronin::Support::Binary::Struct

  member :x,    :uint32
  member :f,    :double
  member :nums, [:uint8, 10]

end

struct = MyStruct.unpack("\x01\x00\x00\x00\x00\x00\x00\x00333333\xD3?\x01\x02\x03\x00\x00\x00\x00\x00\x00\x00")

@api public

# File lib/ronin/support/binary/struct.rb, line 453
def self.unpack(data)
  new(data)
end

Public Instance Methods

[](name) click to toggle source

Reads a value from the struct.

@param [Symbol] name

The member name.

@return [Integer, Float, String, Binary::Array, Binary::Struct]

The value of the member.

@raise [ArgumentError]

The struct does not contain the member.

@example

class MyStruct < Ronin::Support::Binary::Struct

  member :x,    :uint32
  member :f,    :double
  member :nums, [:uint8, 10]

end

struct = MyStruct.new("\x01\x00\x00\x00\x00\x00\x00\x00333333\xD3?\x01\x02\x03\x00\x00\x00\x00\x00\x00\x00")
struct[:x]
# => 1
struct[:f]
# => 0.3
struct[:nums].to_a
# => [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]

@api public

Calls superclass method Ronin::Support::Binary::Memory#[]
# File lib/ronin/support/binary/struct.rb, line 517
def [](name)
  if (member = @type.members[name])
    case member.type
    when CTypes::UnboundedArrayType
      # XXX: but how do we handle an unbounded array of structs?
      @cache[name] ||= begin
                         offset = member.offset
                         length = size - member.offset
                         slice  = byteslice(offset,length)

                         Binary::Array.new(member.type.type,slice)
                       end
    when CTypes::ObjectType
      @cache[name] ||= begin
                         offset = member.offset
                         length = member.type.size
                         slice  = byteslice(offset,length)

                         member.type.unpack(slice)
                       end
    else
      data = super(member.offset,member.type.size)
      member.type.unpack(data)
    end
  else
    raise(ArgumentError,"no such member: #{name.inspect}")
  end
end
[]=(name,value) click to toggle source

Writes a value to the struct.

@param [Symbol] name

The member name.

@param [Integer, Float, String, Array, Struct] value

The value to write.

@return [Integer, Float, String, Array, Struct]

The value of the member.

@raise [ArgumentError]

The struct does not contain the member.

@example

class MyStruct < Ronin::Support::Binary::Struct

  member :x,    :uint32
  member :f,    :double
  member :nums, [:uint8, 10]

end

struct = MyStruct.new
struct[:x] = 1
struct[:x]
# => 1
struct[:f] = 0.3
struct[:f]
# => 0.3
struct[:nums] = [1,2,3]
struct[:nums].to_a
# => [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]

@api public

Calls superclass method Ronin::Support::Binary::Memory#[]=
# File lib/ronin/support/binary/struct.rb, line 583
def []=(name,value)
  if (member = @type.members[name])
    data = member.type.pack(value)

    super(member.offset,member.type.size,data)
    return value
  else
    raise(ArgumentError,"no such member: #{name.inspect}")
  end
end
each() { |name, self| ... } click to toggle source

Enumerates over the members within the struct.

@yield [name, value]

If a block is given, it will be passed each member name and value
from within the struct.

@yieldparam [Symbol] name

The name of the struct memeber.

@yieldparam [Object] value

The decoded value of the struct member.

@return [Enumerator]

If no block is given, an Enumerator object will be returned.

@api public

# File lib/ronin/support/binary/struct.rb, line 612
def each(&block)
  return enum_for(__method__) unless block_given?

  @type.members.each_key do |name|
    yield name, self[name]
  end
end
to_a() click to toggle source

Converts the struct to an Array of values.

@return [::Array<Object>]

The array of values within the struct.

@api public

# File lib/ronin/support/binary/struct.rb, line 640
def to_a
  each.map { |name,value| value }
end
to_h() click to toggle source

Converts the struct to a Hash.

@return [Hash{Symbol => Object}]

The hash of member names and values.

@api public

# File lib/ronin/support/binary/struct.rb, line 628
def to_h
  each.to_h
end