Class: BinStruct::Struct Abstract
- Inherits:
-
Object
- Object
- BinStruct::Struct
- Defined in:
- lib/bin_struct/struct.rb
Overview
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 accessfrag
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 fromfrag
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 fromfrag
attribute.
Creating a new Struct class from another one
Some methods may help in this case:
-
Struct.define_attr_before and Struct.define_bit_attr_before to define a new attribute before an existing one,
-
Struct.define_attr_after and Struct.define_bit_attr_after to define a new attribute after an existing onr,
-
Struct.remove_attr to remove an existing attribute,
-
Struct.update_attr to change options of an attribute (but not its type),
Direct Known Subclasses
Defined Under Namespace
Classes: StructDef
Constant Summary collapse
- FMT_ATTR =
Format to inspect attribute
"%14s %16s: %s\n"
Class Attribute Summary collapse
-
.attr_defs ⇒ Hash
readonly
Get attribute definitions for this class.
-
.bit_attrs ⇒ Hash{Symbol=>Array[Symbol]}
readonly
Get bit attribute defintions for this class.
Class Method Summary collapse
-
.attributes ⇒ Array<Symbol>
Get attribute names.
-
.define_attr(name, type, options = {}) ⇒ void
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.
-
.define_attr_after(other, name, type, options = {}) ⇒ void
Define an attribute, after another one.
-
.define_attr_before(other, name, type, options = {}) ⇒ void
Define a attribute, before another one.
-
.define_bit_attr(attr, endian: :big, default: 0, **fields) ⇒ void
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. -
.define_bit_attr_after(other, name, endian: :big, **fields) ⇒ void
Define a bit attribute after another attribute.
-
.define_bit_attr_before(other, name, endian: :big, **fields) ⇒ void
Define a bit attribute, before another attribute.
-
.inherited(klass) ⇒ void
On inheritage, create @attr_defs class variable.
-
.remove_attr(name) ⇒ void
Remove a previously defined attribute.
-
.update_attr(name, options) ⇒ void
Update a previously defined attribute.
Instance Method Summary collapse
-
#[](attr) ⇒ Structable
Get attribute object.
-
#[]=(attr, obj) ⇒ Object
Set attribute object.
-
#attribute?(attr) ⇒ Boolean
Say if struct has given attribute.
-
#attributes ⇒ Array<Symbol>
Get all attribute names.
-
#bits_on(attr) ⇒ Hash?
Get bit attributes definition for given attribute.
-
#initialize(options = {}) ⇒ Struct
constructor
Create a new Struct object.
-
#inspect {|attr| ... } ⇒ String
Common inspect method for structs.
-
#offset_of(attr) ⇒ Integer
Get offset of given attribute in Struct.
-
#optional?(attr) ⇒ Boolean
Say if this attribue is optional.
-
#optional_attributes ⇒ Object
Get all optional attribute names @return.
-
#present?(attr) ⇒ Boolean
Say if an optional attribute is present.
-
#read(str) ⇒ Struct
Populate object from a binary string.
-
#sz ⇒ nteger
Size of object as binary string.
-
#to_h ⇒ Hash
Return object as a hash.
-
#to_s ⇒ String
Return object as a binary string.
Constructor Details
#initialize(options = {}) ⇒ Struct
Create a new Struct object
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( = {}) @attributes = {} @optional_attributes = {} self.class.attributes.each do |attr| build_attribute(attr) initialize_value(attr, [attr]) initialize_optional(attr) end self.class.bit_attrs.each_value do |bit_fields| bit_fields.each do |bit| send(:"#{bit}=", [bit]) if [bit] end end end |
Class Attribute Details
.attr_defs ⇒ Hash (readonly)
Get attribute definitions for this class.
120 121 122 |
# File 'lib/bin_struct/struct.rb', line 120 def attr_defs @attr_defs end |
.bit_attrs ⇒ Hash{Symbol=>Array[Symbol]} (readonly)
Get bit attribute defintions for this class
123 124 125 |
# File 'lib/bin_struct/struct.rb', line 123 def bit_attrs @bit_attrs end |
Class Method Details
.attributes ⇒ Array<Symbol>
Get attribute names
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
176 177 178 179 180 181 182 183 184 185 |
# File 'lib/bin_struct/struct.rb', line 176 def define_attr(name, type, = {}) attributes << name attr_defs[name] = StructDef.new(type, .delete(:default), .delete(:builder), .delete(:optional), ) 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
210 211 212 213 214 215 |
# File 'lib/bin_struct/struct.rb', line 210 def define_attr_after(other, name, type, = {}) define_attr name, type, 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
195 196 197 198 199 200 |
# File 'lib/bin_struct/struct.rb', line 195 def define_attr_before(other, name, type, = {}) define_attr name, type, 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.
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
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
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
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
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
236 237 238 239 240 241 242 243 244 |
# File 'lib/bin_struct/struct.rb', line 236 def update_attr(name, ) check_existence_of(name) %i[default builder optional].each do |property| attr_defs_property_from(name, property, ) end attr_defs[name]..merge!() end |
Instance Method Details
#[](attr) ⇒ Structable
Get attribute object
394 395 396 |
# File 'lib/bin_struct/struct.rb', line 394 def [](attr) @attributes[attr] end |
#[]=(attr, obj) ⇒ Object
Set attribute 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
411 412 413 |
# File 'lib/bin_struct/struct.rb', line 411 def attribute?(attr) @attributes.key?(attr) end |
#attributes ⇒ Array<Symbol>
Get all attribute names
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.
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.
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.
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
430 431 432 |
# File 'lib/bin_struct/struct.rb', line 430 def optional?(attr) @optional_attributes.key?(attr) end |
#optional_attributes ⇒ Object
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
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
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 |
#sz ⇒ nteger
Size of object as binary string
489 490 491 |
# File 'lib/bin_struct/struct.rb', line 489 def sz to_s.size end |
#to_h ⇒ Hash
Return object as a hash
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_s ⇒ String
Return object as a binary string
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 |