baikal 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,10 @@
1
+ === 1.1.0 / 2014-05-25
2
+
3
+ * Upgraded to the new String#pack interface of modern Ruby (1.9.3+)
4
+ * Discarded the natord access methods in favour of selecting the native byte
5
+ order as the current byte order
6
+ * Wrote more unit tests
7
+
8
+ === 1.0.0 / 2009-09-11
9
+
10
+ * First version
data/Makefile ADDED
@@ -0,0 +1,7 @@
1
+ .PHONY: gem test
2
+
3
+ gem:
4
+ gem build baikal.gemspec
5
+
6
+ test:
7
+ ruby test/test_baikal.rb
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ GPL-3
2
+ History.txt
3
+ Makefile
4
+ Manifest.txt
5
+ README.txt
6
+ baikal.gemspec
7
+ lib/baikal.rb
8
+ lib/baikal/cursor.rb
9
+ lib/baikal/hexdump.rb
10
+ lib/baikal/tweak.rb
11
+ test/test_baikal.rb
data/README.txt ADDED
@@ -0,0 +1,34 @@
1
+ == DESCRIPTION
2
+
3
+ Baikal is a basic Ruby library for constructing, parsing and modifying binary
4
+ objects ('blobs') in a linear manner. Its primary use is facilitating custom
5
+ bytecode engines.
6
+
7
+ == SYNOPSIS
8
+
9
+ require 'baikal'
10
+
11
+ pool = Baikal::Pool.new
12
+ pool.go_network_byte_order
13
+ pool.emit_wyde(42)
14
+ pool.emit_blob "Yellow."
15
+
16
+ open 'dump', 'wb' do |port|
17
+ port.print pool.bytes
18
+ end
19
+
20
+ == REQUIREMENTS
21
+
22
+ Baikal is implemented in plain Ruby.
23
+
24
+ The String#pack interface Baikal uses appeared in Ruby 1.9.3.
25
+
26
+ Ruby 2.1 or later is needed to manipulate octabytes in the native byte order.
27
+ Octabytes in an explicit BigEndian or LittleEndian byte order should work with
28
+ 1.9.3 or later.
29
+
30
+ == LICENSE
31
+
32
+ Copyright (c) 2009-2014 by Andres Soolo <dig@mirky.net>.
33
+
34
+ Baikal is free software, licensed under the GNU General Public License version 3.
data/baikal.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'baikal'
3
+ s.version = '1.1.0'
4
+ s.date = '2014-05-24'
5
+ s.homepage = 'https://github.com/digwuren/baikal'
6
+ s.summary = 'A blob handling toolkit'
7
+ s.author = 'Andres Soolo'
8
+ s.email = 'dig@mirky.net'
9
+ s.files = IO::read('Manifest.txt').split(/\n/)
10
+ s.license = 'GPL-3'
11
+ s.description = <<EOD
12
+ Baikal is a Ruby library for constructing, parsing and modifying binary objects
13
+ ('blobs') in a linear manner. Its primary use is facilitating custom bytecode
14
+ engines.
15
+ EOD
16
+ # s.require_path = 'lib'
17
+ s.test_files = ['test/test_baikal.rb']
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ['README.txt']
20
+ end
data/lib/baikal.rb ADDED
@@ -0,0 +1,459 @@
1
+ #
2
+ # Baikal is a tool for generating, parsing and modifying binary objects.
3
+ #
4
+ # $Id: baikal.rb 90 2009-10-19 15:02:43Z dig $
5
+ #
6
+ module Baikal
7
+ VERSION = '1.1.0'
8
+
9
+ #
10
+ # Represents a byte pool. Byte pools are resizeable arrays of bytes. Data
11
+ # can be stored into or read from byte pool as integers or as blobs.
12
+ #
13
+ # Each byte pool has an associated /integer byte order indicator/; see
14
+ # +go_network_byte_order+, +go_reverse_network_byte_order+ and
15
+ # +go_native_byte_order+.
16
+ #
17
+ # Byte pools have no concept of current position; for linear traversal, see
18
+ # +Cursor+.
19
+ #
20
+ class Pool
21
+ #
22
+ # A Ruby string containing all the bytes currently in this pool.
23
+ #
24
+ attr_reader :bytes
25
+
26
+ #
27
+ # Creates a new byte pool of the host system's native byte order. If
28
+ # +initial_content+ is given, loads its bytes to the newly created byte
29
+ # pool. Otherwise, the byte pool will be empty after creation.
30
+ #
31
+ def initialize initial_content = ''
32
+ raise 'Type mismatch' unless initial_content.is_a? String
33
+ super()
34
+ @bytes = initial_content.dup
35
+ @bytes.force_encoding Encoding::ASCII_8BIT
36
+ @byte_order = '!' # native
37
+ return
38
+ end
39
+
40
+ #
41
+ # Creates a new byte pool of the network byte order.the host system's
42
+ # native endianness. If +initial_content+ is given, loads its bytes to
43
+ # the newly created byte pool. Otherwise, the byte pool will be empty
44
+ # after creation.
45
+ #
46
+ def Pool::new_of_network_byte_order initial_content = ''
47
+ raise 'Type mismatch' unless initial_content.is_a? String
48
+ pool = Pool.new(initial_content)
49
+ pool.go_network_byte_order
50
+ return pool
51
+ end
52
+
53
+ #
54
+ # Creates a new byte pool of the reverse network byte order.the host
55
+ # system's native endianness. If +initial_content+ is given, loads its
56
+ # bytes to the newly created byte pool. Otherwise, the byte pool will
57
+ # be empty after creation.
58
+ #
59
+ def Pool::new_of_reverse_network_byte_order initial_content = ''
60
+ raise 'Type mismatch' unless initial_content.is_a? String
61
+ pool = Pool.new(initial_content)
62
+ pool.go_reverse_network_byte_order
63
+ return pool
64
+ end
65
+
66
+ #
67
+ # Appends the byte given by +value+ to this byte pool, growing the byte
68
+ # pool by one byte.
69
+ #
70
+ def emit_byte value
71
+ set_byte @bytes.size, value
72
+ return
73
+ end
74
+
75
+ #
76
+ # Appends the wyde given by +value+ to this byte pool using the
77
+ # currently selected byte order, growing the byte pool by two bytes.
78
+ #
79
+ def emit_wyde value
80
+ set_wyde @bytes.size, value
81
+ return
82
+ end
83
+
84
+ #
85
+ # Appends the tetrabyte given by +value+ to this byte pool using the
86
+ # currently selected byte order, growing the byte pool by four bytes.
87
+ #
88
+ def emit_tetra value
89
+ set_tetra @bytes.size, value
90
+ return
91
+ end
92
+
93
+ #
94
+ # Appends the octabyte given by +value+ to this byte pool using the
95
+ # currently selected byte order, growing the byte pool by eight bytes.
96
+ #
97
+ def emit_octa value
98
+ set_octa @bytes.size, value
99
+ return
100
+ end
101
+
102
+ #
103
+ # Appends +count+ copies of the byte given by +value+ to this byte
104
+ # pool, growing the byte pool by as many bytes. If +value+ is not
105
+ # given, zero is used by default.
106
+ #
107
+ def emit_repeated_bytes count, value = 0
108
+ raise 'Type mismatch' unless count.is_a? Integer
109
+ raise 'Type mismatch' unless value.is_a? Integer
110
+ @bytes << [value].pack('C') * count
111
+ return
112
+ end
113
+
114
+ #
115
+ # Retrieves one unsigned byte from this byte pool from the given
116
+ # +offset+.
117
+ #
118
+ # Error if such a byte lies outside the boundaries of the pool.
119
+ #
120
+ def get_unsigned_byte offset
121
+ raise 'Type mismatch' unless offset.is_a? Integer
122
+ raise 'Offset out of range' if offset < 0 or offset + 1 > @bytes.size
123
+ return @bytes.unpack("@#{offset}C").first
124
+ end
125
+
126
+ #
127
+ # Retrieves one unsigned wyde from this byte pool from the given
128
+ # +offset+, using the currently selected byte order.
129
+ #
130
+ # Error if such a wyde lies outside the boundaries of the pool, even
131
+ # partially.
132
+ #
133
+ def get_unsigned_wyde offset
134
+ raise 'Type mismatch' unless offset.is_a? Integer
135
+ raise 'Offset out of range' if offset < 0 or offset + 2 > @bytes.size
136
+ return @bytes.unpack("@#{offset} S#@byte_order").first
137
+ end
138
+
139
+ #
140
+ # Retrieves one unsigned tetrabyte from this byte pool from the given
141
+ # +offset+, using the currently selected byte order.
142
+ #
143
+ # Error if such a tetra lies outside the boundaries of the pool, even
144
+ # partially.
145
+ #
146
+ def get_unsigned_tetra offset
147
+ raise 'Type mismatch' unless offset.is_a? Integer
148
+ raise 'Offset out of range' if offset < 0 or offset + 4 > @bytes.size
149
+ return @bytes.unpack("@#{offset} L#@byte_order").first
150
+ end
151
+
152
+ #
153
+ # Retrieves one unsigned octabyte from this byte pool from the given
154
+ # +offset+, using the currently selected byte order.
155
+ #
156
+ # Error if such an octa lies outside the boundaries of the pool, even
157
+ # partially.
158
+ #
159
+ def get_unsigned_octa offset
160
+ raise 'Type mismatch' unless offset.is_a? Integer
161
+ raise 'Offset out of range' if offset < 0 or offset + 8 > @bytes.size
162
+ return @bytes.unpack("@#{offset} Q#@byte_order").first
163
+ end
164
+
165
+ #
166
+ # Retrieves one signed byte from this byte pool from the given
167
+ # +offset+.
168
+ #
169
+ # Error if such a byte lies outside the boundaries of the pool.
170
+ #
171
+ def get_signed_byte offset
172
+ raise 'Type mismatch' unless offset.is_a? Integer
173
+ raise 'Offset out of range' if offset < 0 or offset + 1 > @bytes.size
174
+ return @bytes.unpack("@#{offset} c").first
175
+ end
176
+
177
+ #
178
+ # Retrieves one signed wyde from this byte pool from the given
179
+ # +offset+, using the currently selected byte order.
180
+ #
181
+ # Error if such a wyde lies outside the boundaries of the pool, even
182
+ # partially.
183
+ #
184
+ def get_signed_wyde offset
185
+ raise 'Type mismatch' unless offset.is_a? Integer
186
+ raise 'Offset out of range' if offset < 0 or offset + 2 > @bytes.size
187
+ return @bytes.unpack("@#{offset} s#@byte_order").first
188
+ end
189
+
190
+ #
191
+ # Retrieves one signed tetrabyte from this byte pool from the given
192
+ # +offset+, using the currently selected byte order.
193
+ #
194
+ # Error if such a tetra lies outside the boundaries of the pool, even
195
+ # partially.
196
+ #
197
+ def get_signed_tetra offset
198
+ raise 'Type mismatch' unless offset.is_a? Integer
199
+ raise 'Offset out of range' if offset < 0 or offset + 4 > @bytes.size
200
+ return @bytes.unpack("@#{offset} l#@byte_order").first
201
+ end
202
+
203
+ #
204
+ # Retrieves one signed octabyte from this byte pool from the given
205
+ # +offset+, using the currently selected byte order.
206
+ #
207
+ # Error if such an octa lies outside the boundaries of the pool, even
208
+ # partially.
209
+ #
210
+ def get_signed_octa offset
211
+ raise 'Type mismatch' unless offset.is_a? Integer
212
+ raise 'Offset out of range' if offset < 0 or offset + 8 > @bytes.size
213
+ return @bytes.unpack("@#{offset} q#@byte_order").first
214
+ end
215
+
216
+ #
217
+ # Retrieves an unsigned integer of the given +size+ (which must be either
218
+ # +1+, +2+, +4+ or +8+) from this byte pool from the given +offset+, using
219
+ # the currently selected byte order.
220
+ #
221
+ # Error if such an integer lies outside the boundaries of the pool,
222
+ # even partially.
223
+ #
224
+ def get_unsigned_integer size, offset
225
+ raise 'Type mismatch' unless offset.is_a? Integer
226
+ pack_code = case size
227
+ when 1 then 'C'
228
+ when 2 then "S#@byte_order"
229
+ when 4 then "L#@byte_order"
230
+ when 8 then "Q#@byte_order"
231
+ else raise "Unsupported integer size #{size.inspect}"
232
+ end
233
+ raise 'Offset out of range' if offset < 0 or offset + size > @bytes.size
234
+ return @bytes.unpack("@#{offset} #{pack_code}").first
235
+ end
236
+
237
+ #
238
+ # Sets the byte in this byte pool on the given +offset+ to the given +value+.
239
+ #
240
+ # Error if the offset lies outside the boundaries of the pool. (But it's
241
+ # OK for it to point at the end of the pool.)
242
+ #
243
+ def set_byte offset, value
244
+ raise 'Type mismatch' unless offset.is_a? Integer
245
+ raise 'Type mismatch' unless value.is_a? Integer
246
+ raise 'Offset out of range' if offset < 0 or offset > @bytes.size
247
+ @bytes[offset] = [value].pack('c')
248
+ return
249
+ end
250
+
251
+ #
252
+ # Sets the wyde in this byte pool on the given +offset+ to the given
253
+ # +value+, using the currently selected byte order.
254
+ #
255
+ # Error if the offset lies outside the boundaries of the pool. (But it's
256
+ # OK for it to point at the end of the pool.)
257
+ #
258
+ def set_wyde offset, value
259
+ raise 'Type mismatch' unless offset.is_a? Integer
260
+ raise 'Type mismatch' unless value.is_a? Integer
261
+ raise 'Offset out of range' if offset < 0 or offset > @bytes.size
262
+ @bytes[offset, 2] = [value].pack("s#@byte_order")
263
+ return
264
+ end
265
+
266
+ #
267
+ # Sets the tetrabyte in this byte pool on the given +offset+ to the
268
+ # given +value+, using the currently selected byte order.
269
+ #
270
+ # Error if the offset lies outside the boundaries of the pool. (But it's
271
+ # OK for it to point at the end of the pool.)
272
+ #
273
+ def set_tetra offset, value
274
+ raise 'Type mismatch' unless offset.is_a? Integer
275
+ raise 'Type mismatch' unless value.is_a? Integer
276
+ raise 'Offset out of range' if offset < 0 or offset > @bytes.size
277
+ @bytes[offset, 4] = [value].pack("l#@byte_order")
278
+ return
279
+ end
280
+
281
+ #
282
+ # Sets the octabyte in this byte pool on the given +offset+ to the
283
+ # given +value+, using the currently selected byte order.
284
+ #
285
+ # Error if the offset lies outside the boundaries of the pool. (But it's
286
+ # OK for it to point at the end of the pool.)
287
+ #
288
+ def set_octa offset, value
289
+ raise 'Type mismatch' unless offset.is_a? Integer
290
+ raise 'Type mismatch' unless value.is_a? Integer
291
+ raise 'Offset out of range' if offset < 0 or offset > @bytes.size
292
+ @bytes[offset, 8] = [value].pack("q#@byte_order")
293
+ return
294
+ end
295
+
296
+ #
297
+ # Sets integer of the given +size+ (which must be either +1+, +2+, +4+
298
+ # or +8+) in this byte pool on the given +offset+ to the given +value+,
299
+ # using the currently selected byte order.
300
+ #
301
+ # Error if such an integer lies outside the boundaries of the pool,
302
+ # even partially.
303
+ #
304
+ def set_integer size, offset, value
305
+ raise 'Type mismatch' unless offset.is_a? Integer
306
+ pack_code = case size
307
+ when 1 then 'C'
308
+ when 2 then "S#@byte_order"
309
+ when 4 then "L#@byte_order"
310
+ when 8 then "Q#@byte_order"
311
+ else raise "Unsupported integer size #{size.inspect}"
312
+ end
313
+ raise 'Offset out of range' if offset < 0 or offset > @bytes.size
314
+ @bytes[offset, size] = [value].pack(pack_code)
315
+ return
316
+ end
317
+
318
+ #
319
+ # Appends +blob+ given as a Ruby string to this byte pool, growing the
320
+ # byte pool by the blob's length.
321
+ #
322
+ def emit_blob blob
323
+ @bytes << blob.to_s
324
+ return
325
+ end
326
+
327
+ #
328
+ # Extracts a blob of given +size+ from this byte pool's given +offset+
329
+ # and returns it as a Ruby string.
330
+ #
331
+ # Error if such a blob lies outside of the boundaries of the pool, even partially.
332
+ #
333
+ def get_blob offset, size
334
+ raise 'Type mismatch' unless offset.is_a? Integer
335
+ raise 'Type mismatch' unless size.is_a? Integer
336
+ raise 'Invalid blob size' if size < 0
337
+ raise 'Offset out of range' if offset < 0 or offset + size > @bytes.size
338
+ return @bytes[offset, size]
339
+ end
340
+
341
+ #
342
+ # Stores a +blob+, given as a Ruby string, into this byte pool at the
343
+ # given +offset+.
344
+ #
345
+ # Error if the offset lies outside the boundaries of the pool. (But it's
346
+ # OK for it to point at the end of the pool.)
347
+ #
348
+ def set_blob offset, blob
349
+ raise 'Type mismatch' unless offset.is_a? Integer
350
+ blob = blob.to_s.dup.force_encoding Encoding::ASCII_8BIT
351
+ raise 'Offset out of range' if offset < 0 or offset > @bytes.size
352
+ @bytes[offset, blob.bytesize] = blob
353
+ return
354
+ end
355
+
356
+ #
357
+ # Returns the number of bytes in this byte pool.
358
+ #
359
+ def size
360
+ return @bytes.size
361
+ end
362
+
363
+ #
364
+ # Returns the difference between current size of the byte pool and the
365
+ # alignment requirement given by +alignment+. +alignment+ needs not be
366
+ # a two's power.
367
+ #
368
+ def bytes_until_alignment alignment
369
+ raise 'Type mismatch' unless alignment.is_a? Integer
370
+ raise 'Invalid alignment requirement' unless alignment > 0
371
+ return -@bytes.size % alignment
372
+ end
373
+
374
+ #
375
+ # Pads the byte pool to the specified +alignment+ using the specified
376
+ # +padding+ byte. If +padding+ is not given, zero is used by default.
377
+ #
378
+ def align alignment, padding = 0
379
+ raise 'Type mismatch' unless alignment.is_a? Integer
380
+ raise 'Type mismatch' unless padding.is_a? Integer
381
+ emit_repeated_bytes bytes_until_alignment(alignment), padding
382
+ return
383
+ end
384
+
385
+ #
386
+ # Truncates this byte pool to +new_size+ bytes, discarding all bytes
387
+ # after this offset.
388
+ #
389
+ # Error if the byte pool is already shorter than +new_size+ bytes.
390
+ #
391
+ def truncate new_size
392
+ raise 'Type mismatch' unless new_size.is_a? Integer
393
+ raise 'Invalid size' if new_size < 0
394
+ raise 'Unable to truncate to grow' if new_size > @bytes.size
395
+ @bytes[new_size .. -1] = ''
396
+ return
397
+ end
398
+
399
+ #
400
+ # Selects the network byte order for following multibyte integer read
401
+ # or write operations.
402
+ #
403
+ def go_network_byte_order
404
+ @byte_order = '>'
405
+ return
406
+ end
407
+
408
+ #
409
+ # Selects the reverse network byte order for following multibyte
410
+ # integer read or write operations.
411
+ #
412
+ def go_reverse_network_byte_order
413
+ @byte_order = '<'
414
+ return
415
+ end
416
+
417
+ #
418
+ # Selects the host system's native byte order for following multibyte
419
+ # integer read or write operations.
420
+ #
421
+ def go_native_byte_order
422
+ @byte_order = '!'
423
+ return
424
+ end
425
+
426
+ #
427
+ # Returns the byte order indicator for the currently selected endianness:
428
+ # +>+ for BigEndian, +<+ for LittleEndian, or +!+ for whatever the native
429
+ # byte order is. (+Array#pack+ can accept such since Ruby 1.9.3.)
430
+ #
431
+ def byte_order
432
+ return @byte_order
433
+ end
434
+
435
+ #
436
+ # Returns whether this byte pool's currently selected byte order is the
437
+ # network byte order.
438
+ #
439
+ def network_byte_order_selected?
440
+ return @byte_order == '>'
441
+ end
442
+
443
+ #
444
+ # Returns whether this byte pool's currently selected byte order is the
445
+ # reverse network byte order.
446
+ #
447
+ def reverse_network_byte_order_selected?
448
+ return @byte_order == '<'
449
+ end
450
+
451
+ #
452
+ # Returns whether this byte pool's currently selected byte order is the
453
+ # host system's native byte order.
454
+ #
455
+ def native_byte_order_selected?
456
+ return @byte_order == '!'
457
+ end
458
+ end
459
+ end