packetgen 3.1.1 → 3.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/bin/pgconsole +1 -0
  3. data/lib/packetgen.rb +33 -4
  4. data/lib/packetgen/capture.rb +51 -28
  5. data/lib/packetgen/config.rb +17 -11
  6. data/lib/packetgen/deprecation.rb +34 -8
  7. data/lib/packetgen/header.rb +2 -9
  8. data/lib/packetgen/header/arp.rb +2 -2
  9. data/lib/packetgen/header/asn1_base.rb +2 -2
  10. data/lib/packetgen/header/base.rb +70 -74
  11. data/lib/packetgen/header/bootp.rb +3 -3
  12. data/lib/packetgen/header/dhcp.rb +2 -2
  13. data/lib/packetgen/header/dhcp/option.rb +2 -2
  14. data/lib/packetgen/header/dhcp/options.rb +2 -2
  15. data/lib/packetgen/header/dhcpv6.rb +12 -12
  16. data/lib/packetgen/header/dhcpv6/duid.rb +11 -5
  17. data/lib/packetgen/header/dhcpv6/option.rb +8 -16
  18. data/lib/packetgen/header/dhcpv6/options.rb +2 -2
  19. data/lib/packetgen/header/dhcpv6/relay.rb +2 -2
  20. data/lib/packetgen/header/dns.rb +9 -9
  21. data/lib/packetgen/header/dns/name.rb +20 -9
  22. data/lib/packetgen/header/dns/opt.rb +2 -2
  23. data/lib/packetgen/header/dns/option.rb +2 -2
  24. data/lib/packetgen/header/dns/qdsection.rb +3 -3
  25. data/lib/packetgen/header/dns/question.rb +37 -35
  26. data/lib/packetgen/header/dns/rr.rb +3 -3
  27. data/lib/packetgen/header/dns/rrsection.rb +2 -2
  28. data/lib/packetgen/header/dot11.rb +30 -51
  29. data/lib/packetgen/header/dot11/control.rb +5 -5
  30. data/lib/packetgen/header/dot11/data.rb +11 -7
  31. data/lib/packetgen/header/dot11/element.rb +16 -16
  32. data/lib/packetgen/header/dot11/management.rb +2 -2
  33. data/lib/packetgen/header/dot11/sub_mngt.rb +2 -12
  34. data/lib/packetgen/header/dot1q.rb +2 -2
  35. data/lib/packetgen/header/dot1x.rb +7 -20
  36. data/lib/packetgen/header/eap.rb +30 -33
  37. data/lib/packetgen/header/eap/fast.rb +2 -2
  38. data/lib/packetgen/header/eap/md5.rb +2 -2
  39. data/lib/packetgen/header/eap/tls.rb +2 -2
  40. data/lib/packetgen/header/eap/ttls.rb +2 -2
  41. data/lib/packetgen/header/eth.rb +13 -11
  42. data/lib/packetgen/header/gre.rb +2 -2
  43. data/lib/packetgen/header/http.rb +2 -0
  44. data/lib/packetgen/header/http/headers.rb +6 -4
  45. data/lib/packetgen/header/http/request.rb +36 -21
  46. data/lib/packetgen/header/http/response.rb +7 -7
  47. data/lib/packetgen/header/http/verbs.rb +3 -3
  48. data/lib/packetgen/header/icmp.rb +2 -2
  49. data/lib/packetgen/header/icmpv6.rb +2 -2
  50. data/lib/packetgen/header/igmp.rb +4 -4
  51. data/lib/packetgen/header/igmpv3.rb +3 -3
  52. data/lib/packetgen/header/igmpv3/group_record.rb +8 -6
  53. data/lib/packetgen/header/igmpv3/mq.rb +2 -2
  54. data/lib/packetgen/header/igmpv3/mr.rb +2 -2
  55. data/lib/packetgen/header/ip.rb +30 -31
  56. data/lib/packetgen/header/ip/addr.rb +10 -3
  57. data/lib/packetgen/header/ip/option.rb +8 -10
  58. data/lib/packetgen/header/ip/options.rb +3 -5
  59. data/lib/packetgen/header/ipv6.rb +2 -2
  60. data/lib/packetgen/header/ipv6/addr.rb +9 -2
  61. data/lib/packetgen/header/ipv6/extension.rb +2 -2
  62. data/lib/packetgen/header/ipv6/hop_by_hop.rb +3 -3
  63. data/lib/packetgen/header/llc.rb +2 -2
  64. data/lib/packetgen/header/mdns.rb +2 -2
  65. data/lib/packetgen/header/mld.rb +2 -2
  66. data/lib/packetgen/header/mldv2.rb +2 -2
  67. data/lib/packetgen/header/mldv2/mcast_address_record.rb +4 -2
  68. data/lib/packetgen/header/mldv2/mlq.rb +2 -2
  69. data/lib/packetgen/header/mldv2/mlr.rb +2 -2
  70. data/lib/packetgen/header/ospfv2.rb +9 -9
  71. data/lib/packetgen/header/ospfv2/db_description.rb +2 -2
  72. data/lib/packetgen/header/ospfv2/hello.rb +2 -2
  73. data/lib/packetgen/header/ospfv2/ls_ack.rb +2 -2
  74. data/lib/packetgen/header/ospfv2/ls_request.rb +4 -2
  75. data/lib/packetgen/header/ospfv2/ls_update.rb +2 -2
  76. data/lib/packetgen/header/ospfv2/lsa.rb +9 -5
  77. data/lib/packetgen/header/ospfv2/lsa_header.rb +8 -7
  78. data/lib/packetgen/header/ospfv3.rb +2 -2
  79. data/lib/packetgen/header/ospfv3/db_description.rb +2 -2
  80. data/lib/packetgen/header/ospfv3/hello.rb +2 -2
  81. data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +4 -2
  82. data/lib/packetgen/header/ospfv3/ls_ack.rb +2 -2
  83. data/lib/packetgen/header/ospfv3/ls_request.rb +4 -2
  84. data/lib/packetgen/header/ospfv3/ls_update.rb +2 -2
  85. data/lib/packetgen/header/ospfv3/lsa.rb +5 -5
  86. data/lib/packetgen/header/ospfv3/lsa_header.rb +9 -8
  87. data/lib/packetgen/header/snmp.rb +33 -29
  88. data/lib/packetgen/header/tcp.rb +4 -23
  89. data/lib/packetgen/header/tcp/option.rb +11 -11
  90. data/lib/packetgen/header/tcp/options.rb +2 -2
  91. data/lib/packetgen/header/tftp.rb +6 -6
  92. data/lib/packetgen/header/udp.rb +3 -3
  93. data/lib/packetgen/headerable.rb +5 -4
  94. data/lib/packetgen/inject.rb +23 -0
  95. data/lib/packetgen/inspect.rb +23 -20
  96. data/lib/packetgen/packet.rb +96 -53
  97. data/lib/packetgen/pcap.rb +29 -0
  98. data/lib/packetgen/pcapng.rb +13 -13
  99. data/lib/packetgen/pcapng/block.rb +26 -13
  100. data/lib/packetgen/pcapng/epb.rb +25 -22
  101. data/lib/packetgen/pcapng/file.rb +260 -138
  102. data/lib/packetgen/pcapng/idb.rb +36 -38
  103. data/lib/packetgen/pcapng/shb.rb +51 -53
  104. data/lib/packetgen/pcapng/spb.rb +19 -19
  105. data/lib/packetgen/pcapng/unknown_block.rb +5 -13
  106. data/lib/packetgen/pcaprub_wrapper.rb +81 -0
  107. data/lib/packetgen/proto.rb +2 -2
  108. data/lib/packetgen/types.rb +3 -0
  109. data/lib/packetgen/types/abstract_tlv.rb +28 -8
  110. data/lib/packetgen/types/array.rb +22 -15
  111. data/lib/packetgen/types/cstring.rb +40 -16
  112. data/lib/packetgen/types/enum.rb +8 -3
  113. data/lib/packetgen/types/fieldable.rb +65 -0
  114. data/lib/packetgen/types/fields.rb +182 -117
  115. data/lib/packetgen/types/int.rb +18 -6
  116. data/lib/packetgen/types/int_string.rb +10 -2
  117. data/lib/packetgen/types/length_from.rb +20 -12
  118. data/lib/packetgen/types/oui.rb +4 -2
  119. data/lib/packetgen/types/string.rb +46 -8
  120. data/lib/packetgen/types/tlv.rb +4 -2
  121. data/lib/packetgen/utils.rb +4 -4
  122. data/lib/packetgen/utils/arp_spoofer.rb +2 -2
  123. data/lib/packetgen/version.rb +3 -3
  124. metadata +35 -50
  125. data/.gitignore +0 -13
  126. data/.rubocop.yml +0 -28
  127. data/.travis.yml +0 -17
  128. data/Gemfile +0 -4
  129. data/Rakefile +0 -21
  130. data/packetgen.gemspec +0 -36
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of PacketGen
4
+ # See https://github.com/sdaubert/packetgen for more informations
5
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # This program is published under MIT license.
7
+ require_relative 'pcaprub_wrapper'
8
+
9
+ module PacketGen
10
+ # Module to read PCAP files
11
+ # @author Sylvain Daubert
12
+ # @api private
13
+ # @since 3.1.4
14
+ module Pcap
15
+ # Read a PCAP file
16
+ # @param [String] filename
17
+ # @return [Array<Packet>]
18
+ # @author Kent Gruber
19
+ def self.read(filename)
20
+ packets = []
21
+ PCAPRUBWrapper.read_pcap(filename: filename) do |packet|
22
+ next unless (packet = PacketGen.parse(packet.to_s))
23
+
24
+ packets << packet
25
+ end
26
+ packets
27
+ end
28
+ end
29
+ end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  require 'stringio'
9
9
 
10
10
  module PacketGen
@@ -13,13 +13,13 @@ module PacketGen
13
13
  # @author Sylvain Daubert
14
14
  module PcapNG
15
15
  # Section Header Block type number
16
- SHB_TYPE = Types::Int32.new(0x0A0D0D0A, :little)
16
+ SHB_TYPE = Types::Int32.new(0x0A0D0D0A, :little).freeze
17
17
  # Interface Description Block type number
18
- IDB_TYPE = Types::Int32.new(1, :little)
18
+ IDB_TYPE = Types::Int32.new(1, :little).freeze
19
19
  # Simple Packet Block type number
20
- SPB_TYPE = Types::Int32.new(3, :little)
20
+ SPB_TYPE = Types::Int32.new(3, :little).freeze
21
21
  # Enhanced Packet Block type number
22
- EPB_TYPE = Types::Int32.new(6, :little)
22
+ EPB_TYPE = Types::Int32.new(6, :little).freeze
23
23
 
24
24
  # IEEE 802.3 Ethernet (10Mb, 100Mb, 1000Mb, and up)
25
25
  LINKTYPE_ETHERNET = 1
@@ -46,10 +46,10 @@ module PacketGen
46
46
  end
47
47
  end
48
48
 
49
- require_relative 'pcapng/block.rb'
50
- require_relative 'pcapng/unknown_block.rb'
51
- require_relative 'pcapng/shb.rb'
52
- require_relative 'pcapng/idb.rb'
53
- require_relative 'pcapng/epb.rb'
54
- require_relative 'pcapng/spb.rb'
55
- require_relative 'pcapng/file.rb'
49
+ require_relative 'pcapng/block'
50
+ require_relative 'pcapng/unknown_block'
51
+ require_relative 'pcapng/shb'
52
+ require_relative 'pcapng/idb'
53
+ require_relative 'pcapng/epb'
54
+ require_relative 'pcapng/spb'
55
+ require_relative 'pcapng/file'
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  module PacketGen
9
9
  module PcapNG
10
10
  # @abstract Base class for all block types
@@ -34,7 +34,7 @@ module PacketGen
34
34
  # @return [Boolean]
35
35
  # @since 2.7.0
36
36
  def options?
37
- @fields.key?(:options) && @fields[:options].sz > 0
37
+ @fields.key?(:options) && @fields[:options].sz.positive?
38
38
  end
39
39
 
40
40
  # Calculate block length and update :block_len and block_len2 fields
@@ -49,9 +49,9 @@ module PacketGen
49
49
  # @return [void]
50
50
  def pad_field(*fields)
51
51
  fields.each do |field|
52
- unless (@fields[field].sz % 4).zero?
53
- @fields[field] << "\x00" * (4 - (@fields[field].sz % 4))
54
- end
52
+ obj = @fields[field]
53
+ pad_size = (obj.sz % 4).zero? ? 0 : (4 - (obj.sz % 4))
54
+ obj << "\x00" * pad_size
55
55
  end
56
56
  end
57
57
 
@@ -61,10 +61,8 @@ module PacketGen
61
61
  # Must be called by all subclass #initialize method.
62
62
  # @param [:little, :big] endian
63
63
  # @return [:little, :big] returns endian
64
- def set_endianness(endian)
65
- unless %i[little big].include? endian
66
- raise ArgumentError, "unknown endianness for #{self.class}"
67
- end
64
+ def endianness(endian)
65
+ raise ArgumentError, "unknown endianness for #{self.class}" unless %i[little big].include?(endian)
68
66
 
69
67
  @endian = endian
70
68
  @fields.each { |_f, v| v.endian = endian if v.is_a?(Types::Int) }
@@ -72,9 +70,24 @@ module PacketGen
72
70
  end
73
71
 
74
72
  def check_len_coherency
75
- unless self.block_len == self.block_len2
76
- raise InvalidFileError, 'Incoherency in Block length'
77
- end
73
+ raise InvalidFileError, 'Incoherency in Block length' unless self.block_len == self.block_len2
74
+ end
75
+
76
+ def to_io(str_or_io)
77
+ return str_or_io if str_or_io.respond_to? :read
78
+
79
+ StringIO.new(force_binary(str_or_io.to_s))
80
+ end
81
+
82
+ def remove_padding(io, data_len)
83
+ data_pad_len = (4 - (data_len % 4)) % 4
84
+ io.read data_pad_len
85
+ data_pad_len
86
+ end
87
+
88
+ def read_blocklen2_and_check(io)
89
+ self[:block_len2].read io.read(4)
90
+ check_len_coherency
78
91
  end
79
92
  end
80
93
  end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  module PacketGen
9
9
  module PcapNG
10
10
  # {EPB} represents a Enhanced Packet Block (EPB) of a pcapng file.
@@ -73,7 +73,7 @@ module PacketGen
73
73
  # @option options [Integer] :block_len2 block total length
74
74
  def initialize(options={})
75
75
  super
76
- set_endianness(options[:endian] || :little)
76
+ endianness(options[:endian] || :little)
77
77
  recalc_block_len
78
78
  self.type = options[:type] || PcapNG::EPB_TYPE.to_i
79
79
  end
@@ -82,29 +82,16 @@ module PacketGen
82
82
  # @param [::String,IO] str_or_io
83
83
  # @return [self]
84
84
  def read(str_or_io)
85
- io = if str_or_io.respond_to? :read
86
- str_or_io
87
- else
88
- StringIO.new(force_binary(str_or_io.to_s))
89
- end
85
+ io = to_io(str_or_io)
90
86
  return self if io.eof?
91
87
 
92
- self[:type].read io.read(4)
93
- self[:block_len].read io.read(4)
94
- self[:interface_id].read io.read(4)
95
- self[:tsh].read io.read(4)
96
- self[:tsl].read io.read(4)
97
- self[:cap_len].read io.read(4)
98
- self[:orig_len].read io.read(4)
88
+ %i[type block_len interface_id tsh tsl cap_len orig_len].each do |attr|
89
+ self[attr].read io.read(self[attr].sz)
90
+ end
99
91
  self[:data].read io.read(self.cap_len)
100
- data_pad_len = (4 - (self[:cap_len].to_i % 4)) % 4
101
- io.read data_pad_len
102
- options_len = self.block_len - self.cap_len - data_pad_len
103
- options_len -= MIN_SIZE
104
- self[:options].read io.read(options_len)
105
- self[:block_len2].read io.read(4)
92
+ read_options(io)
93
+ read_blocklen2_and_check(io)
106
94
 
107
- check_len_coherency
108
95
  self
109
96
  end
110
97
 
@@ -114,6 +101,16 @@ module PacketGen
114
101
  Time.at((self.tsh << 32 | self.tsl) * ts_resol)
115
102
  end
116
103
 
104
+ # Set timestamp from a Time object
105
+ # @param [Time] time
106
+ # @return [Time] time
107
+ def timestamp=(time)
108
+ tstamp = (time.to_r / ts_resol).to_i
109
+ self.tsh = (tstamp & 0xffffffff00000000) >> 32
110
+ self.tsl = tstamp & 0xffffffff
111
+ time
112
+ end
113
+
117
114
  # Return the object as a String
118
115
  # @return [String]
119
116
  def to_s
@@ -131,6 +128,12 @@ module PacketGen
131
128
  @interface.ts_resol
132
129
  end
133
130
  end
131
+
132
+ def read_options(io)
133
+ data_pad_len = remove_padding(io, self.cap_len)
134
+ options_len = self.block_len - self.cap_len - data_pad_len - MIN_SIZE
135
+ self[:options].read io.read(options_len)
136
+ end
134
137
  end
135
138
  end
136
139
  end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  module PacketGen
9
9
  module PcapNG
10
10
  # PcapNG::File is a complete Pcap-NG file handler.
@@ -20,6 +20,15 @@ module PacketGen
20
20
  LINKTYPE_IPV6 => 'IPv6'
21
21
  }.freeze
22
22
 
23
+ # @private
24
+ BLOCK_TYPES = Hash[
25
+ PcapNG.constants(false).select { |c| c.to_s.include?('_TYPE') }.map do |c|
26
+ type_value = PcapNG.const_get(c).to_i
27
+ klass = PcapNG.const_get(c.to_s[0..-6]) # @todo use delete_suffix('_TYPE') when support for Ruby 2.4 will stop
28
+ [type_value, klass]
29
+ end
30
+ ].freeze
31
+
23
32
  # Get file sections
24
33
  # @return [Array]
25
34
  attr_accessor :sections
@@ -57,24 +66,15 @@ module PacketGen
57
66
  # @return [Integer] return number of yielded blocks (only if a block is given)
58
67
  # @raise [ArgumentError] cannot read +fname+
59
68
  def readfile(fname, &blk)
60
- unless ::File.readable?(fname)
61
- raise ArgumentError, "cannot read file #{fname}"
62
- end
63
-
64
- ::File.open(fname, 'rb') do |f|
65
- parse_section(f) until f.eof?
66
- end
69
+ raise ArgumentError, "cannot read file #{fname}" unless ::File.readable?(fname)
67
70
 
71
+ ::File.open(fname, 'rb') { |f| parse_section(f) until f.eof? }
68
72
  return unless blk
69
73
 
70
74
  count = 0
71
- @sections.each do |section|
72
- section.interfaces.each do |intf|
73
- intf.packets.each do |pkt|
74
- count += 1
75
- yield pkt
76
- end
77
- end
75
+ each_packet_with_interface do |pkt, _itf|
76
+ count += 1
77
+ yield pkt
78
78
  end
79
79
  count
80
80
  end
@@ -91,12 +91,10 @@ module PacketGen
91
91
  # @return [Integer] number of packets
92
92
  # @raise [ArgumentError] cannot read +fname+
93
93
  def read_packet_bytes(fname, &blk)
94
- count = 0
95
94
  packets = [] unless blk
96
95
 
97
- readfile(fname) do |packet|
96
+ count = readfile(fname) do |packet|
98
97
  if blk
99
- count += 1
100
98
  yield packet.data.to_s, packet.interface.link_type
101
99
  else
102
100
  packets << packet.data.to_s
@@ -117,19 +115,11 @@ module PacketGen
117
115
  # @return [Integer] number of packets
118
116
  # @raise [ArgumentError] cannot read +fname+
119
117
  def read_packets(fname, &blk)
120
- count = 0
121
118
  packets = [] unless blk
122
119
 
123
- read_packet_bytes(fname) do |packet, link_type|
124
- first_header = KNOWN_LINK_TYPES[link_type]
125
- parsed_pkt = if first_header.nil?
126
- # unknown link type, try to guess
127
- Packet.parse(packet)
128
- else
129
- Packet.parse(packet, first_header: first_header)
130
- end
120
+ count = read_packet_bytes(fname) do |packet, link_type|
121
+ parsed_pkt = parse_packet(packet, link_type)
131
122
  if blk
132
- count += 1
133
123
  yield parsed_pkt
134
124
  else
135
125
  packets << parsed_pkt
@@ -151,47 +141,70 @@ module PacketGen
151
141
  @sections.clear
152
142
  end
153
143
 
144
+ # @deprecated
145
+ # Prefer use of {#to_a} or {#to_h}.
154
146
  # Translates a {File} into an array of packets.
155
147
  # @param [Hash] options
156
- # @option options [String] :filename if given, object is cleared and filename
148
+ # @option options [String] :file if given, object is cleared and filename
157
149
  # is analyzed before generating array. Else, array is generated from +self+
158
- # @option options [String] :file same as +:filename+
159
150
  # @option options [Boolean] :keep_timestamps if +true+ (default value: +false+),
160
151
  # generates an array of hashes, each one with timestamp as key and packet
161
152
  # as value. There is one hash per packet.
162
- # @option options [Boolean] :keep_ts same as +:keep_timestamp+
163
153
  # @return [Array<Packet>,Array<Hash>]
164
154
  def file_to_array(options={})
165
- filename = options[:filename] || options[:file]
166
- if filename
167
- clear
168
- readfile filename
155
+ Deprecation.deprecated(self.class, __method__)
156
+
157
+ file = options[:file] || options[:filename]
158
+ reread file
159
+
160
+ ary = []
161
+ blk = if options[:keep_timestamps] || options[:keep_ts]
162
+ proc { |pkt| { pkt.timestamp => pkt.data.to_s } }
163
+ else
164
+ proc { |pkt| pkt.data.to_s }
165
+ end
166
+ each_packet_with_interface do |pkt, _itf|
167
+ ary << blk.call(pkt)
169
168
  end
170
169
 
170
+ ary
171
+ end
172
+
173
+ # Translates a {File} into an array of packets.
174
+ # @return [Array<Packet>]
175
+ # @since 3.1.6
176
+ def to_a
171
177
  ary = []
172
- @sections.each do |section|
173
- section.interfaces.each do |itf|
174
- if options[:keep_timestamps] || options[:keep_ts]
175
- ary.concat(itf.packets.map { |pkt| { pkt.timestamp => pkt.data.to_s } })
176
- else
177
- ary.concat(itf.packets.map { |pkt| pkt.data.to_s })
178
- end
179
- end
178
+ each_packet_with_interface do |pkt, itf|
179
+ ary << parse_packet(pkt.data.to_s, itf.link_type)
180
180
  end
181
+
181
182
  ary
182
183
  end
183
184
 
185
+ # Translates a {File} into a hash with timestamps as keys.
186
+ # @note Only packets from {EPB} sections are extracted, as {SPB} ones do not have timestamp.
187
+ # @return [Hash{Time => Packet}]
188
+ # @since 3.1.6
189
+ def to_h
190
+ hsh = {}
191
+ each_packet_with_interface do |pkt, itf|
192
+ next if pkt.is_a?(SPB)
193
+
194
+ hsh[pkt.timestamp] = parse_packet(pkt.data.to_s, itf.link_type)
195
+ end
196
+
197
+ hsh
198
+ end
199
+
184
200
  # Writes the {File} to a file.
185
201
  # @param [Hash] options
186
202
  # @option options [Boolean] :append (default: +false+) if set to +true+,
187
203
  # the packets are appended to the file, rather than overwriting it
188
204
  # @return [Array] array of 2 elements: filename and size written
205
+ # @todo for 4.0, replace +options+ by +append+ kwarg
189
206
  def to_file(filename, options={})
190
- mode = if options[:append] && ::File.exist?(filename)
191
- 'ab'
192
- else
193
- 'wb'
194
- end
207
+ mode = (options[:append] && ::File.exist?(filename)) ? 'ab' : 'wb'
195
208
  ::File.open(filename, mode) { |f| f.write(self.to_s) }
196
209
  [filename, self.to_s.size]
197
210
  end
@@ -211,6 +224,7 @@ module PacketGen
211
224
  self.to_file(filename.to_s, append: true)
212
225
  end
213
226
 
227
+ # @deprecated Prefer use of {#read_array} or {#read_hash}.
214
228
  # @overload array_to_file(ary)
215
229
  # Update {File} object with packets.
216
230
  # @param [Array] ary as generated by {#file_to_array} or Array of Packet objects.
@@ -219,7 +233,7 @@ module PacketGen
219
233
  # @overload array_to_file(options={})
220
234
  # Update {File} and/or write it to a file
221
235
  # @param [Hash] options
222
- # @option options [String] :filename file written on disk only if given
236
+ # @option options [String] :file file written on disk only if given
223
237
  # @option options [Array] :array can either be an array of packet data,
224
238
  # or a hash-value pair of timestamp => data.
225
239
  # @option options [Time] :timestamp set an initial timestamp
@@ -229,53 +243,13 @@ module PacketGen
229
243
  # the file
230
244
  # @return [Array] see return value from {#to_file}
231
245
  def array_to_file(options={})
232
- case options
233
- when Hash
234
- filename = options[:filename] || options[:file]
235
- ary = options[:array] || options[:arr]
236
- unless ary.is_a? Array
237
- raise ArgumentError, ':array parameter needs to be an array'
238
- end
239
- ts = options[:timestamp] || options[:ts] || Time.now
240
- ts_inc = options[:ts_inc] || 1
241
- append = !options[:append].nil?
242
- when Array
243
- ary = options
244
- ts = Time.now
245
- ts_inc = 1
246
- filename = nil
247
- append = false
248
- else
249
- raise ArgumentError, 'unknown argument. Need either a Hash or Array'
250
- end
246
+ filename, ary, ts, ts_inc, append = array_to_file_options(options)
251
247
 
252
- section = SHB.new
253
- @sections << section
254
- itf = IDB.new(endian: section.endian)
255
- classify_block section, itf
248
+ section = create_new_shb_section
256
249
 
257
- ary.each_with_index do |pkt, i|
258
- case pkt
259
- when Hash
260
- this_ts = pkt.keys.first.to_i
261
- this_cap_len = pkt.values.first.to_s.size
262
- this_data = pkt.values.first.to_s
263
- else
264
- this_ts = (ts + ts_inc * i).to_i
265
- this_cap_len = pkt.to_s.size
266
- this_data = pkt.to_s
267
- end
268
- this_ts = (this_ts / itf.ts_resol).to_i
269
- this_tsh = this_ts >> 32
270
- this_tsl = this_ts & 0xffffffff
271
- this_pkt = EPB.new(endian: section.endian,
272
- interface_id: 0,
273
- tsh: this_tsh,
274
- tsl: this_tsl,
275
- cap_len: this_cap_len,
276
- orig_len: this_cap_len,
277
- data: this_data)
278
- classify_block section, this_pkt
250
+ ary.each do |pkt|
251
+ classify_block(section, epb_from_pkt(pkt, section, ts))
252
+ ts += ts_inc
279
253
  end
280
254
 
281
255
  if filename
@@ -285,60 +259,104 @@ module PacketGen
285
259
  end
286
260
  end
287
261
 
262
+ # Update current object from an array of packets
263
+ # @param [Array<Packet>] packets
264
+ # @param [Time, nil] timestamp initial timestamp, used for first packet
265
+ # @param [Numeric, nil] ts_inc timestamp increment, in seconds, to increment
266
+ # initial timestamp for each packet
267
+ # @return [void]
268
+ # @note if +timestamp+ and/or +ts_inc+ are nil, {SPB} sections are created
269
+ # for each packet, else {EPB} ones are used
270
+ # @since 3.1.6
271
+ def read_array(packets, timestamp: nil, ts_inc: nil)
272
+ ts = timestamp
273
+ section = create_new_shb_section
274
+ packets.each do |pkt|
275
+ block = create_block_from_pkt(pkt, section, ts, ts_inc)
276
+ classify_block(section, block)
277
+ ts = update_ts(ts, ts_inc)
278
+ end
279
+ end
280
+
281
+ # Update current object from a hash of packets and timestamps
282
+ # @param [Hash{Time => Packet}] hsh
283
+ # @return [void]
284
+ # @since 3.1.6
285
+ def read_hash(hsh)
286
+ section = create_new_shb_section
287
+ hsh.each do |ts, pkt|
288
+ block = create_block_from_pkt(pkt, section, ts, 0)
289
+ classify_block(section, block)
290
+ end
291
+ end
292
+
293
+ # @return [String]
294
+ # @since 3.1.6
295
+ def inspect
296
+ str = +''
297
+ sections.each do |section|
298
+ str << section.inspect
299
+ section.interfaces.each do |itf|
300
+ str << itf.inspect
301
+ itf.packets.each { |block| str << block.inspect }
302
+ end
303
+ end
304
+
305
+ str
306
+ end
307
+
288
308
  private
289
309
 
290
310
  # Parse a section. A section is made of at least a SHB. It than may contain
291
- # others blocks, such as IDB, SPB or EPB.
311
+ # others blocks, such as IDB, SPB or EPB.
292
312
  # @param [IO] io
293
313
  # @return [void]
294
314
  def parse_section(io)
295
- shb = SHB.new
296
- type = Types::Int32.new(0, shb.endian).read(io.read(4))
297
- io.seek(-4, IO::SEEK_CUR)
298
- shb = parse(type, io, shb)
315
+ shb = parse_shb(SHB.new, io)
299
316
  raise InvalidFileError, 'no Section header found' unless shb.is_a?(SHB)
300
317
 
301
- if shb.section_len.to_i != 0xffffffffffffffff
302
- # Section length is defined
303
- section = StringIO.new(io.read(shb.section_len.to_i))
304
- until section.eof?
305
- shb = @sections.last
306
- type = Types::Int32.new(0, shb.endian).read(section.read(4))
307
- section.seek(-4, IO::SEEK_CUR)
308
- parse(type, section, shb)
309
- end
310
- else
311
- # section length is undefined
312
- until io.eof?
313
- shb = @sections.last
314
- type = Types::Int32.new(0, shb.endian).read(io.read(4))
315
- io.seek(-4, IO::SEEK_CUR)
316
- parse(type, io, shb)
317
- end
318
+ to_parse = if shb.section_len.to_i != 0xffffffffffffffff
319
+ # Section length is defined
320
+ StringIO.new(io.read(shb.section_len.to_i))
321
+ else
322
+ # section length is undefined
323
+ io
324
+ end
325
+
326
+ until to_parse.eof?
327
+ shb = @sections.last
328
+ parse_shb shb, to_parse
318
329
  end
319
330
  end
320
331
 
332
+ # Parse a SHB
333
+ # @param [SHB] shb SHB to parse
334
+ # @param [IO] io stream from which parse SHB
335
+ # @return [SHB]
336
+ def parse_shb(shb, io)
337
+ type = Types::Int32.new(0, shb.endian).read(io.read(4))
338
+ io.seek(-4, IO::SEEK_CUR)
339
+ parse(type, io, shb)
340
+ end
341
+
321
342
  # Parse a block from its type
322
343
  # @param [Types::Int32] type
323
344
  # @param [IO] io stream from which parse block
324
345
  # @param [SHB] shb header of current section
325
- # @return [void]
346
+ # @return [Block]
326
347
  def parse(type, io, shb)
327
- types = PcapNG.constants(false).select { |c| c.to_s =~ /_TYPE/ }
328
- .map { |c| [PcapNG.const_get(c).to_i, c] }
329
- types = Hash[types]
330
-
331
- if types.key?(type.to_i)
332
- klass = PcapNG.const_get(types[type.to_i].to_s.gsub(/_TYPE/, '').to_sym)
333
- block = klass.new(endian: shb.endian)
334
- else
335
- block = UnknownBlock.new(endian: shb.endian)
336
- end
337
-
348
+ block = guess_block_type(type).new(endian: shb.endian)
338
349
  classify_block shb, block
339
350
  block.read(io)
340
351
  end
341
352
 
353
+ # Guess class to use from type
354
+ # @param [Types::Int] type
355
+ # @return [Block]
356
+ def guess_block_type(type)
357
+ BLOCK_TYPES.key?(type.to_i) ? BLOCK_TYPES[type.to_i] : UnknownBlock
358
+ end
359
+
342
360
  # Classify block from its type
343
361
  # @param [SHB] shb header of current section
344
362
  # @param [Block] block block to classify
@@ -349,18 +367,122 @@ module PacketGen
349
367
  @sections << block
350
368
  when IDB
351
369
  shb << block
352
- block.section = shb
353
- when EPB
354
- shb.interfaces[block.interface_id] << block
355
- block.interface = shb.interfaces[block.interface_id]
356
- when SPB
357
- shb.interfaces[0] << block
358
- block.interface = shb.interfaces[0]
370
+ when SPB, EPB
371
+ ifid = block.is_a?(EPB) ? block.interface_id : 0
372
+ shb.interfaces[ifid] << block
373
+ else
374
+ shb.add_unknown_block(block)
375
+ end
376
+ end
377
+
378
+ def array_to_file_options(options)
379
+ case options
380
+ when Hash
381
+ array_to_file_options_from_hash(options)
382
+ when Array
383
+ [nil, options, Time.now, 1, false]
384
+ else
385
+ raise ArgumentError, 'unknown argument. Need either a Hash or Array'
386
+ end
387
+ end
388
+
389
+ # Extract and check options for #array_to_file
390
+ def array_to_file_options_from_hash(options)
391
+ %i[filename arr ts].each do |deprecated_opt|
392
+ Deprecation.deprecated_option(self.class, :array_to_file, deprecated_opt) if options[deprecated_opt]
393
+ end
394
+
395
+ filename = options[:filename] || options[:file]
396
+ ary = options[:array] || options[:arr]
397
+ raise ArgumentError, ':array parameter needs to be an array' unless ary.is_a? Array
398
+
399
+ ts = options[:timestamp] || options[:ts] || Time.now
400
+ ts_inc = options[:ts_inc] || 1
401
+ append = !options[:append].nil?
402
+
403
+ [filename, ary, ts, ts_inc, append]
404
+ end
405
+
406
+ def create_new_shb_section
407
+ section = SHB.new
408
+ @sections << section
409
+ itf = IDB.new(endian: section.endian)
410
+ classify_block section, itf
411
+
412
+ section
413
+ end
414
+
415
+ # Compute tsh and tsl from ts
416
+ def calc_ts(timeslot, ts_resol)
417
+ this_ts = (timeslot / ts_resol).to_i
418
+
419
+ [this_ts >> 32, this_ts & 0xffffffff]
420
+ end
421
+
422
+ def reread(filename)
423
+ return if filename.nil?
424
+
425
+ clear
426
+ readfile filename
427
+ end
428
+
429
+ def create_block_from_pkt(pkt, section, timestamp, ts_inc)
430
+ if timestamp.nil? || ts_inc.nil?
431
+ spb_from_pkt(pkt, section)
359
432
  else
360
- shb.unknown_blocks << block
361
- block.section = shb
433
+ epb_from_pkt(pkt, section, timestamp)
362
434
  end
363
435
  end
436
+
437
+ def spb_from_pkt(pkt, section)
438
+ pkt_s = pkt.to_s
439
+ size = pkt_s.size
440
+ SPB.new(endian: section.endian,
441
+ block_len: size,
442
+ orig_len: size,
443
+ data: pkt_s)
444
+ end
445
+
446
+ # @todo remove hash case when #array_to_file will be removed
447
+ def epb_from_pkt(pkt, section, timestamp)
448
+ this_ts, this_data = case pkt
449
+ when Hash
450
+ [pkt.keys.first.to_i, pkt.values.first.to_s]
451
+ else
452
+ [timestamp.to_r, pkt.to_s]
453
+ end
454
+ this_cap_len = this_data.size
455
+ this_tsh, this_tsl = calc_ts(this_ts, section.interfaces.last.ts_resol)
456
+ EPB.new(endian: section.endian,
457
+ interface_id: 0,
458
+ tsh: this_tsh,
459
+ tsl: this_tsl,
460
+ cap_len: this_cap_len,
461
+ orig_len: this_cap_len,
462
+ data: this_data)
463
+ end
464
+
465
+ def update_ts(timestamp, ts_inc)
466
+ return nil if timestamp.nil? || ts_inc.nil?
467
+
468
+ timestamp + ts_inc
469
+ end
470
+
471
+ # Iterate over each xPB with its associated interface
472
+ # @return [void]
473
+ # @yieldparam [String] xpb
474
+ # @yieldparam [IDB] itf
475
+ def each_packet_with_interface
476
+ sections.each do |section|
477
+ section.interfaces.each do |itf|
478
+ itf.packets.each { |xpb| yield xpb, itf }
479
+ end
480
+ end
481
+ end
482
+
483
+ def parse_packet(data, link_type)
484
+ Packet.parse(data, first_header: KNOWN_LINK_TYPES[link_type])
485
+ end
364
486
  end
365
487
  end
366
488
  end