Class: Kaitai::Struct::Stream

Inherits:
Object
  • Object
show all
Defined in:
lib/kaitai/struct/struct.rb

Overview

Kaitai::Struct::Stream is an implementation of Kaitai Struct stream API for Ruby. It’s implemented as a wrapper for generic IO objects.

It provides a wide variety of simple methods to read (parse) binary representations of primitive types, such as integer and floating point numbers, byte arrays and strings, and also provides stream positioning / navigation methods with unified cross-language and cross-toolkit semantics.

Typically, end users won’t access Kaitai Stream class manually, but would describe a binary structure format using .ksy language and then would use Kaitai Struct compiler to generate source code in desired target language. That code, in turn, would use this class and API to do the actual parsing job.

Defined Under Namespace

Classes: UndecidedEndiannessError, UnexpectedDataError

Constant Summary collapse

@@big_endian =

Test endianness of the platform

[0x0102].pack('s') == [0x0102].pack('n')

Stream positioning collapse

Integer numbers collapse

Floating point numbers collapse

Unaligned bit values collapse

Byte arrays collapse

Byte array processing collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(arg) ⇒ Stream

Constructs new Kaitai Stream object.

Parameters:

  • arg (String, IO)

    if String, it will be used as byte array to read data from; if IO, if will be used literally as source of data



101
102
103
104
105
106
107
108
109
110
# File 'lib/kaitai/struct/struct.rb', line 101

def initialize(arg)
  if arg.is_a?(String)
    @_io = StringIO.new(arg)
  elsif arg.is_a?(IO)
    @_io = arg
  else
    raise TypeError.new('can be initialized with IO or String only')
  end
  align_to_byte
end

Class Method Details

.bytes_strip_right(bytes, pad_byte) ⇒ Object



385
386
387
388
389
390
391
392
# File 'lib/kaitai/struct/struct.rb', line 385

def self.bytes_strip_right(bytes, pad_byte)
  new_len = bytes.length
  while new_len > 0 and bytes.getbyte(new_len - 1) == pad_byte
    new_len -= 1
  end

  bytes[0, new_len]
end

.bytes_terminate(bytes, term, include_term) ⇒ Object



394
395
396
397
398
399
400
401
402
# File 'lib/kaitai/struct/struct.rb', line 394

def self.bytes_terminate(bytes, term, include_term)
  new_len = 0
  max_len = bytes.length
  while bytes.getbyte(new_len) != term and new_len < max_len
    new_len += 1
  end
  new_len += 1 if include_term and new_len < max_len
  bytes[0, new_len]
end

.open(filename) ⇒ Object

Convenience method to create a Kaitai Stream object, opening a local file with a given filename.

Parameters:

  • filename (String)

    local file to open



116
117
118
# File 'lib/kaitai/struct/struct.rb', line 116

def self.open(filename)
  self.new(File.open(filename, 'rb:ASCII-8BIT'))
end

.process_rotate_left(data, amount, group_size) ⇒ String

Performs a circular left rotation shift for a given buffer by a given amount of bits, using groups of groupSize bytes each time. Right circular rotation should be performed using this procedure with corrected amount.

Parameters:

  • data (String)

    source data to process

  • amount (Fixnum)

    number of bits to shift by

  • group_size (Fixnum)

    number of bytes per group to shift

Returns:

  • (String)

    copy of source array with requested shift applied

Raises:

  • (NotImplementedError)


461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/kaitai/struct/struct.rb', line 461

def self.process_rotate_left(data, amount, group_size)
  raise NotImplementedError.new("unable to rotate group #{group_size} bytes yet") unless group_size == 1

  mask = group_size * 8 - 1
  anti_amount = -amount & mask

  # NB: actually, left bit shift (<<) in Ruby would have required
  # truncation to type_bits size (i.e. something like "& 0xff" for
  # group_size == 8), but we can skip this one, because later these
  # number would be packed with Array#pack, which will do truncation
  # anyway

  data.bytes.map { |x| (x << amount) | (x >> anti_amount) }.pack('C*')
end

.process_xor_many(data, key) ⇒ String

Performs a XOR processing with given data, XORing every byte of input with a key array, repeating key array many times, if necessary (i.e. if data array is longer than key array). Uses pure Ruby implementation suggested by [Thomas Leitner](github.com/gettalong), borrowed from github.com/fny/xorcist/blob/master/bin/benchmark

Parameters:

  • data (String)

    data to process

  • key (String)

    array of bytes to XOR with

Returns:

  • (String)

    processed data



437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/kaitai/struct/struct.rb', line 437

def self.process_xor_many(data, key)
  out = data.dup
  kl = key.length
  ki = 0
  i = 0
  max = data.length
  while i < max
    out.setbyte(i, data.getbyte(i) ^ key.getbyte(ki))
    ki += 1
    ki = 0 if ki >= kl
    i += 1
  end
  out
end

.process_xor_one(data, key) ⇒ String

Performs a XOR processing with given data, XORing every byte of input with a single given value. Uses pure Ruby implementation suggested by [Thomas Leitner](github.com/gettalong), borrowed from github.com/fny/xorcist/blob/master/bin/benchmark

Parameters:

  • data (String)

    data to process

  • key (Fixnum)

    value to XOR with

Returns:

  • (String)

    processed data



416
417
418
419
420
421
422
423
424
425
# File 'lib/kaitai/struct/struct.rb', line 416

def self.process_xor_one(data, key)
  out = data.dup
  i = 0
  max = data.length
  while i < max
    out.setbyte(i, data.getbyte(i) ^ key)
    i += 1
  end
  out
end

.resolve_enum(enum_map, value) ⇒ Object

Resolves value using enum: if the value is not found in the map, we’ll just use literal value per se.



481
482
483
# File 'lib/kaitai/struct/struct.rb', line 481

def self.resolve_enum(enum_map, value)
  enum_map[value] || value
end

Instance Method Details

#align_to_byteObject



287
288
289
290
# File 'lib/kaitai/struct/struct.rb', line 287

def align_to_byte
  @bits_left = 0
  @bits = 0
end

#ensure_fixed_contents(expected) ⇒ String

Reads next len bytes from the stream and ensures that they match expected fixed byte array. If they differ, throws a UnexpectedDataError runtime exception.

Parameters:

  • expected (String)

    contents to be expected

Returns:

  • (String)

    read bytes as byte array, which are guaranteed to equal to expected

Raises:



378
379
380
381
382
383
# File 'lib/kaitai/struct/struct.rb', line 378

def ensure_fixed_contents(expected)
  len = expected.bytesize
  actual = @_io.read(len)
  raise UnexpectedDataError.new(actual, expected) if actual != expected
  actual
end

#eof?true, false

Check if stream pointer is at the end of stream.

Returns:

  • (true, false)

    true if we are located at the end of the stream



128
# File 'lib/kaitai/struct/struct.rb', line 128

def eof?; @_io.eof?; end

#posFixnum

Get current position of a stream pointer.

Returns:

  • (Fixnum)

    pointer position, number of bytes from the beginning of the stream



138
# File 'lib/kaitai/struct/struct.rb', line 138

def pos; @_io.pos; end

#read_bits_int(n) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/kaitai/struct/struct.rb', line 292

def read_bits_int(n)
  bits_needed = n - @bits_left
  if bits_needed > 0
    # 1 bit  => 1 byte
    # 8 bits => 1 byte
    # 9 bits => 2 bytes
    bytes_needed = ((bits_needed - 1) / 8) + 1
    buf = read_bytes(bytes_needed)
    buf.each_byte { |byte|
      @bits <<= 8
      @bits |= byte
      @bits_left += 8
    }
  end

  # raw mask with required number of 1s, starting from lowest bit
  mask = (1 << n) - 1
  # shift mask to align with highest bits available in @bits
  shift_bits = @bits_left - n
  mask <<= shift_bits
  # derive reading result
  res = (@bits & mask) >> shift_bits
  # clear top bits that we've just read => AND with 1s
  @bits_left -= n
  mask = (1 << @bits_left) - 1
  @bits &= mask

  res
end

#read_bytes(n) ⇒ String

Reads designated number of bytes from the stream.

Parameters:

  • n (Fixnum)

    number of bytes to read

Returns:

  • (String)

    read bytes as byte array

Raises:

  • (EOFError)

    if there were less bytes than requested available in the stream



332
333
334
335
336
337
338
339
340
341
# File 'lib/kaitai/struct/struct.rb', line 332

def read_bytes(n)
  r = @_io.read(n)
  if r
    rl = r.bytesize
  else
    rl = 0
  end
  raise EOFError.new("attempted to read #{n} bytes, got only #{rl}") if rl < n
  r
end

#read_bytes_fullString

Reads all the remaining bytes in a stream as byte array.

Returns:

  • (String)

    all remaining bytes in a stream as byte array



346
347
348
# File 'lib/kaitai/struct/struct.rb', line 346

def read_bytes_full
  @_io.read
end

#read_bytes_term(term, include_term, consume_term, eos_error) ⇒ Object



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/kaitai/struct/struct.rb', line 350

def read_bytes_term(term, include_term, consume_term, eos_error)
  r = ''
  loop {
    if @_io.eof?
      if eos_error
        raise EOFError.new("end of stream reached, but no terminator #{term} found")
      else
        return r
      end
    end
    c = @_io.getc
    if c.ord == term
      r << c if include_term
      @_io.seek(@_io.pos - 1) unless consume_term
      return r
    end
    r << c
  }
end

#read_f4beObject


Big-endian




263
264
265
# File 'lib/kaitai/struct/struct.rb', line 263

def read_f4be
  read_bytes(4).unpack('g')[0]
end

#read_f4leObject


Little-endian




275
276
277
# File 'lib/kaitai/struct/struct.rb', line 275

def read_f4le
  read_bytes(4).unpack('e')[0]
end

#read_f8beObject



267
268
269
# File 'lib/kaitai/struct/struct.rb', line 267

def read_f8be
  read_bytes(8).unpack('G')[0]
end

#read_f8leObject



279
280
281
# File 'lib/kaitai/struct/struct.rb', line 279

def read_f8le
  read_bytes(8).unpack('E')[0]
end

#read_s1Object


Signed




153
154
155
# File 'lib/kaitai/struct/struct.rb', line 153

def read_s1
  read_bytes(1).unpack('c')[0]
end

#read_s2beObject

.….….….….….….….….….….….….….….….….….… Big-endian .….….….….….….….….….….….….….….….….….…



161
162
163
# File 'lib/kaitai/struct/struct.rb', line 161

def read_s2be
  to_signed(read_u2be, SIGN_MASK_16)
end

#read_s2leObject

.….….….….….….….….….….….….….….….….….… Little-endian .….….….….….….….….….….….….….….….….….…



183
184
185
# File 'lib/kaitai/struct/struct.rb', line 183

def read_s2le
  to_signed(read_u2le, SIGN_MASK_16)
end

#read_s4beObject



165
166
167
# File 'lib/kaitai/struct/struct.rb', line 165

def read_s4be
  to_signed(read_u4be, SIGN_MASK_32)
end

#read_s4leObject



187
188
189
# File 'lib/kaitai/struct/struct.rb', line 187

def read_s4le
  to_signed(read_u4le, SIGN_MASK_32)
end

#read_s8beObject



170
171
172
# File 'lib/kaitai/struct/struct.rb', line 170

def read_s8be
  read_bytes(8).unpack('q')[0]
end

#read_s8leObject



192
193
194
# File 'lib/kaitai/struct/struct.rb', line 192

def read_s8le
  read_bytes(8).unpack('q')[0]
end

#read_u1Object


Unsigned




205
206
207
# File 'lib/kaitai/struct/struct.rb', line 205

def read_u1
  read_bytes(1).unpack('C')[0]
end

#read_u2beObject

.….….….….….….….….….….….….….….….….….… Big-endian .….….….….….….….….….….….….….….….….….…



213
214
215
# File 'lib/kaitai/struct/struct.rb', line 213

def read_u2be
  read_bytes(2).unpack('n')[0]
end

#read_u2leObject

.….….….….….….….….….….….….….….….….….… Little-endian .….….….….….….….….….….….….….….….….….…



236
237
238
# File 'lib/kaitai/struct/struct.rb', line 236

def read_u2le
  read_bytes(2).unpack('v')[0]
end

#read_u4beObject



217
218
219
# File 'lib/kaitai/struct/struct.rb', line 217

def read_u4be
  read_bytes(4).unpack('N')[0]
end

#read_u4leObject



240
241
242
# File 'lib/kaitai/struct/struct.rb', line 240

def read_u4le
  read_bytes(4).unpack('V')[0]
end

#read_u8beObject



222
223
224
# File 'lib/kaitai/struct/struct.rb', line 222

def read_u8be
  read_bytes(8).unpack('Q')[0]
end

#read_u8leObject



245
246
247
# File 'lib/kaitai/struct/struct.rb', line 245

def read_u8le
  read_bytes(8).unpack('Q')[0]
end

#seek(x) ⇒ Object

Set stream pointer to designated position.

Parameters:

  • x (Fixnum)

    new position (offset in bytes from the beginning of the stream)



133
# File 'lib/kaitai/struct/struct.rb', line 133

def seek(x); @_io.seek(x); end

#sizeFixnum

Get total size of the stream in bytes.

Returns:

  • (Fixnum)

    size of the stream in bytes



143
# File 'lib/kaitai/struct/struct.rb', line 143

def size; @_io.size; end