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     # => Integer
mybs[:attr1]   # => 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 gives 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

define_bit_attr_on creates bit attributes on a previously declared integer attribute. For example, frag attribute in IP header:

define_attr :frag, BinStruct::Int16, default: 0
define_bit_attr_on :frag, :flag_rsv, :flag_df, :flag_mf, :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,

  • #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_attrs_on.



406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/bin_struct/struct.rb', line 406

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 |hsh|
    hsh.each_key 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)


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

def attr_defs
  @attr_defs
end

.bit_attrsHash (readonly)

Get bit attribute defintions for this class

Returns:

  • (Hash)


125
126
127
# File 'lib/bin_struct/struct.rb', line 125

def bit_attrs
  @bit_attrs
end

Class Method Details

.attributesArray<Symbol>

Get attribute names

Returns:



149
150
151
# File 'lib/bin_struct/struct.rb', line 149

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.



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

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: {})

Raises:

  • (ArgumentError)

See Also:



216
217
218
219
220
221
222
223
224
225
# File 'lib/bin_struct/struct.rb', line 216

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

  attributes.delete name
  idx = attributes.index(other)
  raise ArgumentError, "unknown #{other} attribute" if idx.nil?

  attributes[idx + 1, 0] = name
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: {})

Raises:

  • (ArgumentError)

See Also:



197
198
199
200
201
202
203
204
205
206
# File 'lib/bin_struct/struct.rb', line 197

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

  attributes.delete name
  idx = attributes.index(other)
  raise ArgumentError, "unknown #{other} attribute" if idx.nil?

  attributes[idx, 0] = name
end

.define_bit_attrs_on(attr, *args) ⇒ void

This method returns an undefined value.

Define a bit attribute on given attribute

class MyHeader < BinStruct::Struct
  define_attr :flags, BinStruct::Int16
  # define a bit attribute on :flag attribute
  # flag1, flag2 and flag3 are 1-bit attributes
  # type and stype are 3-bit attributes. reserved is a 6-bit attribute
  define_bit_attributes_on :flags, :flag1, :flag2, :flag3, :type, 3, :stype, 3, :reserved, 7
end

A bit attribute of size 1 bit defines 2 methods:

  • #attr which returns a Boolean,

  • #attr= which takes and returns a Boolean.

A bit attribute of more bits defines 2 methods:

  • #attr which returns an Integer,

  • #attr= which takes and returns an Integer.

Parameters:

  • attr (Symbol)

    attribute name (attribute should be a Int subclass)

  • args (Array)

    list of bit attribute names. Name may be followed by bit size. If no size is given, 1 bit is assumed.

Raises:

  • (ArgumentError)

    unknown attr



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/bin_struct/struct.rb', line 273

def define_bit_attrs_on(attr, *args)
  check_existence_of(attr)

  type = attr_defs[attr].type
  raise TypeError, "#{attr} is not a BinStruct::Int" unless type < Int

  total_size = type.new.nbits
  idx = total_size - 1

  until args.empty?
    arg = args.shift
    next unless arg.is_a? Symbol

    size = size_from(args)

    unless attr == :_
      add_bit_methods(attr, arg, size, total_size, idx)
      register_bit_attr_size(attr, arg, size)
    end

    idx -= size
  end
end

.inherited(klass) ⇒ void

This method returns an undefined value.

On inheritage, create @attr_defs class variable

Parameters:

  • klass (Class)


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

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)


230
231
232
233
234
235
# File 'lib/bin_struct/struct.rb', line 230

def remove_attr(name)
  attributes.delete(name)
  @attr_defs.delete(name)
  undef_method name if method_defined?(name)
  undef_method :"#{name}=" if method_defined?(:"#{name}=")
end

.remove_bit_attrs_on(attr) ⇒ void

This method returns an undefined value.

Remove all bit attributes defined on attr

Parameters:

  • attr (Symbol)

    attribute defining bit attributes



300
301
302
303
304
305
306
307
308
# File 'lib/bin_struct/struct.rb', line 300

def remove_bit_attrs_on(attr)
  bits = bit_attrs.delete(attr)
  return if bits.nil?

  bits.each do |bit, size|
    undef_method :"#{bit}="
    undef_method(size == 1 ? "#{bit}?" : bit)
  end
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:



243
244
245
246
247
248
249
250
251
# File 'lib/bin_struct/struct.rb', line 243

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:



426
427
428
# File 'lib/bin_struct/struct.rb', line 426

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

#[]=(attr, obj) ⇒ Object

Set attribute object

Parameters:

  • attr (Symbol)

    attribute name

  • obj (Object)

Returns:

  • (Object)


434
435
436
# File 'lib/bin_struct/struct.rb', line 434

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

#attributesArray<Symbol>

Get all attribute names

Returns:



440
441
442
# File 'lib/bin_struct/struct.rb', line 440

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



542
543
544
# File 'lib/bin_struct/struct.rb', line 542

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:



492
493
494
495
496
497
498
499
500
501
502
# File 'lib/bin_struct/struct.rb', line 492

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



527
528
529
530
531
532
533
534
535
536
537
# File 'lib/bin_struct/struct.rb', line 527

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)


453
454
455
# File 'lib/bin_struct/struct.rb', line 453

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

#optional_attributesObject

Get all optional attribute names @return



446
447
448
# File 'lib/bin_struct/struct.rb', line 446

def optional_attributes
  @optional_attributes.keys
end

#present?(attr) ⇒ Boolean

Say if an optional attribute is present

Returns:

  • (Boolean)


459
460
461
462
463
# File 'lib/bin_struct/struct.rb', line 459

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:



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

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

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

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

  self
end

#sznteger

Size of object as binary string

Returns:

  • (nteger)


513
514
515
# File 'lib/bin_struct/struct.rb', line 513

def sz
  to_s.size
end

#to_hHash

Return object as a hash

Returns:

  • (Hash)

    keys: attributes, values: attribute values



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

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

#to_sString

Return object as a binary string

Returns:



506
507
508
509
# File 'lib/bin_struct/struct.rb', line 506

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