kaitai-struct 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
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.