packetgen 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/packetgen/header/arp.rb +54 -125
  4. data/lib/packetgen/header/base.rb +175 -0
  5. data/lib/packetgen/header/dns/name.rb +110 -0
  6. data/lib/packetgen/header/dns/opt.rb +137 -0
  7. data/lib/packetgen/header/dns/option.rb +17 -0
  8. data/lib/packetgen/header/dns/qdsection.rb +39 -0
  9. data/lib/packetgen/header/dns/question.rb +129 -0
  10. data/lib/packetgen/header/dns/rr.rb +89 -0
  11. data/lib/packetgen/header/dns/rrsection.rb +72 -0
  12. data/lib/packetgen/header/dns.rb +276 -0
  13. data/lib/packetgen/header/esp.rb +38 -70
  14. data/lib/packetgen/header/eth.rb +35 -106
  15. data/lib/packetgen/header/icmp.rb +19 -70
  16. data/lib/packetgen/header/icmpv6.rb +3 -3
  17. data/lib/packetgen/header/ip.rb +54 -210
  18. data/lib/packetgen/header/ipv6.rb +73 -164
  19. data/lib/packetgen/header/tcp/option.rb +34 -50
  20. data/lib/packetgen/header/tcp/options.rb +19 -20
  21. data/lib/packetgen/header/tcp.rb +66 -129
  22. data/lib/packetgen/header/udp.rb +31 -88
  23. data/lib/packetgen/header.rb +5 -10
  24. data/lib/packetgen/inspect.rb +5 -4
  25. data/lib/packetgen/packet.rb +74 -57
  26. data/lib/packetgen/pcapng/block.rb +49 -7
  27. data/lib/packetgen/pcapng/epb.rb +36 -34
  28. data/lib/packetgen/pcapng/file.rb +24 -8
  29. data/lib/packetgen/pcapng/idb.rb +28 -33
  30. data/lib/packetgen/pcapng/shb.rb +35 -39
  31. data/lib/packetgen/pcapng/spb.rb +18 -27
  32. data/lib/packetgen/pcapng/unknown_block.rb +11 -21
  33. data/lib/packetgen/pcapng.rb +9 -7
  34. data/lib/packetgen/types/array.rb +56 -0
  35. data/lib/packetgen/types/fields.rb +325 -0
  36. data/lib/packetgen/types/int.rb +164 -0
  37. data/lib/packetgen/types/int_string.rb +69 -0
  38. data/lib/packetgen/types/string.rb +36 -0
  39. data/lib/packetgen/types/tlv.rb +41 -0
  40. data/lib/packetgen/types.rb +13 -0
  41. data/lib/packetgen/version.rb +1 -1
  42. data/lib/packetgen.rb +1 -1
  43. metadata +19 -6
  44. data/lib/packetgen/header/header_class_methods.rb +0 -106
  45. data/lib/packetgen/header/header_methods.rb +0 -73
  46. data/lib/packetgen/structfu.rb +0 -363
@@ -17,10 +17,7 @@ module PacketGen
17
17
  # Int64 :section_len
18
18
  # String :options Default: ''
19
19
  # Int32 :block_len2
20
- class SHB < Struct.new(:type, :block_len, :magic, :ver_major, :ver_minor,
21
- :section_len, :options, :block_len2)
22
- include StructFu
23
- include Block
20
+ class SHB < Block
24
21
 
25
22
  # @return [:little, :big]
26
23
  attr_accessor :endian
@@ -43,6 +40,27 @@ module PacketGen
43
40
  # +section_len+ value for undefined length
44
41
  SECTION_LEN_UNDEFINED = 0xffffffff_ffffffff
45
42
 
43
+ # @!attribute magic
44
+ # 32-bit magic number
45
+ # @return [Integer]
46
+ define_field_before :block_len2, :magic, Types::Int32, default: MAGIC_INT32
47
+ # @!attribute ver_major
48
+ # 16-bit major version number
49
+ # @return [Integer]
50
+ define_field_before :block_len2, :ver_major, Types::Int16, default: 1
51
+ # @!attribute ver_major
52
+ # 16-bit minor version number
53
+ # @return [Integer]
54
+ define_field_before :block_len2, :ver_minor, Types::Int16, default: 0
55
+ # @!attribute section_len
56
+ # 64-bit section length
57
+ # @return [Integer]
58
+ define_field_before :block_len2, :section_len, Types::Int64,
59
+ default: SECTION_LEN_UNDEFINED
60
+ # @!attribute options
61
+ # @return [Types::String]
62
+ define_field_before :block_len2, :options, Types::String
63
+
46
64
  # @param [Hash] options
47
65
  # @option options [:little, :big] :endian set block endianness
48
66
  # @option options [Integer] :type
@@ -58,28 +76,12 @@ module PacketGen
58
76
  # @option options [::String] :options
59
77
  # @option options [Integer] :block_len2 block total length
60
78
  def initialize(options={})
61
- @endian = set_endianness(options[:endian] || :little)
79
+ super
62
80
  @interfaces = []
63
81
  @unknown_blocks = []
64
- init_fields(options)
65
- super(options[:type], options[:block_len], options[:magic], options[:ver_major],
66
- options[:ver_minor], options[:section_len], options[:options], options[:block_len2])
67
- end
68
-
69
- # Used by {#initialize} to set the initial fields
70
- # @see #initialize possible options
71
- # @param [Hash] options
72
- # @return [Hash] return +options+
73
- def init_fields(options={})
74
- options[:type] = @int32.new(options[:type] || PcapNG::SHB_TYPE.to_i)
75
- options[:block_len] = @int32.new(options[:block_len] || MIN_SIZE)
76
- options[:magic] = @int32.new(options[:magic] || MAGIC_INT32)
77
- options[:ver_major] = @int16.new(options[:ver_major] || 1)
78
- options[:ver_minor] = @int16.new(options[:ver_minor] || 0)
79
- options[:section_len] = @int64.new(options[:section_len] || SECTION_LEN_UNDEFINED)
80
- options[:options] = StructFu::String.new(options[:options] || '')
81
- options[:block_len2] = @int32.new(options[:block_len2] || MIN_SIZE)
82
- options
82
+ set_endianness(options[:endian] || :little)
83
+ recalc_block_len
84
+ self.type = options[:type] || PcapNG::SHB_TYPE.to_i
83
85
  end
84
86
 
85
87
  # Reads a String or a IO to populate the object
@@ -130,10 +132,7 @@ module PacketGen
130
132
  self[:options].read io.read(self[:block_len].to_i - MIN_SIZE)
131
133
  self[:block_len2].read io.read(4)
132
134
 
133
- unless self[:block_len].to_i == self[:block_len2].to_i
134
- raise InvalidFileError, 'Incoherency in Section Header Block'
135
- end
136
-
135
+ check_len_coherency
137
136
  self
138
137
  end
139
138
 
@@ -154,25 +153,22 @@ module PacketGen
154
153
  end
155
154
  pad_field :options
156
155
  recalc_block_len
157
- to_a.map(&:to_s).join + body
156
+ fields.map { |f| @fields[f].to_s }.join + body
158
157
  end
159
158
 
160
159
 
161
160
  private
162
161
 
163
162
  def force_endianness(endian)
164
- set_endianness endian
165
163
  @endian = endian
166
- self[:type] = @int32.new(self[:type].to_i)
167
- self[:block_len] = @int32.new(self[:block_len].to_i)
168
- self[:magic] = @int32.new(self[:magic].to_i)
169
- self[:ver_major] = @int16.new(self[:ver_major].to_i)
170
- self[:ver_minor] = @int16.new(self[:ver_minor].to_i)
171
- self[:section_len] = @int64.new(self[:section_len].to_i)
172
- self[:block_len2] = @int32.new(self[:block_len2].to_i)
164
+ self[:type] = Types::Int32.new(self[:type].to_i, endian)
165
+ self[:block_len] = Types::Int32.new(self[:block_len].to_i, endian)
166
+ self[:magic] = Types::Int32.new(self[:magic].to_i, endian)
167
+ self[:ver_major] = Types::Int16.new(self[:ver_major].to_i, endian)
168
+ self[:ver_minor] = Types::Int16.new(self[:ver_minor].to_i, endian)
169
+ self[:section_len] = Types::Int64.new(self[:section_len].to_i, endian)
170
+ self[:block_len2] = Types::Int32.new(self[:block_len2].to_i, endian)
173
171
  end
174
-
175
172
  end
176
-
177
173
  end
178
174
  end
@@ -14,17 +14,23 @@ module PacketGen
14
14
  # Int32 :orig_len
15
15
  # String :data
16
16
  # Int32 :block_len2
17
- class SPB < Struct.new(:type, :block_len, :orig_len, :data, :block_len2)
18
- include StructFu
19
- include Block
17
+ class SPB < Block
18
+
19
+ # Minimum SPB size
20
+ MIN_SIZE = 4*4
20
21
 
21
22
  # @return [:little, :big]
22
23
  attr_accessor :endian
23
24
  # @return [IPB]
24
25
  attr_accessor :interface
25
26
 
26
- # Minimum SPB size
27
- MIN_SIZE = 4*4
27
+ # @!attribute orig_len
28
+ # 32-bit original length
29
+ # @return [Integer]
30
+ define_field_before :block_len2, :orig_len, Types::Int32, default: 0
31
+ # @!attribute data
32
+ # @return [Types::String]
33
+ define_field_before :block_len2, :data, Types::String
28
34
 
29
35
  # @param [Hash] options
30
36
  # @option options [:little, :big] :endian set block endianness
@@ -36,23 +42,10 @@ module PacketGen
36
42
  # @option options [::String] :options
37
43
  # @option options [Integer] :block_len2 block total length
38
44
  def initialize(options={})
39
- @endian = set_endianness(options[:endian] || :little)
40
- init_fields(options)
41
- super(options[:type], options[:block_len], options[:orig_len], options[:data],
42
- options[:block_len2])
43
- end
44
-
45
- # Used by {#initialize} to set the initial fields
46
- # @param [Hash] options
47
- # @see #initialize possible options
48
- # @return [Hash] return +options+
49
- def init_fields(options={})
50
- options[:type] = @int32.new(options[:type] || PcapNG::SPB_TYPE.to_i)
51
- options[:block_len] = @int32.new(options[:block_len] || MIN_SIZE)
52
- options[:orig_len] = @int32.new(options[:orig_len] || 0)
53
- options[:data] = StructFu::String.new(options[:data] || '')
54
- options[:block_len2] = @int32.new(options[:block_len2] || MIN_SIZE)
55
- options
45
+ super
46
+ set_endianness(options[:endian] || :little)
47
+ recalc_block_len
48
+ self.type = options[:type] || PcapNG::SPB_TYPE.to_i
56
49
  end
57
50
 
58
51
  # Has this block option?
@@ -87,10 +80,8 @@ module PacketGen
87
80
  io.read data_pad_len
88
81
  self[:block_len2].read io.read(4)
89
82
 
90
- unless self[:block_len].to_i == self[:block_len2].to_i
91
- raise InvalidFileError, 'Incoherency in Simple Packet Block'
92
- end
93
-
83
+ check_len_coherency
84
+ self.type = self[:type] || PcapNG::IDB_TYPE.to_i
94
85
  self
95
86
  end
96
87
 
@@ -99,7 +90,7 @@ module PacketGen
99
90
  def to_s
100
91
  pad_field :data
101
92
  recalc_block_len
102
- to_a.map(&:to_s).join
93
+ fields.map { |f| @fields[f].to_s }.join
103
94
  end
104
95
 
105
96
  end
@@ -7,17 +7,19 @@ module PacketGen
7
7
  module PcapNG
8
8
 
9
9
  # {UnknownBlock} is used to handle unsupported blocks of a pcapng file.
10
- class UnknownBlock < Struct.new(:type, :block_len, :body, :block_len2)
11
- include StructFu
12
- include Block
10
+ class UnknownBlock < Block
11
+
12
+ # Minimum Iblock size
13
+ MIN_SIZE = 12
13
14
 
14
15
  # @return [:little, :big]
15
16
  attr_accessor :endian
16
17
  # @return [SHB]
17
18
  attr_accessor :section
18
19
 
19
- # Minimum Iblock size
20
- MIN_SIZE = 12
20
+ # @!attribute body
21
+ # @return [Types::String]
22
+ define_field_before :block_len2, :body, Types::String
21
23
 
22
24
  # @option options [:little, :big] :endian set block endianness
23
25
  # @option options [Integer] :type
@@ -25,21 +27,9 @@ module PacketGen
25
27
  # @option options [::String] :body
26
28
  # @option options [Integer] :block_len2 block total length
27
29
  def initialize(options={})
28
- @endian = set_endianness(options[:endian] || :little)
29
- init_fields(options)
30
- super(options[:type], options[:block_len], options[:body], options[:block_len2])
31
- end
32
-
33
- # Used by {#initialize} to set the initial fields
34
- # @see #initialize possible options
35
- # @param [Hash] options
36
- # @return [Hash] return +options+
37
- def init_fields(options={})
38
- options[:type] = @int32.new(options[:type] || 0)
39
- options[:block_len] = @int32.new(options[:block_len] || MIN_SIZE)
40
- options[:body] = StructFu::String.new(options[:body] || '')
41
- options[:block_len2] = @int32.new(options[:block_len2] || MIN_SIZE)
42
- options
30
+ super
31
+ set_endianness(options[:endian] || :little)
32
+ recalc_block_len
43
33
  end
44
34
 
45
35
  # Has this block option?
@@ -76,7 +66,7 @@ module PacketGen
76
66
  def to_s
77
67
  pad_field :body
78
68
  recalc_block_len
79
- to_a.map(&:to_s).join
69
+ @fields.values.map(&:to_s).join
80
70
  end
81
71
 
82
72
  end
@@ -11,18 +11,20 @@ module PacketGen
11
11
  module PcapNG
12
12
 
13
13
  # Section Header Block type number
14
- SHB_TYPE = StructFu::Int32.new(0x0A0D0D0A, :little)
14
+ SHB_TYPE = Types::Int32.new(0x0A0D0D0A, :little)
15
15
  # Interface Description Block type number
16
- IDB_TYPE = StructFu::Int32.new(1, :little)
16
+ IDB_TYPE = Types::Int32.new(1, :little)
17
17
  # Simple Packet Block type number
18
- SPB_TYPE = StructFu::Int32.new(3, :little)
18
+ SPB_TYPE = Types::Int32.new(3, :little)
19
19
  # Enhanced Packet Block type number
20
- EPB_TYPE = StructFu::Int32.new(6, :little)
20
+ EPB_TYPE = Types::Int32.new(6, :little)
21
21
 
22
- # Various LINKTYPE values from http://www.tcpdump.org/linktypes.html
23
- # FIXME: only ETHERNET type is defined as this is the only link layer
24
- # type supported by PacketGen
22
+ # IEEE 802.3 Ethernet (10Mb, 100Mb, 1000Mb, and up)
25
23
  LINKTYPE_ETHERNET = 1
24
+ # Raw IPv4; the packet begins with an IPv4 header.
25
+ LINKTYPE_IPV4 = 228
26
+ # Raw IPv6; the packet begins with an IPv6 header.
27
+ LINKTYPE_IPV6 = 229
26
28
 
27
29
  # Base error class for PcapNG
28
30
  class Error < PacketGen::Error; end
@@ -0,0 +1,56 @@
1
+ # This file is part of PacketGen
2
+ # See https://github.com/sdaubert/packetgen for more informations
3
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
+ # This program is published under MIT license.
5
+
6
+ module PacketGen
7
+ module Types
8
+
9
+ # @abstract
10
+ # Array supporting some fields methods
11
+ # @author Sylvain Daubert
12
+ class Array < ::Array
13
+
14
+ # @abstract depend on private method +#record_from_hash+ which should be
15
+ # declared by subclasses.
16
+ # Add an object to this array
17
+ # @param [Object] obj type depends on subclass
18
+ # @return [Array] self
19
+ def push(obj)
20
+ obj = case obj
21
+ when Hash
22
+ record_from_hash obj
23
+ else
24
+ obj
25
+ end
26
+ super(obj)
27
+ end
28
+ alias :<< :push
29
+
30
+ # Get binary string
31
+ # @return [String]
32
+ def to_s
33
+ map(&:to_s).join
34
+ end
35
+
36
+ # Get a human readable string
37
+ # @return [String]
38
+ def to_human
39
+ map(&:to_human).join(',')
40
+ end
41
+
42
+ # Get size in bytes
43
+ # @return [Integer]
44
+ def sz
45
+ to_s.size
46
+ end
47
+
48
+ # Force binary encoding for +str+
49
+ # @param [String] str
50
+ # @return [String] binary encoded string
51
+ def force_binary(str)
52
+ PacketGen.force_binary str
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,325 @@
1
+ # This file is part of PacketGen
2
+ # See https://github.com/sdaubert/packetgen for more informations
3
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
+ # This program is published under MIT license.
5
+
6
+ module PacketGen
7
+ module Types
8
+
9
+ # @abstract
10
+ # Set of fields
11
+ # @author Sylvain Daubert
12
+ class Fields
13
+
14
+ # @private
15
+ @ordered_fields = []
16
+ # @private
17
+ @field_defs = {}
18
+ # @private
19
+ @bit_fields = []
20
+
21
+ # On inheritage, create +@field_defs+ class variable
22
+ # @param [Class] klass
23
+ # @return [void]
24
+ def self.inherited(klass)
25
+ ordered = @ordered_fields.clone
26
+ field_defs = @field_defs.clone
27
+ bf = @bit_fields.clone
28
+ klass.class_eval do
29
+ @ordered_fields = ordered
30
+ @field_defs = field_defs
31
+ @bit_fields = bf
32
+ end
33
+ end
34
+
35
+ # Define a field in class
36
+ # class BinaryStruct < PacketGen::Types::Fields
37
+ # # 8-bit value
38
+ # define_field :value1, Types::Int8
39
+ # # 16-bit value
40
+ # define_field :value2, Types::Int16
41
+ # # specific class, may use a specific constructor
42
+ # define_field :value3, MyClass, builder: ->(obj) { Myclass.new(obj) }
43
+ # end
44
+ #
45
+ # bs = BinaryStruct.new
46
+ # bs[value1] # => Types::Int8
47
+ # bs.value1 # => Integer
48
+ # @param [Symbol] name field name
49
+ # @param [Object] type class or instance
50
+ # @param [Hash] options Unrecognized options are passed to object builder if
51
+ # +:builder+ option is not set.
52
+ # @option options [Object] :default default value
53
+ # @option options [Lambda] :builder lambda to construct this field.
54
+ # Parameter to this lambda is the caller object.
55
+ # @return [void]
56
+ def self.define_field(name, type, options={})
57
+ define = []
58
+ if type < Types::Int
59
+ define << "def #{name}; self[:#{name}].to_i; end"
60
+ define << "def #{name}=(val) self[:#{name}].read val; end"
61
+ elsif type.instance_methods.include? :to_human and
62
+ type.instance_methods.include? :from_human
63
+ define << "def #{name}; self[:#{name}].to_human; end"
64
+ define << "def #{name}=(val) self[:#{name}].from_human val; end"
65
+ else
66
+ define << "def #{name}; self[:#{name}]; end\n"
67
+ define << "def #{name}=(val) self[:#{name}].read val; end"
68
+ end
69
+
70
+ define.delete(1) if type.instance_methods.include? "#{name}=".to_sym
71
+ define.delete(0) if type.instance_methods.include? name
72
+ class_eval define.join("\n")
73
+ @field_defs[name] = [type, options.delete(:default), options.delete(:builder),
74
+ options]
75
+ @ordered_fields << name
76
+ end
77
+
78
+ # Define a field, before another one
79
+ # @param [Symbol,nil] other field name to create a new one before. If +nil+,
80
+ # new field is appended.
81
+ # @param [Symbol] name field name to create
82
+ # @param [Object] type class or instance
83
+ # @param [Hash] options See {.define_field}.
84
+ # @return [void]
85
+ # @see .define_field
86
+ def self.define_field_before(other, name, type, options={})
87
+ define_field name, type, options
88
+ unless other.nil?
89
+ @ordered_fields.delete name
90
+ idx = @ordered_fields.index(other)
91
+ raise ArgumentError, "unknown #{other} field" if idx.nil?
92
+ @ordered_fields[idx, 0] = name
93
+ end
94
+ end
95
+
96
+ # Define a field, after another one
97
+ # @param [Symbol,nil] other field name to create a new one after. If +nil+,
98
+ # new field is appended.
99
+ # @param [Symbol] name field name to create
100
+ # @param [Object] type class or instance
101
+ # @param [Hash] options See {.define_field}.
102
+ # @return [void]
103
+ # @see .define_field
104
+ def self.define_field_after(other, name, type, options={})
105
+ define_field name, type, options
106
+ unless other.nil?
107
+ @ordered_fields.delete name
108
+ idx = @ordered_fields.index(other)
109
+ raise ArgumentError, "unknown #{other} field" if idx.nil?
110
+ @ordered_fields[idx+1, 0] = name
111
+ end
112
+ end
113
+
114
+ # Define a bitfield on given attribute
115
+ # class MyHeader < PacketGen::Types::Fields
116
+ # define_field :flags, Types::Int16
117
+ # # define a bit field on :flag attribute:
118
+ # # flag1, flag2 and flag3 are 1-bit fields
119
+ # # type and stype are 3-bit fields. reserved is a 6-bit field
120
+ # define_bit_fields_on :flags, :flag1, :flag2, :flag3, :type, 3, :stype, 3, :reserved: 7
121
+ # end
122
+ # A bitfield of size 1 bit defines 2 methods:
123
+ # * +#field?+ which returns a Boolean,
124
+ # * +#field=+ which takes and returns a Boolean.
125
+ # A bitfield of more bits defines 2 methods:
126
+ # * +#field+ which returns an Integer,
127
+ # * +#field=+ which takes and returns an Integer.
128
+ # @param [Symbol] attr attribute name (attribute should be a {Types::Int}
129
+ # subclass)
130
+ # @param [Array] args list of bitfield names. Name may be followed
131
+ # by bitfield size. If no size is given, 1 bit is assumed.
132
+ # @return [void]
133
+ def self.define_bit_fields_on(attr, *args)
134
+ attr_def = @field_defs[attr]
135
+ raise ArgumentError, "unknown #{attr} field" if attr_def.nil?
136
+ type = attr_def.first
137
+ unless type < Types::Int
138
+ raise TypeError, "#{attr} is not a PacketGen::Types::Int"
139
+ end
140
+ total_size = type.new.width * 8
141
+ idx = total_size - 1
142
+
143
+ field = args.shift
144
+ while field
145
+ next unless field.is_a? Symbol
146
+ size = if args.first.is_a? Integer
147
+ args.shift
148
+ else
149
+ 1
150
+ end
151
+ unless field == :_
152
+ shift = idx - (size - 1)
153
+ field_mask = (2**size - 1) << shift
154
+ clear_mask = (2**total_size - 1) & (~field_mask & (2**total_size - 1))
155
+
156
+ if size == 1
157
+ class_eval <<-EOM
158
+ def #{field}?
159
+ val = (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
160
+ val != 0
161
+ end
162
+ def #{field}=(v)
163
+ val = v ? 1 : 0
164
+ self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
165
+ self[:#{attr}].value |= val << #{shift}
166
+ end
167
+ EOM
168
+ else
169
+ class_eval <<-EOM
170
+ def #{field}
171
+ (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
172
+ end
173
+ def #{field}=(v)
174
+ self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
175
+ self[:#{attr}].value |= (v & #{2**size - 1}) << #{shift}
176
+ end
177
+ EOM
178
+ end
179
+
180
+ @bit_fields << field
181
+ end
182
+
183
+ idx -= size
184
+ field = args.shift
185
+ end
186
+ end
187
+
188
+ # Create a new header object
189
+ # @param [Hash] options Keys are symbols. They should have name of object
190
+ # attributes, as defined by {.define_field} and by {.define_bit_field}.
191
+ def initialize(options={})
192
+ @fields = {}
193
+ self.class.class_eval { @field_defs }.each do |field, ary|
194
+ default = ary[1].is_a?(Proc) ? ary[1].call : ary[1]
195
+ @fields[field] = if ary[2]
196
+ ary[2].call(self)
197
+ elsif !ary[3].empty?
198
+ ary[0].new(ary[3])
199
+ else
200
+ ary[0].new
201
+ end
202
+
203
+ value = options[field] || default
204
+ if ary[0] < Types::Int
205
+ @fields[field].read(value)
206
+ elsif ary[0] <= Types::String
207
+ @fields[field].read(value)
208
+ else
209
+ @fields[field].from_human(value) if @fields[field].respond_to? :from_human
210
+ end
211
+ end
212
+ self.class.class_eval { @bit_fields }.each do |bit_field|
213
+ self.send "#{bit_field}=", options[bit_field] if options[bit_field]
214
+ end
215
+ end
216
+
217
+ # Get field object
218
+ # @param [Symbol] field
219
+ # @return [Object]
220
+ def [](field)
221
+ @fields[field]
222
+ end
223
+
224
+ # Set field object
225
+ # @param [Symbol] field
226
+ # @param [Object] obj
227
+ # @return [Object]
228
+ def []=(field, obj)
229
+ @fields[field] = obj
230
+ end
231
+
232
+ # Get all field names
233
+ # @return [Array<Symbol>]
234
+ def fields
235
+ @ordered_fields ||= self.class.class_eval { @ordered_fields }
236
+ end
237
+
238
+ # Return header protocol name
239
+ # @return [String]
240
+ def protocol_name
241
+ self.class.to_s.sub(/.*::/, '')
242
+ end
243
+
244
+ # Populate object from a binary string
245
+ # @param [String] str
246
+ # @return [Fields] self
247
+ def read(str)
248
+ return self if str.nil?
249
+ force_binary str
250
+ start = 0
251
+ fields.each do |field|
252
+ if self[field].respond_to? :width
253
+ width = self[field].width
254
+ self[field].read str[start, width]
255
+ start += width
256
+ elsif self[field].respond_to? :sz
257
+ self[field].read str[start..-1]
258
+ size = self[field].sz
259
+ start += size
260
+ else
261
+ self[field].read str[start..-1]
262
+ start = str.size
263
+ end
264
+ end
265
+
266
+ self
267
+ end
268
+
269
+ # Common inspect method for headers
270
+ # @return [String]
271
+ def inspect
272
+ str = Inspect.dashed_line(self.class, 2)
273
+ @fields.each do |attr, value|
274
+ next if attr == :body
275
+ str << Inspect.inspect_attribute(attr, value, 2)
276
+ end
277
+ str
278
+ end
279
+
280
+ # Return object as a binary string
281
+ # @return [String]
282
+ def to_s
283
+ fields.map { |f| force_binary @fields[f].to_s }.join
284
+ end
285
+
286
+ # Size of object as binary strinf
287
+ # @return [nteger]
288
+ def sz
289
+ to_s.size
290
+ end
291
+
292
+ # Return object as a hash
293
+ # @return [Hash] keys: attributes, values: attribute values
294
+ def to_h
295
+ Hash[fields.map { |f| [f, @fields[f]] }]
296
+ end
297
+
298
+ # Used to set body as value of body object.
299
+ # @param [String,Int,Fields,nil] value
300
+ # @return [void]
301
+ # @raise [BodyError] no body on given object
302
+ # @raise [ArgumentError] cannot cram +body+ in +:body+ field
303
+ def body=(value)
304
+ raise BodyError, 'no body field' unless @fields.has_key? :body
305
+ case body
306
+ when ::String
307
+ self[:body].read value
308
+ when Int, Fields
309
+ self[:body] = value
310
+ when NilClass
311
+ self[:body] = Types::String.new.read('')
312
+ else
313
+ raise ArgumentError, "Can't cram a #{body.class} in a :body field"
314
+ end
315
+ end
316
+
317
+ # Force str to binary encoding
318
+ # @param [String] str
319
+ # @return [String]
320
+ def force_binary(str)
321
+ PacketGen.force_binary(str)
322
+ end
323
+ end
324
+ end
325
+ end