class PacketGen::PcapNG::File

PcapNG::File is a complete Pcap-NG file handler. @author Sylvain Daubert

Constants

BLOCK_TYPES

@private

Known link types

Attributes

sections[RW]

Get file sections @return [Array]

Public Class Methods

new() click to toggle source
# File lib/packetgen/pcapng/file.rb, line 36
def initialize
  @sections = []
end

Public Instance Methods

append(filename='out.pcapng') click to toggle source

Shorthand method for appending to a file. @param [#to_s] filename @return [Array] see return value from {#to_file}

# File lib/packetgen/pcapng/file.rb, line 223
def append(filename='out.pcapng')
  self.to_file(filename.to_s, append: true)
end
array_to_file(options={}) click to toggle source

@deprecated Prefer use of {#read_array} or {#read_hash}. @overload array_to_file(ary)

Update {File} object with packets.
@param [Array] ary as generated by {#file_to_array} or Array of Packet objects.
               Update {File} object without writing file on disk
@return [self]

@overload array_to_file(options={})

Update {File} and/or write it to a file
@param [Hash] options
@option options [String] :file file written on disk only if given
@option options [Array] :array can either be an array of packet data,
                               or a hash-value pair of timestamp => data.
@option options [Time] :timestamp set an initial timestamp
@option options [Integer] :ts_inc set the increment between timestamps.
                                  Defaults to 1
@option options [Boolean] :append if +true+, append packets to the end of
                                  the file
@return [Array] see return value from {#to_file}
# File lib/packetgen/pcapng/file.rb, line 245
def array_to_file(options={})
  filename, ary, ts, ts_inc, append = array_to_file_options(options)

  section = create_new_shb_section

  ary.each do |pkt|
    classify_block(section, epb_from_pkt(pkt, section, ts))
    ts += ts_inc
  end

  if filename
    self.to_f(filename, append: append)
  else
    self
  end
end
clear() click to toggle source

Clear the contents of the Pcapng::File. @return [void]

# File lib/packetgen/pcapng/file.rb, line 140
def clear
  @sections.clear
end
file_to_array(options={}) click to toggle source

@deprecated

Prefer use of {#to_a} or {#to_h}.

Translates a {File} into an array of packets. @param [Hash] options @option options [String] :file if given, object is cleared and filename

is analyzed before generating array. Else, array is generated from +self+

@option options [Boolean] :keep_timestamps if true (default value: false),

generates an array of hashes, each one with timestamp as key and packet
as value. There is one hash per packet.

@return [Array<Packet>,Array<Hash>]

# File lib/packetgen/pcapng/file.rb, line 154
def file_to_array(options={})
  Deprecation.deprecated(self.class, __method__)

  file = options[:file] || options[:filename]
  reread file

  ary = []
  blk = if options[:keep_timestamps] || options[:keep_ts]
          proc { |pkt| { pkt.timestamp => pkt.data.to_s } }
        else
          proc { |pkt| pkt.data.to_s }
        end
  each_packet_with_interface do |pkt, _itf|
    ary << blk.call(pkt)
  end

  ary
end
inspect() click to toggle source

@return [String] @since 3.1.6

# File lib/packetgen/pcapng/file.rb, line 297
def inspect
  str = +''
  sections.each do |section|
    str << section.inspect
    section.interfaces.each do |itf|
      str << itf.inspect
      itf.packets.each { |block| str << block.inspect }
    end
  end

  str
end
read(str) click to toggle source

Read a string to populate the object. Note that this appends new blocks to the Pcapng::File object. @param [String] str @return [self]

# File lib/packetgen/pcapng/file.rb, line 44
def read(str)
  PacketGen.force_binary(str)
  io = StringIO.new(str)
  parse_section(io)
  self
end
read!(str) click to toggle source

Clear the contents of the Pcapng::File prior to reading in a new string. This string should contain a Section Header Block and an Interface Description Block to create a conform pcapng file. @param [String] str @return [self]

# File lib/packetgen/pcapng/file.rb, line 56
def read!(str)
  clear
  read(str)
end
read_array(packets, timestamp: nil, ts_inc: nil) click to toggle source

Update current object from an array of packets @param [Array<Packet>] packets @param [Time, nil] timestamp initial timestamp, used for first packet @param [Numeric, nil] ts_inc timestamp increment, in seconds, to increment

initial timestamp for each packet

@return [self] @note if timestamp and/or ts_inc are nil, {SPB} sections are created

for each packet, else {EPB} ones are used

@since 3.1.6

# File lib/packetgen/pcapng/file.rb, line 271
def read_array(packets, timestamp: nil, ts_inc: nil)
  ts = timestamp
  section = create_new_shb_section
  packets.each do |pkt|
    block = create_block_from_pkt(pkt, section, ts, ts_inc)
    classify_block(section, block)
    ts = update_ts(ts, ts_inc)
  end
  self
end
read_hash(hsh) click to toggle source

Update current object from a hash of packets and timestamps @param [Hash{Time => Packet}] hsh @return [self] @since 3.1.6

# File lib/packetgen/pcapng/file.rb, line 286
def read_hash(hsh)
  section = create_new_shb_section
  hsh.each do |ts, pkt|
    block = create_block_from_pkt(pkt, section, ts, 0)
    classify_block(section, block)
  end
  self
end
read_packet_bytes(fname) { |data.to_s, interface.link_type| ... } click to toggle source

Give an array of raw packets (raw data from packets). If a block is given, yield raw packet data from the given file. @overload read_packet_bytes(fname)

@param [String] fname pcapng file name
@return [Array] array of packet raw data

@overload read_packet_bytes(fname)

@param [String] fname pcapng file name
@yieldparam [String] raw packet raw data
@yieldparam [Integer] interface's link_type from which packet was captured
@return [Integer] number of packets

@raise [ArgumentError] cannot read fname

# File lib/packetgen/pcapng/file.rb, line 93
def read_packet_bytes(fname, &blk)
  packets = [] unless blk

  count = readfile(fname) do |packet|
    if blk
      yield packet.data.to_s, packet.interface.link_type
    else
      packets << packet.data.to_s
    end
  end

  blk ? count : packets
end
read_packets(fname) { |parsed_pkt| ... } click to toggle source

Return an array of parsed packets. If a block is given, yield parsed packets from the given file. @overload read_packets(fname)

@param [String] fname pcapng file name
@return [Array<Packet>]

@overload read_packets(fname)

@param [String] fname pcapng file name
@yieldparam [Packet] packet
@return [Integer] number of packets

@raise [ArgumentError] cannot read fname

# File lib/packetgen/pcapng/file.rb, line 117
def read_packets(fname, &blk)
  packets = [] unless blk

  count = read_packet_bytes(fname) do |packet, link_type|
    parsed_pkt = parse_packet(packet, link_type)
    if blk
      yield parsed_pkt
    else
      packets << parsed_pkt
    end
  end

  blk ? count : packets
end
readfile(fname) { |pkt| ... } click to toggle source

Read a given file and analyze it. If given a block, it will yield PcapNG::EPB or PcapNG::SPB objects. This is the only way to get packet timestamps. @param [String] fname pcapng file name @yieldparam [EPB,SPB] block @return [Integer] return number of yielded blocks (only if a block is given) @raise [ArgumentError] cannot read fname

# File lib/packetgen/pcapng/file.rb, line 68
def readfile(fname, &blk)
  raise ArgumentError, "cannot read file #{fname}" unless ::File.readable?(fname)

  ::File.open(fname, 'rb') { |f| parse_section(f) until f.eof? }
  return unless blk

  count = 0
  each_packet_with_interface do |pkt, _itf|
    count += 1
    yield pkt
  end
  count
end
to_a() click to toggle source

Translates a {File} into an array of packets. @return [Array<Packet>] @since 3.1.6

# File lib/packetgen/pcapng/file.rb, line 176
def to_a
  ary = []
  each_packet_with_interface do |pkt, itf|
    ary << parse_packet(pkt.data.to_s, itf.link_type)
  end

  ary
end
to_f(filename, options={})
Alias for: to_file
to_file(filename, options={}) click to toggle source

Writes the {File} to a file. @param [Hash] options @option options [Boolean] :append (default: false) if set to true,

the packets are appended to the file, rather than overwriting it

@return [Array] array of 2 elements: filename and size written @todo for 4.0, replace options by append kwarg

# File lib/packetgen/pcapng/file.rb, line 206
def to_file(filename, options={})
  mode = (options[:append] && ::File.exist?(filename)) ? 'ab' : 'wb'
  ::File.open(filename, mode) { |f| f.write(self.to_s) }
  [filename, self.to_s.size]
end
Also aliased as: to_f
to_h() click to toggle source

Translates a {File} into a hash with timestamps as keys. @note Only packets from {EPB} sections are extracted, as {SPB} ones do not have timestamp. @return [Hash{Time => Packet}] @since 3.1.6

# File lib/packetgen/pcapng/file.rb, line 189
def to_h
  hsh = {}
  each_packet_with_interface do |pkt, itf|
    next if pkt.is_a?(SPB)

    hsh[pkt.timestamp] = parse_packet(pkt.data.to_s, itf.link_type)
  end

  hsh
end
to_s() click to toggle source

Return the object as a String @return [String]

# File lib/packetgen/pcapng/file.rb, line 134
def to_s
  @sections.map(&:to_s).join
end
write(filename='out.pcapng') click to toggle source

Shorthand method for writing to a file. @param [#to_s] filename @return [Array] see return value from {#to_file}

# File lib/packetgen/pcapng/file.rb, line 216
def write(filename='out.pcapng')
  self.to_file(filename.to_s, append: false)
end

Private Instance Methods

array_to_file_options(options) click to toggle source
# File lib/packetgen/pcapng/file.rb, line 380
def array_to_file_options(options)
  case options
  when Hash
    array_to_file_options_from_hash(options)
  when Array
    [nil, options, Time.now, 1, false]
  else
    raise ArgumentError, 'unknown argument. Need either a Hash or Array'
  end
end
array_to_file_options_from_hash(options) click to toggle source

Extract and check options for array_to_file

# File lib/packetgen/pcapng/file.rb, line 392
def array_to_file_options_from_hash(options)
  %i[filename arr ts].each do |deprecated_opt|
    Deprecation.deprecated_option(self.class, :array_to_file, deprecated_opt) if options[deprecated_opt]
  end

  filename = options[:filename] || options[:file]
  ary = options[:array] || options[:arr]
  raise ArgumentError, ':array parameter needs to be an array' unless ary.is_a? Array

  ts = options[:timestamp] || options[:ts] || Time.now
  ts_inc = options[:ts_inc] || 1
  append = !options[:append].nil?

  [filename, ary, ts, ts_inc, append]
end
calc_ts(timeslot, ts_resol) click to toggle source

Compute tsh and tsl from ts

# File lib/packetgen/pcapng/file.rb, line 418
def calc_ts(timeslot, ts_resol)
  this_ts = (timeslot / ts_resol).to_i

  [this_ts >> 32, this_ts & 0xffffffff]
end
classify_block(shb, block) click to toggle source

Classify block from its type @param [SHB] shb header of current section @param [Block] block block to classify @return [void]

# File lib/packetgen/pcapng/file.rb, line 366
def classify_block(shb, block)
  case block
  when SHB
    @sections << block
  when IDB
    shb << block
  when SPB, EPB
    ifid = block.is_a?(EPB) ? block.interface_id : 0
    shb.interfaces[ifid] << block
  else
    shb.add_unknown_block(block)
  end
end
create_block_from_pkt(pkt, section, timestamp, ts_inc) click to toggle source
# File lib/packetgen/pcapng/file.rb, line 431
def create_block_from_pkt(pkt, section, timestamp, ts_inc)
  if timestamp.nil? || ts_inc.nil?
    spb_from_pkt(pkt, section)
  else
    epb_from_pkt(pkt, section, timestamp)
  end
end
create_new_shb_section() click to toggle source
# File lib/packetgen/pcapng/file.rb, line 408
def create_new_shb_section
  section = SHB.new
  @sections << section
  itf = IDB.new(endian: section.endian)
  classify_block section, itf

  section
end
each_packet_with_interface() { |xpb, itf| ... } click to toggle source

Iterate over each xPB with its associated interface @return [void] @yieldparam [String] xpb @yieldparam [IDB] itf

# File lib/packetgen/pcapng/file.rb, line 477
def each_packet_with_interface
  sections.each do |section|
    section.interfaces.each do |itf|
      itf.packets.each { |xpb| yield xpb, itf }
    end
  end
end
epb_from_pkt(pkt, section, timestamp) click to toggle source

@todo remove hash case when array_to_file will be removed

# File lib/packetgen/pcapng/file.rb, line 449
def epb_from_pkt(pkt, section, timestamp)
  this_ts, this_data = case pkt
                       when Hash
                         [pkt.keys.first.to_i, pkt.values.first.to_s]
                       else
                         [timestamp.to_r, pkt.to_s]
                       end
  this_cap_len = this_data.size
  this_tsh, this_tsl = calc_ts(this_ts, section.interfaces.last.ts_resol)
  EPB.new(endian: section.endian,
          interface_id: 0,
          tsh: this_tsh,
          tsl: this_tsl,
          cap_len: this_cap_len,
          orig_len: this_cap_len,
          data: this_data)
end
guess_block_type(type) click to toggle source

Guess class to use from type @param [Types::Int] type @return [Block]

# File lib/packetgen/pcapng/file.rb, line 358
def guess_block_type(type)
  BLOCK_TYPES.key?(type.to_i) ? BLOCK_TYPES[type.to_i] : UnknownBlock
end
parse(type, io, shb) click to toggle source

Parse a block from its type @param [Types::Int32] type @param [IO] io stream from which parse block @param [SHB] shb header of current section @return [Block]

# File lib/packetgen/pcapng/file.rb, line 349
def parse(type, io, shb)
  block = guess_block_type(type).new(endian: shb.endian)
  classify_block shb, block
  block.read(io)
end
parse_packet(data, link_type) click to toggle source
# File lib/packetgen/pcapng/file.rb, line 485
def parse_packet(data, link_type)
  Packet.parse(data, first_header: KNOWN_LINK_TYPES[link_type])
end
parse_section(io) click to toggle source

Parse a section. A section is made of at least a SHB. It than may contain others blocks, such as IDB, SPB or EPB. @param [IO] io @return [void]

# File lib/packetgen/pcapng/file.rb, line 316
def parse_section(io)
  shb = parse_shb(SHB.new, io)
  raise InvalidFileError, 'no Section header found' unless shb.is_a?(SHB)

  to_parse = if shb.section_len.to_i != 0xffffffffffffffff
               # Section length is defined
               StringIO.new(io.read(shb.section_len.to_i))
             else
               # section length is undefined
               io
             end

  until to_parse.eof?
    shb = @sections.last
    parse_shb shb, to_parse
  end
end
parse_shb(shb, io) click to toggle source

Parse a SHB @param [SHB] shb SHB to parse @param [IO] io stream from which parse SHB @return [SHB]

# File lib/packetgen/pcapng/file.rb, line 338
def parse_shb(shb, io)
  type = Types::Int32.new(0, shb.endian).read(io.read(4))
  io.seek(-4, IO::SEEK_CUR)
  parse(type, io, shb)
end
reread(filename) click to toggle source
# File lib/packetgen/pcapng/file.rb, line 424
def reread(filename)
  return if filename.nil?

  clear
  readfile filename
end
spb_from_pkt(pkt, section) click to toggle source
# File lib/packetgen/pcapng/file.rb, line 439
def spb_from_pkt(pkt, section)
  pkt_s = pkt.to_s
  size = pkt_s.size
  SPB.new(endian: section.endian,
          block_len: size,
          orig_len: size,
          data: pkt_s)
end
update_ts(timestamp, ts_inc) click to toggle source
# File lib/packetgen/pcapng/file.rb, line 467
def update_ts(timestamp, ts_inc)
  return nil if timestamp.nil? || ts_inc.nil?

  timestamp + ts_inc
end