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.
@@ -0,0 +1,297 @@
1
+ # $Id$
2
+
3
+ require_relative '../baikal'
4
+
5
+ module Baikal
6
+ #
7
+ # Represents an offset into a byte pool, permitting traversing the byte
8
+ # pool in a linear manner.
9
+ #
10
+ class Cursor
11
+ #
12
+ # The cursor's current offset into the underlying byte pool.
13
+ #
14
+ attr_accessor :offset
15
+
16
+ #
17
+ # Creates a cursor of the given +pool+, initialising it to point at
18
+ # its given +offset+. If +offset+ is not given, defaults to zero, that
19
+ # is to say, the byte pool's beginning.
20
+ #
21
+ def initialize pool, offset = 0
22
+ raise 'Type mismatch' unless pool.is_a? Baikal::Pool
23
+ raise 'Type mismatch' unless offset.is_a? Integer
24
+ super()
25
+ @pool = pool
26
+ @offset = offset
27
+ return
28
+ end
29
+
30
+ #
31
+ # Retrieves an unsigned byte from the underlying pool's current
32
+ # position and advances the cursor by one byte.
33
+ #
34
+ # Error if the byte referred to by this cursor lies outside the
35
+ # boundaries of the underlying pool.
36
+ #
37
+ def parse_unsigned_byte
38
+ value = @pool.get_unsigned_byte(@offset)
39
+ @offset += 1
40
+ return value
41
+ end
42
+
43
+ #
44
+ # Retrieves an unsigned wyde from the underlying pool's current
45
+ # position using the pool's currently selected byte order and advances
46
+ # the cursor by two bytes.
47
+ #
48
+ # Error if the wyde referred to by this cursor lies outside the
49
+ # boundaries of the underlying pool, even partially.
50
+ #
51
+ def parse_unsigned_wyde
52
+ value = @pool.get_unsigned_wyde(@offset)
53
+ @offset += 2
54
+ return value
55
+ end
56
+
57
+ #
58
+ # Retrieves an unsigned tetrabyte from the underlying pool's current
59
+ # position using the pool's currently selected byte order and advances
60
+ # the cursor by four bytes.
61
+ #
62
+ # Error if the tetra referred to by this cursor lies outside the
63
+ # boundaries of the underlying pool, even partially.
64
+ #
65
+ def parse_unsigned_tetra
66
+ value = @pool.get_unsigned_tetra(@offset)
67
+ @offset += 4
68
+ return value
69
+ end
70
+
71
+ #
72
+ # Retrieves an unsigned octabyte from the underlying pool's current
73
+ # position using the pool's currently selected byte order and advances
74
+ # the cursor by eight bytes.
75
+ #
76
+ # Error if the octa referred to by this cursor lies outside the
77
+ # boundaries of the underlying pool, even partially.
78
+ #
79
+ def parse_unsigned_octa
80
+ value = @pool.get_unsigned_octa(@offset)
81
+ @offset += 8
82
+ return value
83
+ end
84
+
85
+ #
86
+ # Retrieves a signed byte from the underlying pool's current position
87
+ # and advances the cursor by one byte.
88
+ #
89
+ # Error if the byte referred to by this cursor lies outside the
90
+ # boundaries of the underlying pool.
91
+ #
92
+ def parse_signed_byte
93
+ value = @pool.get_signed_byte(@offset)
94
+ @offset += 1
95
+ return value
96
+ end
97
+
98
+ #
99
+ # Retrieves a signed wyde from the underlying pool's current position
100
+ # using the pool's currently selected byte order and advances the
101
+ # cursor by two bytes.
102
+ #
103
+ # Error if the wyde referred to by this cursor lies outside the
104
+ # boundaries of the underlying pool, even partially.
105
+ #
106
+ def parse_signed_wyde
107
+ value = @pool.get_signed_wyde(@offset)
108
+ @offset += 2
109
+ return value
110
+ end
111
+
112
+ #
113
+ # Retrieves a signed tetrabyte from the underlying pool's current
114
+ # position using the pool's currently selected byte order and advances
115
+ # the cursor by four bytes.
116
+ #
117
+ # Error if the tetra referred to by this cursor lies outside the
118
+ # boundaries of the underlying pool, even partially.
119
+ #
120
+ def parse_signed_tetra
121
+ value = @pool.get_signed_tetra(@offset)
122
+ @offset += 4
123
+ return value
124
+ end
125
+
126
+ #
127
+ # Retrieves a signed octabyte from the underlying pool's current
128
+ # position using the pool's currently selected byte order and advances
129
+ # the cursor by eight bytes.
130
+ #
131
+ # Error if the octa referred to by this cursor lies outside the
132
+ # boundaries of the underlying pool, even partially.
133
+ #
134
+ def parse_signed_octa
135
+ value = @pool.get_signed_octa(@offset)
136
+ @offset += 8
137
+ return value
138
+ end
139
+
140
+ #
141
+ # Retrieves an unsigned byte from the underlying pool's current
142
+ # position. Does not affect the position.
143
+ #
144
+ # Error if the byte referred to by this cursor lies outside the
145
+ # boundaries of the underlying pool.
146
+ #
147
+ def peek_unsigned_byte
148
+ return @pool.get_unsigned_byte(@offset)
149
+ end
150
+
151
+ #
152
+ # Retrieves an unsigned wyde from the underlying pool's current
153
+ # position using the pool's currently selected byte order. Does not
154
+ # affect the position.
155
+ #
156
+ # Error if the wyde referred to by this cursor lies outside the
157
+ # boundaries of the underlying pool, even partially.
158
+ #
159
+ def peek_unsigned_wyde
160
+ return @pool.get_unsigned_wyde(@offset)
161
+ end
162
+
163
+ #
164
+ # Retrieves an unsigned tetrabyte from the underlying pool's current
165
+ # position using the pool's currently selected byte order. Does not
166
+ # affect the position.
167
+ #
168
+ # Error if the tetra referred to by this cursor lies outside the
169
+ # boundaries of the underlying pool, even partially.
170
+ #
171
+ def peek_unsigned_tetra
172
+ return @pool.get_unsigned_tetra(@offset)
173
+ end
174
+
175
+ #
176
+ # Retrieves an unsigned octabyte from the underlying pool's current
177
+ # position using the pool's currently selected byte order. Does not
178
+ # affect the position.
179
+ #
180
+ # Error if the octa referred to by this cursor lies outside the
181
+ # boundaries of the underlying pool, even partially.
182
+ #
183
+ def peek_unsigned_octa
184
+ return @pool.get_unsigned_octa(@offset)
185
+ end
186
+
187
+ #
188
+ # Retrieves a signed byte from the underlying pool's current position.
189
+ # Does not affect the position.
190
+ #
191
+ # Error if the byte referred to by this cursor lies outside the
192
+ # boundaries of the underlying pool.
193
+ #
194
+ def peek_signed_byte
195
+ return @pool.get_signed_byte(@offset)
196
+ end
197
+
198
+ #
199
+ # Retrieves a signed wyde from the underlying pool's current position
200
+ # using the pool's currently selected byte order. Does not affect the
201
+ # position.
202
+ #
203
+ # Error if the wyde referred to by this cursor lies outside the
204
+ # boundaries of the underlying pool, even partially.
205
+ #
206
+ def peek_signed_wyde
207
+ return @pool.get_signed_wyde(@offset)
208
+ end
209
+
210
+ #
211
+ # Retrieves a signed tetrabyte from the underlying pool's current
212
+ # position using the pool's currently selected byte order. Does not
213
+ # affect the position.
214
+ #
215
+ # Error if the tetra referred to by this cursor lies outside the
216
+ # boundaries of the underlying pool, even partially.
217
+ #
218
+ def peek_signed_tetra
219
+ return @pool.get_signed_tetra(@offset)
220
+ end
221
+
222
+ #
223
+ # Retrieves a signed octabyte from the underlying pool's current
224
+ # position using the pool's currently selected byte order. Does not
225
+ # affect the position.
226
+ #
227
+ # Error if the octa referred to by this cursor lies outside the
228
+ # boundaries of the underlying pool, even partially.
229
+ #
230
+ def peek_signed_octa
231
+ return @pool.get_signed_octa(@offset)
232
+ end
233
+
234
+ #
235
+ # Retrieves an unsigned integer of the given +size+ (which must be +1+,
236
+ # +2+, +4+ or +8+) from the underlying pool's current position using the
237
+ # pool's currently selected byte order and advances the cursor by the
238
+ # integer's size.
239
+ #
240
+ # Error if the integer of this size referred to by this cursor lies
241
+ # outside the boundaries of the underlying pool, even partially.
242
+ #
243
+ def parse_unsigned_integer size
244
+ value = @pool.get_unsigned_integer(size, @offset)
245
+ @offset += size
246
+ return value
247
+ end
248
+
249
+ #
250
+ # Retrieves a specified number of bytes from the underlying pool's
251
+ # current position and advances the cursor by the same number.
252
+ #
253
+ # Error if the blob referred to by this cursor and this byte count lies
254
+ # outside the boundaries of the underlying pool, even partially.
255
+ #
256
+ def parse_blob size
257
+ raise 'Type mismatch' unless size.is_a? Integer
258
+ raise 'Invalid blob size' unless size >= 0
259
+ blob = @pool.get_blob(@offset, size)
260
+ @offset += size
261
+ return blob
262
+ end
263
+
264
+ #
265
+ # Moves the cursor forwards by +delta+ bytes, passing a part of the
266
+ # byte sequence without parsing.
267
+ #
268
+ # Error if +delta+ is negative.
269
+ #
270
+ def skip delta
271
+ raise 'Type mismatch' unless delta.is_a? Integer
272
+ raise 'Invalid byte count' if delta < 0
273
+ @offset += delta
274
+ return
275
+ end
276
+
277
+ #
278
+ # Moves the cursor backwards by +delta+ bytes.
279
+ #
280
+ # Error if +delta+ is negative.
281
+ #
282
+ def unskip delta
283
+ raise 'Type mismatch' unless delta.is_a? Integer
284
+ raise 'Invalid byte count' if delta < 0
285
+ @offset -= delta
286
+ return
287
+ end
288
+
289
+ #
290
+ # Returns +true+ if the cursor has passed the last byte currently in
291
+ # the underlying pool or +false+ otherwise.
292
+ #
293
+ def eof?
294
+ return @offset >= @pool.size
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,257 @@
1
+ # $Id: hexdump.rb 1275 2009-10-03 10:28:54Z digwuren $
2
+
3
+ module Baikal
4
+ #
5
+ # Hexdumps bytes from +data_source+ into the given +port+ (by default,
6
+ # +$stdout+) using the given format (by default, +DEFAULT_HEXDUMP_FORMAT+).
7
+ # Uses the +length+ method of +data_source+ to determine byte count and the
8
+ # +[]+ method with integer range arguments to extract row-sized slices.
9
+ # +data_source+ being a +String+ instance behaves as expected.
10
+ #
11
+ def Baikal::hexdump data_source, port = $stdout, format = Hexdump::DEFAULT_HEXDUMP_FORMAT
12
+ row = Hexdump::Row.new
13
+ row.expected_size = format.bytes_per_row
14
+ # iterate over rows
15
+ row.offset = 0
16
+ block_row_counter = 0
17
+ while row.offset < data_source.bytesize do
18
+ if format.rows_per_block != 0 then
19
+ block_row_counter += 1
20
+ if block_row_counter == format.rows_per_block then
21
+ port.puts # block separator
22
+ block_row_counter = 0
23
+ end
24
+ end
25
+ row.data = data_source.unpack "@#{row.offset} C#{format.bytes_per_row}"
26
+ format.format_row row, port
27
+ row.offset += format.bytes_per_row
28
+ end
29
+ return
30
+ end
31
+
32
+ module Hexdump
33
+ #
34
+ # Represents the row currently being processed by +hexdump+.
35
+ #
36
+ class Row
37
+ #
38
+ # Offset of the first byte on this row.
39
+ #
40
+ attr_accessor :offset
41
+
42
+ #
43
+ # The row's expected size, as per +Hexdump::Format#bytes_per_row+.
44
+ #
45
+ attr_accessor :expected_size
46
+
47
+ #
48
+ # Bytes of this row in an +Array+ instance. For the last row, some of
49
+ # the trailing elements may be +nil+.
50
+ #
51
+ attr_accessor :data
52
+ end
53
+
54
+ #
55
+ # Represents a particular field of a hexdump format. Abstract class; see
56
+ # +Field::Offset+, +Field::Decoration+, and +Field::Data+ as practical
57
+ # hexdump fields.
58
+ #
59
+ class Field
60
+ #
61
+ # Returns a string representing this field's contribution to dumping
62
+ # +row+, an instance of +Hexdump::Row+
63
+ #
64
+ def format row
65
+ end
66
+
67
+ #
68
+ # The +Offset+ field outputs the offset of a hexdump row, formatted
69
+ # via a printf template.
70
+ #
71
+ class Offset < Field
72
+ def initialize template
73
+ super()
74
+ @template = template
75
+ return
76
+ end
77
+
78
+ def format row
79
+ return sprintf(@template, row.offset)
80
+ end
81
+ end
82
+
83
+ #
84
+ # The +Decoration+ field outputs a literal string.
85
+ #
86
+ class Decoration < Field
87
+ def initialize content
88
+ super()
89
+ @content = content
90
+ return
91
+ end
92
+
93
+ def format row
94
+ return @content
95
+ end
96
+ end
97
+
98
+ #
99
+ # The +Data+ field outputs data on a hexdump row, formatted using a
100
+ # supplied +Proc+ instance. +grouping_rules+ are specified as
101
+ # pairs of group size and separator. All group sizes are processed
102
+ # in parallel. In group boundaries where multiple grouping rules
103
+ # would match, only the leftmost one is used.
104
+ #
105
+ class Data < Field
106
+ def initialize formatter, *grouping_rules
107
+ super()
108
+ @formatter = formatter
109
+ @grouping_rules = grouping_rules
110
+ return
111
+ end
112
+
113
+ def format row
114
+ output = ""
115
+ (0 ... row.expected_size).each do |column|
116
+ if column != 0 then
117
+ rule = @grouping_rules.detect{|divisor, separator| column % divisor == 0}
118
+ output << rule[1] if rule
119
+ end
120
+ output << @formatter.call(row.data[column])
121
+ end
122
+ return output
123
+ end
124
+
125
+ #
126
+ # Formats the byte as a zero-padded two-digit lowercase hexadecimal number.
127
+ #
128
+ # :stopdoc: Unfortunately, RDoc gets confused by thunk constants.
129
+ LOWERCASE_HEX = proc do |value|
130
+ if value then
131
+ raise 'Type mismatch' unless value.is_a? Integer
132
+ sprintf("%02x", value)
133
+ else
134
+ " "
135
+ end
136
+ end
137
+
138
+ #
139
+ # Formats the byte as a zero-padded two-digit uppercase hexadecimal number.
140
+ #
141
+ UPPERCASE_HEX = proc do |value|
142
+ if value then
143
+ raise 'Type mismatch' unless value.is_a? Integer
144
+ sprintf("%02X", value)
145
+ else
146
+ " "
147
+ end
148
+ end
149
+
150
+ #
151
+ # Formats the byte as a zero-padded three-digit octal number.
152
+ #
153
+ OCTAL = proc do |value|
154
+ if value then
155
+ raise 'Type mismatch' unless value.is_a? Integer
156
+ sprintf("%03o", value)
157
+ else
158
+ " "
159
+ end
160
+ end
161
+
162
+ #
163
+ # Formats the byte as a space-padded three-digit decimal number.
164
+ #
165
+ DECIMAL = proc do |value|
166
+ if value then
167
+ raise 'Type mismatch' unless value.is_a? Integer
168
+ sprintf("%3i", value)
169
+ else
170
+ " "
171
+ end
172
+ end
173
+
174
+ #
175
+ # Formats the byte as an ASCII character. Nonprintable characters are
176
+ # replaced by a period.
177
+ #
178
+ ASCII = proc do |value|
179
+ if value then
180
+ raise 'Type mismatch' unless value.is_a? Integer
181
+ value >= 0x20 && value <= 0x7E ? value.chr : "."
182
+ else
183
+ " "
184
+ end
185
+ end
186
+
187
+ #
188
+ # Decodes the byte as a Latin-1 character and formats it in UTF-8.
189
+ # Nonprintable characters are replaced by a period, as in
190
+ # +ASCII+.
191
+ #
192
+ LATIN1 = proc do |value|
193
+ if value then
194
+ raise 'Type mismatch' unless value.is_a? Integer
195
+ if (0x20 .. 0x7E).include? value or (0xA0 .. 0xFF).include? value then
196
+ [value].pack('U')
197
+ else
198
+ "."
199
+ end
200
+ else
201
+ " "
202
+ end
203
+ end
204
+
205
+ # :startdoc:
206
+ end
207
+ end
208
+
209
+ #
210
+ # Describes a textual hexdump format.
211
+ #
212
+ class Format
213
+ attr_reader :rows_per_block # zero indicates no block separation
214
+ attr_reader :bytes_per_row # zero indicates no group separation
215
+ attr_reader :fields
216
+
217
+ #
218
+ # Creates a new +Hexdump::Format+ instance with the specified structure.
219
+ # +bytes_per_row+ specifies the number of bytes to be listed on every
220
+ # row; +fields+ (a list of +Hexdump::Field+ instances) contains the
221
+ # formatting rules. +rows_per_block+, if given and nonzero, will cause
222
+ # an empty line to be printed after every block of that many rows.
223
+ #
224
+ def initialize bytes_per_row, fields, rows_per_block = 0
225
+ super()
226
+ @bytes_per_row = bytes_per_row
227
+ @fields = fields
228
+ @rows_per_block = rows_per_block
229
+ return
230
+ end
231
+
232
+ #
233
+ # Formats a given +row+ (an instance of +Hexdump::Row+) according to
234
+ # formatting rules embodied in this +Hexdump_Format+ instance and
235
+ # outputs the result into the given +port+
236
+ #
237
+ def format_row row, port
238
+ raise 'Type mismatch' unless row.is_a? Hexdump::Row
239
+ port.puts @fields.map{|field| field.format(row)}.join('')
240
+ return
241
+ end
242
+ end
243
+
244
+ #
245
+ # The default hexdump format uses five-digit offsets (fits up to a
246
+ # mebibyte of data, which should be enough for everybody) and lists the
247
+ # content on sixteen bytes per row, both in hexadecimal (grouped by
248
+ # four bytes) and ASCII-or-dot form.
249
+ #
250
+ DEFAULT_HEXDUMP_FORMAT = Format.new(16, [
251
+ Field::Offset.new("%05X: "),
252
+ Field::Data.new(Field::Data::UPPERCASE_HEX, [4, ' '], [1, ' ']),
253
+ Field::Decoration.new(" "),
254
+ Field::Data.new(Field::Data::ASCII),
255
+ ])
256
+ end
257
+ end