Class: BinStruct::Struct Abstract

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

Overview

This class is abstract.

Set of attributes

This class is a base class to define headers or anything else with a binary format containing multiple attributes.

Basics

A Struct subclass is generaly composed of multiple binary attributes. These attributes have each a given type. All Structable types are supported.

To define a new subclass, it has to inherit from Struct. And some class methods have to be used to declare attributes:

class MyBinaryStructure < BinStruct::Struct
  # define a first Int8 attribute, with default value: 1
  define_attr :attr1, BinStruct::Int8, default: 1
  #define a second attribute, of kind Int32
  define_attr :attr2, BinStruct::Int32
end

These defintions create 4 methods: #attr1, #attr1=, #attr2 and #attr2=. All these methods take and/or return Integers.

Attributes may also be accessed through #[] ans #[]=. These methods give access to type object:

mybs = MyBinaryStructure.new
mybs.attr1.class     # => Integer
mybs[:attr1].class   # => BinStruct::Int8

#initialize accepts an option hash to populate attributes. Keys are attribute name symbols, and values are those expected by writer accessor.

#read is able to populate object from a binary string.

#to_s returns binary string from object.

Add attributes

Struct.define_attr adds an attribute to Struct subclass. A lot of attribute types may be defined: integer types, string types (to handle a stream of bytes). More complex attribute types may be defined using others Struct subclasses:

# define a 16-bit little-endian integer attribute, named type
define_attr :type, BinStruct::Int16le
# define a string attribute
define_attr :body, BinStruct::String
# define a attribute using a complex type (Struct subclass)
define_attr :oui, BinStruct::OUI

This example creates six methods on our Struct subclass: #type, #type=, #body, #body=, #mac_addr and #mac_addr=.

Struct.define_attr has many options (third optional Hash argument):

  • :default to define default attribute value. It may be a simple value (an Integer for an Int attribute, for example) or a lambda,

  • :builder to give a builder/constructor lambda to create attribute. The lambda takes 2 arguments: Struct subclass object owning attribute, and type class as passes as second argument to Struct.define_attr,

  • :optional to define this attribute as optional. This option takes a lambda parameter used to say if this attribute is present or not. The lambda takes an argument (Struct subclass object owning attribute),

For example:

# 32-bit integer attribute defaulting to 1
define_attr :type, BinStruct::Int32, default: 1
# 16-bit integer attribute, created with a random value. Each instance of this
# object will have a different value.
define_attr :id, BinStruct::Int16, default: ->(obj) { rand(65535) }
# a size attribute
define_attr :body_size, BinStruct::Int16
# String attribute which length is taken from body_size attribute
define_attr :body, BinStruct::String, builder: ->(obj, type) { type.new(length_from: obj[:body_size]) }
# 16-bit enumeration type. As :default not specified, default to first value of enum
define_attr :type_class, BinStruct::Int16Enum, enum: { 'class1' => 1, 'class2' => 2}
# optional attribute. Only present if another attribute has a certain value
define_attr :opt1, BinStruct::Int16, optional: ->(h) { h.type == 42 }

Generating bit attributes

Struct.define_bit_attr creates a bit attribute. For example, frag attribute in IP header:

define_bit_attr :frag, flag_rsv: 1, flag_df: 1, flag_mf: 1, fragment_offset: 13

This example generates methods:

  • #frag and #frag= to access frag attribute as a 16-bit integer,

  • #flag_rsv?, #flag_rsv=, #flag_df?, #flag_df=, #flag_mf? and #flag_mf= to access Boolean RSV, MF and DF flags from frag attribute,

  • #flag_rsv, #flag_df and +#flag_mf# to read RSV, MF and DF flags as Integer,

  • #fragment_offset and #fragment_offset= to access 13-bit integer fragment offset subattribute from frag attribute.

Creating a new Struct class from another one

Some methods may help in this case:

Author:

  • Sylvain Daubert (2016-2024)

  • LemonTree55

Direct Known Subclasses

AbstractTLV, OUI

Defined Under Namespace

Classes: StructDef

Constant Summary collapse

FMT_ATTR =

Format to inspect attribute

"%14s %16s: %s\n"

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Struct

Create a new Struct object

Parameters:

  • options (Hash) (defaults to: {})

    Keys are symbols. They should have name of object attributes, as defined by define_attr and by define_bit_attr.



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/bin_struct/struct.rb', line 374

def initialize(options = {})
  @attributes = {}
  @optional_attributes = {}

  self.class.attributes.each do |attr|
    build_attribute(attr)
    initialize_value(attr, options[attr])
    initialize_optional(attr)
  end

  self.class.bit_attrs.each_value do |bit_fields|
    bit_fields.each do |bit|
      send(:"#{bit}=", options[bit]) if options[bit]
    end
  end
end

Class Attribute Details

.attr_defsHash (readonly)

Get attribute definitions for this class.

Returns:

  • (Hash)


120
121
122
# File 'lib/bin_struct/struct.rb', line 120

def attr_defs
  @attr_defs
end

.bit_attrsHash{Symbol=>Array[Symbol]} (readonly)

Get bit attribute defintions for this class

Returns:

  • (Hash{Symbol=>Array[Symbol]})


123
124
125
# File 'lib/bin_struct/struct.rb', line 123

def bit_attrs
  @bit_attrs
end

Class Method Details

.attributesArray<Symbol>

Get attribute names

Returns:



147
148
149
# File 'lib/bin_struct/struct.rb', line 147

def attributes
  @ordered_attrs
end

.define_attr(name, type, options = {}) ⇒ void

This method returns an undefined value.

Define an attribute in class

class BinaryStruct < BinStruct::Struct
  # 8-bit value
  define_attr :value1, BinStruct::Int8
  # 16-bit value
  define_attr :value2, BinStruct::Int16
  # specific class, may use a specific constructor
  define_attr :value3, MyClass, builder: ->(obj, type) { type.new(obj) }
end

bs = BinaryStruct.new
bs[value1]   # => BinStruct::Int8
bs.value1    # => Integer

Parameters:

  • name (Symbol)

    attribute name

  • type (Structable)

    class or instance

  • options (Hash) (defaults to: {})

    Unrecognized options are passed to object builder if :builder option is not set.

Options Hash (options):

  • :default (Object)

    default value. May be a proc. This lambda take one argument: the caller object.

  • :builder (Lambda)

    lambda to construct this attribute. Parameters to this lambda is the caller object and the attribute type class.

  • :optional (Lambda)

    define this attribute as optional. Given lambda is used to known if this attribute is present or not. Parameter to this lambda is the being defined Struct object.



176
177
178
179
180
181
182
183
184
185
# File 'lib/bin_struct/struct.rb', line 176

def define_attr(name, type, options = {})
  attributes << name
  attr_defs[name] = StructDef.new(type,
                                  options.delete(:default),
                                  options.delete(:builder),
                                  options.delete(:optional),
                                  options)

  add_methods(name, type)
end

.define_attr_after(other, name, type, options = {}) ⇒ void

This method returns an undefined value.

Define an attribute, after another one

Parameters:

  • other (Symbol, nil)

    attribute name to create a new one after. If nil, new attribute is appended.

  • name (Symbol)

    attribute name to create

  • type (Structable)

    class or instance

  • options (Hash) (defaults to: {})

See Also:



210
211
212
213
214
215
# File 'lib/bin_struct/struct.rb', line 210

def define_attr_after(other, name, type, options = {})
  define_attr name, type, options
  return if other.nil?

  move_attr(name, after: other)
end

.define_attr_before(other, name, type, options = {}) ⇒ void

This method returns an undefined value.

Define a attribute, before another one

Parameters:

  • other (Symbol, nil)

    attribute name to create a new one before. If nil, new attribute is appended.

  • name (Symbol)

    attribute name to create

  • type (Structable)

    class or instance

  • options (Hash) (defaults to: {})

See Also:



195
196
197
198
199
200
# File 'lib/bin_struct/struct.rb', line 195

def define_attr_before(other, name, type, options = {})
  define_attr name, type, options
  return if other.nil?

  move_attr(name, before: other)
end

.define_bit_attr(attr, endian: :big, default: 0, **fields) ⇒ void

This method returns an undefined value.

Define a bit attribute

class MyHeader < BinStruct::Struct
  # define a 16-bit attribute named :flag
  # flag1, flag2 and flag3 are 1-bit attributes
  # type and stype are 3-bit attributes, reserved is a 7-bit attribute
  define_bit_attr :flags, flag1: 1, flag2: 1, flag3: 1, type: 3, stype: 3, reserved: 7
end

A bit attribute of size 1 bit defines 3 methods:

  • #attr which returns an Integer,

  • #attr? which returns a Boolean,

  • #attr= which accepts an Integer or a Boolean.

A bit attribute of more bits defines only 2 methods:

  • #attr which returns an Integer,

  • #attr= which takes an Integer.

Parameters:

  • attr (Symbol)

    attribute name

  • endian (:big, :little, :native) (defaults to: :big)

    endianess of Integer

  • default (Integer) (defaults to: 0)

    default value for whole attribute

  • fields (Hash{Symbol=>Integer})

    Hash defining fields. Keys are field names, values are field sizes.

Since:

  • 0.3.0



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/bin_struct/struct.rb', line 266

def define_bit_attr(attr, endian: :big, default: 0, **fields)
  width = fields.reduce(0) { |acc, ary| acc + ary.last }
  bit_attr_klass = BitAttr.create(width: width, endian: endian, **fields)
  define_attr(attr, bit_attr_klass, default: default)
  fields.each_key { |field| register_bit_attr_field(attr, field) }
  define_str = +''
  bit_attr_klass.new.bit_methods.each do |meth|
    define_str << if meth.to_s.end_with?('=')
                    "def #{meth}(value); self[:#{attr}].#{meth}(value); end\n"
                  else
                    "def #{meth}; self[:#{attr}].#{meth}; end\n"
                  end
  end
  class_eval(define_str)
end

.define_bit_attr_after(other, name, endian: :big, **fields) ⇒ void

This method returns an undefined value.

Define a bit attribute after another attribute

Parameters:

  • other (Symbol, nil)

    attribute name to create a new one after. If nil, new attribute is appended.

  • name (Symbol)

    attribute name to create

  • endian (:big, :little, :native) (defaults to: :big)

    endianess of Integer

  • fields (Hash{Symbol=>Integer})

    Hash defining fields. Keys are field names, values are field sizes.

See Also:

Since:

  • 0.3.0



307
308
309
310
311
312
# File 'lib/bin_struct/struct.rb', line 307

def define_bit_attr_after(other, name, endian: :big, **fields)
  define_bit_attr(name, endian: endian, **fields)
  return if other.nil?

  move_attr(name, after: other)
end

.define_bit_attr_before(other, name, endian: :big, **fields) ⇒ void

This method returns an undefined value.

Define a bit attribute, before another attribute

Parameters:

  • other (Symbol, nil)

    attribute name to create a new one before. If nil, new attribute is appended.

  • name (Symbol)

    attribute name to create

  • endian (:big, :little, :native) (defaults to: :big)

    endianess of Integer

  • fields (Hash{Symbol=>Integer})

    Hash defining fields. Keys are field names, values are field sizes.

See Also:

Since:

  • 0.3.0



291
292
293
294
295
296
# File 'lib/bin_struct/struct.rb', line 291

def define_bit_attr_before(other, name, endian: :big, **fields)
  define_bit_attr(name, endian: endian, **fields)
  return if other.nil?

  move_attr(name, before: other)
end

.inherited(klass) ⇒ void

This method returns an undefined value.

On inheritage, create @attr_defs class variable

Parameters:

  • klass (Class)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/bin_struct/struct.rb', line 128

def inherited(klass)
  super

  attr_defs = {}
  @attr_defs.each do |k, v|
    attr_defs[k] = v.clone
  end
  ordered = @ordered_attrs.clone
  bf = bit_attrs.clone

  klass.class_eval do
    @ordered_attrs = ordered
    @attr_defs = attr_defs
    @bit_attrs = bf
  end
end

.remove_attr(name) ⇒ void

This method returns an undefined value.

Remove a previously defined attribute

Parameters:

  • name (Symbol)


220
221
222
223
224
225
226
227
228
# File 'lib/bin_struct/struct.rb', line 220

def remove_attr(name)
  attributes.delete(name)
  attr_def = attr_defs.delete(name)
  undef_method name if method_defined?(name)
  undef_method :"#{name}=" if method_defined?(:"#{name}=")
  return unless bit_attrs[name]

  attr_def.type.new.bit_methods.each { |meth| undef_method(meth) }
end

.update_attr(name, options) ⇒ void

This method returns an undefined value.

Update a previously defined attribute

Parameters:

  • name (Symbol)

    attribute name to create

  • options (Hash)

Raises:

  • (ArgumentError)

    unknown attribute

See Also:



236
237
238
239
240
241
242
243
244
# File 'lib/bin_struct/struct.rb', line 236

def update_attr(name, options)
  check_existence_of(name)

  %i[default builder optional].each do |property|
    attr_defs_property_from(name, property, options)
  end

  attr_defs[name].options.merge!(options)
end

Instance Method Details

#[](attr) ⇒ Structable

Get attribute object

Parameters:

  • attr (Symbol)

    attribute name

Returns:



394
395
396
# File 'lib/bin_struct/struct.rb', line 394

def [](attr)
  @attributes[attr]
end

#[]=(attr, obj) ⇒ Object

Set attribute object

Parameters:

  • attr (Symbol)

    attribute name

  • obj (Object)

Returns:

  • (Object)


402
403
404
# File 'lib/bin_struct/struct.rb', line 402

def []=(attr, obj)
  @attributes[attr] = obj
end

#attribute?(attr) ⇒ Boolean

Say if struct has given attribute

Parameters:

  • attr (Symbol)

    attribute name

Returns:

  • (Boolean)

Author:

  • LemonTree55

Since:

  • 0.4.0



411
412
413
# File 'lib/bin_struct/struct.rb', line 411

def attribute?(attr)
  @attributes.key?(attr)
end

#attributesArray<Symbol>

Get all attribute names

Returns:



417
418
419
# File 'lib/bin_struct/struct.rb', line 417

def attributes
  self.class.attributes
end

#bits_on(attr) ⇒ Hash?

Get bit attributes definition for given attribute.

Parameters:

  • attr (Symbol)

    attribute defining bit attributes

Returns:

  • (Hash, nil)

    keys: bit attributes, values: their size in bits



518
519
520
# File 'lib/bin_struct/struct.rb', line 518

def bits_on(attr)
  self.class.bit_attrs[attr]
end

#inspect {|attr| ... } ⇒ String

Common inspect method for structs.

A block may be given to differently format some attributes. This may be used by subclasses to handle specific attributes.

Yield Parameters:

  • attr (Symbol)

    attribute to inspect

Yield Returns:

  • (String, nil)

    the string to print for attr, or nil to let inspect generate it

Returns:



468
469
470
471
472
473
474
475
476
477
478
# File 'lib/bin_struct/struct.rb', line 468

def inspect
  str = inspect_titleize
  attributes.each do |attr|
    next if attr == :body
    next unless present?(attr)

    result = yield(attr) if block_given?
    str << (result || inspect_attribute(attr, self[attr], 1))
  end
  str
end

#offset_of(attr) ⇒ Integer

Get offset of given attribute in BinStruct::Struct.

Parameters:

  • attr (Symbol)

    attribute name

Returns:

  • (Integer)

Raises:

  • (ArgumentError)

    unknown attribute



503
504
505
506
507
508
509
510
511
512
513
# File 'lib/bin_struct/struct.rb', line 503

def offset_of(attr)
  raise ArgumentError, "#{attr} is an unknown attribute of #{self.class}" unless @attributes.include?(attr)

  offset = 0
  attributes.each do |a|
    break offset if a == attr
    next unless present?(a)

    offset += self[a].sz
  end
end

#optional?(attr) ⇒ Boolean

Say if this attribue is optional

Parameters:

  • attr (Symbol)

    attribute name

Returns:

  • (Boolean)


430
431
432
# File 'lib/bin_struct/struct.rb', line 430

def optional?(attr)
  @optional_attributes.key?(attr)
end

#optional_attributesObject

Get all optional attribute names @return



423
424
425
# File 'lib/bin_struct/struct.rb', line 423

def optional_attributes
  @optional_attributes.keys
end

#present?(attr) ⇒ Boolean

Say if an optional attribute is present

Returns:

  • (Boolean)


436
437
438
439
440
# File 'lib/bin_struct/struct.rb', line 436

def present?(attr)
  return true unless optional?(attr)

  @optional_attributes[attr].call(self)
end

#read(str) ⇒ Struct

Populate object from a binary string

Parameters:

Returns:



445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/bin_struct/struct.rb', line 445

def read(str)
  return self if str.nil?

  start = 0
  attributes.each do |attr|
    next unless present?(attr)

    obj = self[attr].read(str.b[start..])
    start += self[attr].sz
    self[attr] = obj unless obj == self[attr]
  end

  self
end

#sznteger

Size of object as binary string

Returns:

  • (nteger)


489
490
491
# File 'lib/bin_struct/struct.rb', line 489

def sz
  to_s.size
end

#to_hHash

Return object as a hash

Returns:

  • (Hash)

    keys: attributes, values: attribute values



495
496
497
# File 'lib/bin_struct/struct.rb', line 495

def to_h
  attributes.to_h { |attr| [attr, @attributes[attr].to_human] }
end

#to_sString

Return object as a binary string

Returns:



482
483
484
485
# File 'lib/bin_struct/struct.rb', line 482

def to_s
  attributes.select { |attr| present?(attr) }
            .map! { |attr| @attributes[attr].to_s.b }.join
end