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.
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:
-
#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
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) { Myclass.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.
-
.inherited(klass) ⇒ void
On inheritage, create @field_defs class variable.
Instance Method Summary collapse
-
#[](field) ⇒ Object
Get field object.
-
#[]=(field, obj) ⇒ Object
Set field object.
-
#body=(value) ⇒ void
Used to set body as value of body object.
-
#fields ⇒ Array<Symbol>
Get all field names.
-
#force_binary(str) ⇒ String
Force str to binary encoding.
-
#initialize(options = {}) ⇒ Fields
constructor
Create a new header object.
-
#inspect ⇒ String
Common inspect method for headers.
-
#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
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(={}) @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 = [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}=", [bit_field] if [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:
-
#fieldwhich returns an Integer, -
#field=which takes and returns an Integer.
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
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, ={}) 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, .delete(:default), .delete(:builder), ] @ordered_fields << name end |
.define_field_after(other, name, type, options = {}) ⇒ void
This method returns an undefined value.
Define a field, after another one
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, ={}) define_field name, type, 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
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, ={}) define_field name, type, 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.
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 |
#fields ⇒ Array<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 |
#inspect ⇒ String
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 |
#sz ⇒ nteger
Size of object as binary string
358 359 360 |
# File 'lib/packetgen/types/fields.rb', line 358 def sz to_s.size end |
#to_h ⇒ Hash
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_s ⇒ String
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 |