baikal 1.1.0

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.
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