Class: PacketGen::Types::Fields Abstract
- Inherits:
-
Object
- Object
- PacketGen::Types::Fields
- Defined in:
- lib/packetgen/types/fields.rb
Overview
Set of fields
This class is a base class to define headers or anything else with a binary format containing multiple fields.
Basics
A Fields subclass is generaly composed of multiple binary fields. These fields have each a given type. All Fieldable types are supported.
To define a new subclass, it has to inherit from Fields. And some class methods have to be used to declare attributes/fields:
class MyBinaryStructure < PacketGen::Types::Fields
# define a first Int8 attribute, with default value: 1
define_field :attr1, PacketGen::Types::Int8, default: 1
#define a second attribute, of kind Int32
define_field :attr2, PacketGen::Types::Int32
end
These defintions create 4 methods: #attr1
, #attr1=
, #attr2
and #attr2=
. All these methods take and/or return Integers.
Fields may also be accessed through #[] ans #[]=. These methods give access to type object:
mybs = MyBinaryStructure.new
mybs.attr1 # => Integer
mybs[:attr1] # => PacketGen::Types::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 Fields
Fields.define_field adds a field to Fields subclass. A lot of field types may be defined: integer types, string types (to handle a stream of bytes). More complex field types may be defined using others Fields subclasses:
# define a 16-bit little-endian integer field, named type
define_field :type, PacketGen::Types::Int16le
# define a string field
define_field :body, PacketGen::Types::String
# define a field using a complex type (Fields subclass)
define_field :mac_addr, PacketGen::Eth::MacAddr
This example creates six methods on our Fields subclass: #type
, #type=
, #body
, #body=
, #mac_addr
and #mac_addr=
.
Fields.define_field has many options (third optional Hash argument):
-
:default
gives default field value. It may be a simple value (an Integer for an Int field, for example) or a lambda, -
:builder
to give a builder/constructor lambda to create field. The lambda takes 2 argument: Fields subclass object owning field, and type class as passes as second argument to .define_field, -
:optional
to define this field as optional. This option takes a lambda parameter used to say if this field is present or not. The lambda takes an argument (Fields subclass object owning field), -
:enum
to define Hash enumeration for an Enum type.
For example:
# 32-bit integer field defaulting to 1
define_field :type, PacketGen::Types::Int32, default: 1
# 16-bit integer field, created with a random value. Each instance of this
# object will have a different value.
define_field :id, PacketGen::Types::Int16, default: ->(obj) { rand(65535) }
# a size field
define_field :body_size, PacketGen::Types::Int16
# String field which length is taken from body_size field
define_field :body, PacketGen::Types::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_field :type_class, PacketGen::Types::Int16Enum, enum: { 'class1' => 1, 'class2' => 2}
# optional field. Only present if another field has a certain value
define_field :opt1, PacketGen::Types::Int16, optional: ->(h) { h.type == 42 }
Fields.define_field_before and Fields.define_field_after are also defined to relatively create a field from anoher one (for example, when adding a field in a subclass).
Generating bit fields
Fields.define_bit_fields_on creates a bit field on a previuously declared integer field. For example, frag
field in IP header:
define_field :frag, Types::Int16, default: 0
define_bit_fields_on :frag, :flag_rsv, :flag_df, :flag_mf, :fragment_offset, 13
This example generates methods:
-
#frag
and#frag=
to accessfrag
field 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
field, -
#fragment_offset
and#fragment_offset=
to access 13-bit integer fragment offset subfield fromfrag
field.
Creating a new field class from another one
Some methods may help in this case:
-
Fields.define_field_before to define a new field before an existing one,
-
Fields.define_field_after to define a new field after an existing onr,
-
Fields.remove_field to remove an existing field,
-
uptade_fied to change options of a field (but not its type),
-
Fields.remove_bit_fields_on to remove a bit fields definition.
Direct Known Subclasses
Header::Base, Header::DHCPv6::DUID, Header::DHCPv6::Option, Header::DNS::Question, Header::Eth::MacAddr, Header::IGMPv3::GroupRecord, Header::IP::Addr, Header::IP::Option, Header::IPv6::Addr, Header::IPv6::Pad1, Header::MLDv2::McastAddressRecord, Header::OSPFv2::External, Header::OSPFv2::LSAHeader, Header::OSPFv2::LSR, Header::OSPFv2::Link, Header::OSPFv2::TosMetric, Header::OSPFv3::IPv6Prefix, Header::OSPFv3::LSAHeader, Header::OSPFv3::LSR, Header::OSPFv3::Link, Header::TCP::Option, PcapNG::Block, AbstractTLV, OUI, TLV
Defined Under Namespace
Classes: FieldDef
Class Attribute Summary collapse
-
.bit_fields ⇒ Hash
readonly
Get bit fields defintions for this class.
-
.field_defs ⇒ Hash
readonly
Get field definitions for this class.
Class Method Summary collapse
-
.define_bit_fields_on(attr, *args) ⇒ void
Define a bitfield on given attribute class MyHeader < PacketGen::Types::Fields define_field :flags, Types::Int16 # define a bit field on :flag attribute: # flag1, flag2 and flag3 are 1-bit fields # type and stype are 3-bit fields.
-
.define_field(name, type, options = {}) ⇒ void
Define a field in class class BinaryStruct < PacketGen::Types::Fields # 8-bit value define_field :value1, Types::Int8 # 16-bit value define_field :value2, Types::Int16 # specific class, may use a specific constructor define_field :value3, MyClass, builder: ->(obj, type) { type.new(obj) } end.
-
.define_field_after(other, name, type, options = {}) ⇒ void
Define a field, after another one.
-
.define_field_before(other, name, type, options = {}) ⇒ void
Define a field, before another one.
-
.fields ⇒ Array<Symbol>
Get field names.
-
.inherited(klass) ⇒ void
On inheritage, create @field_defs class variable.
-
.remove_bit_fields_on(attr) ⇒ void
Remove all bit fields defined on
attr
. -
.remove_field(name) ⇒ void
Remove a previously defined field.
-
.update_field(field, options) ⇒ void
Update a previously defined field.
Instance Method Summary collapse
-
#[](field) ⇒ Fieldable
Get field object.
-
#[]=(field, obj) ⇒ Object
Set field object.
-
#bits_on(field) ⇒ Hash?
Get bit fields definition for given field.
-
#fields ⇒ Array<Symbol>
Get all field names.
-
#initialize(options = {}) ⇒ Fields
constructor
Create a new fields object.
-
#inspect {|attr| ... } ⇒ String
Common inspect method for headers.
-
#offset_of(field) ⇒ Integer
Get offset of given field in Fields structure.
-
#optional?(field) ⇒ Boolean
Say if this field is optional.
-
#optional_fields ⇒ Object
Get all optional field name @return.
-
#present?(field) ⇒ Boolean
Say if an optional field is present.
-
#read(str) ⇒ Fields
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 = {}) ⇒ Fields
Create a new fields object
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'lib/packetgen/types/fields.rb', line 416 def initialize(={}) @fields = {} @optional_fields = {} self.class.fields.each do |field| build_field field initialize_value field, [field] initialize_optional field end self.class.bit_fields.each_value do |hsh| hsh.each_key do |bit_field| self.send(:"#{bit_field}=", [bit_field]) if [bit_field] end end end |
Class Attribute Details
.bit_fields ⇒ Hash (readonly)
Get bit fields defintions for this class
127 128 129 |
# File 'lib/packetgen/types/fields.rb', line 127 def bit_fields @bit_fields end |
.field_defs ⇒ Hash (readonly)
Get field definitions for this class.
123 124 125 |
# File 'lib/packetgen/types/fields.rb', line 123 def field_defs @field_defs end |
Class Method Details
.define_bit_fields_on(attr, *args) ⇒ void
This method returns an undefined value.
Define a bitfield on given attribute
class MyHeader < PacketGen::Types::Fields
define_field :flags, Types::Int16
# define a bit field on :flag attribute:
# flag1, flag2 and flag3 are 1-bit fields
# type and stype are 3-bit fields. reserved is a 6-bit field
define_bit_fields_on :flags, :flag1, :flag2, :flag3, :type, 3, :stype, 3, :reserved, 7
end
A bitfield of size 1 bit defines 2 methods:
-
#field?
which returns a Boolean, -
#field=
which takes and returns a Boolean.
A bitfield of more bits defines 2 methods:
-
#field
which returns an Integer, -
#field=
which takes and returns an Integer.
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/packetgen/types/fields.rb', line 280 def define_bit_fields_on(attr, *args) check_existence_of attr type = field_defs[attr].type raise TypeError, "#{attr} is not a PacketGen::Types::Int" unless type < Types::Int total_size = type.new.nbits idx = total_size - 1 until args.empty? field = args.shift next unless field.is_a? Symbol size = size_from(args) unless field == :_ add_bit_methods(attr, field, size, total_size, idx) register_bit_field_size(attr, field, size) end idx -= size end end |
.define_field(name, type, options = {}) ⇒ void
This method returns an undefined value.
Define a field in class
class BinaryStruct < PacketGen::Types::Fields
# 8-bit value
define_field :value1, Types::Int8
# 16-bit value
define_field :value2, Types::Int16
# specific class, may use a specific constructor
define_field :value3, MyClass, builder: ->(obj, type) { type.new(obj) }
end
bs = BinaryStruct.new
bs[value1] # => Types::Int8
bs.value1 # => Integer
182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/packetgen/types/fields.rb', line 182 def define_field(name, type, ={}) fields << name field_defs[name] = FieldDef.new(type, .delete(:default), .delete(:builder), .delete(:optional), .delete(:enum), ) add_methods(name, type) end |
.define_field_after(other, name, type, options = {}) ⇒ void
This method returns an undefined value.
Define a field, after another one
221 222 223 224 225 226 227 228 229 230 |
# File 'lib/packetgen/types/fields.rb', line 221 def define_field_after(other, name, type, ={}) define_field name, type, return if other.nil? fields.delete name idx = fields.index(other) raise ArgumentError, "unknown #{other} field" if idx.nil? fields[idx + 1, 0] = name end |
.define_field_before(other, name, type, options = {}) ⇒ void
This method returns an undefined value.
Define a field, before another one
202 203 204 205 206 207 208 209 210 211 |
# File 'lib/packetgen/types/fields.rb', line 202 def define_field_before(other, name, type, ={}) define_field name, type, return if other.nil? fields.delete name idx = fields.index(other) raise ArgumentError, "unknown #{other} field" if idx.nil? fields[idx, 0] = name end |
.fields ⇒ Array<Symbol>
Get field names
151 152 153 |
# File 'lib/packetgen/types/fields.rb', line 151 def fields @ordered_fields end |
.inherited(klass) ⇒ void
This method returns an undefined value.
On inheritage, create @field_defs class variable
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/packetgen/types/fields.rb', line 132 def inherited(klass) super field_defs = {} @field_defs.each do |k, v| field_defs[k] = v.clone end ordered = @ordered_fields.clone bf = bit_fields.clone klass.class_eval do @ordered_fields = ordered @field_defs = field_defs @bit_fields = bf end end |
.remove_bit_fields_on(attr) ⇒ void
This method returns an undefined value.
Remove all bit fields defined on attr
308 309 310 311 312 313 314 315 316 |
# File 'lib/packetgen/types/fields.rb', line 308 def remove_bit_fields_on(attr) fields = bit_fields.delete(attr) return if fields.nil? fields.each do |field, size| undef_method :"#{field}=" undef_method(size == 1 ? "#{field}?" : field) end end |
.remove_field(name) ⇒ void
This method returns an undefined value.
Remove a previously defined field
236 237 238 239 240 241 |
# File 'lib/packetgen/types/fields.rb', line 236 def remove_field(name) fields.delete name @field_defs.delete name undef_method name if method_defined?(name) undef_method :"#{name}=" if method_defined?(:"#{name}=") end |
.update_field(field, options) ⇒ void
This method returns an undefined value.
Update a previously defined field
250 251 252 253 254 255 256 257 258 |
# File 'lib/packetgen/types/fields.rb', line 250 def update_field(field, ) check_existence_of field %i[default builder optional enum].each do |property| field_defs_property_from(field, property, ) end field_defs[field]..merge!() end |
Instance Method Details
#[](field) ⇒ Fieldable
Get field object
436 437 438 |
# File 'lib/packetgen/types/fields.rb', line 436 def [](field) @fields[field] end |
#[]=(field, obj) ⇒ Object
Set field object
444 445 446 |
# File 'lib/packetgen/types/fields.rb', line 444 def []=(field, obj) @fields[field] = obj end |
#bits_on(field) ⇒ Hash?
Get bit fields definition for given field.
552 553 554 |
# File 'lib/packetgen/types/fields.rb', line 552 def bits_on(field) self.class.bit_fields[field] end |
#fields ⇒ Array<Symbol>
Get all field names
450 451 452 |
# File 'lib/packetgen/types/fields.rb', line 450 def fields self.class.fields end |
#inspect {|attr| ... } ⇒ String
Common inspect method for headers.
A block may be given to differently format some attributes. This may be used by subclasses to handle specific fields.
501 502 503 504 505 506 507 508 509 510 511 |
# File 'lib/packetgen/types/fields.rb', line 501 def inspect str = Inspect.dashed_line(self.class, 1) fields.each do |attr| next if attr == :body next unless present?(attr) result = yield(attr) if block_given? str << (result || Inspect.inspect_attribute(attr, self[attr], 1)) end str end |
#offset_of(field) ⇒ Integer
Get offset of given field in PacketGen::Types::Fields structure.
536 537 538 539 540 541 542 543 544 545 546 |
# File 'lib/packetgen/types/fields.rb', line 536 def offset_of(field) raise ArgumentError, "#{field} is an unknown field of #{self.class}" unless @fields.include?(field) offset = 0 fields.each do |f| break offset if f == field next unless present?(f) offset += self[f].sz end end |
#optional?(field) ⇒ Boolean
Say if this field is optional
462 463 464 |
# File 'lib/packetgen/types/fields.rb', line 462 def optional?(field) @optional_fields.key? field end |
#optional_fields ⇒ Object
Get all optional field name @return
456 457 458 |
# File 'lib/packetgen/types/fields.rb', line 456 def optional_fields @optional_fields.keys end |
#present?(field) ⇒ Boolean
Say if an optional field is present
468 469 470 471 472 |
# File 'lib/packetgen/types/fields.rb', line 468 def present?(field) return true unless optional?(field) @optional_fields[field].call(self) end |
#read(str) ⇒ Fields
Populate object from a binary string
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
# File 'lib/packetgen/types/fields.rb', line 477 def read(str) return self if str.nil? force_binary str start = 0 fields.each do |field| next unless present?(field) obj = self[field].read str[start..] start += self[field].sz self[field] = obj unless obj == self[field] end self end |
#sz ⇒ nteger
Size of object as binary string
522 523 524 |
# File 'lib/packetgen/types/fields.rb', line 522 def sz to_s.size end |
#to_h ⇒ Hash
Return object as a hash
528 529 530 |
# File 'lib/packetgen/types/fields.rb', line 528 def to_h fields.to_h { |f| [f, @fields[f].to_human] } end |
#to_s ⇒ String
Return object as a binary string
515 516 517 518 |
# File 'lib/packetgen/types/fields.rb', line 515 def to_s fields.select { |f| present?(f) } .map! { |f| force_binary @fields[f].to_s }.join end |