packetgen 3.1.1 → 3.1.6

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 (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