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.
- checksums.yaml +4 -4
- data/lib/kaitai/struct/struct.rb +249 -57
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 023b59d1105bf10850106f5bf1ace310c9d532b7
|
4
|
+
data.tar.gz: 1ad3a5d47c5ce5be18b79bfbcd9910b1f0d9ed7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d5b1a4726c8c5c010baaca1cb771daf32762b84fd77cf408b91ebadd6e6a865d3fba69b32deb9618a3463e6c68789c2e9ec7cc401524939c729ab519b832457
|
7
|
+
data.tar.gz: 0a3dc2c987d2a327bae5b0ecff06bde62c6039df1180416aad5f4d966fab8d618b0462f29d3f64e7858caee032b7c64f572b6a4579d74eaf461f89830c7f3d54
|
data/lib/kaitai/struct/struct.rb
CHANGED
@@ -3,8 +3,13 @@ require 'stringio'
|
|
3
3
|
module Kaitai
|
4
4
|
module Struct
|
5
5
|
|
6
|
-
VERSION = '0.
|
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
|
-
|
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
|
-
#
|
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
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
#
|
140
|
+
# Integer numbers
|
68
141
|
# ========================================================================
|
69
142
|
|
70
|
-
|
71
|
-
|
143
|
+
# ------------------------------------------------------------------------
|
144
|
+
# Signed
|
145
|
+
# ------------------------------------------------------------------------
|
146
|
+
|
147
|
+
def read_s1
|
148
|
+
read_bytes(1).unpack('c')[0]
|
72
149
|
end
|
73
150
|
|
74
|
-
|
75
|
-
|
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
|
79
|
-
|
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
|
84
|
-
read_bytes(8).unpack('
|
186
|
+
def read_s8le
|
187
|
+
read_bytes(8).unpack('q')[0]
|
85
188
|
end
|
86
189
|
else
|
87
|
-
def
|
88
|
-
|
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
|
-
#
|
114
|
-
#
|
115
|
-
|
116
|
-
def read_s1
|
117
|
-
read_bytes(1).unpack('c')[0]
|
118
|
-
end
|
226
|
+
# ........................................................................
|
227
|
+
# Little-endian
|
228
|
+
# ........................................................................
|
119
229
|
|
120
|
-
def
|
121
|
-
|
230
|
+
def read_u2le
|
231
|
+
read_bytes(2).unpack('v')[0]
|
122
232
|
end
|
123
233
|
|
124
|
-
def
|
125
|
-
|
234
|
+
def read_u4le
|
235
|
+
read_bytes(4).unpack('V')[0]
|
126
236
|
end
|
127
237
|
|
128
238
|
unless @@big_endian
|
129
|
-
def
|
130
|
-
read_bytes(8).unpack('
|
239
|
+
def read_u8le
|
240
|
+
read_bytes(8).unpack('Q')[0]
|
131
241
|
end
|
132
242
|
else
|
133
|
-
def
|
134
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
249
|
+
# ========================================================================
|
250
|
+
# Floating point numbers
|
251
|
+
# ========================================================================
|
141
252
|
|
142
|
-
|
143
|
-
|
253
|
+
# ------------------------------------------------------------------------
|
254
|
+
# Big-endian
|
255
|
+
# ------------------------------------------------------------------------
|
256
|
+
|
257
|
+
def read_f4be
|
258
|
+
read_bytes(4).unpack('g')[0]
|
144
259
|
end
|
145
260
|
|
146
|
-
|
147
|
-
|
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
|
159
|
-
|
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.
|
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-
|
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.
|