packetfu 1.1.10 → 1.1.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/.gitignore +3 -0
  5. data/.travis.yml +8 -0
  6. data/CONTRIBUTING.md +47 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +1 -1
  9. data/README.rdoc +35 -30
  10. data/Rakefile +4 -4
  11. data/bench/octets.rb +9 -9
  12. data/examples/100kpackets.rb +12 -12
  13. data/examples/ackscan.rb +16 -16
  14. data/examples/arp.rb +35 -35
  15. data/examples/arphood.rb +36 -36
  16. data/examples/dissect_thinger.rb +6 -6
  17. data/examples/new-simple-stats.rb +23 -23
  18. data/examples/packetfu-shell.rb +25 -25
  19. data/examples/simple-sniffer.rb +9 -9
  20. data/examples/simple-stats.rb +23 -23
  21. data/examples/slammer.rb +3 -3
  22. data/gem-public_cert.pem +21 -0
  23. data/lib/packetfu.rb +149 -127
  24. data/lib/packetfu/capture.rb +169 -169
  25. data/lib/packetfu/config.rb +52 -52
  26. data/lib/packetfu/inject.rb +56 -56
  27. data/lib/packetfu/packet.rb +531 -528
  28. data/lib/packetfu/pcap.rb +579 -579
  29. data/lib/packetfu/protos/arp.rb +90 -90
  30. data/lib/packetfu/protos/arp/header.rb +158 -158
  31. data/lib/packetfu/protos/arp/mixin.rb +36 -36
  32. data/lib/packetfu/protos/eth.rb +44 -44
  33. data/lib/packetfu/protos/eth/header.rb +243 -243
  34. data/lib/packetfu/protos/eth/mixin.rb +3 -3
  35. data/lib/packetfu/protos/hsrp.rb +69 -69
  36. data/lib/packetfu/protos/hsrp/header.rb +107 -107
  37. data/lib/packetfu/protos/hsrp/mixin.rb +29 -29
  38. data/lib/packetfu/protos/icmp.rb +71 -71
  39. data/lib/packetfu/protos/icmp/header.rb +82 -82
  40. data/lib/packetfu/protos/icmp/mixin.rb +14 -14
  41. data/lib/packetfu/protos/invalid.rb +49 -49
  42. data/lib/packetfu/protos/ip.rb +69 -69
  43. data/lib/packetfu/protos/ip/header.rb +291 -291
  44. data/lib/packetfu/protos/ip/mixin.rb +40 -40
  45. data/lib/packetfu/protos/ipv6.rb +50 -50
  46. data/lib/packetfu/protos/ipv6/header.rb +188 -188
  47. data/lib/packetfu/protos/ipv6/mixin.rb +29 -29
  48. data/lib/packetfu/protos/tcp.rb +176 -176
  49. data/lib/packetfu/protos/tcp/ecn.rb +35 -35
  50. data/lib/packetfu/protos/tcp/flags.rb +74 -74
  51. data/lib/packetfu/protos/tcp/header.rb +268 -268
  52. data/lib/packetfu/protos/tcp/hlen.rb +32 -32
  53. data/lib/packetfu/protos/tcp/mixin.rb +46 -46
  54. data/lib/packetfu/protos/tcp/option.rb +321 -321
  55. data/lib/packetfu/protos/tcp/options.rb +95 -95
  56. data/lib/packetfu/protos/tcp/reserved.rb +35 -35
  57. data/lib/packetfu/protos/udp.rb +159 -123
  58. data/lib/packetfu/protos/udp/header.rb +91 -91
  59. data/lib/packetfu/protos/udp/mixin.rb +3 -3
  60. data/lib/packetfu/structfu.rb +280 -280
  61. data/lib/packetfu/utils.rb +292 -225
  62. data/lib/packetfu/version.rb +41 -41
  63. data/packetfu.gemspec +14 -3
  64. data/spec/arp_spec.rb +191 -0
  65. data/spec/eth_spec.rb +148 -0
  66. data/spec/icmp_spec.rb +97 -0
  67. data/spec/ip_spec.rb +78 -0
  68. data/spec/ipv6_spec.rb +81 -0
  69. data/spec/packet_spec.rb +61 -59
  70. data/spec/packet_subclasses_spec.rb +9 -10
  71. data/spec/packetfu_spec.rb +55 -62
  72. data/spec/sample3.pcap +0 -0
  73. data/spec/spec_helper.rb +44 -0
  74. data/spec/structfu_spec.rb +270 -271
  75. data/spec/tcp_spec.rb +76 -77
  76. data/spec/udp_spec.rb +32 -0
  77. data/spec/utils_spec.rb +95 -0
  78. data/test/all_tests.rb +14 -17
  79. data/test/func_lldp.rb +3 -3
  80. data/test/ptest.rb +2 -2
  81. data/test/test_capture.rb +45 -45
  82. data/test/test_eth.rb +70 -68
  83. data/test/test_hsrp.rb +9 -9
  84. data/test/test_inject.rb +18 -18
  85. data/test/test_invalid.rb +16 -16
  86. data/test/test_octets.rb +23 -21
  87. data/test/test_packet.rb +156 -154
  88. data/test/test_pcap.rb +172 -170
  89. data/test/test_structfu.rb +99 -97
  90. data/test/test_tcp.rb +322 -320
  91. data/test/test_udp.rb +78 -76
  92. metadata +108 -44
  93. metadata.gz.sig +2 -0
  94. data/spec/ethpacket_spec.rb +0 -74
  95. data/test/test_arp.rb +0 -135
  96. data/test/test_icmp.rb +0 -62
  97. data/test/test_ip.rb +0 -50
  98. data/test/test_ip6.rb +0 -68
@@ -3,607 +3,607 @@
3
3
 
4
4
  module StructFu
5
5
 
6
- # Set the endianness for the various Int classes. Takes either :little or :big.
7
- def set_endianness(e=nil)
8
- unless [:little, :big].include? e
9
- raise ArgumentError, "Unknown endianness for #{self.class}"
10
- end
11
- @int32 = e == :little ? Int32le : Int32be
12
- @int16 = e == :little ? Int16le : Int16be
13
- return e
14
- end
15
-
16
- # Instead of returning the "size" of the object, which is usually the
17
- # number of elements of the Struct, returns the size of the object after
18
- # a to_s. Essentially, a short version of self.to_size.size
19
- def sz
20
- self.to_s.size
21
- end
6
+ # Set the endianness for the various Int classes. Takes either :little or :big.
7
+ def set_endianness(e=nil)
8
+ unless [:little, :big].include? e
9
+ raise ArgumentError, "Unknown endianness for #{self.class}"
10
+ end
11
+ @int32 = e == :little ? Int32le : Int32be
12
+ @int16 = e == :little ? Int16le : Int16be
13
+ return e
14
+ end
15
+
16
+ # Instead of returning the "size" of the object, which is usually the
17
+ # number of elements of the Struct, returns the size of the object after
18
+ # a to_s. Essentially, a short version of self.to_size.size
19
+ def sz
20
+ self.to_s.size
21
+ end
22
22
 
23
23
  end
24
24
 
25
25
  module PacketFu
26
26
 
27
- # PcapHeader represents the header portion of a libpcap file (the packets
28
- # themselves are in the PcapPackets array). See
29
- # http://wiki.wireshark.org/Development/LibpcapFileFormat for details.
30
- #
31
- # Depending on the endianness (set with :endian), elements are either
32
- # :little endian or :big endian.
33
- #
34
- # ==== PcapHeader Definition
35
- #
36
- # Symbol :endian Default: :little
37
- # Int32 :magic Default: 0xa1b2c3d4 # :big is 0xd4c3b2a1
38
- # Int16 :ver_major Default: 2
39
- # Int16 :ver_minor Default: 4
40
- # Int32 :thiszone
41
- # Int32 :sigfigs
42
- # Int32 :snaplen Default: 0xffff
43
- # Int32 :network Default: 1
44
- class PcapHeader < Struct.new(:endian, :magic, :ver_major, :ver_minor,
45
- :thiszone, :sigfigs, :snaplen, :network)
46
- include StructFu
47
-
48
- MAGIC_INT32 = 0xa1b2c3d4
49
- MAGIC_LITTLE = [MAGIC_INT32].pack("V")
50
- MAGIC_BIG = [MAGIC_INT32].pack("N")
51
-
52
- def initialize(args={})
53
- set_endianness(args[:endian] ||= :little)
54
- init_fields(args)
55
- super(args[:endian], args[:magic], args[:ver_major],
56
- args[:ver_minor], args[:thiszone], args[:sigfigs],
57
- args[:snaplen], args[:network])
58
- end
59
-
60
- # Called by initialize to set the initial fields.
61
- def init_fields(args={})
62
- args[:magic] = @int32.new(args[:magic] || PcapHeader::MAGIC_INT32)
63
- args[:ver_major] = @int16.new(args[:ver_major] || 2)
64
- args[:ver_minor] ||= @int16.new(args[:ver_minor] || 4)
65
- args[:thiszone] ||= @int32.new(args[:thiszone])
66
- args[:sigfigs] ||= @int32.new(args[:sigfigs])
67
- args[:snaplen] ||= @int32.new(args[:snaplen] || 0xffff)
68
- args[:network] ||= @int32.new(args[:network] || 1)
69
- return args
70
- end
71
-
72
- # Returns the object in string form.
73
- def to_s
74
- self.to_a[1,7].map {|x| x.to_s}.join
75
- end
76
-
77
- # Reads a string to populate the object.
78
- # TODO: Need to test this by getting a hold of a big endian pcap file.
79
- # Conversion from big to little shouldn't be that big of a deal.
80
- def read(str)
81
- force_binary(str)
82
- return self if str.nil?
83
- str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
84
- if str[0,4] == self[:magic].to_s
85
- self[:magic].read str[0,4]
86
- self[:ver_major].read str[4,2]
87
- self[:ver_minor].read str[6,2]
88
- self[:thiszone].read str[8,4]
89
- self[:sigfigs].read str[12,4]
90
- self[:snaplen].read str[16,4]
91
- self[:network].read str[20,4]
92
- else
93
- raise "Incorrect magic for libpcap"
94
- end
95
- self
96
- end
97
-
98
- end
99
-
100
- # The Timestamp class defines how Timestamps appear in libpcap files.
101
- #
102
- # ==== Header Definition
103
- #
104
- # Symbol :endian Default: :little
105
- # Int32 :sec
106
- # Int32 :usec
107
- class Timestamp < Struct.new(:endian, :sec, :usec)
108
- include StructFu
109
-
110
- def initialize(args={})
111
- set_endianness(args[:endian] ||= :little)
112
- init_fields(args)
113
- super(args[:endian], args[:sec], args[:usec])
114
- end
115
-
116
- # Called by initialize to set the initial fields.
117
- def init_fields(args={})
118
- args[:sec] = @int32.new(args[:sec])
119
- args[:usec] = @int32.new(args[:usec])
120
- return args
121
- end
122
-
123
- # Returns the object in string form.
124
- def to_s
125
- self.to_a[1,2].map {|x| x.to_s}.join
126
- end
127
-
128
- # Reads a string to populate the object.
129
- def read(str)
130
- force_binary(str)
131
- return self if str.nil?
132
- self[:sec].read str[0,4]
133
- self[:usec].read str[4,4]
134
- self
135
- end
136
-
137
- end
138
-
139
- # PcapPacket defines how individual packets are stored in a libpcap-formatted
140
- # file.
141
- #
142
- # ==== Header Definition
143
- #
144
- # Timestamp :timestamp
145
- # Int32 :incl_len
146
- # Int32 :orig_len
147
- # String :data
148
- class PcapPacket < Struct.new(:endian, :timestamp, :incl_len,
149
- :orig_len, :data)
150
- include StructFu
151
- def initialize(args={})
152
- set_endianness(args[:endian] ||= :little)
153
- init_fields(args)
154
- super(args[:endian], args[:timestamp], args[:incl_len],
155
- args[:orig_len], args[:data])
156
- end
157
-
158
- # Called by initialize to set the initial fields.
159
- def init_fields(args={})
160
- args[:timestamp] = Timestamp.new(:endian => args[:endian]).read(args[:timestamp])
161
- args[:incl_len] = args[:incl_len].nil? ? @int32.new(args[:data].to_s.size) : @int32.new(args[:incl_len])
162
- args[:orig_len] = @int32.new(args[:orig_len])
163
- args[:data] = StructFu::String.new.read(args[:data])
164
- end
165
-
166
- # Returns the object in string form.
167
- def to_s
168
- self.to_a[1,4].map {|x| x.to_s}.join
169
- end
170
-
171
- # Reads a string to populate the object.
172
- def read(str)
173
- return unless str
174
- force_binary(str)
175
- self[:timestamp].read str[0,8]
176
- self[:incl_len].read str[8,4]
177
- self[:orig_len].read str[12,4]
178
- self[:data].read str[16,self[:incl_len].to_i]
179
- self
180
- end
181
-
182
- end
183
-
184
- # PcapPackets is a collection of PcapPacket objects.
185
- class PcapPackets < Array
186
-
187
- include StructFu
188
-
189
- attr_accessor :endian # probably ought to be read-only but who am i.
190
-
191
- def initialize(args={})
192
- @endian = args[:endian] || :little
193
- end
194
-
195
- def force_binary(str)
196
- str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
197
- end
198
-
199
- # Reads a string to populate the object. Note, this read takes in the
200
- # whole pcap file, since we need to see the magic to know what
201
- # endianness we're dealing with.
202
- def read(str)
203
- force_binary(str)
204
- return self if str.nil?
205
- if str[0,4] == PcapHeader::MAGIC_BIG
206
- @endian = :big
207
- elsif str[0,4] == PcapHeader::MAGIC_LITTLE
208
- @endian = :little
209
- else
210
- raise ArgumentError, "Unknown file format for #{self.class}"
211
- end
212
- body = str[24,str.size]
213
- while body.size > 16 # TODO: catch exceptions on malformed packets at end
214
- p = PcapPacket.new(:endian => @endian)
215
- p.read(body)
216
- self<<p
217
- body = body[p.sz,body.size]
218
- end
219
- self
220
- end
221
-
222
- def to_s
223
- self.join
224
- end
225
-
226
- end
227
-
228
- # PcapFile is a complete libpcap file struct, made up of two elements, a
229
- # PcapHeader and PcapPackets.
230
- #
231
- # See http://wiki.wireshark.org/Development/LibpcapFileFormat
232
- #
233
- # PcapFile also can behave as a singleton class, which is usually the better
234
- # way to handle pcap files of really any size, since it doesn't require
235
- # storing packets before handing them off to a given block. This is really
236
- # the way to go.
237
- class PcapFile < Struct.new(:endian, :head, :body)
238
- include StructFu
239
-
240
- class << self
241
-
242
- # Takes a given file and returns an array of the packet bytes. Here
243
- # for backwards compatibilty.
244
- def file_to_array(fname)
245
- PcapFile.new.file_to_array(:f => fname)
246
- end
247
-
248
- # Takes a given file name, and reads out the packets. If given a block,
249
- # it will yield back a PcapPacket object per packet found.
250
- def read(fname,&block)
251
- file_header = PcapHeader.new
252
- pcap_packets = PcapPackets.new
253
- unless File.readable? fname
254
- raise ArgumentError, "Cannot read file `#{fname}'"
255
- end
256
- begin
257
- file_handle = File.open(fname, "rb")
258
- file_header.read file_handle.read(24)
259
- packet_count = 0
260
- pcap_packet = PcapPacket.new(:endian => file_header.endian)
261
- while pcap_packet.read file_handle.read(16) do
262
- len = pcap_packet.incl_len
263
- pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i))
264
- packet_count += 1
265
- if pcap_packet.data.size < len.to_i
266
- warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
267
- break
268
- end
269
- if block
270
- yield pcap_packet
271
- else
272
- pcap_packets << pcap_packet.clone
273
- end
274
- end
275
- ensure
276
- file_handle.close
277
- end
278
- block ? packet_count : pcap_packets
279
- end
280
-
281
- # Takes a filename, and an optional block. If a block is given,
282
- # yield back the raw packet data from the given file. Otherwise,
283
- # return an array of parsed packets.
284
- def read_packet_bytes(fname,&block)
285
- count = 0
286
- packets = [] unless block
287
- read(fname) do |packet|
288
- if block
289
- count += 1
290
- yield packet.data.to_s
291
- else
292
- packets << packet.data.to_s
293
- end
294
- end
295
- block ? count : packets
296
- end
297
-
298
- alias :file_to_array :read_packet_bytes
299
-
300
- # Takes a filename, and an optional block. If a block is given,
301
- # yield back parsed packets from the given file. Otherwise, return
302
- # an array of parsed packets.
303
- #
304
- # This is a brazillian times faster than the old methods of extracting
305
- # packets from files.
306
- def read_packets(fname,&block)
307
- count = 0
308
- packets = [] unless block
309
- read_packet_bytes(fname) do |packet|
310
- if block
311
- count += 1
312
- yield Packet.parse(packet)
313
- else
314
- packets << Packet.parse(packet)
315
- end
316
- end
317
- block ? count : packets
318
- end
319
-
320
- end
321
-
322
- def initialize(args={})
323
- init_fields(args)
324
- @filename = args.delete :filename
325
- super(args[:endian], args[:head], args[:body])
326
- end
327
-
328
- # Called by initialize to set the initial fields.
329
- def init_fields(args={})
330
- args[:head] = PcapHeader.new(:endian => args[:endian]).read(args[:head])
331
- args[:body] = PcapPackets.new(:endian => args[:endian]).read(args[:body])
332
- return args
333
- end
334
-
335
- # Returns the object in string form.
336
- def to_s
337
- self[:head].to_s + self[:body].map {|p| p.to_s}.join
338
- end
339
-
340
- # Clears the contents of the PcapFile.
341
- def clear
342
- self[:body].clear
343
- end
344
-
345
- # Reads a string to populate the object. Note that this appends new packets to
346
- # any existing packets in the PcapFile.
347
- def read(str)
348
- force_binary(str)
349
- self[:head].read str[0,24]
350
- self[:body].read str
351
- self
352
- end
353
-
354
- # Clears the contents of the PcapFile prior to reading in a new string.
355
- def read!(str)
356
- clear
357
- force_binary(str)
358
- self.read str
359
- end
360
-
361
- # A shorthand method for opening a file and reading in the packets. Note
362
- # that readfile clears any existing packets, since that seems to be the
363
- # typical use.
364
- def readfile(file)
365
- fdata = File.open(file, "rb") {|f| f.read}
366
- self.read! fdata
367
- end
368
-
369
- # Calls the class method with this object's @filename
370
- def read_packet_bytes(fname=@filename,&block)
371
- raise ArgumentError, "Need a file" unless fname
372
- return self.class.read_packet_bytes(fname, &block)
373
- end
374
-
375
- # Calls the class method with this object's @filename
376
- def read_packets(fname=@filename,&block)
377
- raise ArgumentError, "Need a file" unless fname
378
- return self.class.read_packets(fname, &block)
379
- end
380
-
381
- # file_to_array() translates a libpcap file into an array of packets.
382
- # Note that this strips out pcap timestamps -- if you'd like to retain
383
- # timestamps and other libpcap file information, you will want to
384
- # use read() instead.
385
- def file_to_array(args={})
386
- filename = args[:filename] || args[:file] || args[:f]
387
- if filename
388
- self.read! File.open(filename, "rb") {|f| f.read}
389
- end
390
- if args[:keep_timestamps] || args[:keep_ts] || args[:ts]
391
- self[:body].map {|x| {x.timestamp.to_s => x.data.to_s} }
392
- else
393
- self[:body].map {|x| x.data.to_s}
394
- end
395
- end
396
-
397
- alias_method :f2a, :file_to_array
398
-
399
- # Takes an array of packets (as generated by file_to_array), and writes them
400
- # to a file. Valid arguments are:
401
- #
402
- # :filename
403
- # :array # Can either be an array of packet data, or a hash-value pair of timestamp => data.
404
- # :timestamp # Sets an initial timestamp
405
- # :ts_inc # Sets the increment between timestamps. Defaults to 1 second.
406
- # :append # If true, then the packets are appended to the end of a file.
407
- def array_to_file(args={})
408
- if args.kind_of? Hash
409
- filename = args[:filename] || args[:file] || args[:f]
410
- arr = args[:array] || args[:arr] || args[:a]
411
- ts = args[:timestamp] || args[:ts] || Time.now.to_i
412
- ts_inc = args[:timestamp_increment] || args[:ts_inc] || 1
413
- append = !!args[:append]
414
- elsif args.kind_of? Array
415
- arr = args
416
- filename = append = nil
417
- else
418
- raise ArgumentError, "Unknown argument. Need either a Hash or Array."
419
- end
420
- unless arr.kind_of? Array
421
- raise ArgumentError, "Need an array to read packets from"
422
- end
423
- arr.each_with_index do |p,i|
424
- if p.kind_of? Hash # Binary timestamps are included
425
- this_ts = p.keys.first
426
- this_incl_len = p.values.first.size
427
- this_orig_len = this_incl_len
428
- this_data = p.values.first
429
- else # it's an array
430
- this_ts = Timestamp.new(:endian => self[:endian], :sec => ts + (ts_inc * i)).to_s
431
- this_incl_len = p.to_s.size
432
- this_orig_len = this_incl_len
433
- this_data = p.to_s
434
- end
435
- this_pkt = PcapPacket.new({:endian => self[:endian],
436
- :timestamp => this_ts,
437
- :incl_len => this_incl_len,
438
- :orig_len => this_orig_len,
439
- :data => this_data }
440
- )
441
- self[:body] << this_pkt
442
- end
443
- if filename
444
- self.to_f(:filename => filename, :append => append)
445
- else
446
- self
447
- end
448
- end
449
-
450
- alias_method :a2f, :array_to_file
451
-
452
- # Just like array_to_file, but clears any existing packets from the array first.
453
- def array_to_file!(arr)
454
- clear
455
- array_to_file(arr)
456
- end
457
-
458
- alias_method :a2f!, :array_to_file!
459
-
460
- # Writes the PcapFile to a file. Takes the following arguments:
461
- #
462
- # :filename # The file to write to.
463
- # :append # If set to true, the packets are appended to the file, rather than overwriting.
464
- def to_file(args={})
465
- filename = args[:filename] || args[:file] || args[:f]
466
- unless (!filename.nil? || filename.kind_of?(String))
467
- raise ArgumentError, "Need a :filename for #{self.class}"
468
- end
469
- append = args[:append]
470
- if append
471
- if File.exists? filename
472
- File.open(filename,'ab') {|file| file.write(self.body.to_s)}
473
- else
474
- File.open(filename,'wb') {|file| file.write(self.to_s)}
475
- end
476
- else
477
- File.open(filename,'wb') {|file| file.write(self.to_s)}
478
- end
479
- [filename, self.body.sz, self.body.size]
480
- end
481
-
482
- alias_method :to_f, :to_file
483
-
484
- # Shorthand method for writing to a file. Can take either :file => 'name.pcap' or
485
- # simply 'name.pcap'
486
- def write(filename='out.pcap')
487
- if filename.kind_of?(Hash)
488
- f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
489
- else
490
- f = filename.to_s
491
- end
492
- self.to_file(:filename => f.to_s, :append => false)
493
- end
494
-
495
- # Shorthand method for appending to a file. Can take either :file => 'name.pcap' or
496
- # simply 'name.pcap'
497
- def append(filename='out.pcap')
498
- if filename.kind_of?(Hash)
499
- f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
500
- else
501
- f = filename.to_s
502
- end
503
- self.to_file(:filename => f, :append => true)
504
- end
505
-
506
- end
27
+ # PcapHeader represents the header portion of a libpcap file (the packets
28
+ # themselves are in the PcapPackets array). See
29
+ # http://wiki.wireshark.org/Development/LibpcapFileFormat for details.
30
+ #
31
+ # Depending on the endianness (set with :endian), elements are either
32
+ # :little endian or :big endian.
33
+ #
34
+ # ==== PcapHeader Definition
35
+ #
36
+ # Symbol :endian Default: :little
37
+ # Int32 :magic Default: 0xa1b2c3d4 # :big is 0xd4c3b2a1
38
+ # Int16 :ver_major Default: 2
39
+ # Int16 :ver_minor Default: 4
40
+ # Int32 :thiszone
41
+ # Int32 :sigfigs
42
+ # Int32 :snaplen Default: 0xffff
43
+ # Int32 :network Default: 1
44
+ class PcapHeader < Struct.new(:endian, :magic, :ver_major, :ver_minor,
45
+ :thiszone, :sigfigs, :snaplen, :network)
46
+ include StructFu
47
+
48
+ MAGIC_INT32 = 0xa1b2c3d4
49
+ MAGIC_LITTLE = [MAGIC_INT32].pack("V")
50
+ MAGIC_BIG = [MAGIC_INT32].pack("N")
51
+
52
+ def initialize(args={})
53
+ set_endianness(args[:endian] ||= :little)
54
+ init_fields(args)
55
+ super(args[:endian], args[:magic], args[:ver_major],
56
+ args[:ver_minor], args[:thiszone], args[:sigfigs],
57
+ args[:snaplen], args[:network])
58
+ end
59
+
60
+ # Called by initialize to set the initial fields.
61
+ def init_fields(args={})
62
+ args[:magic] = @int32.new(args[:magic] || PcapHeader::MAGIC_INT32)
63
+ args[:ver_major] = @int16.new(args[:ver_major] || 2)
64
+ args[:ver_minor] ||= @int16.new(args[:ver_minor] || 4)
65
+ args[:thiszone] ||= @int32.new(args[:thiszone])
66
+ args[:sigfigs] ||= @int32.new(args[:sigfigs])
67
+ args[:snaplen] ||= @int32.new(args[:snaplen] || 0xffff)
68
+ args[:network] ||= @int32.new(args[:network] || 1)
69
+ return args
70
+ end
71
+
72
+ # Returns the object in string form.
73
+ def to_s
74
+ self.to_a[1,7].map {|x| x.to_s}.join
75
+ end
76
+
77
+ # Reads a string to populate the object.
78
+ # TODO: Need to test this by getting a hold of a big endian pcap file.
79
+ # Conversion from big to little shouldn't be that big of a deal.
80
+ def read(str)
81
+ force_binary(str)
82
+ return self if str.nil?
83
+ str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
84
+ if str[0,4] == self[:magic].to_s
85
+ self[:magic].read str[0,4]
86
+ self[:ver_major].read str[4,2]
87
+ self[:ver_minor].read str[6,2]
88
+ self[:thiszone].read str[8,4]
89
+ self[:sigfigs].read str[12,4]
90
+ self[:snaplen].read str[16,4]
91
+ self[:network].read str[20,4]
92
+ else
93
+ raise "Incorrect magic for libpcap"
94
+ end
95
+ self
96
+ end
97
+
98
+ end
99
+
100
+ # The Timestamp class defines how Timestamps appear in libpcap files.
101
+ #
102
+ # ==== Header Definition
103
+ #
104
+ # Symbol :endian Default: :little
105
+ # Int32 :sec
106
+ # Int32 :usec
107
+ class Timestamp < Struct.new(:endian, :sec, :usec)
108
+ include StructFu
109
+
110
+ def initialize(args={})
111
+ set_endianness(args[:endian] ||= :little)
112
+ init_fields(args)
113
+ super(args[:endian], args[:sec], args[:usec])
114
+ end
115
+
116
+ # Called by initialize to set the initial fields.
117
+ def init_fields(args={})
118
+ args[:sec] = @int32.new(args[:sec])
119
+ args[:usec] = @int32.new(args[:usec])
120
+ return args
121
+ end
122
+
123
+ # Returns the object in string form.
124
+ def to_s
125
+ self.to_a[1,2].map {|x| x.to_s}.join
126
+ end
127
+
128
+ # Reads a string to populate the object.
129
+ def read(str)
130
+ force_binary(str)
131
+ return self if str.nil?
132
+ self[:sec].read str[0,4]
133
+ self[:usec].read str[4,4]
134
+ self
135
+ end
136
+
137
+ end
138
+
139
+ # PcapPacket defines how individual packets are stored in a libpcap-formatted
140
+ # file.
141
+ #
142
+ # ==== Header Definition
143
+ #
144
+ # Timestamp :timestamp
145
+ # Int32 :incl_len
146
+ # Int32 :orig_len
147
+ # String :data
148
+ class PcapPacket < Struct.new(:endian, :timestamp, :incl_len,
149
+ :orig_len, :data)
150
+ include StructFu
151
+ def initialize(args={})
152
+ set_endianness(args[:endian] ||= :little)
153
+ init_fields(args)
154
+ super(args[:endian], args[:timestamp], args[:incl_len],
155
+ args[:orig_len], args[:data])
156
+ end
157
+
158
+ # Called by initialize to set the initial fields.
159
+ def init_fields(args={})
160
+ args[:timestamp] = Timestamp.new(:endian => args[:endian]).read(args[:timestamp])
161
+ args[:incl_len] = args[:incl_len].nil? ? @int32.new(args[:data].to_s.size) : @int32.new(args[:incl_len])
162
+ args[:orig_len] = @int32.new(args[:orig_len])
163
+ args[:data] = StructFu::String.new.read(args[:data])
164
+ end
165
+
166
+ # Returns the object in string form.
167
+ def to_s
168
+ self.to_a[1,4].map {|x| x.to_s}.join
169
+ end
170
+
171
+ # Reads a string to populate the object.
172
+ def read(str)
173
+ return unless str
174
+ force_binary(str)
175
+ self[:timestamp].read str[0,8]
176
+ self[:incl_len].read str[8,4]
177
+ self[:orig_len].read str[12,4]
178
+ self[:data].read str[16,self[:incl_len].to_i]
179
+ self
180
+ end
181
+
182
+ end
183
+
184
+ # PcapPackets is a collection of PcapPacket objects.
185
+ class PcapPackets < Array
186
+
187
+ include StructFu
188
+
189
+ attr_accessor :endian # probably ought to be read-only but who am i.
190
+
191
+ def initialize(args={})
192
+ @endian = args[:endian] || :little
193
+ end
194
+
195
+ def force_binary(str)
196
+ str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
197
+ end
198
+
199
+ # Reads a string to populate the object. Note, this read takes in the
200
+ # whole pcap file, since we need to see the magic to know what
201
+ # endianness we're dealing with.
202
+ def read(str)
203
+ force_binary(str)
204
+ return self if str.nil?
205
+ if str[0,4] == PcapHeader::MAGIC_BIG
206
+ @endian = :big
207
+ elsif str[0,4] == PcapHeader::MAGIC_LITTLE
208
+ @endian = :little
209
+ else
210
+ raise ArgumentError, "Unknown file format for #{self.class}"
211
+ end
212
+ body = str[24,str.size]
213
+ while body.size > 16 # TODO: catch exceptions on malformed packets at end
214
+ p = PcapPacket.new(:endian => @endian)
215
+ p.read(body)
216
+ self<<p
217
+ body = body[p.sz,body.size]
218
+ end
219
+ self
220
+ end
221
+
222
+ def to_s
223
+ self.join
224
+ end
225
+
226
+ end
227
+
228
+ # PcapFile is a complete libpcap file struct, made up of two elements, a
229
+ # PcapHeader and PcapPackets.
230
+ #
231
+ # See http://wiki.wireshark.org/Development/LibpcapFileFormat
232
+ #
233
+ # PcapFile also can behave as a singleton class, which is usually the better
234
+ # way to handle pcap files of really any size, since it doesn't require
235
+ # storing packets before handing them off to a given block. This is really
236
+ # the way to go.
237
+ class PcapFile < Struct.new(:endian, :head, :body)
238
+ include StructFu
239
+
240
+ class << self
241
+
242
+ # Takes a given file and returns an array of the packet bytes. Here
243
+ # for backwards compatibilty.
244
+ def file_to_array(fname)
245
+ PcapFile.new.file_to_array(:f => fname)
246
+ end
247
+
248
+ # Takes a given file name, and reads out the packets. If given a block,
249
+ # it will yield back a PcapPacket object per packet found.
250
+ def read(fname,&block)
251
+ file_header = PcapHeader.new
252
+ pcap_packets = PcapPackets.new
253
+ unless File.readable? fname
254
+ raise ArgumentError, "Cannot read file `#{fname}'"
255
+ end
256
+ begin
257
+ file_handle = File.open(fname, "rb")
258
+ file_header.read file_handle.read(24)
259
+ packet_count = 0
260
+ pcap_packet = PcapPacket.new(:endian => file_header.endian)
261
+ while pcap_packet.read file_handle.read(16) do
262
+ len = pcap_packet.incl_len
263
+ pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i))
264
+ packet_count += 1
265
+ if pcap_packet.data.size < len.to_i
266
+ warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
267
+ break
268
+ end
269
+ if block
270
+ yield pcap_packet
271
+ else
272
+ pcap_packets << pcap_packet.clone
273
+ end
274
+ end
275
+ ensure
276
+ file_handle.close
277
+ end
278
+ block ? packet_count : pcap_packets
279
+ end
280
+
281
+ # Takes a filename, and an optional block. If a block is given,
282
+ # yield back the raw packet data from the given file. Otherwise,
283
+ # return an array of parsed packets.
284
+ def read_packet_bytes(fname,&block)
285
+ count = 0
286
+ packets = [] unless block
287
+ read(fname) do |packet|
288
+ if block
289
+ count += 1
290
+ yield packet.data.to_s
291
+ else
292
+ packets << packet.data.to_s
293
+ end
294
+ end
295
+ block ? count : packets
296
+ end
297
+
298
+ alias :file_to_array :read_packet_bytes
299
+
300
+ # Takes a filename, and an optional block. If a block is given,
301
+ # yield back parsed packets from the given file. Otherwise, return
302
+ # an array of parsed packets.
303
+ #
304
+ # This is a brazillian times faster than the old methods of extracting
305
+ # packets from files.
306
+ def read_packets(fname,&block)
307
+ count = 0
308
+ packets = [] unless block
309
+ read_packet_bytes(fname) do |packet|
310
+ if block
311
+ count += 1
312
+ yield Packet.parse(packet)
313
+ else
314
+ packets << Packet.parse(packet)
315
+ end
316
+ end
317
+ block ? count : packets
318
+ end
319
+
320
+ end
321
+
322
+ def initialize(args={})
323
+ init_fields(args)
324
+ @filename = args.delete :filename
325
+ super(args[:endian], args[:head], args[:body])
326
+ end
327
+
328
+ # Called by initialize to set the initial fields.
329
+ def init_fields(args={})
330
+ args[:head] = PcapHeader.new(:endian => args[:endian]).read(args[:head])
331
+ args[:body] = PcapPackets.new(:endian => args[:endian]).read(args[:body])
332
+ return args
333
+ end
334
+
335
+ # Returns the object in string form.
336
+ def to_s
337
+ self[:head].to_s + self[:body].map {|p| p.to_s}.join
338
+ end
339
+
340
+ # Clears the contents of the PcapFile.
341
+ def clear
342
+ self[:body].clear
343
+ end
344
+
345
+ # Reads a string to populate the object. Note that this appends new packets to
346
+ # any existing packets in the PcapFile.
347
+ def read(str)
348
+ force_binary(str)
349
+ self[:head].read str[0,24]
350
+ self[:body].read str
351
+ self
352
+ end
353
+
354
+ # Clears the contents of the PcapFile prior to reading in a new string.
355
+ def read!(str)
356
+ clear
357
+ force_binary(str)
358
+ self.read str
359
+ end
360
+
361
+ # A shorthand method for opening a file and reading in the packets. Note
362
+ # that readfile clears any existing packets, since that seems to be the
363
+ # typical use.
364
+ def readfile(file)
365
+ fdata = File.open(file, "rb") {|f| f.read}
366
+ self.read! fdata
367
+ end
368
+
369
+ # Calls the class method with this object's @filename
370
+ def read_packet_bytes(fname=@filename,&block)
371
+ raise ArgumentError, "Need a file" unless fname
372
+ return self.class.read_packet_bytes(fname, &block)
373
+ end
374
+
375
+ # Calls the class method with this object's @filename
376
+ def read_packets(fname=@filename,&block)
377
+ raise ArgumentError, "Need a file" unless fname
378
+ return self.class.read_packets(fname, &block)
379
+ end
380
+
381
+ # file_to_array() translates a libpcap file into an array of packets.
382
+ # Note that this strips out pcap timestamps -- if you'd like to retain
383
+ # timestamps and other libpcap file information, you will want to
384
+ # use read() instead.
385
+ def file_to_array(args={})
386
+ filename = args[:filename] || args[:file] || args[:f]
387
+ if filename
388
+ self.read! File.open(filename, "rb") {|f| f.read}
389
+ end
390
+ if args[:keep_timestamps] || args[:keep_ts] || args[:ts]
391
+ self[:body].map {|x| {x.timestamp.to_s => x.data.to_s} }
392
+ else
393
+ self[:body].map {|x| x.data.to_s}
394
+ end
395
+ end
396
+
397
+ alias_method :f2a, :file_to_array
398
+
399
+ # Takes an array of packets (as generated by file_to_array), and writes them
400
+ # to a file. Valid arguments are:
401
+ #
402
+ # :filename
403
+ # :array # Can either be an array of packet data, or a hash-value pair of timestamp => data.
404
+ # :timestamp # Sets an initial timestamp
405
+ # :ts_inc # Sets the increment between timestamps. Defaults to 1 second.
406
+ # :append # If true, then the packets are appended to the end of a file.
407
+ def array_to_file(args={})
408
+ if args.kind_of? Hash
409
+ filename = args[:filename] || args[:file] || args[:f]
410
+ arr = args[:array] || args[:arr] || args[:a]
411
+ ts = args[:timestamp] || args[:ts] || Time.now.to_i
412
+ ts_inc = args[:timestamp_increment] || args[:ts_inc] || 1
413
+ append = !!args[:append]
414
+ elsif args.kind_of? Array
415
+ arr = args
416
+ filename = append = nil
417
+ else
418
+ raise ArgumentError, "Unknown argument. Need either a Hash or Array."
419
+ end
420
+ unless arr.kind_of? Array
421
+ raise ArgumentError, "Need an array to read packets from"
422
+ end
423
+ arr.each_with_index do |p,i|
424
+ if p.kind_of? Hash # Binary timestamps are included
425
+ this_ts = p.keys.first
426
+ this_incl_len = p.values.first.size
427
+ this_orig_len = this_incl_len
428
+ this_data = p.values.first
429
+ else # it's an array
430
+ this_ts = Timestamp.new(:endian => self[:endian], :sec => ts + (ts_inc * i)).to_s
431
+ this_incl_len = p.to_s.size
432
+ this_orig_len = this_incl_len
433
+ this_data = p.to_s
434
+ end
435
+ this_pkt = PcapPacket.new({:endian => self[:endian],
436
+ :timestamp => this_ts,
437
+ :incl_len => this_incl_len,
438
+ :orig_len => this_orig_len,
439
+ :data => this_data }
440
+ )
441
+ self[:body] << this_pkt
442
+ end
443
+ if filename
444
+ self.to_f(:filename => filename, :append => append)
445
+ else
446
+ self
447
+ end
448
+ end
449
+
450
+ alias_method :a2f, :array_to_file
451
+
452
+ # Just like array_to_file, but clears any existing packets from the array first.
453
+ def array_to_file!(arr)
454
+ clear
455
+ array_to_file(arr)
456
+ end
457
+
458
+ alias_method :a2f!, :array_to_file!
459
+
460
+ # Writes the PcapFile to a file. Takes the following arguments:
461
+ #
462
+ # :filename # The file to write to.
463
+ # :append # If set to true, the packets are appended to the file, rather than overwriting.
464
+ def to_file(args={})
465
+ filename = args[:filename] || args[:file] || args[:f]
466
+ unless (!filename.nil? || filename.kind_of?(String))
467
+ raise ArgumentError, "Need a :filename for #{self.class}"
468
+ end
469
+ append = args[:append]
470
+ if append
471
+ if File.exists? filename
472
+ File.open(filename,'ab') {|file| file.write(self.body.to_s)}
473
+ else
474
+ File.open(filename,'wb') {|file| file.write(self.to_s)}
475
+ end
476
+ else
477
+ File.open(filename,'wb') {|file| file.write(self.to_s)}
478
+ end
479
+ [filename, self.body.sz, self.body.size]
480
+ end
481
+
482
+ alias_method :to_f, :to_file
483
+
484
+ # Shorthand method for writing to a file. Can take either :file => 'name.pcap' or
485
+ # simply 'name.pcap'
486
+ def write(filename='out.pcap')
487
+ if filename.kind_of?(Hash)
488
+ f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
489
+ else
490
+ f = filename.to_s
491
+ end
492
+ self.to_file(:filename => f.to_s, :append => false)
493
+ end
494
+
495
+ # Shorthand method for appending to a file. Can take either :file => 'name.pcap' or
496
+ # simply 'name.pcap'
497
+ def append(filename='out.pcap')
498
+ if filename.kind_of?(Hash)
499
+ f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
500
+ else
501
+ f = filename.to_s
502
+ end
503
+ self.to_file(:filename => f, :append => true)
504
+ end
505
+
506
+ end
507
507
 
508
508
  end
509
509
 
510
510
  module PacketFu
511
511
 
512
- # Read is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
513
- # in 0.3.0 and beyond. Expect it to go away completely by version 1.0. So, the main use
514
- # of this class is to learn how to do exactly the same things using the PcapFile object.
515
- class Read
512
+ # Read is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
513
+ # in 0.3.0 and beyond. Expect it to go away completely by version 1.0. So, the main use
514
+ # of this class is to learn how to do exactly the same things using the PcapFile object.
515
+ class Read
516
516
 
517
- class << self
517
+ class << self
518
518
 
519
- # Reads the magic string of a pcap file, and determines
520
- # if it's :little or :big endian.
521
- def get_byte_order(pcap_file)
522
- byte_order = ((pcap_file[0,4] == PcapHeader::MAGIC_LITTLE) ? :little : :big)
523
- return byte_order
524
- end
519
+ # Reads the magic string of a pcap file, and determines
520
+ # if it's :little or :big endian.
521
+ def get_byte_order(pcap_file)
522
+ byte_order = ((pcap_file[0,4] == PcapHeader::MAGIC_LITTLE) ? :little : :big)
523
+ return byte_order
524
+ end
525
525
 
526
- # set_byte_order is pretty much totally deprecated.
527
- def set_byte_order(byte_order)
528
- PacketFu.instance_variable_set(:@byte_order,byte_order)
529
- return true
530
- end
526
+ # set_byte_order is pretty much totally deprecated.
527
+ def set_byte_order(byte_order)
528
+ PacketFu.instance_variable_set(:@byte_order,byte_order)
529
+ return true
530
+ end
531
531
 
532
- # A wrapper for PcapFile#file_to_array, but only returns the array. Actually
533
- # using the PcapFile object is going to be more useful.
534
- def file_to_array(args={})
535
- filename = args[:filename] || args[:file] || args[:out]
536
- raise ArgumentError, "Need a :filename in string form to read from." if (filename.nil? || filename.class != String)
537
- PcapFile.new.file_to_array(args)
538
- end
532
+ # A wrapper for PcapFile#file_to_array, but only returns the array. Actually
533
+ # using the PcapFile object is going to be more useful.
534
+ def file_to_array(args={})
535
+ filename = args[:filename] || args[:file] || args[:out]
536
+ raise ArgumentError, "Need a :filename in string form to read from." if (filename.nil? || filename.class != String)
537
+ PcapFile.new.file_to_array(args)
538
+ end
539
539
 
540
- alias_method :f2a, :file_to_array
540
+ alias_method :f2a, :file_to_array
541
541
 
542
- end
542
+ end
543
543
 
544
- end
544
+ end
545
545
 
546
546
  end
547
547
 
548
548
  module PacketFu
549
549
 
550
- # Write is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
551
- # in 0.3.0 and beyond. Expect it to go away completely by version 1.0, as working with
552
- # PacketFu::PcapFile directly is generally going to be more rewarding.
553
- class Write
554
-
555
- class << self
556
-
557
- # format_packets: Pretty much totally deprecated.
558
- def format_packets(args={})
559
- arr = args[:arr] || args[:array] || []
560
- ts = args[:ts] || args[:timestamp] || Time.now.to_i
561
- ts_inc = args[:ts_inc] || args[:timestamp_increment]
562
- pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
563
- :arr => arr,
564
- :ts => ts,
565
- :ts_inc => ts_inc)
566
- pkts.body
567
- end
568
-
569
- # array_to_file is a largely deprecated function for writing arrays of pcaps to a file.
570
- # Use PcapFile#array_to_file instead.
571
- def array_to_file(args={})
572
- filename = args[:filename] || args[:file] || args[:out] || :nowrite
573
- arr = args[:arr] || args[:array] || []
574
- ts = args[:ts] || args[:timestamp] || args[:time_stamp] || Time.now.to_f
575
- ts_inc = args[:ts_inc] || args[:timestamp_increment] || args[:time_stamp_increment]
576
- byte_order = args[:byte_order] || args[:byteorder] || args[:endian] || args[:endianness] || :little
577
- append = args[:append]
578
- Read.set_byte_order(byte_order) if [:big, :little].include? byte_order
579
- pf = PcapFile.new
580
- pf.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
581
- :arr => arr,
582
- :ts => ts,
583
- :ts_inc => ts_inc)
584
- if filename && filename != :nowrite
585
- if append
586
- pf.append(filename)
587
- else
588
- pf.write(filename)
589
- end
590
- return [filename,pf.to_s.size,arr.size,ts,ts_inc]
591
- else
592
- return [nil,pf.to_s.size,arr.size,ts,ts_inc]
593
- end
594
-
595
- end
596
-
597
- alias_method :a2f, :array_to_file
598
-
599
- # Shorthand method for appending to a file. Also shouldn't use.
600
- def append(args={})
601
- array_to_file(args.merge(:append => true))
602
- end
603
-
604
- end
605
-
606
- end
550
+ # Write is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
551
+ # in 0.3.0 and beyond. Expect it to go away completely by version 1.0, as working with
552
+ # PacketFu::PcapFile directly is generally going to be more rewarding.
553
+ class Write
554
+
555
+ class << self
556
+
557
+ # format_packets: Pretty much totally deprecated.
558
+ def format_packets(args={})
559
+ arr = args[:arr] || args[:array] || []
560
+ ts = args[:ts] || args[:timestamp] || Time.now.to_i
561
+ ts_inc = args[:ts_inc] || args[:timestamp_increment]
562
+ pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
563
+ :arr => arr,
564
+ :ts => ts,
565
+ :ts_inc => ts_inc)
566
+ pkts.body
567
+ end
568
+
569
+ # array_to_file is a largely deprecated function for writing arrays of pcaps to a file.
570
+ # Use PcapFile#array_to_file instead.
571
+ def array_to_file(args={})
572
+ filename = args[:filename] || args[:file] || args[:out] || :nowrite
573
+ arr = args[:arr] || args[:array] || []
574
+ ts = args[:ts] || args[:timestamp] || args[:time_stamp] || Time.now.to_f
575
+ ts_inc = args[:ts_inc] || args[:timestamp_increment] || args[:time_stamp_increment]
576
+ byte_order = args[:byte_order] || args[:byteorder] || args[:endian] || args[:endianness] || :little
577
+ append = args[:append]
578
+ Read.set_byte_order(byte_order) if [:big, :little].include? byte_order
579
+ pf = PcapFile.new
580
+ pf.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
581
+ :arr => arr,
582
+ :ts => ts,
583
+ :ts_inc => ts_inc)
584
+ if filename && filename != :nowrite
585
+ if append
586
+ pf.append(filename)
587
+ else
588
+ pf.write(filename)
589
+ end
590
+ return [filename,pf.to_s.size,arr.size,ts,ts_inc]
591
+ else
592
+ return [nil,pf.to_s.size,arr.size,ts,ts_inc]
593
+ end
594
+
595
+ end
596
+
597
+ alias_method :a2f, :array_to_file
598
+
599
+ # Shorthand method for appending to a file. Also shouldn't use.
600
+ def append(args={})
601
+ array_to_file(args.merge(:append => true))
602
+ end
603
+
604
+ end
605
+
606
+ end
607
607
 
608
608
  end
609
609