Class: PacketGen::Types::Fields Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/packetgen/types/fields.rb

Overview

This class is abstract.

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 access frag 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 from frag field,

  • #fragment_offset and #fragment_offset= to access 13-bit integer fragment offset subfield from frag field.

Creating a new field class from another one

Some methods may help in this case:

Author:

  • Sylvain Daubert

Defined Under Namespace

Classes: FieldDef

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Fields

Create a new fields object

Parameters:



415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/packetgen/types/fields.rb', line 415

def initialize(options={})
  @fields = {}
  @optional_fields = {}

  self.class.fields.each do |field|
    build_field field
    initialize_value field, options[field]
    initialize_optional field
  end

  self.class.bit_fields.each do |_, hsh|
    hsh.each_key do |bit_field|
      self.send "#{bit_field}=", options[bit_field] if options[bit_field]
    end
  end
end

Class Attribute Details

.bit_fieldsHash (readonly)

Get bit fields defintions for this class

Returns:

  • (Hash)

Since:

  • 3.1.5



126
127
128
# File 'lib/packetgen/types/fields.rb', line 126

def bit_fields
  @bit_fields
end

.field_defsHash (readonly)

Get field definitions for this class.

Returns:

  • (Hash)

Since:

  • 3.1.0



122
123
124
# File 'lib/packetgen/types/fields.rb', line 122

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.

Parameters:

  • attr (Symbol)

    attribute name (attribute should be a Int subclass)

  • args (Array)

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

Raises:

  • (ArgumentError)

    unknown attr



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/packetgen/types/fields.rb', line 279

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

Parameters:

  • name (Symbol)

    field name

  • type (Fieldable)

    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 field. Parameters to this lambda is the caller object and the field type class.

  • :optional (Lambda)

    define this field as optional. Given lambda is used to known if this field is present or not. Parameter to this lambda is the being defined Field object.

  • :enum (Hash)

    mandatory option for an Enum type. Define enumeration: hash’s keys are String, and values are Integer.



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/packetgen/types/fields.rb', line 181

def define_field(name, type, options={})
  fields << name
  field_defs[name] = FieldDef.new(type,
                                  options.delete(:default),
                                  options.delete(:builder),
                                  options.delete(:optional),
                                  options.delete(:enum),
                                  options)

  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

Parameters:

  • other (Symbol, nil)

    field name to create a new one after. If nil, new field is appended.

  • name (Symbol)

    field name to create

  • type (Fieldable)

    class or instance

  • options (Hash) (defaults to: {})

Raises:

  • (ArgumentError)

See Also:



220
221
222
223
224
225
226
227
228
229
# File 'lib/packetgen/types/fields.rb', line 220

def define_field_after(other, name, type, options={})
  define_field name, type, options
  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

Parameters:

  • other (Symbol, nil)

    field name to create a new one before. If nil, new field is appended.

  • name (Symbol)

    field name to create

  • type (Fieldable)

    class or instance

  • options (Hash) (defaults to: {})

Raises:

  • (ArgumentError)

See Also:



201
202
203
204
205
206
207
208
209
210
# File 'lib/packetgen/types/fields.rb', line 201

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

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

  fields[idx, 0] = name
end

.fieldsArray<Symbol>

Get field names

Returns:



150
151
152
# File 'lib/packetgen/types/fields.rb', line 150

def fields
  @ordered_fields
end

.inherited(klass) ⇒ void

This method returns an undefined value.

On inheritage, create @field_defs class variable

Parameters:

  • klass (Class)


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/packetgen/types/fields.rb', line 131

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

Parameters:

  • attr (Symbol)

    attribute defining bit fields

Since:

  • 2.8.4



307
308
309
310
311
312
313
314
315
# File 'lib/packetgen/types/fields.rb', line 307

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

Parameters:

  • name (Symbol)

Since:

  • 2.8.4



235
236
237
238
239
240
# File 'lib/packetgen/types/fields.rb', line 235

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

Parameters:

  • field (Symbol)

    field name to create

  • options (Hash)

Raises:

  • (ArgumentError)

    unknown field

See Also:

Since:

  • 2.8.4



249
250
251
252
253
254
255
256
257
# File 'lib/packetgen/types/fields.rb', line 249

def update_field(field, options)
  check_existence_of field

  %i[default builder optional enum].each do |property|
    field_defs_property_from(field, property, options)
  end

  field_defs[field].options.merge!(options)
end

Instance Method Details

#[](field) ⇒ Fieldable

Get field object

Parameters:

  • field (Symbol)

Returns:



435
436
437
# File 'lib/packetgen/types/fields.rb', line 435

def [](field)
  @fields[field]
end

#[]=(field, obj) ⇒ Object

Set field object

Parameters:

  • field (Symbol)
  • obj (Object)

Returns:

  • (Object)


443
444
445
# File 'lib/packetgen/types/fields.rb', line 443

def []=(field, obj)
  @fields[field] = obj
end

#bits_on(field) ⇒ Hash?

Get bit fields definition for given field.

Parameters:

  • field (Symbol)

    defining bit fields

Returns:

  • (Hash, nil)

    keys: bit fields, values: their size in bits

Since:

  • 2.8.3



551
552
553
# File 'lib/packetgen/types/fields.rb', line 551

def bits_on(field)
  self.class.bit_fields[field]
end

#fieldsArray<Symbol>

Get all field names

Returns:



449
450
451
# File 'lib/packetgen/types/fields.rb', line 449

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.

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:



500
501
502
503
504
505
506
507
508
509
510
# File 'lib/packetgen/types/fields.rb', line 500

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.

Parameters:

  • field (Symbol)

Returns:

  • (Integer)

Raises:

  • (ArgumentError)

    unknown field



535
536
537
538
539
540
541
542
543
544
545
# File 'lib/packetgen/types/fields.rb', line 535

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

Returns:

  • (Boolean)


461
462
463
# File 'lib/packetgen/types/fields.rb', line 461

def optional?(field)
  @optional_fields.key? field
end

#optional_fieldsObject

Get all optional field name @return



455
456
457
# File 'lib/packetgen/types/fields.rb', line 455

def optional_fields
  @optional_fields.keys
end

#present?(field) ⇒ Boolean

Say if an optional field is present

Returns:

  • (Boolean)


467
468
469
470
471
# File 'lib/packetgen/types/fields.rb', line 467

def present?(field)
  return true unless optional?(field)

  @optional_fields[field].call(self)
end

#read(str) ⇒ Fields

Populate object from a binary string

Parameters:

Returns:



476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/packetgen/types/fields.rb', line 476

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

#sznteger

Size of object as binary string

Returns:

  • (nteger)


521
522
523
# File 'lib/packetgen/types/fields.rb', line 521

def sz
  to_s.size
end

#to_hHash

Return object as a hash

Returns:

  • (Hash)

    keys: attributes, values: attribute values



527
528
529
# File 'lib/packetgen/types/fields.rb', line 527

def to_h
  fields.to_h { |f| [f, @fields[f].to_human] }
end

#to_sString

Return object as a binary string

Returns:



514
515
516
517
# File 'lib/packetgen/types/fields.rb', line 514

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