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 types from PacketGen::Types module are supported, and all Fields subclasses may also be used as field type.
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 afield 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):
-
:defaultgives default field value. It may be a simple value (an Integer for an Int field, for example) or a lambda, -
:builderto give a builder/constructor lambda to create field. The lambda takes one argument: Fields subclass object owning field, -
:optionalto define this field as optional. This option takes a lambda parameter used to say if this field is present or not, -
:enumto 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: ->{ 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:
-
#fragand#frag=to accessfragfield 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 fromfragfield, -
#fragment_offsetand#fragment_offset=to access 13-bit integer fragment offset subfield fromfragfield.
Direct Known Subclasses
Header::Base, Header::DHCPv6::DUID, Header::DHCPv6::Option, Header::Eth::MacAddr, Header::IGMPv3::GroupRecord, Header::IKE::Attribute, Header::IKE::SAProposal, Header::IKE::TrafficSelector, Header::IKE::Transform, 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, PcapNG::Block, OUI, TLV
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.
-
.delete_field(name) ⇒ void
deprecated
Deprecated.
Use Fields.remove_field instead.
-
.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) ⇒ Object
Get field object.
-
#[]=(field, obj) ⇒ Object
Set field object.
-
#bits_on(field) ⇒ Hash?
Get bit fields definition for given field.
- #body=(value) ⇒ void deprecated Deprecated.
-
#fields ⇒ Array<Symbol>
Get all field names.
-
#force_binary(str) ⇒ String
deprecated
Deprecated.
Will be a private method
-
#initialize(options = {}) ⇒ Fields
constructor
Create a new header object.
-
#inspect {|attr| ... } ⇒ String
Common inspect method for headers.
-
#is_optional?(field) ⇒ Boolean
deprecated
Deprecated.
Use #optional? instead.
-
#is_present?(field) ⇒ Boolean
deprecated
Deprecated.
Use #present? instead.
-
#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.
-
#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 header object
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'lib/packetgen/types/fields.rb', line 355 def initialize(={}) @fields = {} @optional_fields = {} field_defs = self.class.class_eval { @field_defs } self.class.fields.each do |field| ary = field_defs[field] type, default, builder, optional, enum, = ary default = default.to_proc.call(self) if default.is_a?(Proc) @fields[field] = if builder builder.call(self, type) elsif enum type.new(enum) elsif !.empty? type.new() else type.new end value = [field] || default if value.class <= type @fields[field] = value elsif type < Types::Enum case value when ::String @fields[field].value = value else @fields[field].read(value) end elsif type < Types::Int @fields[field].read(value) elsif type <= Types::String @fields[field].read(value) elsif @fields[field].respond_to? :from_human @fields[field].from_human(value) end @optional_fields[field] = optional if optional end self.class.class_eval { @bit_fields }.each do |_, hsh| hsh.each_key do |bit_field| self.send "#{bit_field}=", [bit_field] if [bit_field] end end 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:
-
#fieldwhich returns an Integer, -
#field=which takes and returns an Integer.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/packetgen/types/fields.rb', line 278 def define_bit_fields_on(attr, *args) attr_def = @field_defs[attr] raise ArgumentError, "unknown #{attr} field" if attr_def.nil? type = attr_def.first unless type < Types::Int raise TypeError, "#{attr} is not a PacketGen::Types::Int" end total_size = type.new.width * 8 idx = total_size - 1 field = args.shift while field next unless field.is_a? Symbol size = if args.first.is_a? Integer args.shift else 1 end unless field == :_ shift = idx - (size - 1) field_mask = (2**size - 1) << shift clear_mask = (2**total_size - 1) & (~field_mask & (2**total_size - 1)) if size == 1 class_eval " def \#{field}?\n val = (self[:\#{attr}].to_i & \#{field_mask}) >> \#{shift}\n val != 0\n end\n def \#{field}=(v)\n val = v ? 1 : 0\n self[:\#{attr}].value = self[:\#{attr}].to_i & \#{clear_mask}\n self[:\#{attr}].value |= val << \#{shift}\n end\n METHODS\n else\n class_eval <<-METHODS\n def \#{field}\n (self[:\#{attr}].to_i & \#{field_mask}) >> \#{shift}\n end\n def \#{field}=(v)\n self[:\#{attr}].value = self[:\#{attr}].to_i & \#{clear_mask}\n self[:\#{attr}].value |= (v & \#{2**size - 1}) << \#{shift}\n end\n METHODS\n end\n\n @bit_fields[attr] = {} if @bit_fields[attr].nil?\n @bit_fields[attr][field] = size\n end\n\n idx -= size\n field = args.shift\n end\nend\n" |
.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
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/packetgen/types/fields.rb', line 155 def define_field(name, type, ={}) define = [] if type < Types::Enum define << "def #{name}; self[:#{name}].to_i; end" define << "def #{name}=(val) self[:#{name}].value = val; end" elsif type < Types::Int define << "def #{name}; self[:#{name}].to_i; end" define << "def #{name}=(val) self[:#{name}].read val; end" elsif type.instance_methods.include?(:to_human) && type.instance_methods.include?(:from_human) define << "def #{name}; self[:#{name}].to_human; end" define << "def #{name}=(val) self[:#{name}].from_human val; end" else define << "def #{name}; self[:#{name}]; end\n" define << "def #{name}=(val) self[:#{name}].read val; end" end define.delete(1) if type.instance_methods.include? "#{name}=".to_sym define.delete(0) if type.instance_methods.include? name class_eval define.join("\n") @field_defs[name] = [type, .delete(:default), .delete(:builder), .delete(:optional), .delete(:enum), ] fields << name end |
.define_field_after(other, name, type, options = {}) ⇒ void
This method returns an undefined value.
Define a field, after another one
210 211 212 213 214 215 216 217 218 219 |
# File 'lib/packetgen/types/fields.rb', line 210 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
191 192 193 194 195 196 197 198 199 200 |
# File 'lib/packetgen/types/fields.rb', line 191 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 |
.delete_field(name) ⇒ void
Use remove_field instead.
This method returns an undefined value.
Delete a previously defined field
237 238 239 240 |
# File 'lib/packetgen/types/fields.rb', line 237 def delete_field(name) Deprecation.deprecated(self, __method__, 'remove_field', klass_method: true) remove_field name end |
.fields ⇒ Array<Symbol>
Get field names
124 125 126 |
# File 'lib/packetgen/types/fields.rb', line 124 def fields @ordered_fields end |
.inherited(klass) ⇒ void
This method returns an undefined value.
On inheritage, create @field_defs class variable
108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/packetgen/types/fields.rb', line 108 def inherited(klass) 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
341 342 343 344 345 346 347 348 349 |
# File 'lib/packetgen/types/fields.rb', line 341 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
225 226 227 228 229 230 |
# File 'lib/packetgen/types/fields.rb', line 225 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
249 250 251 252 253 254 255 256 257 |
# File 'lib/packetgen/types/fields.rb', line 249 def update_field(field, ) raise ArgumentError, "unkown #{field} field for #{self}" unless @field_defs.key?(field) @field_defs[field][1] = .delete(:default) if .key?(:default) @field_defs[field][2] = .delete(:builder) if .key?(:builder) @field_defs[field][3] = .delete(:optional) if .key?(:optional) @field_defs[field][4] = .delete(:enum) if .key?(:enum) @field_defs[field][5].merge!() end |
Instance Method Details
#[](field) ⇒ Object
Get field object
404 405 406 |
# File 'lib/packetgen/types/fields.rb', line 404 def [](field) @fields[field] end |
#[]=(field, obj) ⇒ Object
Set field object
412 413 414 |
# File 'lib/packetgen/types/fields.rb', line 412 def []=(field, obj) @fields[field] = obj end |
#bits_on(field) ⇒ Hash?
Get bit fields definition for given field.
574 575 576 |
# File 'lib/packetgen/types/fields.rb', line 574 def bits_on(field) self.class.class_eval { @bit_fields }[field] end |
#body=(value) ⇒ void
This method returns an undefined value.
Used to set body as value of body object.
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/packetgen/types/fields.rb', line 530 def body=(value) Deprecation.deprecated(self.class, __method__) raise BodyError, 'no body field' unless @fields.key? :body case body when ::String self[:body].read value when Int, Fields self[:body] = value when NilClass self[:body] = Types::String.new.read('') else raise ArgumentError, "Can't cram a #{body.class} in a :body field" end end |
#fields ⇒ Array<Symbol>
Get all field names
418 419 420 |
# File 'lib/packetgen/types/fields.rb', line 418 def fields self.class.fields end |
#force_binary(str) ⇒ String
Will be a private method
Force str to binary encoding
550 551 552 |
# File 'lib/packetgen/types/fields.rb', line 550 def force_binary(str) PacketGen.force_binary(str) 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.
493 494 495 496 497 498 499 500 501 502 503 |
# File 'lib/packetgen/types/fields.rb', line 493 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 |
#is_optional?(field) ⇒ Boolean
Use #optional? instead.
434 435 436 437 |
# File 'lib/packetgen/types/fields.rb', line 434 def is_optional?(field) Deprecation.deprecated(self.class, __method__, 'optional?', klass_method: true) optional? field end |
#is_present?(field) ⇒ Boolean
Use #present? instead.
Say if an optional field is present
450 451 452 453 |
# File 'lib/packetgen/types/fields.rb', line 450 def is_present?(field) Deprecation.deprecated(self.class, __method__, 'present?', klass_method: true) present? field end |
#offset_of(field) ⇒ Integer
Get offset of given field in PacketGen::Types::Fields structure.
558 559 560 561 562 563 564 565 566 567 568 |
# File 'lib/packetgen/types/fields.rb', line 558 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
429 430 431 |
# File 'lib/packetgen/types/fields.rb', line 429 def optional?(field) @optional_fields.key? field end |
#optional_fields ⇒ Object
Get all optional field name
423 424 425 |
# File 'lib/packetgen/types/fields.rb', line 423 def optional_fields @optional_fields.keys end |
#present?(field) ⇒ Boolean
Say if an optional field is present
441 442 443 444 445 |
# File 'lib/packetgen/types/fields.rb', line 441 def present?(field) return true unless optional?(field) @optional_fields[field].call(self) end |
#read(str) ⇒ Fields
Populate object from a binary string
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 |
# File 'lib/packetgen/types/fields.rb', line 458 def read(str) return self if str.nil? force_binary str start = 0 fields.each do |field| next unless present?(field) obj = nil if self[field].respond_to? :width width = self[field].width obj = self[field].read str[start, width] start += width elsif self[field].respond_to? :sz obj = self[field].read str[start..-1] size = self[field].sz start += size else obj = self[field].read str[start..-1] start = str.size end self[field] = obj unless obj == self[field] end self end |
#sz ⇒ nteger
Size of object as binary string
514 515 516 |
# File 'lib/packetgen/types/fields.rb', line 514 def sz to_s.size end |
#to_h ⇒ Hash
Return object as a hash
520 521 522 |
# File 'lib/packetgen/types/fields.rb', line 520 def to_h Hash[fields.map { |f| [f, @fields[f].to_human] }] end |
#to_s ⇒ String
Return object as a binary string
507 508 509 510 |
# File 'lib/packetgen/types/fields.rb', line 507 def to_s fields.select { |f| present?(f) } .map! { |f| force_binary @fields[f].to_s }.join end |