packetgen 2.8.7 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -1
  3. data/README.md +5 -4
  4. data/lib/packetgen.rb +6 -12
  5. data/lib/packetgen/capture.rb +43 -39
  6. data/lib/packetgen/config.rb +0 -1
  7. data/lib/packetgen/deprecation.rb +1 -1
  8. data/lib/packetgen/header.rb +9 -9
  9. data/lib/packetgen/header/asn1_base.rb +10 -10
  10. data/lib/packetgen/header/base.rb +42 -101
  11. data/lib/packetgen/header/dhcp/option.rb +5 -11
  12. data/lib/packetgen/header/dhcpv6/duid.rb +2 -0
  13. data/lib/packetgen/header/dhcpv6/option.rb +2 -19
  14. data/lib/packetgen/header/dhcpv6/options.rb +7 -0
  15. data/lib/packetgen/header/dns.rb +5 -23
  16. data/lib/packetgen/header/dns/name.rb +1 -0
  17. data/lib/packetgen/header/dns/qdsection.rb +1 -0
  18. data/lib/packetgen/header/dns/question.rb +3 -7
  19. data/lib/packetgen/header/dns/rr.rb +3 -0
  20. data/lib/packetgen/header/dns/rrsection.rb +1 -0
  21. data/lib/packetgen/header/dot11.rb +1 -17
  22. data/lib/packetgen/header/dot1x.rb +1 -0
  23. data/lib/packetgen/header/eap.rb +4 -7
  24. data/lib/packetgen/header/eth.rb +2 -0
  25. data/lib/packetgen/header/http/headers.rb +3 -0
  26. data/lib/packetgen/header/http/request.rb +5 -4
  27. data/lib/packetgen/header/http/response.rb +5 -4
  28. data/lib/packetgen/header/icmp.rb +6 -0
  29. data/lib/packetgen/header/icmpv6.rb +6 -0
  30. data/lib/packetgen/header/igmpv3/mq.rb +2 -0
  31. data/lib/packetgen/header/ip.rb +32 -30
  32. data/lib/packetgen/header/ip/addr.rb +1 -0
  33. data/lib/packetgen/header/ip/option.rb +23 -20
  34. data/lib/packetgen/header/ip/options.rb +11 -24
  35. data/lib/packetgen/header/ipv6.rb +45 -34
  36. data/lib/packetgen/header/ipv6/addr.rb +2 -0
  37. data/lib/packetgen/header/ipv6/hop_by_hop.rb +7 -31
  38. data/lib/packetgen/header/mdns.rb +1 -0
  39. data/lib/packetgen/header/mldv2/mlq.rb +2 -0
  40. data/lib/packetgen/header/ospfv2/lsa.rb +15 -25
  41. data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +1 -1
  42. data/lib/packetgen/header/ospfv3/lsa.rb +8 -25
  43. data/lib/packetgen/header/snmp.rb +2 -0
  44. data/lib/packetgen/header/tcp.rb +23 -2
  45. data/lib/packetgen/header/tcp/option.rb +51 -52
  46. data/lib/packetgen/header/tcp/options.rb +17 -52
  47. data/lib/packetgen/header/tftp.rb +3 -0
  48. data/lib/packetgen/header/udp.rb +8 -0
  49. data/lib/packetgen/packet.rb +119 -102
  50. data/lib/packetgen/pcapng/block.rb +4 -10
  51. data/lib/packetgen/pcapng/epb.rb +4 -4
  52. data/lib/packetgen/pcapng/file.rb +7 -3
  53. data/lib/packetgen/pcapng/idb.rb +2 -2
  54. data/lib/packetgen/pcapng/shb.rb +3 -3
  55. data/lib/packetgen/pcapng/spb.rb +1 -8
  56. data/lib/packetgen/pcapng/unknown_block.rb +0 -7
  57. data/lib/packetgen/types.rb +1 -0
  58. data/lib/packetgen/types/array.rb +73 -71
  59. data/lib/packetgen/types/cstring.rb +1 -1
  60. data/lib/packetgen/types/enum.rb +3 -3
  61. data/lib/packetgen/types/fields.rb +66 -106
  62. data/lib/packetgen/types/int.rb +9 -5
  63. data/lib/packetgen/types/length_from.rb +45 -0
  64. data/lib/packetgen/types/oui.rb +2 -0
  65. data/lib/packetgen/types/string.rb +10 -16
  66. data/lib/packetgen/types/tlv.rb +7 -15
  67. data/lib/packetgen/utils.rb +8 -8
  68. data/lib/packetgen/utils/arp_spoofer.rb +1 -2
  69. data/lib/packetgen/version.rb +1 -1
  70. metadata +3 -21
  71. data/lib/packetgen/header/crypto.rb +0 -62
  72. data/lib/packetgen/header/esp.rb +0 -413
  73. data/lib/packetgen/header/ike.rb +0 -243
  74. data/lib/packetgen/header/ike/auth.rb +0 -165
  75. data/lib/packetgen/header/ike/cert.rb +0 -76
  76. data/lib/packetgen/header/ike/certreq.rb +0 -66
  77. data/lib/packetgen/header/ike/id.rb +0 -99
  78. data/lib/packetgen/header/ike/ke.rb +0 -79
  79. data/lib/packetgen/header/ike/nonce.rb +0 -40
  80. data/lib/packetgen/header/ike/notify.rb +0 -176
  81. data/lib/packetgen/header/ike/payload.rb +0 -315
  82. data/lib/packetgen/header/ike/sa.rb +0 -561
  83. data/lib/packetgen/header/ike/sk.rb +0 -261
  84. data/lib/packetgen/header/ike/ts.rb +0 -270
  85. data/lib/packetgen/header/ike/vendor_id.rb +0 -39
  86. data/lib/packetgen/header/netbios.rb +0 -20
  87. data/lib/packetgen/header/netbios/datagram.rb +0 -105
  88. data/lib/packetgen/header/netbios/name.rb +0 -67
  89. data/lib/packetgen/header/netbios/session.rb +0 -64
@@ -5,87 +5,52 @@
5
5
 
6
6
  # frozen_string_literal: true
7
7
 
8
+ require_relative 'option'
9
+
8
10
  module PacketGen
9
11
  module Header
10
12
  class TCP
11
13
  # Container for TCP options in {TCP TCP header}.
12
14
  # @author Sylvain Daubert
13
15
  class Options < Types::Array
16
+ set_of Option
17
+
14
18
  # Get {Option} subclasses
15
19
  # @return [Array<Class>]
16
20
  def self.option_classes
17
21
  return @klasses if defined? @klasses
22
+
18
23
  @klasses = []
19
24
  Option.constants.each do |cst|
20
25
  next unless cst.to_s.end_with? '_KIND'
26
+
21
27
  optname = cst.to_s.sub(/_KIND/, '')
22
28
  @klasses[Option.const_get(cst)] = TCP.const_get(optname)
23
29
  end
24
30
  @klasses
25
31
  end
26
32
 
27
- # Read TCP header options from a string
28
- # @param [String] str binary string
29
- # @return [self]
30
- def read(str)
31
- clear
32
- return self if str.nil?
33
- PacketGen.force_binary str
34
-
35
- i = 0
36
- klasses = self.class.option_classes
37
- while i < str.to_s.length
38
- kind = str[i, 1].unpack('C').first
39
- this_option = if klasses[kind].nil?
40
- Option.new
41
- else
42
- klasses[kind].new
43
- end
44
- this_option.read str[i, str.size]
45
- unless this_option.length?
46
- this_option.length = nil
47
- this_option.value = nil
48
- end
49
- self << this_option
50
- i += this_option.sz
51
- end
52
- self
53
- end
54
-
55
- # @deprecated use {#push} or {#<<}
56
- # Add a well-known option
57
- # @param [String] opt option name
58
- # @param [Object] value
59
- # @return [self]
60
- # @raise [ArgumentError] unknown option
61
- def add(opt, value=nil)
62
- Deprecation.deprecated(self.class, __method__, 'push')
63
- option = record_from_hash(opt: opt, value: value)
64
- self << option
65
- self
66
- end
67
-
68
33
  private
69
34
 
70
35
  def record_from_hash(hsh)
71
36
  if hsh.key? :opt
72
37
  klassname = hsh.delete(:opt)
73
- if TCP.const_defined?(klassname)
74
- klass = TCP.const_get(klassname)
75
- unless klass < Option
76
- raise ArgumentError, 'opt should be a TCP::Option subclass'
77
- end
78
- klass.new(hsh)
79
- else
80
- raise ArgumentError, 'opt should be a TCP::Option subclass'
81
- end
38
+ raise ArgumentError, 'opt should be a TCP::Option subclass' unless TCP.const_defined?(klassname)
39
+
40
+ klass = TCP.const_get(klassname)
41
+ raise ArgumentError, 'opt should be a TCP::Option subclass' unless klass < Option
42
+
43
+ klass.new(hsh)
82
44
  else
83
45
  hsh
84
46
  end
85
47
  end
48
+
49
+ def real_type(opt)
50
+ klasses = self.class.option_classes
51
+ klasses[opt.kind].nil? ? OPtion : klasses[opt.kind]
52
+ end
86
53
  end
87
54
  end
88
55
  end
89
56
  end
90
-
91
- require_relative 'option'
@@ -104,9 +104,11 @@ module PacketGen
104
104
  ary.each do |pkt|
105
105
  if server_tid.nil?
106
106
  next unless pkt.is?('UDP') && (pkt.udp.dport == client_tid)
107
+
107
108
  server_tid = pkt.udp.sport
108
109
  else
109
110
  next unless pkt.is?('UDP')
111
+
110
112
  tids = [server_tid, client_tid]
111
113
  ports = [pkt.udp.sport, pkt.udp.dport]
112
114
  next unless (tids - ports).empty?
@@ -132,6 +134,7 @@ module PacketGen
132
134
  # @return [void]
133
135
  def added_to_packet(packet)
134
136
  return if packet.respond_to? :tftp
137
+
135
138
  packet.instance_eval("def tftp(arg=nil); header(#{self.class}, arg); end")
136
139
  end
137
140
 
@@ -7,6 +7,14 @@
7
7
 
8
8
  module PacketGen
9
9
  module Header
10
+ # UDP header ({https://tools.ietf.org/html/rfc768 RFC 768})
11
+ # 0 1 2 3
12
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
13
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
14
+ # | Source Port | Destination Port |
15
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16
+ # | Length | Checksum |
17
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
10
18
  # A UDP header consists of:
11
19
  # * a source port field ({#sport}, {Types::Int16} type),
12
20
  # * a destination port field ({#dport}, +Int16+ type),
@@ -46,15 +46,10 @@ module PacketGen
46
46
  # == Save packets to a file
47
47
  # Packet.write 'file.pcapng', packets
48
48
  #
49
- # @since 2.0.0
50
- #
51
- # Packet accessor has changed. When header class is in a namespace
52
- # (for example Dot11::* header classes), to avoid clashes in names, such
53
- # accessors are named +namespace_class+. For example {Header::Dot11::Data}
54
- # header is now accessed through +Packet#dot11_data+ and nor more +Packet#data+).
55
49
  # @author Sylvain Daubert
56
50
  class Packet
57
- # @return [Array<Header::Base]
51
+ # Get packet headers, ordered as they appear in the packet.
52
+ # @return [Array<Header::Base>]
58
53
  attr_reader :headers
59
54
 
60
55
  # Create a new Packet
@@ -78,19 +73,14 @@ module PacketGen
78
73
  new.parse binary_str, first_header: first_header
79
74
  end
80
75
 
81
- # Capture packets
82
- # @param [Hash] options capture options
83
- # @option options [String] :iface interface on which capture
84
- # packets on. Default: Use default interface lookup.
85
- # @option options [Integer] :max maximum number of packets to capture
86
- # @option options [Integer] :timeout maximum number of seconds before end
87
- # of capture
88
- # @option options [String] :filter bpf filter
89
- # @option options [Boolean] :promiscuous
90
- # @yieldparam [Packet] packet if a block is given, yield each captured packet
76
+ # Capture packets from wire.
77
+ # Same arguments as {Capture#initialize}
78
+ # @see Capture#initialize
79
+ # @yieldparam [Packet,String] packet if a block is given, yield each
80
+ # captured packet (Packet or raw data String, depending on +:parse+ option)
91
81
  # @return [Array<Packet>] captured packet
92
- def self.capture(options={})
93
- capture = Capture.new(options)
82
+ def self.capture(**kwargs)
83
+ capture = Capture.new(kwargs)
94
84
  if block_given?
95
85
  capture.start { |packet| yield packet }
96
86
  else
@@ -105,14 +95,17 @@ module PacketGen
105
95
  # @param [String] filename PcapNG or Pcap file.
106
96
  # @return [Array<Packet>]
107
97
  # @author Sylvain Daubert
108
- # @author Kent Gruber
98
+ # @author Kent Gruber - Pcap format
99
+ # @since 2.0.0 Also read Pcap format.
109
100
  def self.read(filename)
110
101
  PcapNG::File.new.read_packets filename
111
102
  rescue StandardError => e
112
103
  raise ArgumentError, e unless File.extname(filename.downcase) == '.pcap'
104
+
113
105
  packets = []
114
106
  PCAPRUB::Pcap.open_offline(filename).each_packet do |packet|
115
107
  next unless (packet = PacketGen.parse(packet.to_s))
108
+
116
109
  packets << packet
117
110
  end
118
111
  packets
@@ -135,7 +128,7 @@ module PacketGen
135
128
  @headers = []
136
129
  end
137
130
 
138
- # Add a protocol on packet stack
131
+ # Add a protocol header in packet.
139
132
  # @param [String] protocol
140
133
  # @param [Hash] options protocol specific options
141
134
  # @return [self]
@@ -160,24 +153,27 @@ module PacketGen
160
153
  nxt = prev.body
161
154
  header = klass.new(options.merge!(packet: self))
162
155
  add_header header, previous_header: prev
163
- idx = @headers.index(prev) + 1
164
- @headers[idx, 0] = header
156
+ idx = headers.index(prev) + 1
157
+ headers[idx, 0] = header
165
158
  header[:body] = nxt
166
159
  self
167
160
  end
168
161
 
169
- # Check if a protocol header is embedded in packet
162
+ # Check if a protocol header is embedded in packet.
163
+ # pkt = PacketGen.gen('IP').add('UDP')
164
+ # pkt.is?('IP') #=> true
165
+ # pkt.is?('TCP') #=> false
170
166
  # @return [Boolean]
171
167
  # @raise [ArgumentError] unknown protocol
172
168
  def is?(protocol)
173
169
  klass = check_protocol protocol
174
- @headers.any? { |h| h.is_a? klass }
170
+ headers.any? { |h| h.is_a? klass }
175
171
  end
176
172
 
177
173
  # Recalculate all packet checksums
178
174
  # @return [void]
179
175
  def calc_checksum
180
- @headers.reverse_each do |header|
176
+ headers.reverse_each do |header|
181
177
  header.calc_checksum if header.respond_to? :calc_checksum
182
178
  end
183
179
  end
@@ -185,7 +181,7 @@ module PacketGen
185
181
  # Recalculate all packet length fields
186
182
  # @return [void]
187
183
  def calc_length
188
- @headers.each do |header|
184
+ headers.each do |header|
189
185
  header.calc_length if header.respond_to? :calc_length
190
186
  end
191
187
  end
@@ -200,23 +196,23 @@ module PacketGen
200
196
  # Get packet body
201
197
  # @return [Types]
202
198
  def body
203
- @headers.last.body if @headers.last.respond_to? :body
199
+ last_header[:body] if last_header.respond_to? :body
204
200
  end
205
201
 
206
202
  # Set packet body
207
203
  # @param [String] str
208
204
  # @return [void]
209
205
  def body=(str)
210
- @headers.last.body = str
206
+ last_header.body = str
211
207
  end
212
208
 
213
- # Get binary string
209
+ # Get binary string (i.e. binary string sent on or received from network).
214
210
  # @return [String]
215
211
  def to_s
216
- @headers.first.to_s
212
+ first_header.to_s
217
213
  end
218
214
 
219
- # Write a PCapNG file to disk.
215
+ # Write packet to a PCapNG file on disk.
220
216
  # @param [String] filename
221
217
  # @return [Array] see return from {PcapNG::File#to_file}
222
218
  # @see File
@@ -225,27 +221,26 @@ module PacketGen
225
221
  end
226
222
  alias write to_f
227
223
 
228
- # send packet on wire. Use first header +#to_w+ method.
224
+ # Send packet on wire. Use first header +#to_w+ method.
229
225
  # @param [String] iface interface name. Default to first non-loopback interface
230
- # @param [Boolean] calc call {#calc} on packet before sending it
226
+ # @param [Boolean] calc if +true+, call {#calc} on packet before sending it.
231
227
  # @param [Integer] number number of times to send the packets
232
228
  # @param [Integer,Float] interval time, in seconds, between sending 2 packets
233
229
  # @return [void]
234
230
  # @since 2.1.4 add `calc`, `number` and `interval` parameters
235
- def to_w(iface=nil, calc: false, number: 1, interval: 1)
231
+ # @since 3.0.0 +calc+ defaults to +true+
232
+ def to_w(iface=nil, calc: true, number: 1, interval: 1)
236
233
  iface ||= PacketGen.default_iface
237
- if @headers.first.respond_to? :to_w
234
+
235
+ if first_header.respond_to? :to_w
238
236
  self.calc if calc
239
- if number == 1
240
- @headers.first.to_w(iface)
241
- else
242
- number.times do
243
- @headers.first.to_w(iface)
244
- sleep interval
245
- end
237
+
238
+ number.times do
239
+ first_header.to_w(iface)
240
+ sleep interval if number > 1
246
241
  end
247
242
  else
248
- type = @headers.first.protocol_name
243
+ type = first_header.protocol_name
249
244
  raise WireError, "don't known how to send a #{type} packet on wire"
250
245
  end
251
246
  end
@@ -264,22 +259,20 @@ module PacketGen
264
259
  end
265
260
 
266
261
  # Remove headers from +self+
267
- # @param [Array<Header>] headers
262
+ # @param [Array<Header>] hdrs
268
263
  # @return [self] +self+ with some headers removed
269
264
  # @raise [FormatError] any headers not in +self+
270
265
  # @raise [FormatError] removed headers result in an unknown binding
271
266
  # @since 1.1.0
272
- def decapsulate(*headers)
273
- headers.each do |header|
274
- idx = @headers.index(header)
267
+ def decapsulate(*hdrs)
268
+ hdrs.each do |hdr|
269
+ idx = headers.index(hdr)
275
270
  raise FormatError, 'header not in packet!' if idx.nil?
276
271
 
277
- prev_header = idx > 0 ? @headers[idx - 1] : nil
278
- next_header = (idx + 1) < @headers.size ? @headers[idx + 1] : nil
279
- @headers.delete_at(idx)
280
- if prev_header && next_header
281
- add_header(next_header, previous_header: prev_header)
282
- end
272
+ prev_hdr = idx > 0 ? headers[idx - 1] : nil
273
+ next_hdr = (idx + 1) < headers.size ? headers[idx + 1] : nil
274
+ headers.delete_at(idx)
275
+ add_header(next_hdr, previous_header: prev_hdr) if prev_hdr && next_hdr
283
276
  end
284
277
  rescue ArgumentError => ex
285
278
  raise FormatError, ex.message
@@ -291,7 +284,7 @@ module PacketGen
291
284
  # @return [Packet] self
292
285
  # @raise [ArgumentError] +first_header+ is an unknown header
293
286
  def parse(binary_str, first_header: nil)
294
- @headers.clear
287
+ headers.clear
295
288
 
296
289
  if first_header.nil?
297
290
  # No decoding forced for first header. Have to guess it!
@@ -300,18 +293,20 @@ module PacketGen
300
293
  raise ParseError, 'cannot identify first header in string'
301
294
  end
302
295
  end
296
+
303
297
  add first_header
304
- @headers[-1, 1] = @headers.last.read(binary_str)
298
+ headers[-1, 1] = last_header.read(binary_str)
305
299
 
306
300
  # Decode upper headers recursively
307
301
  decode_bottom_up
308
302
  self
309
303
  end
310
304
 
305
+ # Get packet as a pretty formatted string.
311
306
  # @return [String]
312
307
  def inspect
313
308
  str = Inspect.dashed_line(self.class)
314
- @headers.each do |header|
309
+ headers.each do |header|
315
310
  str << header.inspect
316
311
  end
317
312
  str << Inspect.inspect_body(body)
@@ -325,8 +320,9 @@ module PacketGen
325
320
 
326
321
  # Invert all possible fields in packet to create a reply.
327
322
  # @return [self]
323
+ # @since 2.7.0
328
324
  def reply!
329
- @headers.each do |header|
325
+ headers.each do |header|
330
326
  header.reply! if header.respond_to?(:reply!)
331
327
  end
332
328
  self
@@ -335,6 +331,7 @@ module PacketGen
335
331
  # Forge a new packet from current one with all possible fields
336
332
  # inverted. The new packet may be a reply to current one.
337
333
  # @return [Packet]
334
+ # @since 2.7.0
338
335
  def reply
339
336
  pkt = dup
340
337
  pkt.reply!
@@ -345,12 +342,24 @@ module PacketGen
345
342
  # Dup +@headers+ instance variable. Internally used by +#dup+ and +#clone+
346
343
  # @return [void]
347
344
  def initialize_copy(_other)
348
- @headers = @headers.map(&:dup)
349
- @headers.each do |header|
345
+ @headers = headers.map(&:dup)
346
+ headers.each do |header|
350
347
  add_magic_header_method header
351
348
  end
352
349
  end
353
350
 
351
+ # Give first header of packet
352
+ # @return [Header::Base]
353
+ def first_header
354
+ headers.first
355
+ end
356
+
357
+ # Give last header of packet
358
+ # @return [Header::Base]
359
+ def last_header
360
+ headers.last
361
+ end
362
+
354
363
  # @overload header(klass, layer=1)
355
364
  # @param [Class] klass
356
365
  # @param [Integer] layer
@@ -360,17 +369,15 @@ module PacketGen
360
369
  # @raise [ArgumentError] unknown option
361
370
  # @return [Header::Base]
362
371
  def header(klass, arg)
363
- headers = @headers.select { |h| h.is_a? klass }
364
372
  layer = arg.is_a?(Integer) ? arg : 1
365
- header = headers[layer - 1]
366
-
367
- if arg.is_a? Hash
368
- arg.each do |key, value|
369
- unless header.respond_to? "#{key}="
370
- raise ArgumentError, "unknown #{key} attribute for #{klass}"
371
- end
372
- header.send "#{key}=", value
373
+ header = headers.select { |h| h.is_a? klass }[layer - 1]
374
+ return header unless arg.is_a? Hash
375
+
376
+ arg.each do |key, value|
377
+ unless header.respond_to? "#{key}="
378
+ raise ArgumentError, "unknown #{key} attribute for #{klass}"
373
379
  end
380
+ header.send "#{key}=", value
374
381
  end
375
382
 
376
383
  header
@@ -382,6 +389,7 @@ module PacketGen
382
389
  def check_protocol(protocol)
383
390
  klass = Header.get_header_class_by_name(protocol)
384
391
  raise ArgumentError, "unknown #{protocol} protocol" if klass.nil?
392
+
385
393
  klass
386
394
  end
387
395
 
@@ -391,77 +399,86 @@ module PacketGen
391
399
  # @param [Boolean] parsing
392
400
  # @return [void]
393
401
  def add_header(header, previous_header: nil, parsing: false)
394
- prev_header = previous_header || @headers.last
402
+ prev_header = previous_header || last_header
395
403
  if prev_header
396
404
  bindings = prev_header.class.known_headers[header.class]
405
+ bindings = prev_header.class.known_headers[header.class.superclass] if bindings.nil?
397
406
  if bindings.nil?
398
- bindings = prev_header.class.known_headers[header.class.superclass]
399
- if bindings.nil?
400
- msg = "#{prev_header.class} knowns no layer association with #{header.protocol_name}. ".dup
401
- msg << "Try #{prev_header.class}.bind_layer(#{header.class}, "
402
- msg << "#{prev_header.method_name}_proto_field: "
403
- msg << "value_for_#{header.method_name})"
404
- raise ArgumentError, msg
405
- end
407
+ msg = "#{prev_header.class} knowns no layer association with #{header.protocol_name}. ".dup
408
+ msg << "Try #{prev_header.class}.bind_layer(#{header.class}, "
409
+ msg << "#{prev_header.method_name}_proto_field: "
410
+ msg << "value_for_#{header.method_name})"
411
+ raise ArgumentError, msg
406
412
  end
413
+
407
414
  bindings.set(prev_header) if !bindings.empty? && !parsing
408
415
  prev_header[:body] = header
409
416
  end
410
417
  header.packet = self
411
- @headers << header unless previous_header
418
+ headers << header unless previous_header
412
419
 
413
420
  return if respond_to? header.method_name
421
+
414
422
  add_magic_header_method header
415
423
  end
416
424
 
425
+ # Add method to access +header+
426
+ # @param [Header::Base] header
427
+ # @return [void]
417
428
  def add_magic_header_method(header)
418
429
  self.instance_eval "def #{header.method_name}(arg=nil);" \
419
430
  "header(#{header.class}, arg); end"
420
431
  end
421
432
 
433
+ # Try to guess header from +binary_str+
434
+ # @param [String] binary_str
435
+ # @return [String] header/protocol name
422
436
  def guess_first_header(binary_str)
423
437
  first_header = nil
424
438
  Header.all.each do |hklass|
425
439
  hdr = hklass.new(packet: self)
426
440
  # #read may return another object (more specific class)
427
441
  hdr = hdr.read(binary_str)
428
- # First header is found when:
429
- # * for one known header,
442
+ # First header is found when, for one known header,
430
443
  # * +#parse?+ is true
431
444
  # * it exists a known binding with a upper header
432
445
  next unless hdr.parse?
433
- search_header(hdr) do
434
- first_header = hklass.to_s.gsub(/.*::/, '')
435
- end
446
+
447
+ first_header = hklass.to_s.gsub(/.*::/, '') if search_upper_header(hdr)
436
448
  break unless first_header.nil?
437
449
  end
438
450
  first_header
439
451
  end
440
452
 
453
+ # Decode packet bottom up
454
+ # @return [void]
441
455
  def decode_bottom_up
442
- decode_packet_bottom_up = true
443
- while decode_packet_bottom_up
444
- last_known_hdr = @headers.last
445
- break unless last_known_hdr.respond_to? :body
446
- break if last_known_hdr.body.empty?
447
- search_header(last_known_hdr) do |nh|
448
- str = last_known_hdr.body
449
- nheader = nh.new(packet: self)
450
- nheader = nheader.read(str)
451
- next unless nheader.parse?
452
- add_header nheader, parsing: true
453
- end
454
- decode_packet_bottom_up = (@headers.last != last_known_hdr)
456
+ loop do
457
+ last_known_hdr = last_header
458
+ break if !last_known_hdr.respond_to?(:body) || last_known_hdr.body.empty?
459
+
460
+ nh = search_upper_header(last_known_hdr)
461
+ break if nh.nil?
462
+
463
+ nheader = nh.new(packet: self)
464
+ nheader = nheader.read(last_known_hdr.body)
465
+ next unless nheader.parse?
466
+
467
+ add_header nheader, parsing: true
468
+ break if last_header == last_known_hdr
455
469
  end
456
470
  end
457
471
 
458
- def search_header(hdr)
472
+ # Search a upper header for +hdr+
473
+ # @param [Header::Base] hdr
474
+ # @return [void]
475
+ # @yieldparam [Header::Base] found upper header
476
+ def search_upper_header(hdr)
459
477
  hdr.class.known_headers.each do |nh, bindings|
460
- if bindings.check?(hdr)
461
- yield nh
462
- break
463
- end
478
+ return nh if bindings.check?(hdr)
464
479
  end
480
+
481
+ nil
465
482
  end
466
483
  end
467
484
  end