binary_finery 0.0.1
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/LICENSE +19 -0
- data/README +61 -0
- data/README.md +61 -0
- data/Rakefile +19 -0
- data/lib/binary_finery.rb +348 -0
- data/test/helper.rb +75 -0
- data/test/test_binary_finery.rb +303 -0
- metadata +88 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Michelangelo Altamore
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
19
|
+
|
data/README
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Binary Finary
|
2
|
+
=============
|
3
|
+
|
4
|
+
Mixes in a _fluent interface_ to any IO entity for reading and
|
5
|
+
writing binary data. It handles (de)serialization of:
|
6
|
+
|
7
|
+
- Integer numbers
|
8
|
+
- Null terminated strings
|
9
|
+
- Fixed size strings
|
10
|
+
|
11
|
+
|
12
|
+
Requirements
|
13
|
+
------------
|
14
|
+
|
15
|
+
Binary Finary assumes that the stream where
|
16
|
+
is mixed in, provides the following methods:
|
17
|
+
|
18
|
+
- read or read_nonblock
|
19
|
+
- write or write_nonblock
|
20
|
+
|
21
|
+
|
22
|
+
It will run under Ruby version 1.8.7 or newer.
|
23
|
+
|
24
|
+
|
25
|
+
Examples
|
26
|
+
--------
|
27
|
+
|
28
|
+
File.open(my_file.bin) do |f|
|
29
|
+
f.extend(BinaryFinary)
|
30
|
+
version = f.read_uint16_big
|
31
|
+
length = f.read_uint32_little
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
Install
|
36
|
+
-------
|
37
|
+
|
38
|
+
$ gem install binary_finary
|
39
|
+
|
40
|
+
|
41
|
+
Contributing
|
42
|
+
------------
|
43
|
+
|
44
|
+
If you'd like to hack on, please follow these instructions.
|
45
|
+
To get all of the dependencies, install the gem first.
|
46
|
+
|
47
|
+
1. Fork the project and clone down your fork
|
48
|
+
2. Create a branch with a descriptive name to contain your change
|
49
|
+
4. Hack away
|
50
|
+
5. Add tests and make sure everything still passes by running rake
|
51
|
+
6. Do not change the version number, I will do that on my end
|
52
|
+
7. If necessary, rebase your commits into logical chunks, without errors
|
53
|
+
8. Push the branch up to GitHub
|
54
|
+
9. Send me (altamic) a pull request for your branch
|
55
|
+
|
56
|
+
|
57
|
+
Copyright
|
58
|
+
=========
|
59
|
+
|
60
|
+
© Copyright 2011 Michelangelo Altamore. See LICENSE for details.
|
61
|
+
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Binary Finary
|
2
|
+
=============
|
3
|
+
|
4
|
+
Mixes in a _fluent interface_ to any IO entity for reading and
|
5
|
+
writing binary data. It handles (de)serialization of:
|
6
|
+
|
7
|
+
- Integer numbers
|
8
|
+
- Null terminated strings
|
9
|
+
- Fixed size strings
|
10
|
+
|
11
|
+
|
12
|
+
Requirements
|
13
|
+
------------
|
14
|
+
|
15
|
+
Binary Finary assumes that the stream where
|
16
|
+
is mixed in, provides the following methods:
|
17
|
+
|
18
|
+
- read or read_nonblock
|
19
|
+
- write or write_nonblock
|
20
|
+
|
21
|
+
|
22
|
+
It will run under Ruby version 1.8.7 or newer.
|
23
|
+
|
24
|
+
|
25
|
+
Examples
|
26
|
+
--------
|
27
|
+
|
28
|
+
File.open(my_file.bin) do |f|
|
29
|
+
f.extend(BinaryFinary)
|
30
|
+
version = f.read_uint16_big
|
31
|
+
length = f.read_uint32_little
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
Install
|
36
|
+
-------
|
37
|
+
|
38
|
+
$ gem install binary_finary
|
39
|
+
|
40
|
+
|
41
|
+
Contributing
|
42
|
+
------------
|
43
|
+
|
44
|
+
If you'd like to hack on, please follow these instructions.
|
45
|
+
To get all of the dependencies, install the gem first.
|
46
|
+
|
47
|
+
1. Fork the project and clone down your fork
|
48
|
+
2. Create a branch with a descriptive name to contain your change
|
49
|
+
4. Hack away
|
50
|
+
5. Add tests and make sure everything still passes by running rake
|
51
|
+
6. Do not change the version number, I will do that on my end
|
52
|
+
7. If necessary, rebase your commits into logical chunks, without errors
|
53
|
+
8. Push the branch up to GitHub
|
54
|
+
9. Send me (altamic) a pull request for your branch
|
55
|
+
|
56
|
+
|
57
|
+
Copyright
|
58
|
+
=========
|
59
|
+
|
60
|
+
© Copyright 2011 Michelangelo Altamore. See LICENSE for details.
|
61
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift('lib')
|
4
|
+
|
5
|
+
desc "Open an irb session preloaded with this library"
|
6
|
+
task :console do
|
7
|
+
sh "irb -rubygems -r ./lib/binary_finery.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
require 'rake/testtask'
|
13
|
+
Rake::TestTask.new(:test) do |test|
|
14
|
+
test.pattern = 'test/**/{helper,test_*}.rb'
|
15
|
+
test.warning = true
|
16
|
+
test.verbose = true
|
17
|
+
end
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,348 @@
|
|
1
|
+
##
|
2
|
+
# BinaryFinery mixin is meant to be used as a
|
3
|
+
# fluent interface to any IO entity for reading
|
4
|
+
# or writing binary data.
|
5
|
+
# This module assumes that read(n) and write(str)
|
6
|
+
# methods are available where this module is mixed in.
|
7
|
+
# It handles (de)serialization of:
|
8
|
+
#
|
9
|
+
# - Integer numbers ✓
|
10
|
+
# - Null terminated strings ✓
|
11
|
+
# - Fixed size strings ✓
|
12
|
+
#
|
13
|
+
|
14
|
+
# In order to be plaftorm agnostic, Binary inspects
|
15
|
+
# at load time machine's capabilities and adjust its
|
16
|
+
# own operations accordingly.
|
17
|
+
#
|
18
|
+
# BinaryFinary performs operations accepted by the
|
19
|
+
# following grammar:
|
20
|
+
#
|
21
|
+
# operation ::= 'read' | 'write'
|
22
|
+
# integer_type ::= 'uint' | 'int'
|
23
|
+
# bits ::= '8' | '16' | '32' | '64' | '128' | '256'
|
24
|
+
# endianness ::= 'native' | 'little' | 'big' | 'network'
|
25
|
+
# OP_INT ::= operation '_' integer_type bits ('_' endianness)?
|
26
|
+
#
|
27
|
+
# str_padding ::= 'null_padded' | 'c_'
|
28
|
+
# string_flavor ::= 'string' | 'fixed_string' | 'binary_string'
|
29
|
+
# str_preposition ::= '_of_'
|
30
|
+
# integer ::= [0-9]+
|
31
|
+
# str_size ::= 'bytes'
|
32
|
+
# OP_STR ::= operation '_' (str_padding '_')?
|
33
|
+
# string_flavor '_' str_preposition integer '_' str_size
|
34
|
+
#
|
35
|
+
module BinaryFinery
|
36
|
+
OP_RE = /(read|write)_/
|
37
|
+
INT_RE = /(uint|int)(8|16|32|64)_?(native|little|big|network)?/
|
38
|
+
STR_RE = /(null_padded|c_)?((binary_|fixed_)?string)(_of_)[0-9]+(bytes)/
|
39
|
+
OP_INT_RE = Regexp::compile(OP_RE.source + INT_RE.source)
|
40
|
+
OP_STR_RE = Regexp::compile(OP_RE.source + STR_RE.source)
|
41
|
+
|
42
|
+
KNOWN_RE = Regexp::compile(OP_INT_RE.source + OP_STR_RE.source)
|
43
|
+
|
44
|
+
NUL = 0.chr
|
45
|
+
|
46
|
+
# Returns the number of bytes used to encode an integer number
|
47
|
+
INTEGER_SIZE_IN_BYTES = module_eval { 1.size }
|
48
|
+
def integer_size_in_bytes() INTEGER_SIZE_IN_BYTES end
|
49
|
+
alias :word_size_in_bytes :integer_size_in_bytes
|
50
|
+
|
51
|
+
def little_endian_byte_order() :little end
|
52
|
+
def big_endian_byte_order() :big end
|
53
|
+
alias :network_byte_order :big_endian_byte_order
|
54
|
+
|
55
|
+
# A machine architecture is said to be little endian if puts first the
|
56
|
+
# LSB. We evaluate the first byte of the number 1 packed as an integer.
|
57
|
+
# While first_byte is a Fixnum in Ruby 1.8.x, it is a string in 1.9.x;
|
58
|
+
# in latter case we employ the ord method to obtain the ordinal number
|
59
|
+
# associated,if is the case. Underlying machine is l.e. if the LSB is 1.
|
60
|
+
|
61
|
+
NATIVE_BYTE_ORDER = module_eval do
|
62
|
+
first_byte = [1].pack('i')[0]
|
63
|
+
first_byte = first_byte.ord if RUBY_VERSION =~ /^1\.9/
|
64
|
+
first_byte == 1 ? :little : :big
|
65
|
+
end
|
66
|
+
|
67
|
+
def native_byte_order() NATIVE_BYTE_ORDER end
|
68
|
+
|
69
|
+
def little_endian_platform?() native_byte_order.equal? :little end
|
70
|
+
def big_endian_platform?() native_byte_order.equal? :big end
|
71
|
+
alias :network_endian_platform? :big_endian_platform?
|
72
|
+
|
73
|
+
@@pack_mappings = {
|
74
|
+
1 => { :uint => { :native => 'C' },
|
75
|
+
:int => { :native => 'c' } },
|
76
|
+
2 => { :uint => { :native => 'S', :little => 'v', :big => 'n' },
|
77
|
+
:int => { :native => 's', :little => 'v', :big => 'n' } },
|
78
|
+
4 => { :uint => { :native => 'L', :little => 'V', :big => 'N' },
|
79
|
+
:int => { :native => 'l', :little => 'V', :big => 'N' } },
|
80
|
+
8 => { :uint => { :native => 'Q' },
|
81
|
+
:int => { :native => 'q' } } }
|
82
|
+
|
83
|
+
BIT_MASK = { 4 => 0xF, 8 => 0xFF, 16 => 0xFFFFF,
|
84
|
+
32 => 0xFFFFFFFF, 64 => 0xFFFFFFFFFFFFFFFF,
|
85
|
+
128 => 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF }
|
86
|
+
|
87
|
+
DEFAULT_BIT_MASK = module_eval { (1.size >> 1) * 8 }
|
88
|
+
|
89
|
+
def msb(num, bits=DEFAULT_BIT_MASK) (num & BIT_MASK[bits]) end
|
90
|
+
def lsb(num, bits=DEFAULT_BIT_MASK) ((num >> bits) & BIT_MASK[bits]) end
|
91
|
+
|
92
|
+
def split_msb_lsb(num, bits)
|
93
|
+
[msb(num, bits), lsb(num,bits) ]
|
94
|
+
end
|
95
|
+
|
96
|
+
def concat(msb, lsb, bits)
|
97
|
+
lsb + (msb << bits)
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_uint256_little
|
101
|
+
lsb = read_uint128_little
|
102
|
+
msb = read_uint128_little
|
103
|
+
concat(msb, lsb, 128)
|
104
|
+
end
|
105
|
+
|
106
|
+
def read_int256_little
|
107
|
+
val = read_uint256_little
|
108
|
+
val > (2**256 - 1) ? val : -val
|
109
|
+
end
|
110
|
+
|
111
|
+
def read_uint256_big
|
112
|
+
msb = read_uint64_big
|
113
|
+
lsb = read_uint64_big
|
114
|
+
concat(msb, lsb, 128)
|
115
|
+
end
|
116
|
+
alias :read_uint256_network :read_uint256_big
|
117
|
+
|
118
|
+
def read_uint256_native
|
119
|
+
little_endian_platform? ? read_uint256_little : read_uint256_big
|
120
|
+
end
|
121
|
+
alias :read_uint256 :read_uint256_native
|
122
|
+
|
123
|
+
def write_uint256_little(n)
|
124
|
+
msb, lsb = split_msb_lsb(n, 128)
|
125
|
+
write_uint128_little(msb)
|
126
|
+
write_uint128_little(lsb)
|
127
|
+
end
|
128
|
+
|
129
|
+
def write_int256_little(n)
|
130
|
+
n = 2**256 - n
|
131
|
+
write_uint256_little(n)
|
132
|
+
end
|
133
|
+
|
134
|
+
def write_uint256_big(n)
|
135
|
+
lsb, msb = split_msb_lsb(n, 128)
|
136
|
+
write_uint128_big(msb)
|
137
|
+
write_uint128_big(lsb)
|
138
|
+
end
|
139
|
+
|
140
|
+
def write_uint256_native(n)
|
141
|
+
little_endian_platform? ? write_uint256_little(n) : write_uint256_big(n)
|
142
|
+
end
|
143
|
+
alias :write_uint256 :write_uint256_native
|
144
|
+
|
145
|
+
def read_uint128_little
|
146
|
+
lsb = read_uint64_little
|
147
|
+
msb = read_uint64_little
|
148
|
+
concat(msb, lsb, 64)
|
149
|
+
end
|
150
|
+
|
151
|
+
def read_int128_little
|
152
|
+
val = read_uint128_little
|
153
|
+
val > (2**128 - 1) ? val : -val
|
154
|
+
end
|
155
|
+
|
156
|
+
def read_uint128_big
|
157
|
+
msb = read_uint64_big
|
158
|
+
lsb = read_uint64_big
|
159
|
+
concat(msb, lsb, 64)
|
160
|
+
end
|
161
|
+
alias :read_uint128_network :read_uint128_big
|
162
|
+
|
163
|
+
def read_uint128_native
|
164
|
+
little_endian_platform? ? read_uint128_little : read_uint128_big
|
165
|
+
end
|
166
|
+
alias :read_uint128 :read_uint128_native
|
167
|
+
|
168
|
+
def write_uint128_little(n)
|
169
|
+
lsb, msb = split_msb_lsb(n, 64)
|
170
|
+
write_uint64_little(lsb)
|
171
|
+
write_uint64_little(msb)
|
172
|
+
end
|
173
|
+
|
174
|
+
def write_int128_little(n)
|
175
|
+
n = 2**128 - n
|
176
|
+
write_uint128_little(n)
|
177
|
+
end
|
178
|
+
|
179
|
+
def write_uint128_big(n)
|
180
|
+
lsb, msb = split_msb_lsb(n, 64)
|
181
|
+
write_uint64_big(msb)
|
182
|
+
write_uint64_big(lsb)
|
183
|
+
end
|
184
|
+
|
185
|
+
def write_uint128_native(n)
|
186
|
+
little_endian_platform? ? write_uint128_little(n) : write_uint128_big(n)
|
187
|
+
end
|
188
|
+
alias :write_uint128 :write_uint128_native
|
189
|
+
|
190
|
+
def read_uint64_little
|
191
|
+
lsb = readn_unpack(4, 'L', :little)
|
192
|
+
msb = readn_unpack(4, 'L', :little)
|
193
|
+
concat(msb, lsb, 32)
|
194
|
+
end
|
195
|
+
|
196
|
+
def read_uint64_big
|
197
|
+
msb = readn_unpack(4, 'L', :big)
|
198
|
+
lsb = readn_unpack(4, 'L', :big)
|
199
|
+
concat(msb, lsb, 32)
|
200
|
+
end
|
201
|
+
alias :read_uint64_network :read_uint64_big
|
202
|
+
|
203
|
+
def read_uint64_native
|
204
|
+
little_endian_platform? ? read_uint64_little : read_uint64_big
|
205
|
+
end
|
206
|
+
alias :read_uint64 :read_uint64_native
|
207
|
+
|
208
|
+
def write_uint64_little(n)
|
209
|
+
lsb, msb = split_msb_lsb(n,32)
|
210
|
+
write_pack(lsb, 'L', :little)
|
211
|
+
write_pack(msb, 'L', :little)
|
212
|
+
end
|
213
|
+
|
214
|
+
def write_uint64_big(n)
|
215
|
+
lsb, msb = split_msb_lsb(n,32)
|
216
|
+
write_pack(msb, 'L', :big)
|
217
|
+
write_pack(lsb, 'L', :big)
|
218
|
+
end
|
219
|
+
|
220
|
+
def write_uint64_native(n)
|
221
|
+
little_endian_platform? ? write_uint64_little(n) : write_uint64_big(n)
|
222
|
+
end
|
223
|
+
alias :write_uint64 :write_uint64_native
|
224
|
+
|
225
|
+
# obtains the correct pack format for the arguments
|
226
|
+
def format(byte_size, type, byte_order)
|
227
|
+
byte_order = :native if byte_order.nil?
|
228
|
+
byte_order = :big if byte_order.equal?(:network)
|
229
|
+
@@pack_mappings[byte_size][type][byte_order]
|
230
|
+
end
|
231
|
+
|
232
|
+
# read n bytes and unpack, swapping bytes as per endianness
|
233
|
+
def readn_unpack(size, template, byte_order=NATIVE_BYTE_ORDER)
|
234
|
+
str = readn(size)
|
235
|
+
str.reverse! if not native_byte_order.equal? byte_order # spotted problem in pack
|
236
|
+
str.unpack(template).first
|
237
|
+
end
|
238
|
+
|
239
|
+
# read exactly n characters from the buffer, otherwise raise an exception.
|
240
|
+
def readn(n)
|
241
|
+
str = read(n)
|
242
|
+
raise "couldn't read #{n} characters." if str.nil? or str.size != n
|
243
|
+
str
|
244
|
+
end
|
245
|
+
|
246
|
+
def read_null_padded_string(size)
|
247
|
+
str = readn(size)
|
248
|
+
str.split(/NUL/).first or str
|
249
|
+
end
|
250
|
+
|
251
|
+
def read_c_string
|
252
|
+
readline(sep_string = NUL)
|
253
|
+
end
|
254
|
+
|
255
|
+
def read_string(opt={:size => nil, :padding => NUL})
|
256
|
+
if opt[:size]
|
257
|
+
read_fixed_size_string(opt[:size], opt[:padding])
|
258
|
+
else
|
259
|
+
read_c_string
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# writes a number and pack it, swapping bytes as per endianness
|
264
|
+
def write_pack(number, template, byte_order=NATIVE_BYTE_ORDER)
|
265
|
+
str = [number].pack(template)
|
266
|
+
str.reverse! if not native_byte_order.equal? byte_order # blame Array#pack
|
267
|
+
write(str)
|
268
|
+
end
|
269
|
+
|
270
|
+
# writes the string and appends NUL
|
271
|
+
def write_c_string(str)
|
272
|
+
#TODO: improve input validation
|
273
|
+
raise ArgumentError, "Invalid Ruby string" if str.include?(NUL)
|
274
|
+
write(str)
|
275
|
+
write(NUL)
|
276
|
+
end
|
277
|
+
|
278
|
+
def write_string(content, opt = {:padding => nil, :size => content.size})
|
279
|
+
return if not (opt[:size].kind_of? Integer)
|
280
|
+
output_string = content[0..opt[:size]]
|
281
|
+
output_string = output_string.ljust(opt[:size], opt[:padding]) if opt[:padding]
|
282
|
+
write(output_string)
|
283
|
+
end
|
284
|
+
|
285
|
+
def write_fixed_size_string(content="")
|
286
|
+
write_string(content, :size => content.size)
|
287
|
+
end
|
288
|
+
|
289
|
+
def write_null_padded_string(str, opt = {:size => str.size})
|
290
|
+
write_string(str, padding => "\000", :size => str.size)
|
291
|
+
end
|
292
|
+
|
293
|
+
def method_missing(method_name, *args, &block)
|
294
|
+
if method_name.to_s =~ OP_INT_RE
|
295
|
+
op, type, bits, byte_order = Regexp.last_match[1..4]
|
296
|
+
# string → sym
|
297
|
+
op, type, byte_order = [op, type].map!(&:to_sym)
|
298
|
+
# adjust bits to bytes
|
299
|
+
byte_size = (bits.to_i / 8)
|
300
|
+
# normalize endianness
|
301
|
+
byte_order = byte_order.to_sym unless byte_order.nil?
|
302
|
+
byte_order = :big if byte_order == 'network'
|
303
|
+
|
304
|
+
fmt = format(byte_size,type,byte_order)
|
305
|
+
|
306
|
+
case op
|
307
|
+
when :read
|
308
|
+
self.class.send :define_method, method_name do
|
309
|
+
readn_unpack(byte_size, fmt, byte_order)
|
310
|
+
end
|
311
|
+
self.send method_name
|
312
|
+
when :write
|
313
|
+
if (args.first.kind_of? Integer) && (args.size == 1)
|
314
|
+
self.class.send :define_method, method_name do |value|
|
315
|
+
write_pack(value, fmt, byte_order)
|
316
|
+
end
|
317
|
+
self.send method_name, args.first
|
318
|
+
end
|
319
|
+
end
|
320
|
+
elsif method_name.to_s =~ OP_STR_RE
|
321
|
+
op, string_flavor = Regexp.last_match[1..2]
|
322
|
+
case op
|
323
|
+
when :read
|
324
|
+
options = args.first.indexes(:size, :padding)
|
325
|
+
self.send :read_string, options
|
326
|
+
when :write
|
327
|
+
str, options = args.shift, args
|
328
|
+
self.send :write_string, str, options
|
329
|
+
end
|
330
|
+
else
|
331
|
+
super
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Tells the byte size required for the method passed as argument.
|
336
|
+
# When recognized.
|
337
|
+
def size_of(type, object=nil)
|
338
|
+
case
|
339
|
+
when type.to_s =~ INT_RE then Regexp.last_match[2].to_i / 8
|
340
|
+
when type.to_s =~ STR_RE then Regexp.last_match[-2].to_i
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def recognize?(type)
|
345
|
+
type.to_s =~ Regexp.union(INT_RE,STR_RE)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
# TODO: resort to test_unit where minitest
|
4
|
+
# is not available and don't require
|
5
|
+
# anything
|
6
|
+
begin; gem 'minitest' if RUBY_VERSION =~ /^1\.9/; rescue Gem::LoadError; end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'minitest/autorun'
|
10
|
+
rescue LoadError
|
11
|
+
require 'rubygems'
|
12
|
+
require 'minitest/autorun'
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'binary_finery'
|
16
|
+
|
17
|
+
module BinaryFinery
|
18
|
+
class TestCase < MiniTest::Unit::TestCase
|
19
|
+
#
|
20
|
+
# Test::Unit backwards compatibility
|
21
|
+
#
|
22
|
+
begin
|
23
|
+
alias :assert_no_match :refute_match
|
24
|
+
alias :assert_not_nil :refute_nil
|
25
|
+
alias :assert_raise :assert_raises
|
26
|
+
alias :assert_not_equal :refute_equal
|
27
|
+
end if RUBY_VERSION =~ /^1\.9/
|
28
|
+
|
29
|
+
def fixture_path
|
30
|
+
File.join(File.dirname(__FILE__), 'fixtures')
|
31
|
+
end
|
32
|
+
|
33
|
+
def lib_path
|
34
|
+
File.join(File.dirname(__FILE__), '../lib')
|
35
|
+
end
|
36
|
+
|
37
|
+
def random_string(length)
|
38
|
+
(0...length).inject("") { |m,n| m << (?A + rand(25)).chr }
|
39
|
+
end
|
40
|
+
|
41
|
+
def random_integer(bits, type=:uint)
|
42
|
+
(type.equal? :int) ? -1*rand(2**(bits-1)) : rand(2**bits)
|
43
|
+
end
|
44
|
+
|
45
|
+
def pack_integer(integer, opt={:bytes => 4, :type => :uint, :endian => nil})
|
46
|
+
bytes, type, endian = opt[:bytes], opt[:type], opt[:endian]
|
47
|
+
endian = :big if endian == :network
|
48
|
+
endian = :native if endian == nil
|
49
|
+
|
50
|
+
pack_mapping = {
|
51
|
+
1 => { :uint => { :native => 'C' },
|
52
|
+
:int => { :native => 'c' } },
|
53
|
+
2 => { :uint => { :native => 'S', :little => 'v', :big => 'n' },
|
54
|
+
:int => { :native => 's', :little => 'v', :big => 'n' } },
|
55
|
+
4 => { :uint => { :native => 'L', :little => 'V', :big => 'N' },
|
56
|
+
:int => { :native => 'l', :little => 'V', :big => 'N' } },
|
57
|
+
8 => { :uint => { :native => 'Q' },
|
58
|
+
:int => { :native => 'q' } } }
|
59
|
+
|
60
|
+
if bytes == 8 && (endian == :little || endian == :big)
|
61
|
+
bytes = 4
|
62
|
+
format = pack_mapping[bytes][type][endian]
|
63
|
+
msb, lsb = (integer & 0xFFFFFFFF), (integer >> 16 & 0xFFFFFFFF)
|
64
|
+
array = [msb, lsb]
|
65
|
+
array.reverse! if [1,"\x01"].include?([1].pack('i')[0]) # little endian
|
66
|
+
array.pack(format*2)
|
67
|
+
else
|
68
|
+
format = pack_mapping[bytes][type][endian]
|
69
|
+
[integer].pack(format)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
def StringIO.of_size(size, &block)
|
5
|
+
if (not size.kind_of?(Integer)) || (size < 0)
|
6
|
+
raise ArgumentError, 'positive integer required'
|
7
|
+
end
|
8
|
+
new(0.chr * size, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
class TestBinaryFinery < BinaryFinery::TestCase
|
12
|
+
def setup
|
13
|
+
@mockup = StringIO.new.extend(BinaryFinery)
|
14
|
+
end
|
15
|
+
|
16
|
+
def host_endianness
|
17
|
+
first_byte = [1].pack('i')[0]
|
18
|
+
[1,"\x01"].include?(first_byte) ? :little : :big
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_detect_platform_endianness
|
22
|
+
assert_equal BinaryFinery::NATIVE_BYTE_ORDER, host_endianness
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_detect_platform_integer_size
|
26
|
+
assert_equal BinaryFinery::INTEGER_SIZE_IN_BYTES, 1.size
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_buffer_includes_binary
|
30
|
+
assert(@mockup.class.included_modules & [BinaryFinery])
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_read_correctly_1_byte
|
34
|
+
buf = StringIO.of_size(2).extend(BinaryFinery)
|
35
|
+
buf.rewind
|
36
|
+
buf.write_uint8(0x55)
|
37
|
+
buf.rewind
|
38
|
+
|
39
|
+
assert_equal 0x55, buf.read_uint8
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_does_not_care_about_overflow
|
43
|
+
overflow = 0xFF + 1
|
44
|
+
buf = StringIO.of_size(1).extend(BinaryFinery)
|
45
|
+
buf.rewind
|
46
|
+
buf.write_uint8(overflow)
|
47
|
+
buf.rewind
|
48
|
+
|
49
|
+
assert_equal 0, buf.read_uint8
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_read_correctly_2_bytes
|
53
|
+
buf = StringIO.of_size(2).extend(BinaryFinery)
|
54
|
+
buf.rewind
|
55
|
+
buf.write_uint16_little(0x55)
|
56
|
+
buf.rewind
|
57
|
+
|
58
|
+
assert_equal 0x55, buf.read_uint16_little
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_does_not_care_about_overflow_for_uint32
|
62
|
+
overflow = 0xFFFFFFFF + 1
|
63
|
+
buf = StringIO.of_size(4).extend(BinaryFinery)
|
64
|
+
buf.rewind
|
65
|
+
buf.write_uint32(overflow)
|
66
|
+
buf.rewind
|
67
|
+
|
68
|
+
assert_equal 0, buf.read_uint32
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_read_correctly_4_bytes
|
72
|
+
v2 = [0x32, 0x24, 0x00, 0x00]
|
73
|
+
v2.reverse! if host_endianness.equal? :little
|
74
|
+
packed = v2.pack("C" * v2.size)
|
75
|
+
buf = StringIO.new(packed).extend(BinaryFinery)
|
76
|
+
|
77
|
+
buf.size.times do |i|
|
78
|
+
assert_equal v2[i], buf.read_uint8
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_write_fixed_size_string
|
83
|
+
content = 'new blips are arbitrarily created in the electronic transaction system of the Federal Reserve (known as FedWire), no outside detection is possible whatsoever because there is no outside system that verifies (or even can verify) the total quantity of FedWire deposits'
|
84
|
+
buf = StringIO.of_size(content.size).extend(BinaryFinery)
|
85
|
+
buf.rewind
|
86
|
+
buf.write_fixed_size_string(content)
|
87
|
+
buf.rewind
|
88
|
+
|
89
|
+
assert_equal content, buf.gets.chomp
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_find_bytesize_for_int16_little
|
93
|
+
buf = StringIO.new('').extend(BinaryFinery)
|
94
|
+
assert_equal 2, buf.size_of('read_int16_little')
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_write_uint64_native
|
98
|
+
n = 0xB70A4625F1A224CF # Big endian
|
99
|
+
bytes = [0xB7, 0x0A, 0x46, 0x25, 0xF1, 0xA2, 0x24, 0xCF]
|
100
|
+
bytes.reverse! if BinaryFinery::NATIVE_BYTE_ORDER.equal? :little
|
101
|
+
|
102
|
+
buf = StringIO.of_size(8).extend(BinaryFinery)
|
103
|
+
|
104
|
+
buf.rewind
|
105
|
+
buf.write_uint64(n)
|
106
|
+
buf.rewind
|
107
|
+
|
108
|
+
bytes.each_with_index do |byte, i|
|
109
|
+
buf.pos = i
|
110
|
+
assert_equal byte, buf.read_uint8
|
111
|
+
end
|
112
|
+
buf.rewind
|
113
|
+
expected = bytes.pack("C"*bytes.size)
|
114
|
+
assert_equal n, buf.read_uint64
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_write_uint64_little
|
118
|
+
# write_uint64_little(1) # services
|
119
|
+
n = 1
|
120
|
+
buf = StringIO.of_size(8).extend(BinaryFinery)
|
121
|
+
buf.write_uint64_little(n)
|
122
|
+
buf.rewind
|
123
|
+
|
124
|
+
assert_equal n, buf.read_uint64_little
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_write_uint128_little
|
128
|
+
# write_uint128_network(0xFFFF00000000) # ip_address
|
129
|
+
n = 0xFFFF00000000
|
130
|
+
buf = StringIO.of_size(16).extend(BinaryFinery)
|
131
|
+
buf.write_uint128_little(n)
|
132
|
+
buf.rewind
|
133
|
+
|
134
|
+
assert_equal n, buf.read_uint128_little
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_write_uint128_big
|
138
|
+
n = 0xFFFF00000000
|
139
|
+
buf = StringIO.of_size(16).extend(BinaryFinery)
|
140
|
+
buf.write_uint128_big(n)
|
141
|
+
buf.rewind
|
142
|
+
|
143
|
+
assert_equal n, buf.read_uint128_big
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_read_uint128_little
|
147
|
+
n = rand(2**128)
|
148
|
+
assert_equal 16, n.size
|
149
|
+
buf = StringIO.of_size(16).extend(BinaryFinery)
|
150
|
+
buf.write_uint128_little(n)
|
151
|
+
buf.rewind
|
152
|
+
|
153
|
+
assert_equal n, buf.read_uint128_little
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_read_uint256_little
|
157
|
+
n = rand(2**256)
|
158
|
+
assert_equal 32, n.size
|
159
|
+
buf = StringIO.of_size(32).extend(BinaryFinery)
|
160
|
+
buf.write_uint256_little(n)
|
161
|
+
buf.rewind
|
162
|
+
|
163
|
+
assert_equal n, buf.read_uint256_little
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_read_int128_little
|
167
|
+
n = rand(2**64)
|
168
|
+
buf = StringIO.of_size(16).extend(BinaryFinery)
|
169
|
+
buf.write_int128_little(n)
|
170
|
+
buf.rewind
|
171
|
+
|
172
|
+
assert_equal n, buf.read_int128_little
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_read_int128_little
|
176
|
+
n = -rand(2**64)
|
177
|
+
buf = StringIO.of_size(16).extend(BinaryFinery)
|
178
|
+
buf.write_int128_little(n)
|
179
|
+
buf.rewind
|
180
|
+
|
181
|
+
assert_equal n, buf.read_int128_little
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_read_int256_little
|
185
|
+
n = rand(2**128)
|
186
|
+
buf = StringIO.of_size(32).extend(BinaryFinery)
|
187
|
+
buf.write_int256_little(n)
|
188
|
+
buf.rewind
|
189
|
+
|
190
|
+
assert_equal n, buf.read_int256_little
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_read_int256_little
|
194
|
+
n = -rand(2**128)
|
195
|
+
buf = StringIO.of_size(32).extend(BinaryFinery)
|
196
|
+
buf.write_int256_little(n)
|
197
|
+
buf.rewind
|
198
|
+
|
199
|
+
assert_equal n, buf.read_int256_little
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_write_null_padded_string
|
203
|
+
str = "ciao\000\000\000\000"
|
204
|
+
buf = StringIO.of_size(32).extend(BinaryFinery)
|
205
|
+
|
206
|
+
buf.write_string("ciao", :padding => "\000", :size => str.size)
|
207
|
+
buf.rewind
|
208
|
+
|
209
|
+
assert_equal str, buf.readn(str.size)
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_write_c_string
|
213
|
+
str = "ciao\000"
|
214
|
+
buf = StringIO.of_size(32).extend(BinaryFinery)
|
215
|
+
|
216
|
+
buf.write_c_string("ciao")
|
217
|
+
buf.rewind
|
218
|
+
|
219
|
+
assert_equal str, buf.read_c_string
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_read_null_padded_string
|
223
|
+
str = "ciao\000\000\000\000"
|
224
|
+
buf = StringIO.of_size(32).extend(BinaryFinery)
|
225
|
+
|
226
|
+
buf.write_string("ciao", :padding => "\000", :size => str.size)
|
227
|
+
buf.rewind
|
228
|
+
|
229
|
+
assert_equal str, buf.read_null_padded_string(8)
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
# test read
|
235
|
+
bytes_ary = [1,2,4,8]
|
236
|
+
|
237
|
+
bytes_ary.each do |bytes|
|
238
|
+
[:int, :uint].each do |type|
|
239
|
+
[:native, :little, :big, :network].each do |mapped_endian|
|
240
|
+
next if bytes.equal? 1
|
241
|
+
TestBinaryFinery.class_eval do
|
242
|
+
bits = bytes * 8
|
243
|
+
packing = { :bytes => bytes, :type => type, :endian => mapped_endian }
|
244
|
+
|
245
|
+
# read
|
246
|
+
read_method = "read_#{type}#{bits}_#{mapped_endian}"
|
247
|
+
define_method "test_#{read_method}" do
|
248
|
+
number = random_integer(bits, type)
|
249
|
+
packed_number = pack_integer(number, packing)
|
250
|
+
buf = StringIO.new(packed_number).extend(BinaryFinery)
|
251
|
+
|
252
|
+
assert number, buf.send(read_method)
|
253
|
+
end
|
254
|
+
|
255
|
+
# write
|
256
|
+
write_method = "write_#{type}#{bits}_#{mapped_endian}"
|
257
|
+
define_method "test_#{write_method}" do
|
258
|
+
number = random_integer(bits, type)
|
259
|
+
buf = StringIO.of_size(bytes).extend(BinaryFinery)
|
260
|
+
buf.send(write_method, number)
|
261
|
+
buf.rewind
|
262
|
+
|
263
|
+
assert number, buf.send(read_method)
|
264
|
+
end
|
265
|
+
|
266
|
+
#size
|
267
|
+
define_method "test_#{type}#{bits}_length_is_#{bytes}_bytes" do
|
268
|
+
assert bytes, StringIO.new('').extend(BinaryFinery).size_of(read_method)
|
269
|
+
assert bytes, StringIO.new('').extend(BinaryFinery).size_of(write_method)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
[nil].each do |no_endian_specified|
|
274
|
+
bits = bytes * 8
|
275
|
+
packing = { :bytes => bytes,
|
276
|
+
:type => type,
|
277
|
+
:endian => no_endian_specified }
|
278
|
+
|
279
|
+
TestBinaryFinery.class_eval do
|
280
|
+
read_method = "read_#{type}#{bits}"
|
281
|
+
define_method "test_#{read_method}" do
|
282
|
+
number = random_integer(bits, type)
|
283
|
+
packed = pack_integer(number, packing)
|
284
|
+
buf = StringIO.new(packed).extend(BinaryFinery)
|
285
|
+
|
286
|
+
assert number, buf.send(read_method)
|
287
|
+
end
|
288
|
+
|
289
|
+
write_method = "write_#{type}#{bits}"
|
290
|
+
define_method "test_#{write_method}" do
|
291
|
+
number = random_integer(bits, type)
|
292
|
+
buf = StringIO.of_size(bytes).extend(BinaryFinery)
|
293
|
+
buf.rewind
|
294
|
+
buf.send(write_method, number)
|
295
|
+
buf.rewind
|
296
|
+
|
297
|
+
assert number, buf.send(read_method)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: binary_finery
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michelangelo Altamore
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-07 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: minitest
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 11
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
version: 2.1.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
description: |-
|
37
|
+
BinaryFinery mixes in a fluent interface to any
|
38
|
+
IO entity for reading or writing binary data.
|
39
|
+
email: michelangelo@altamore.org
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- README
|
48
|
+
- README.md
|
49
|
+
- LICENSE
|
50
|
+
- Rakefile
|
51
|
+
- lib/binary_finery.rb
|
52
|
+
- test/helper.rb
|
53
|
+
- test/test_binary_finery.rb
|
54
|
+
homepage: https://github.com/altamic/binary_finary
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.10
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: A fluent interface for reading or writing binary data
|
87
|
+
test_files: []
|
88
|
+
|