Class: Ronin::Binary::Struct

Inherits:
Object
  • Object
show all
Defined in:
lib/ronin/binary/struct.rb

Overview

Generic Binary Struct class.

Example

class Packet < Binary::Struct

  endian :network

  layout :length, :uint32,
         :data,   [:uchar, 48]

end

pkt = Packet.new
pkt.length = 5
pkt.data   = 'hello'

buffer = pkt.pack
# => "\x00\x00\x00\x05hello\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

new_pkt = Packet.unpack(buffer)
# => #<Packet: length: 5, data: "hello">

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeStruct

Initializes the structure.


57
58
59
60
61
62
# File 'lib/ronin/binary/struct.rb', line 57

def initialize
  # initialize the fields in order
  self.class.layout.each do |name|
    self[name] = self.class.default(self.class.fields[name])
  end
end

Class Method Details

.default(type) ⇒ Integer, ... (protected)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Default value for a field.


464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/ronin/binary/struct.rb', line 464

def self.default(type)
  type, length = type

  if length
    if Template::STRING_TYPES.include?(type)
      # arrays of chars should be Strings
      String.new
    else
      # create an array of values
      Array.new(length) { |index| default(type) }
    end
  else
    if type.kind_of?(Symbol)
      if    Template::INT_TYPES.include?(type)    then 0
      elsif Template::FLOAT_TYPES.include?(type)  then 0.0
      elsif Template::CHAR_TYPES.include?(type)   then "\0"
      elsif Template::STRING_TYPES.include?(type) then ''
      end
    elsif type < Struct
      type.new
    end
  end
end

.each_field {|struct, name, type| ... } ⇒ Enumerator (protected)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Enumerates the fields of the structure, and all nested structures.

Yields:

  • (struct, name, type)

    The given block will be passed each structure, field name and field type.

Yield Parameters:

  • struct (Struct)

    The structure class.

  • name (Symbol)

    The name of the field.

  • type (type, (type, length))

    The type of the field.


509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/ronin/binary/struct.rb', line 509

def self.each_field(&block)
  return enum_for(__method__) unless block

  layout.each do |name|
    type, length = field = fields[name]

    if type.kind_of?(Symbol)
      yield self, name, field
    elsif type < Struct
      if length
        length.times { type.each_field(&block) }
      else
        type.each_field(&block)
      end
    end
  end
end

.endian(type = nil) ⇒ :little, ... (protected)

Sets or gets the endianness of the structure.


377
378
379
380
381
# File 'lib/ronin/binary/struct.rb', line 377

def self.endian(type=nil)
  if type then @endian = type.to_sym
  else         @endian
  end
end

.field?(name) ⇒ Boolean

Determines if a field exists in the structure.


85
86
87
# File 'lib/ronin/binary/struct.rb', line 85

def self.field?(name)
  fields.has_key?(name.to_sym)
end

.fieldsHash{Symbol => type, (type, length)}

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The fields in the structure.


72
73
74
# File 'lib/ronin/binary/struct.rb', line 72

def self.fields
  @fields ||= {}
end

.layout(*fields) ⇒ Array<Symbol> (protected)

The layout of the structure.

Examples:

layout :length, :uint32,
       :data,   [:uchar, 256]

396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/ronin/binary/struct.rb', line 396

def self.layout(*fields)
  unless fields.empty?
    @layout = []
    @fields = {}

    fields.each_slice(2) do |name,(type,length)|
      type = typedefs.fetch(type,type)

      unless (type.kind_of?(Symbol) || type < Struct)
        raise(TypeError,"#{type.inspect} is not a Symbol or #{Struct}")
      end

      @layout      << name
      @fields[name] = [type, length]

      attr_accessor name
    end
  end

  return (@layout ||= [])
end

.template(fields, options = {}) ⇒ Template (protected)

Creates a new template for the structure.


449
450
451
# File 'lib/ronin/binary/struct.rb', line 449

def self.template(fields,options={})
  Template.new(fields,options)
end

.templatesHash{Hash => Template} (protected)

The templates for the structure.


426
427
428
429
430
431
432
433
# File 'lib/ronin/binary/struct.rb', line 426

def self.templates
  @templates ||= Hash.new do |hash,options|
    fields  = each_field.map { |struct,name,field| field }
    options = {:endian => self.endian}.merge(options)

    hash[options] = template(fields,options)
  end
end

.typedef(type, type_alias) ⇒ Object (protected)

Defines a typedef.


296
297
298
299
300
301
302
303
304
# File 'lib/ronin/binary/struct.rb', line 296

def self.typedef(type,type_alias)
  type = typedefs.fetch(type,type)

  unless (type.kind_of?(Symbol) || type < Struct)
    raise(TypeError,"#{type.inspect} is not a Symbol or #{Struct}")
  end

  typedefs[type_alias] = typedefs.fetch(type,type)
end

.typedefsHash{Symbol => Symbol} (protected)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Typedefs.


283
284
285
# File 'lib/ronin/binary/struct.rb', line 283

def self.typedefs
  @@typedefs ||= {}
end

.unpack(data, options = {}) ⇒ Struct

Unpacks data into the structure.

Options Hash (options):

  • :endian (:little, :big, :network)

    The endianness to apply to the types.


104
105
106
# File 'lib/ronin/binary/struct.rb', line 104

def self.unpack(data,options={})
  new().unpack(data,options)
end

Instance Method Details

#[](name) ⇒ Integer, ...

Reads a value from the structure.

Raises:

  • (ArgumentError)

    The structure does not contain the field.


133
134
135
136
137
# File 'lib/ronin/binary/struct.rb', line 133

def [](name)
  if field?(name) then send(name)
  else                 raise(ArgumentError,"no such field '#{name}'")
  end
end

#[]=(name, value) ⇒ Integer, ...

Writes a value to the structure.

Raises:

  • (ArgumentError)

    The structure does not contain the field.


154
155
156
157
158
# File 'lib/ronin/binary/struct.rb', line 154

def []=(name,value)
  if field?(name) then send("#{name}=",value)
  else                 raise(ArgumentError,"no such field '#{name}'")
  end
end

#clearStruct

Clears the fields of the structure.


188
189
190
191
192
193
194
# File 'lib/ronin/binary/struct.rb', line 188

def clear
  each_field do |struct,name,field|
    struct[name] = self.class.default(field)
  end

  return self
end

#each_field {|struct, name, type| ... } ⇒ Enumerator (protected)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Enumerates the fields of the structure, and all nested structure.

Yields:

  • (struct, name, type)

    The given block will be passed each structure, field name and type.

Yield Parameters:

  • struct (Struct)

    The structure instance.

  • name (Symbol)

    The name of the field.

  • type (type, (type, length))

    The type of the field.


547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/ronin/binary/struct.rb', line 547

def each_field(&block)
  return enum_for(__method__) unless block

  self.class.layout.each do |name|
    type, length = field = self.class.fields[name]

    if type.kind_of?(Symbol)
      yield self, name, field
    elsif type < Struct
      if length
        self[name].each { |struct| struct.each_field(&block) }
      else
        self[name].each_field(&block)
      end
    end
  end
end

#field?(name) ⇒ Boolean

Determines if a field exists in the structure.


117
118
119
# File 'lib/ronin/binary/struct.rb', line 117

def field?(name)
  self.class.field?(name)
end

#inspectString

Inspects the structure.


267
268
269
270
271
# File 'lib/ronin/binary/struct.rb', line 267

def inspect
  "#<#{self.class}: " << self.class.layout.map { |name|
    "#{name}: " << self[name].inspect
  }.join(', ') << '>'
end

#pack(options = {}) ⇒ String

Packs the structure.

Options Hash (options):

  • :endian (:little, :big, :network)

    The endianness to apply to the types.


208
209
210
# File 'lib/ronin/binary/struct.rb', line 208

def pack(options={})
  self.class.templates[options].pack(*values.flatten)
end

#to_sObject

See Also:


250
251
252
# File 'lib/ronin/binary/struct.rb', line 250

def to_s
  pack
end

#to_strObject

See Also:


257
258
259
# File 'lib/ronin/binary/struct.rb', line 257

def to_str
  pack
end

#unpack(data, options = {}) ⇒ Struct

Unpacks data into the structure.

Options Hash (options):

  • :endian (:little, :big, :network)

    The endianness to apply to the types.


227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/ronin/binary/struct.rb', line 227

def unpack(data,options={})
  values = self.class.templates[options].unpack(data)

  each_field do |struct,name,(type,length)|
    struct[name] = if length
                     if Template::STRING_TYPES.include?(type)
                       # string types are combined into a single String
                       values.shift
                     else
                       # shift off an Array of elements
                       values.shift(length)
                     end
                   else
                     values.shift
                   end
  end

  return self
end

#valuesArray<Integer, Float, String, Struct>

The values within the structure.


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/ronin/binary/struct.rb', line 166

def values
  normalize = lambda { |value|
    case value
    when Struct then value.values
    else             value
    end
  }

  self.class.layout.map do |name|
    case (value = self[name])
    when Array then value.map(&normalize)
    else            normalize[value]
    end
  end
end