kaitai-struct 0.3 → 0.4

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kaitai/struct/struct.rb +249 -57
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b07542b0b53559b463ae219d91b5132ae32443f1
4
- data.tar.gz: 70117ba80efbb033f7ab465e4226b42a3c4eb238
3
+ metadata.gz: 023b59d1105bf10850106f5bf1ace310c9d532b7
4
+ data.tar.gz: 1ad3a5d47c5ce5be18b79bfbcd9910b1f0d9ed7b
5
5
  SHA512:
6
- metadata.gz: 902540aad3e004cfae1a1771b462c85bbfdcdc87d0dbbe2b05d3d08fd5277d2d539eb0ed0c8cf788eac4f44afda293af5b6a624f3d47161100f67ba01343716c
7
- data.tar.gz: 25fb636cef7318d76d5139e255b81a9ee4caa78f9a4b440bb4dda437e05720ca0d919bc6bcc97ea93c31952dd3447989e0cc3af72357e8fc5c6bf0e3da8f205b
6
+ metadata.gz: 5d5b1a4726c8c5c010baaca1cb771daf32762b84fd77cf408b91ebadd6e6a865d3fba69b32deb9618a3463e6c68789c2e9ec7cc401524939c729ab519b832457
7
+ data.tar.gz: 0a3dc2c987d2a327bae5b0ecff06bde62c6039df1180416aad5f4d966fab8d618b0462f29d3f64e7858caee032b7c64f572b6a4579d74eaf461f89830c7f3d54
@@ -3,8 +3,13 @@ require 'stringio'
3
3
  module Kaitai
4
4
  module Struct
5
5
 
6
- VERSION = '0.3'
6
+ VERSION = '0.4'
7
7
 
8
+ ##
9
+ # Common base class for all structured generated by Kaitai Struct.
10
+ # Stores stream object that this object was parsed from in {#_io},
11
+ # stores reference to parent structure in {#_parent} and root
12
+ # structure in {#_root} and provides a few helper methods.
8
13
  class Struct
9
14
 
10
15
  def initialize(_io, _parent = nil, _root = self)
@@ -13,14 +18,67 @@ class Struct
13
18
  @_root = _root
14
19
  end
15
20
 
21
+ ##
22
+ # Factory method to instantiate a Kaitai Struct-powered structure,
23
+ # parsing it from a local file with a given filename.
24
+ # @param filename [String] local file to parse
16
25
  def self.from_file(filename)
17
26
  self.new(Stream.open(filename))
18
27
  end
19
28
 
20
- attr_reader :_io
29
+ ##
30
+ # Implementation of {Object#inspect} to aid debugging (at the very
31
+ # least, to aid exception raising) for KS-based classes. This one
32
+ # uses a bit terser syntax than Ruby's default one, purposely skips
33
+ # any internal fields (i.e. starting with `_`, such as `_io`,
34
+ # `_parent` and `_root`) to reduce confusion, and does no
35
+ # recursivity tracking (as proper general-purpose `inspect`
36
+ # implementation should do) because there are no endless recursion
37
+ # in KS-based classes by design (except for already mentioned
38
+ # internal navigation variables).
39
+ def inspect
40
+ vars = []
41
+ instance_variables.each { |nsym|
42
+ nstr = nsym.to_s
43
+
44
+ # skip all internal variables
45
+ next if nstr[0..1] == '@_'
46
+
47
+ # strip mandatory `@` at the beginning of the name for brevity
48
+ nstr = nstr[1..-1]
49
+
50
+ nvalue = instance_variable_get(nsym).inspect
51
+
52
+ vars << "#{nstr}=#{nvalue}"
53
+ }
54
+
55
+ "#{self.class}(#{vars.join(' ')})"
56
+ end
57
+
58
+ attr_reader :_io, :_parent, :_root
21
59
  end
22
60
 
61
+ ##
62
+ # Kaitai::Stream is an implementation of
63
+ # {https://github.com/kaitai-io/kaitai_struct/wiki/Kaitai-Struct-stream-API
64
+ # Kaitai Struct stream API} for Ruby. It's implemented as a wrapper
65
+ # for generic IO objects.
66
+ #
67
+ # It provides a wide variety of simple methods to read (parse) binary
68
+ # representations of primitive types, such as integer and floating
69
+ # point numbers, byte arrays and strings, and also provides stream
70
+ # positioning / navigation methods with unified cross-language and
71
+ # cross-toolkit semantics.
72
+ #
73
+ # Typically, end users won't access Kaitai Stream class manually, but
74
+ # would describe a binary structure format using .ksy language and
75
+ # then would use Kaitai Struct compiler to generate source code in
76
+ # desired target language. That code, in turn, would use this class
77
+ # and API to do the actual parsing job.
23
78
  class Stream
79
+ ##
80
+ # Exception class for an error that occurs when some fixed content
81
+ # was expected to appear, but actual data read was different.
24
82
  class UnexpectedDataError < Exception
25
83
  def initialize(actual, expected)
26
84
  super("Unexpected fixed contents: got #{Stream.format_hex(actual)}, was waiting for #{Stream.format_hex(expected)}")
@@ -29,6 +87,10 @@ class Stream
29
87
  end
30
88
  end
31
89
 
90
+ ##
91
+ # Constructs new Kaitai Stream object.
92
+ # @param arg [String, IO] if String, it will be used as byte array to read data from;
93
+ # if IO, if will be used literally as source of data
32
94
  def initialize(arg)
33
95
  if arg.is_a?(String)
34
96
  @_io = StringIO.new(arg)
@@ -39,57 +101,109 @@ class Stream
39
101
  end
40
102
  end
41
103
 
104
+ ##
105
+ # Convenience method to create a Kaitai Stream object, opening a
106
+ # local file with a given filename.
107
+ # @param filename [String] local file to open
42
108
  def self.open(filename)
43
109
  self.new(File.open(filename, 'rb:ASCII-8BIT'))
44
110
  end
45
111
 
112
+ # Test endianness of the platform
113
+ @@big_endian = [0x0102].pack('s') == [0x0102].pack('n')
114
+
46
115
  # ========================================================================
47
- # Forwarding of IO API calls
116
+ # Stream positioning
48
117
  # ========================================================================
49
118
 
119
+ ##
120
+ # Check if stream pointer is at the end of stream.
121
+ # @return [true, false] true if we are located at the end of the stream
50
122
  def eof?; @_io.eof?; end
123
+
124
+ ##
125
+ # Set stream pointer to designated position.
126
+ # @param x [Fixnum] new position (offset in bytes from the beginning of the stream)
51
127
  def seek(x); @_io.seek(x); end
52
- def pos; @_io.pos; end
53
128
 
54
- # Test endianness of the platform
55
- @@big_endian = [0x0102].pack('s') == [0x0102].pack('n')
129
+ ##
130
+ # Get current position of a stream pointer.
131
+ # @return [Fixnum] pointer position, number of bytes from the beginning of the stream
132
+ def pos; @_io.pos; end
56
133
 
57
- def ensure_fixed_contents(size, expected)
58
- buf = @_io.read(size)
59
- actual = buf.bytes
60
- if actual != expected
61
- raise UnexpectedDataError.new(actual, expected)
62
- end
63
- buf
64
- end
134
+ ##
135
+ # Get total size of the stream in bytes.
136
+ # @return [Fixnum] size of the stream in bytes
137
+ def size; @_io.size; end
65
138
 
66
139
  # ========================================================================
67
- # Unsigned
140
+ # Integer numbers
68
141
  # ========================================================================
69
142
 
70
- def read_u1
71
- read_bytes(1).unpack('C')[0]
143
+ # ------------------------------------------------------------------------
144
+ # Signed
145
+ # ------------------------------------------------------------------------
146
+
147
+ def read_s1
148
+ read_bytes(1).unpack('c')[0]
72
149
  end
73
150
 
74
- def read_u2le
75
- read_bytes(2).unpack('v')[0]
151
+ # ........................................................................
152
+ # Big-endian
153
+ # ........................................................................
154
+
155
+ def read_s2be
156
+ to_signed(read_u2be, SIGN_MASK_16)
76
157
  end
77
158
 
78
- def read_u4le
79
- read_bytes(4).unpack('V')[0]
159
+ def read_s4be
160
+ to_signed(read_u4be, SIGN_MASK_32)
161
+ end
162
+
163
+ if @@big_endian
164
+ def read_s8be
165
+ read_bytes(8).unpack('q')[0]
166
+ end
167
+ else
168
+ def read_s8be
169
+ to_signed(read_u8be, SIGN_MASK_64)
170
+ end
171
+ end
172
+
173
+ # ........................................................................
174
+ # Little-endian
175
+ # ........................................................................
176
+
177
+ def read_s2le
178
+ to_signed(read_u2le, SIGN_MASK_16)
179
+ end
180
+
181
+ def read_s4le
182
+ to_signed(read_u4le, SIGN_MASK_32)
80
183
  end
81
184
 
82
185
  unless @@big_endian
83
- def read_u8le
84
- read_bytes(8).unpack('Q')[0]
186
+ def read_s8le
187
+ read_bytes(8).unpack('q')[0]
85
188
  end
86
189
  else
87
- def read_u8le
88
- a, b = read_bytes(8).unpack('VV')
89
- (b << 32) + a
190
+ def read_s8le
191
+ to_signed(read_u8le, SIGN_MASK_64)
90
192
  end
91
193
  end
92
194
 
195
+ # ------------------------------------------------------------------------
196
+ # Unsigned
197
+ # ------------------------------------------------------------------------
198
+
199
+ def read_u1
200
+ read_bytes(1).unpack('C')[0]
201
+ end
202
+
203
+ # ........................................................................
204
+ # Big-endian
205
+ # ........................................................................
206
+
93
207
  def read_u2be
94
208
  read_bytes(2).unpack('n')[0]
95
209
  end
@@ -109,56 +223,67 @@ class Stream
109
223
  end
110
224
  end
111
225
 
112
- # ========================================================================
113
- # Signed
114
- # ========================================================================
115
-
116
- def read_s1
117
- read_bytes(1).unpack('c')[0]
118
- end
226
+ # ........................................................................
227
+ # Little-endian
228
+ # ........................................................................
119
229
 
120
- def read_s2le
121
- to_signed(read_u2le, SIGN_MASK_16)
230
+ def read_u2le
231
+ read_bytes(2).unpack('v')[0]
122
232
  end
123
233
 
124
- def read_s4le
125
- to_signed(read_u4le, SIGN_MASK_32)
234
+ def read_u4le
235
+ read_bytes(4).unpack('V')[0]
126
236
  end
127
237
 
128
238
  unless @@big_endian
129
- def read_s8le
130
- read_bytes(8).unpack('q')[0]
239
+ def read_u8le
240
+ read_bytes(8).unpack('Q')[0]
131
241
  end
132
242
  else
133
- def read_s8le
134
- to_signed(read_u8le, SIGN_MASK_64)
243
+ def read_u8le
244
+ a, b = read_bytes(8).unpack('VV')
245
+ (b << 32) + a
135
246
  end
136
247
  end
137
248
 
138
- def read_s2be
139
- to_signed(read_u2be, SIGN_MASK_16)
140
- end
249
+ # ========================================================================
250
+ # Floating point numbers
251
+ # ========================================================================
141
252
 
142
- def read_s4be
143
- to_signed(read_u4be, SIGN_MASK_32)
253
+ # ------------------------------------------------------------------------
254
+ # Big-endian
255
+ # ------------------------------------------------------------------------
256
+
257
+ def read_f4be
258
+ read_bytes(4).unpack('g')[0]
144
259
  end
145
260
 
146
- if @@big_endian
147
- def read_s8be
148
- read_bytes(8).unpack('q')[0]
149
- end
150
- else
151
- def read_s8be
152
- to_signed(read_u8be, SIGN_MASK_64)
153
- end
261
+ def read_f8be
262
+ read_bytes(8).unpack('G')[0]
154
263
  end
155
264
 
156
- # ========================================================================
265
+ # ------------------------------------------------------------------------
266
+ # Little-endian
267
+ # ------------------------------------------------------------------------
157
268
 
158
- def read_bytes_full
159
- @_io.read
269
+ def read_f4le
270
+ read_bytes(4).unpack('e')[0]
271
+ end
272
+
273
+ def read_f8le
274
+ read_bytes(8).unpack('E')[0]
160
275
  end
161
276
 
277
+ # ========================================================================
278
+ # Byte arrays
279
+ # ========================================================================
280
+
281
+ ##
282
+ # Reads designated number of bytes from the stream.
283
+ # @param n [Fixnum] number of bytes to read
284
+ # @return [String] read bytes as byte array
285
+ # @raise [EOFError] if there were less bytes than requested
286
+ # available in the stream
162
287
  def read_bytes(n)
163
288
  r = @_io.read(n)
164
289
  if r
@@ -170,6 +295,33 @@ class Stream
170
295
  r
171
296
  end
172
297
 
298
+ ##
299
+ # Reads all the remaining bytes in a stream as byte array.
300
+ # @return [String] all remaining bytes in a stream as byte array
301
+ def read_bytes_full
302
+ @_io.read
303
+ end
304
+
305
+ ##
306
+ # Reads next len bytes from the stream and ensures that they match
307
+ # expected fixed byte array. If they differ, throws a
308
+ # {UnexpectedDataError} runtime exception.
309
+ # @param len [Fixnum] number of bytes to read
310
+ # @param expected [String] contents to be expected
311
+ # @return [String] read bytes as byte array, which are guaranteed to
312
+ # equal to expected
313
+ # @raise [UnexpectedDataError]
314
+ def ensure_fixed_contents(len, expected)
315
+ buf = @_io.read(len)
316
+ actual = buf.bytes
317
+ if actual != expected
318
+ raise UnexpectedDataError.new(actual, expected)
319
+ end
320
+ buf
321
+ end
322
+
323
+ # ========================================================================
324
+ # Strings
173
325
  # ========================================================================
174
326
 
175
327
  def read_str_eos(encoding)
@@ -201,7 +353,47 @@ class Stream
201
353
  end
202
354
 
203
355
  # ========================================================================
356
+ # Byte array processing
357
+ # ========================================================================
358
+
359
+ ##
360
+ # Performs a XOR processing with given data, XORing every byte of
361
+ # input with a single given value.
362
+ # @param data [String] data to process
363
+ # @param key [Fixnum] value to XOR with
364
+ # @return [String] processed data
365
+ def process_xor_one(data, key)
366
+ data.bytes.map { |x| x ^ key }.pack('C*')
367
+ end
368
+
369
+ ##
370
+ # Performs a XOR processing with given data, XORing every byte of
371
+ # input with a key array, repeating key array many times, if
372
+ # necessary (i.e. if data array is longer than key array).
373
+ # @param data [String] data to process
374
+ # @param key [String] array of bytes to XOR with
375
+ # @return [String] processed data
376
+ def process_xor_many(data, key)
377
+ kb = key.bytes
378
+ kl = kb.size
379
+ ki = 0
380
+ data.bytes.map { |x|
381
+ r = x ^ kb[ki]
382
+ ki += 1
383
+ ki = 0 if ki >= kl
384
+ r
385
+ }.pack('C*')
386
+ end
204
387
 
388
+ ##
389
+ # Performs a circular left rotation shift for a given buffer by a
390
+ # given amount of bits, using groups of groupSize bytes each
391
+ # time. Right circular rotation should be performed using this
392
+ # procedure with corrected amount.
393
+ # @param data [String] source data to process
394
+ # @param amount [Fixnum] number of bits to shift by
395
+ # @param group_size [Fixnum] number of bytes per group to shift
396
+ # @return [String] copy of source array with requested shift applied
205
397
  def process_rotate_left(data, amount, group_size)
206
398
  raise NotImplementedError.new("unable to rotate group #{group_size} bytes yet") unless group_size == 1
207
399
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kaitai-struct
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: '0.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikhail Yakshin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-19 00:00:00.000000000 Z
11
+ date: 2016-08-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Kaitai Struct is a declarative language used for describe various binary data structures, laid out in files or in memory: i.e. binary file formats, network stream packet formats, etc.