jbangert-bindata 1.5.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.
Files changed (71) hide show
  1. data/.gitignore +1 -0
  2. data/BSDL +22 -0
  3. data/COPYING +52 -0
  4. data/ChangeLog.rdoc +204 -0
  5. data/Gemfile +2 -0
  6. data/INSTALL +11 -0
  7. data/NEWS.rdoc +164 -0
  8. data/README.md +54 -0
  9. data/Rakefile +13 -0
  10. data/bindata.gemspec +31 -0
  11. data/doc/manual.haml +407 -0
  12. data/doc/manual.md +1649 -0
  13. data/examples/NBT.txt +149 -0
  14. data/examples/gzip.rb +161 -0
  15. data/examples/ip_address.rb +22 -0
  16. data/examples/list.rb +124 -0
  17. data/examples/nbt.rb +178 -0
  18. data/lib/bindata.rb +33 -0
  19. data/lib/bindata/alignment.rb +83 -0
  20. data/lib/bindata/array.rb +335 -0
  21. data/lib/bindata/base.rb +388 -0
  22. data/lib/bindata/base_primitive.rb +214 -0
  23. data/lib/bindata/bits.rb +87 -0
  24. data/lib/bindata/choice.rb +216 -0
  25. data/lib/bindata/count_bytes_remaining.rb +35 -0
  26. data/lib/bindata/deprecated.rb +50 -0
  27. data/lib/bindata/dsl.rb +312 -0
  28. data/lib/bindata/float.rb +80 -0
  29. data/lib/bindata/int.rb +184 -0
  30. data/lib/bindata/io.rb +274 -0
  31. data/lib/bindata/lazy.rb +105 -0
  32. data/lib/bindata/offset.rb +91 -0
  33. data/lib/bindata/params.rb +135 -0
  34. data/lib/bindata/primitive.rb +135 -0
  35. data/lib/bindata/record.rb +110 -0
  36. data/lib/bindata/registry.rb +92 -0
  37. data/lib/bindata/rest.rb +35 -0
  38. data/lib/bindata/sanitize.rb +290 -0
  39. data/lib/bindata/skip.rb +48 -0
  40. data/lib/bindata/string.rb +145 -0
  41. data/lib/bindata/stringz.rb +96 -0
  42. data/lib/bindata/struct.rb +388 -0
  43. data/lib/bindata/trace.rb +94 -0
  44. data/lib/bindata/version.rb +3 -0
  45. data/setup.rb +1585 -0
  46. data/spec/alignment_spec.rb +61 -0
  47. data/spec/array_spec.rb +331 -0
  48. data/spec/base_primitive_spec.rb +238 -0
  49. data/spec/base_spec.rb +376 -0
  50. data/spec/bits_spec.rb +163 -0
  51. data/spec/choice_spec.rb +263 -0
  52. data/spec/count_bytes_remaining_spec.rb +38 -0
  53. data/spec/deprecated_spec.rb +31 -0
  54. data/spec/example.rb +21 -0
  55. data/spec/float_spec.rb +37 -0
  56. data/spec/int_spec.rb +216 -0
  57. data/spec/io_spec.rb +352 -0
  58. data/spec/lazy_spec.rb +217 -0
  59. data/spec/primitive_spec.rb +202 -0
  60. data/spec/record_spec.rb +530 -0
  61. data/spec/registry_spec.rb +108 -0
  62. data/spec/rest_spec.rb +26 -0
  63. data/spec/skip_spec.rb +27 -0
  64. data/spec/spec_common.rb +58 -0
  65. data/spec/string_spec.rb +300 -0
  66. data/spec/stringz_spec.rb +118 -0
  67. data/spec/struct_spec.rb +350 -0
  68. data/spec/system_spec.rb +380 -0
  69. data/tasks/manual.rake +36 -0
  70. data/tasks/rspec.rake +17 -0
  71. metadata +208 -0
@@ -0,0 +1,80 @@
1
+ require 'bindata/base_primitive'
2
+
3
+ module BinData
4
+ # Defines a number of classes that contain a floating point number.
5
+ # The float is defined by precision and endian.
6
+
7
+ module FloatingPoint #:nodoc: all
8
+ class << self
9
+ def define_methods(float_class, precision, endian)
10
+ float_class.module_eval <<-END
11
+ def do_num_bytes
12
+ #{create_num_bytes_code(precision)}
13
+ end
14
+
15
+ #---------------
16
+ private
17
+
18
+ def sensible_default
19
+ 0.0
20
+ end
21
+
22
+ def value_to_binary_string(val)
23
+ #{create_to_binary_s_code(precision, endian)}
24
+ end
25
+
26
+ def read_and_return_value(io)
27
+ #{create_read_code(precision, endian)}
28
+ end
29
+ END
30
+ end
31
+
32
+ def create_num_bytes_code(precision)
33
+ (precision == :single) ? 4 : 8
34
+ end
35
+
36
+ def create_read_code(precision, endian)
37
+ if precision == :single
38
+ unpack = (endian == :little) ? 'e' : 'g'
39
+ nbytes = 4
40
+ else # double_precision
41
+ unpack = (endian == :little) ? 'E' : 'G'
42
+ nbytes = 8
43
+ end
44
+
45
+ "io.readbytes(#{nbytes}).unpack('#{unpack}').at(0)"
46
+ end
47
+
48
+ def create_to_binary_s_code(precision, endian)
49
+ if precision == :single
50
+ pack = (endian == :little) ? 'e' : 'g'
51
+ else # double_precision
52
+ pack = (endian == :little) ? 'E' : 'G'
53
+ end
54
+
55
+ "[val].pack('#{pack}')"
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ # Single precision floating point number in little endian format
62
+ class FloatLe < BinData::BasePrimitive
63
+ FloatingPoint.define_methods(self, :single, :little)
64
+ end
65
+
66
+ # Single precision floating point number in big endian format
67
+ class FloatBe < BinData::BasePrimitive
68
+ FloatingPoint.define_methods(self, :single, :big)
69
+ end
70
+
71
+ # Double precision floating point number in little endian format
72
+ class DoubleLe < BinData::BasePrimitive
73
+ FloatingPoint.define_methods(self, :double, :little)
74
+ end
75
+
76
+ # Double precision floating point number in big endian format
77
+ class DoubleBe < BinData::BasePrimitive
78
+ FloatingPoint.define_methods(self, :double, :big)
79
+ end
80
+ end
@@ -0,0 +1,184 @@
1
+ require 'bindata/base_primitive'
2
+
3
+ module BinData
4
+ # Defines a number of classes that contain an integer. The integer
5
+ # is defined by endian, signedness and number of bytes.
6
+
7
+ module Int #:nodoc: all
8
+ class << self
9
+ def define_class(nbits, endian, signed)
10
+ name = class_name(nbits, endian, signed)
11
+ unless BinData.const_defined?(name)
12
+ BinData.module_eval <<-END
13
+ class #{name} < BinData::BasePrimitive
14
+ Int.define_methods(self, #{nbits}, :#{endian}, :#{signed})
15
+ end
16
+ END
17
+ end
18
+
19
+ BinData.const_get(name)
20
+ end
21
+
22
+ def class_name(nbits, endian, signed)
23
+ endian_str = (endian == :big) ? "be" : "le"
24
+ base = (signed == :signed) ? "Int" : "Uint"
25
+
26
+ "#{base}#{nbits}#{endian_str}"
27
+ end
28
+
29
+ def define_methods(int_class, nbits, endian, signed)
30
+ raise "nbits must be divisible by 8" unless (nbits % 8).zero?
31
+
32
+ int_class.module_eval <<-END
33
+ def assign(val)
34
+ #{create_clamp_code(nbits, signed)}
35
+ super(val)
36
+ end
37
+
38
+ def do_num_bytes
39
+ #{nbits / 8}
40
+ end
41
+
42
+ #---------------
43
+ private
44
+
45
+ def sensible_default
46
+ 0
47
+ end
48
+
49
+ def value_to_binary_string(val)
50
+ #{create_clamp_code(nbits, signed)}
51
+ #{create_int2uint_code(nbits) if signed == :signed}
52
+ #{create_to_binary_s_code(nbits, endian)}
53
+ end
54
+
55
+ def read_and_return_value(io)
56
+ val = #{create_read_code(nbits, endian)}
57
+ #{create_uint2int_code(nbits) if signed == :signed}
58
+ val
59
+ end
60
+ END
61
+ end
62
+
63
+ #-------------
64
+ private
65
+
66
+ def create_clamp_code(nbits, signed)
67
+ if signed == :signed
68
+ max = (1 << (nbits - 1)) - 1
69
+ min = -(max + 1)
70
+ else
71
+ min = 0
72
+ max = (1 << nbits) - 1
73
+ end
74
+
75
+ "val = (val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val"
76
+ end
77
+
78
+ def create_int2uint_code(nbits)
79
+ "val = val & #{(1 << nbits) - 1}"
80
+ end
81
+
82
+ def create_uint2int_code(nbits)
83
+ "val = val - #{1 << nbits} if (val >= #{1 << (nbits - 1)})"
84
+ end
85
+
86
+ def create_read_code(nbits, endian)
87
+ bits_per_word = bytes_per_word(nbits) * 8
88
+ nwords = nbits / bits_per_word
89
+ nbytes = nbits / 8
90
+
91
+ idx = (0 ... nwords).to_a
92
+ idx.reverse! if (endian == :big)
93
+
94
+ parts = (0 ... nwords).collect do |i|
95
+ if i.zero?
96
+ "a.at(#{idx[i]})"
97
+ else
98
+ "(a.at(#{idx[i]}) << #{bits_per_word * i})"
99
+ end
100
+ end
101
+
102
+ unpack_str = "a = io.readbytes(#{nbytes}).unpack('#{pack_directive(nbits, endian)}')"
103
+ assemble_str = parts.join(" + ")
104
+
105
+ "(#{unpack_str}; #{assemble_str})"
106
+ end
107
+
108
+ def create_to_binary_s_code(nbits, endian)
109
+ # special case 8bit integers for speed
110
+ return "val.chr" if nbits == 8
111
+
112
+ bits_per_word = bytes_per_word(nbits) * 8
113
+ nwords = nbits / bits_per_word
114
+ mask = (1 << bits_per_word) - 1
115
+
116
+ vals = (0 ... nwords).collect do |i|
117
+ i.zero? ? "val" : "(val >> #{bits_per_word * i})"
118
+ end
119
+ vals.reverse! if (endian == :big)
120
+
121
+ parts = (0 ... nwords).collect { |i| "#{vals[i]} & #{mask}" }
122
+ array_str = "[" + parts.join(", ") + "]"
123
+
124
+ "#{array_str}.pack('#{pack_directive(nbits, endian)}')"
125
+ end
126
+
127
+ def bytes_per_word(nbits)
128
+ (nbits % 32).zero? ? 4 : (nbits % 16).zero? ? 2 : 1
129
+ end
130
+
131
+ def pack_directive(nbits, endian)
132
+ bits_per_word = bytes_per_word(nbits) * 8
133
+ nwords = nbits / bits_per_word
134
+
135
+ if (nbits % 32).zero?
136
+ d = (endian == :big) ? 'N' : 'V'
137
+ elsif (nbits % 16).zero?
138
+ d = (endian == :big) ? 'n' : 'v'
139
+ else
140
+ d = 'C'
141
+ end
142
+
143
+ d * nwords
144
+ end
145
+ end
146
+ end
147
+
148
+
149
+ # Unsigned 1 byte integer.
150
+ class Uint8 < BinData::BasePrimitive
151
+ Int.define_methods(self, 8, :little, :unsigned)
152
+ end
153
+
154
+ # Signed 1 byte integer.
155
+ class Int8 < BinData::BasePrimitive
156
+ Int.define_methods(self, 8, :little, :signed)
157
+ end
158
+
159
+ # Create classes on demand
160
+ class << self
161
+ alias_method :const_missing_without_int, :const_missing
162
+ def const_missing_with_int(name)
163
+ name = name.to_s
164
+ mappings = {
165
+ /^Uint(\d+)be$/ => [:big, :unsigned],
166
+ /^Uint(\d+)le$/ => [:little, :unsigned],
167
+ /^Int(\d+)be$/ => [:big, :signed],
168
+ /^Int(\d+)le$/ => [:little, :signed],
169
+ }
170
+
171
+ mappings.each_pair do |regex, args|
172
+ if regex =~ name
173
+ nbits = $1.to_i
174
+ if (nbits % 8).zero?
175
+ return Int.define_class(nbits, *args)
176
+ end
177
+ end
178
+ end
179
+
180
+ const_missing_without_int(name)
181
+ end
182
+ alias_method :const_missing, :const_missing_with_int
183
+ end
184
+ end
@@ -0,0 +1,274 @@
1
+ require 'stringio'
2
+
3
+ module BinData
4
+ # A wrapper around an IO object. The wrapper provides a consistent
5
+ # interface for BinData objects to use when accessing the IO.
6
+ class IO
7
+
8
+ # The underlying IO is unseekable
9
+ class Unseekable < StandardError; end
10
+
11
+ # Creates a StringIO around +str+.
12
+ def self.create_string_io(str = "")
13
+ if str.respond_to?(:force_encoding)
14
+ str = str.dup.force_encoding(Encoding::BINARY)
15
+ end
16
+ StringIO.new(str)
17
+ end
18
+
19
+ # Create a new IO wrapper around +io+. +io+ must support #read if used
20
+ # for reading, #write if used for writing, #pos if reading the current
21
+ # stream position and #seek if setting the current stream position. If
22
+ # +io+ is a string it will be automatically wrapped in an StringIO object.
23
+ #
24
+ # The IO can handle bitstreams in either big or little endian format.
25
+ #
26
+ # M byte1 L M byte2 L
27
+ # S 76543210 S S fedcba98 S
28
+ # B B B B
29
+ #
30
+ # In big endian format:
31
+ # readbits(6), readbits(5) #=> [765432, 10fed]
32
+ #
33
+ # In little endian format:
34
+ # readbits(6), readbits(5) #=> [543210, a9876]
35
+ #
36
+ def initialize(io)
37
+ raise ArgumentError, "io must not be a BinData::IO" if BinData::IO === io
38
+
39
+ # wrap strings in a StringIO
40
+ if io.respond_to?(:to_str)
41
+ io = BinData::IO.create_string_io(io.to_str)
42
+ end
43
+
44
+ @raw_io = io
45
+
46
+ # initial stream position if stream supports positioning
47
+ @initial_pos = current_position rescue 0
48
+
49
+ # bits when reading
50
+ @rnbits = 0
51
+ @rval = 0
52
+ @rendian = nil
53
+
54
+ # bits when writing
55
+ @wnbits = 0
56
+ @wval = 0
57
+ @wendian = nil
58
+ end
59
+
60
+ # Access to the underlying raw io.
61
+ attr_reader :raw_io
62
+
63
+ # Returns the current offset of the io stream. The exact value of
64
+ # the offset when reading bitfields is not defined.
65
+ def offset
66
+ current_position - @initial_pos
67
+ rescue Unseekable
68
+ 0
69
+ end
70
+
71
+ # The number of bytes remaining in the input stream.
72
+ def num_bytes_remaining
73
+ pos = current_position
74
+ @raw_io.seek(0, ::IO::SEEK_END)
75
+ bytes_remaining = current_position - pos
76
+ @raw_io.seek(pos, ::IO::SEEK_SET)
77
+
78
+ bytes_remaining
79
+ rescue Unseekable
80
+ 0
81
+ end
82
+
83
+ # Seek +n+ bytes from the current position in the io stream.
84
+ def seekbytes(n)
85
+ reset_read_bits
86
+ @raw_io.seek(n, ::IO::SEEK_CUR)
87
+ rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE
88
+ skipbytes(n)
89
+ end
90
+
91
+ # Reads exactly +n+ bytes from +io+.
92
+ #
93
+ # If the data read is nil an EOFError is raised.
94
+ #
95
+ # If the data read is too short an IOError is raised.
96
+ def readbytes(n)
97
+ reset_read_bits
98
+
99
+ str = @raw_io.read(n)
100
+ raise EOFError, "End of file reached" if str.nil?
101
+ raise IOError, "data truncated" if str.size < n
102
+ str
103
+ end
104
+
105
+ # Reads all remaining bytes from the stream.
106
+ def read_all_bytes
107
+ reset_read_bits
108
+ @raw_io.read
109
+ end
110
+
111
+ # Reads exactly +nbits+ bits from the stream. +endian+ specifies whether
112
+ # the bits are stored in +:big+ or +:little+ endian format.
113
+ def readbits(nbits, endian)
114
+ if @rendian != endian
115
+ # don't mix bits of differing endian
116
+ reset_read_bits
117
+ @rendian = endian
118
+ end
119
+
120
+ if endian == :big
121
+ read_big_endian_bits(nbits)
122
+ else
123
+ read_little_endian_bits(nbits)
124
+ end
125
+ end
126
+
127
+ # Discards any read bits so the stream becomes aligned at the
128
+ # next byte boundary.
129
+ def reset_read_bits
130
+ @rnbits = 0
131
+ @rval = 0
132
+ end
133
+
134
+ # Writes the given string of bytes to the io stream.
135
+ def writebytes(str)
136
+ flushbits
137
+ @raw_io.write(str)
138
+ end
139
+
140
+ # Writes +nbits+ bits from +val+ to the stream. +endian+ specifies whether
141
+ # the bits are to be stored in +:big+ or +:little+ endian format.
142
+ def writebits(val, nbits, endian)
143
+ if @wendian != endian
144
+ # don't mix bits of differing endian
145
+ flushbits
146
+ @wendian = endian
147
+ end
148
+
149
+ clamped_val = val & mask(nbits)
150
+
151
+ if endian == :big
152
+ write_big_endian_bits(clamped_val, nbits)
153
+ else
154
+ write_little_endian_bits(clamped_val, nbits)
155
+ end
156
+ end
157
+
158
+ # To be called after all +writebits+ have been applied.
159
+ def flushbits
160
+ raise "Internal state error nbits = #{@wnbits}" if @wnbits >= 8
161
+
162
+ if @wnbits > 0
163
+ writebits(0, 8 - @wnbits, @wendian)
164
+ end
165
+ end
166
+ alias_method :flush, :flushbits
167
+
168
+ #---------------
169
+ private
170
+
171
+ def current_position
172
+ @raw_io.pos
173
+ rescue NoMethodError, Errno::ESPIPE
174
+ raise Unseekable
175
+ end
176
+
177
+ def skipbytes(n)
178
+ # skip over data in 8k blocks
179
+ while n > 0
180
+ bytes_to_read = [n, 8192].min
181
+ @raw_io.read(bytes_to_read)
182
+ n -= bytes_to_read
183
+ end
184
+ end
185
+
186
+ def read_big_endian_bits(nbits)
187
+ while @rnbits < nbits
188
+ accumulate_big_endian_bits
189
+ end
190
+
191
+ val = (@rval >> (@rnbits - nbits)) & mask(nbits)
192
+ @rnbits -= nbits
193
+ @rval &= mask(@rnbits)
194
+
195
+ val
196
+ end
197
+
198
+ def accumulate_big_endian_bits
199
+ byte = @raw_io.read(1)
200
+ raise EOFError, "End of file reached" if byte.nil?
201
+ byte = byte.unpack('C').at(0) & 0xff
202
+
203
+ @rval = (@rval << 8) | byte
204
+ @rnbits += 8
205
+ end
206
+
207
+ def read_little_endian_bits(nbits)
208
+ while @rnbits < nbits
209
+ accumulate_little_endian_bits
210
+ end
211
+
212
+ val = @rval & mask(nbits)
213
+ @rnbits -= nbits
214
+ @rval >>= nbits
215
+
216
+ val
217
+ end
218
+
219
+ def accumulate_little_endian_bits
220
+ byte = @raw_io.read(1)
221
+ raise EOFError, "End of file reached" if byte.nil?
222
+ byte = byte.unpack('C').at(0) & 0xff
223
+
224
+ @rval = @rval | (byte << @rnbits)
225
+ @rnbits += 8
226
+ end
227
+
228
+ def write_big_endian_bits(val, nbits)
229
+ while nbits > 0
230
+ bits_req = 8 - @wnbits
231
+ if nbits >= bits_req
232
+ msb_bits = (val >> (nbits - bits_req)) & mask(bits_req)
233
+ nbits -= bits_req
234
+ val &= mask(nbits)
235
+
236
+ @wval = (@wval << bits_req) | msb_bits
237
+ @raw_io.write(@wval.chr)
238
+
239
+ @wval = 0
240
+ @wnbits = 0
241
+ else
242
+ @wval = (@wval << nbits) | val
243
+ @wnbits += nbits
244
+ nbits = 0
245
+ end
246
+ end
247
+ end
248
+
249
+ def write_little_endian_bits(val, nbits)
250
+ while nbits > 0
251
+ bits_req = 8 - @wnbits
252
+ if nbits >= bits_req
253
+ lsb_bits = val & mask(bits_req)
254
+ nbits -= bits_req
255
+ val >>= bits_req
256
+
257
+ @wval = @wval | (lsb_bits << @wnbits)
258
+ @raw_io.write(@wval.chr)
259
+
260
+ @wval = 0
261
+ @wnbits = 0
262
+ else
263
+ @wval = @wval | (val << @wnbits)
264
+ @wnbits += nbits
265
+ nbits = 0
266
+ end
267
+ end
268
+ end
269
+
270
+ def mask(nbits)
271
+ (1 << nbits) - 1
272
+ end
273
+ end
274
+ end