packetgen 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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