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 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):

  • :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 one argument: Fields subclass object owning field.

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::Type::Int16
# String field which length is taken from body_size field
define_field :body, PacketGen::Type::String, builder: ->(obj) { PacketGen::Type::String.new('', length_from: obj[:body_size]) }

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.

Direct Known Subclasses

Header::Base, PcapNG::Block, OUI, TLV

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Fields

Create a new header object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/packetgen/types/fields.rb', line 267

def initialize(options={})
  @fields = {}
  self.class.class_eval { @field_defs }.each do |field, ary|
    default = ary[1].is_a?(Proc) ? ary[1].call : ary[1]
    @fields[field] = if ary[2]
                       ary[2].call(self)
                     elsif !ary[3].empty?
                       ary[0].new(ary[3])
                     else
                       ary[0].new
                     end

    value = options[field] || default
    if ary[0] < Types::Int
      @fields[field].read(value)
    elsif ary[0] <= Types::String
      @fields[field].read(value)
    else
      @fields[field].from_human(value) if @fields[field].respond_to? :from_human
    end
  end
  self.class.class_eval { @bit_fields }.each do |bit_field|
    self.send "#{bit_field}=", options[bit_field] if options[bit_field]
  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:

  • #field which returns an Integer,

  • #field= which takes and returns an Integer.

Raises:

  • (ArgumentError)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/packetgen/types/fields.rb', line 209

def self.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        EOM\n      else\n          class_eval <<-EOM\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        EOM\n      end\n\n      @bit_fields << field\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) { Myclass.new(obj) }
end

bs = BinaryStruct.new
bs[value1]   # => Types::Int8
bs.value1    # => Integer

Options Hash (options):

  • :default (Object)

    default value

  • :builder (Lambda)

    lambda to construct this field. Parameter to this lambda is the caller object.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/packetgen/types/fields.rb', line 132

def self.define_field(name, type, options={})
  define = []
  if 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 and
       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, options.delete(:default), options.delete(:builder),
                       options]
  @ordered_fields << name
end

.define_field_after(other, name, type, options = {}) ⇒ void

This method returns an undefined value.

Define a field, after another one

See Also:



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

def self.define_field_after(other, name, type, options={})
  define_field name, type, options
  unless other.nil?
    @ordered_fields.delete name
    idx = @ordered_fields.index(other)
    raise ArgumentError, "unknown #{other} field" if idx.nil?
    @ordered_fields[idx+1, 0] = name
  end
end

.define_field_before(other, name, type, options = {}) ⇒ void

This method returns an undefined value.

Define a field, before another one

See Also:



162
163
164
165
166
167
168
169
170
# File 'lib/packetgen/types/fields.rb', line 162

def self.define_field_before(other, name, type, options={})
  define_field name, type, options
  unless other.nil?
    @ordered_fields.delete name
    idx = @ordered_fields.index(other)
    raise ArgumentError, "unknown #{other} field" if idx.nil?
    @ordered_fields[idx, 0] = name
  end
end

.inherited(klass) ⇒ void

This method returns an undefined value.

On inheritage, create @field_defs class variable



100
101
102
103
104
105
106
107
108
109
# File 'lib/packetgen/types/fields.rb', line 100

def self.inherited(klass)
  ordered = @ordered_fields.clone
  field_defs = @field_defs.clone
  bf = @bit_fields.clone
  klass.class_eval do
    @ordered_fields = ordered
    @field_defs = field_defs
    @bit_fields = bf
  end
end

Instance Method Details

#[](field) ⇒ Object

Get field object



296
297
298
# File 'lib/packetgen/types/fields.rb', line 296

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

#[]=(field, obj) ⇒ Object

Set field object



304
305
306
# File 'lib/packetgen/types/fields.rb', line 304

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

#body=(value) ⇒ void

This method returns an undefined value.

Used to set body as value of body object.

Raises:

  • (BodyError)

    no body on given object

  • (ArgumentError)

    cannot cram body in :body field



373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/packetgen/types/fields.rb', line 373

def body=(value)
  raise BodyError, 'no body field'  unless @fields.has_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

#fieldsArray<Symbol>

Get all field names



310
311
312
# File 'lib/packetgen/types/fields.rb', line 310

def fields
  @ordered_fields ||= self.class.class_eval { @ordered_fields }
end

#force_binary(str) ⇒ String

Force str to binary encoding



390
391
392
# File 'lib/packetgen/types/fields.rb', line 390

def force_binary(str)
  PacketGen.force_binary(str)
end

#inspectString

Common inspect method for headers



341
342
343
344
345
346
347
348
# File 'lib/packetgen/types/fields.rb', line 341

def inspect
  str = Inspect.dashed_line(self.class, 2)
  @fields.each do |attr, value|
    next if attr == :body
    str << Inspect.inspect_attribute(attr, value, 2)
  end
  str
end

#read(str) ⇒ Fields

Populate object from a binary string



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/packetgen/types/fields.rb', line 317

def read(str)
  return self if str.nil?
  force_binary str
  start = 0
  fields.each do |field|
    if self[field].respond_to? :width
      width = self[field].width
      self[field].read str[start, width]
      start += width
    elsif self[field].respond_to? :sz
      self[field].read str[start..-1]
      size = self[field].sz
      start += size
    else
      self[field].read str[start..-1]
      start = str.size
    end
  end

  self
end

#sznteger

Size of object as binary string



358
359
360
# File 'lib/packetgen/types/fields.rb', line 358

def sz
  to_s.size
end

#to_hHash

Return object as a hash



364
365
366
# File 'lib/packetgen/types/fields.rb', line 364

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

#to_sString

Return object as a binary string



352
353
354
# File 'lib/packetgen/types/fields.rb', line 352

def to_s
  fields.map { |f| force_binary @fields[f].to_s }.join
end