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