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 # => 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 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, -
#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 to define a new attribute before an existing one,
-
Struct.define_attr_after to define a new attribute after an existing onr,
-
remove_attribute to remove an existing attribute,
-
uptade_fied to change options of an attribute (but not its type),
-
Struct.remove_bit_attrs_on to remove bit attribute definition.
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
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_attrs_on(attr, *args) ⇒ void
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.
-
.inherited(klass) ⇒ void
On inheritage, create @attr_defs class variable.
-
.remove_attr(name) ⇒ void
Remove a previously defined attribute.
-
.remove_bit_attrs_on(attr) ⇒ void
Remove all bit attributes defined on
attr
. -
.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.
-
#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
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( = {}) @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 |hsh| hsh.each_key do |bit| send(:"#{bit}=", [bit]) if [bit] end end end |
Class Attribute Details
.attr_defs ⇒ Hash (readonly)
Get attribute definitions for this class.
122 123 124 |
# File 'lib/bin_struct/struct.rb', line 122 def attr_defs @attr_defs end |
.bit_attrs ⇒ Hash (readonly)
Get bit attribute defintions for this class
125 126 127 |
# File 'lib/bin_struct/struct.rb', line 125 def bit_attrs @bit_attrs end |
Class Method Details
.attributes ⇒ Array<Symbol>
Get attribute names
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
178 179 180 181 182 183 184 185 186 187 |
# File 'lib/bin_struct/struct.rb', line 178 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
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, = {}) define_attr name, type, 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
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, = {}) define_attr name, type, 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.
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
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
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
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
243 244 245 246 247 248 249 250 251 |
# File 'lib/bin_struct/struct.rb', line 243 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
426 427 428 |
# File 'lib/bin_struct/struct.rb', line 426 def [](attr) @attributes[attr] end |
#[]=(attr, obj) ⇒ Object
Set attribute object
434 435 436 |
# File 'lib/bin_struct/struct.rb', line 434 def []=(attr, obj) @attributes[attr] = obj end |
#attributes ⇒ Array<Symbol>
Get all attribute names
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.
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.
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.
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
453 454 455 |
# File 'lib/bin_struct/struct.rb', line 453 def optional?(attr) @optional_attributes.key?(attr) end |
#optional_attributes ⇒ Object
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
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
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 |
#sz ⇒ nteger
Size of object as binary string
513 514 515 |
# File 'lib/bin_struct/struct.rb', line 513 def sz to_s.size end |
#to_h ⇒ Hash
Return object as a hash
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_s ⇒ String
Return object as a binary string
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 |