bistro 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d3005111ea2c6d80cebb2b9fbb18942b70b8c90d6c785bb1b54a65db01e978fa
4
+ data.tar.gz: 62fdd3216e53989c4fcb18b1a8a38c3399b44c2daac249c9cabfcafd4d907415
5
+ SHA512:
6
+ metadata.gz: e656723aae47b109c66804e0ec03838193c464b3a4d0377f7691f8ea02f0c783fbfc5e4ad04fcda4bdab88a8012d23f242f25ac688ecab8e86c84406b6a51fd8
7
+ data.tar.gz: ae09a95c27537d26b0bfcfa6c418e72cbe95c358ef0e3d10976924ecb0d2c8894341060887427c97fad8b3abcd7b3765bf77f9449a859b11e2a8902090c69761
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2019 Dev Null Productions <devnullproductions@gmail.com>
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,103 @@
1
+ # Bistro
2
+
3
+ Bistro is a class for dealing with BInary STRuctured data. It simplifies
4
+ expressing what the binary structure looks like, with the ability to name the
5
+ parts. Given this definition, it is easy to encode/decode the binary structure
6
+ from/to a Hash.
7
+
8
+ ## Example Usage
9
+
10
+ As an example, we will show reading and writing a .gif header. This example is
11
+ also in spec/gif_spec.rb.
12
+
13
+ ### Create the structure definition
14
+
15
+ ```ruby
16
+ gif_header = Bistro.new([
17
+ "a3", :magic,
18
+ "a3", :version,
19
+ "S", :width,
20
+ "S", :height,
21
+ "a", :flags,
22
+ "C", :bg_color_index,
23
+ "C", :pixel_aspect_ratio
24
+ ])
25
+ ```
26
+
27
+ ### Read the header
28
+
29
+ ```ruby
30
+ header_size = gif_header.size
31
+ header = File.open("test.gif", "rb") { |f| f.read(header_size) }
32
+ gif_header.decode(header)
33
+
34
+ => {
35
+ :magic => "GIF",
36
+ :version => "89a",
37
+ :width => 16,
38
+ :height => 16,
39
+ :flags => "\x80",
40
+ :bg_color_index => 0,
41
+ :pixel_aspect_ratio => 0
42
+ }
43
+ ```
44
+
45
+ ### Write the header
46
+
47
+ ```ruby
48
+ header = gif_header.encode({
49
+ :magic => "GIF",
50
+ :version => "89a",
51
+ :width => 16,
52
+ :height => 16,
53
+ :flags => "\x80",
54
+ :bg_color_index => 0,
55
+ :pixel_aspect_ratio => 0
56
+ })
57
+ File.open("test.gif", "wb") { |f| f.write(header) }
58
+
59
+ => "GIF89a\x10\x00\x10\x00\x80\x00\x00"
60
+ ```
61
+
62
+ ## Note about bit and nibble formats
63
+
64
+ Bistro supports bit formats and nibble formats, however note that the
65
+ underlying Ruby methods, [pack](http://ruby-doc.org/core-2.2.0/Array.html#method-i-pack)
66
+ and [unpack](http://ruby-doc.org/core-2.2.0/String.html#method-i-unpack), support
67
+ less than 8 bits by reading an entire byte, even if all of the bits are not used.
68
+
69
+ For example,
70
+
71
+ ```ruby
72
+ s = "\xFF\x00" # binary: 1111111100000000
73
+ s.unpack("b8b8") # => ["11111111", "00000000"]
74
+ s.unpack("b4b4b4b4") # => ["1111", "0000", "", ""]
75
+ ```
76
+
77
+ One might expect that the latter would read 4 bits, then the next 4 bits, etc,
78
+ yielding `["1111", "1111", "0000", "0000"]`, but that is not the case. Instead,
79
+ the first b4 reads a full byte's worth, then discards the unused 4 bits, and the
80
+ same happens for the next b4. The third and fourth b4 have nothing left to read,
81
+ and so just return empty strings.
82
+
83
+ ## Installation
84
+
85
+ Add this line to your application's Gemfile:
86
+
87
+ gem 'bistro'
88
+
89
+ And then execute:
90
+
91
+ $ bundle
92
+
93
+ Or install it yourself as:
94
+
95
+ $ gem install bistro
96
+
97
+ ## Contributing
98
+
99
+ 1. Fork it
100
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
101
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
102
+ 4. Push to the branch (`git push origin my-new-feature`)
103
+ 5. Create new Pull Request
@@ -0,0 +1,229 @@
1
+ require "bistro/version"
2
+ require "enumerator"
3
+
4
+ class Bistro
5
+ SIZES = {
6
+ 'A' => 1, # String with trailing NULs and spaces removed
7
+ 'a' => 1, # String
8
+ 'B' => 1, # Extract bits from each character (MSB first)
9
+ 'b' => 1, # Extract bits from each character (LSB first)
10
+ 'C' => 1, # Extract a character as an unsigned integer
11
+ 'c' => 1, # Extract a character as a signed integer
12
+ 'E' => nil, # Treat sizeof(double) characters as a double in little-endian byte order
13
+ 'e' => nil, # Treat sizeof(float) characters as a float in little-endian byte order
14
+ 'G' => nil, # Treat sizeof(double) characters as a double in network byte order
15
+ 'g' => nil, # Treat sizeof(float) characters as a float in network byte order
16
+ 'H' => 1, # Extract hex nibbles from each character (most significant first)
17
+ 'h' => 1, # Extract hex nibbles from each character (least significant first)
18
+ 'I' => 4, # Treat sizeof(int) successive characters as an unsigned native integer
19
+ 'i' => 4, # Treat sizeof(int) successive characters as a signed native integer
20
+ 'L' => 4, # Treat 4 successive characters as an unsigned native long integer
21
+ 'l' => 4, # Treat 4 successive characters as a signed native long integer
22
+ 'M' => 1, # Extract a quoted printable string
23
+ 'm' => 1, # Extract a Base64 encoded string
24
+ 'N' => 4, # Treat 4 characters as an unsigned long in network byte order
25
+ 'n' => 2, # Treat 2 characters as an unsigned short in network byte order
26
+ 'P' => nil, # Treat sizeof(char *) characters as a pointer, and return len characters from the referenced location
27
+ 'p' => nil, # Treat sizeof(char *) characters as a pointer to a null-terminated string
28
+ 'Q' => 8, # Treat 8 characters as an unsigned quad word (64 bits)
29
+ 'q' => 8, # Treat 8 characters as a signed quad word (64 bits)
30
+ 'S' => 2, # Treat 2 successive characters as an unsigned short in native byte order
31
+ 's' => 2, # Treat 2 successive characters as a signed short in native byte order
32
+ 'U' => nil, # Extract UTF-8 characters as unsigned integers
33
+ 'u' => nil, # Extract a UU-encoded string
34
+ 'V' => 4, # Treat 4 characters as an unsigned long in little-endian byte order
35
+ 'v' => 2, # Treat 2 characters as an unsigned short in little-endian byte order
36
+ 'w' => nil, # BER-compressed integer
37
+ 'X' => -1, # Skip backward one character
38
+ 'x' => 1, # Skip forward one character
39
+ 'Z' => 1, # String with trailing NULs removed
40
+ }
41
+
42
+ STRING_FORMATS = %w(A a B b H h M m u)
43
+ BIT_FORMATS = %w(B b)
44
+ NIBBLE_FORMATS = %w(H h)
45
+ ENDIAN_FORMATS = %w(I i L l Q q S s)
46
+ ENDIAN_MODIFIERS = %w(> <)
47
+ MODIFIERS = ENDIAN_MODIFIERS
48
+
49
+ def initialize(definition = nil)
50
+ self.definition = definition unless definition.nil?
51
+ end
52
+
53
+ def definition
54
+ @definition
55
+ end
56
+
57
+ def definition=(value)
58
+ if value.kind_of?(self.class)
59
+ @definition = value.definition.dup
60
+ else
61
+ value = value.to_a.map(&:reverse).flatten if value.kind_of?(Hash)
62
+ value = Array(value)
63
+ self.class.validate_definition(value)
64
+ @definition = value
65
+ end
66
+ @size = @decode_format = @decode_name = nil
67
+ end
68
+
69
+ def size
70
+ @size ||= self.class.get_size(@definition)
71
+ end
72
+
73
+ def decode(data, num = 1)
74
+ values = self.decode_to_array(data, num)
75
+ return self.decoded_array_to_hash!(values) if num == 1
76
+
77
+ result = []
78
+ num.times { result << self.decoded_array_to_hash!(values) }
79
+ return result
80
+ end
81
+
82
+ def decode_to_array(data, num = 1)
83
+ raise ArgumentError, "data cannot be nil" if data.nil?
84
+ @decode_format, @decode_names = self.class.prep_decode(@definition) if @decode_format.nil?
85
+ format = (num == 1) ? @decode_format : @decode_format * num
86
+ return data.unpack(format)
87
+ end
88
+
89
+ def decoded_array_to_hash!(array)
90
+ hash = {}
91
+ @decode_names.each do |k|
92
+ v = array.shift
93
+ next if k.nil?
94
+ hash[k] = v
95
+ end
96
+ return hash
97
+ end
98
+
99
+ def encode(hash)
100
+ return encode_hash(hash) unless hash.kind_of?(Array)
101
+
102
+ data = ""
103
+ hash.each { |h| data << self.encode_hash(h) }
104
+ return data
105
+ end
106
+
107
+ def encode_hash(hash)
108
+ data = ""
109
+ @definition.each_slice(2) do |format, name|
110
+ raise "member not found: #{name}" unless name.nil? || hash.has_key?(name)
111
+ value = unless name.nil?
112
+ hash[name]
113
+ else
114
+ STRING_FORMATS.include?(format[0, 1]) ? '0' : 0
115
+ end
116
+ data << [value].pack(format)
117
+ end
118
+ return data
119
+ end
120
+
121
+ def ==(other)
122
+ self.definition == other.definition
123
+ end
124
+
125
+ def each(&block)
126
+ self.definition.each_slice(2, &block)
127
+ end
128
+
129
+ #
130
+ # Methods to handle the old style of calling
131
+ #
132
+
133
+ @@structs_by_definition = {}
134
+
135
+ def self.clear_structs_by_definition_cache
136
+ @@structs_by_definition.clear
137
+ end
138
+
139
+ def self.sizeof(definition)
140
+ struct_by_definition(definition).size
141
+ end
142
+
143
+ def self.decode(data, definition)
144
+ struct_by_definition(definition).decode(data)
145
+ end
146
+
147
+ def self.encode(hash, definition)
148
+ struct_by_definition(definition).encode(hash)
149
+ end
150
+
151
+ private
152
+
153
+ def self.struct_by_definition(definition)
154
+ @@structs_by_definition[definition] ||= definition.kind_of?(self) ? definition : self.new(definition)
155
+ end
156
+
157
+ def self.validate_definition(definition)
158
+ raise "definition must be an array of format/name pairs" if definition.empty? || definition.length % 2 != 0
159
+ definition.each_slice(2) do |format, _|
160
+ type, count = format[0, 1], format[1..-1]
161
+ modifier, modcount = count[0, 1], count[1..-1]
162
+ validate_definition_entry_type(type)
163
+ if valid_definition_entry_modifier?(modifier)
164
+ validate_definition_endian_modifier(modifier, type)
165
+ validate_definition_entry_count(modcount)
166
+ else
167
+ validate_definition_entry_count(count)
168
+ end
169
+ end
170
+ end
171
+
172
+ def self.validate_definition_entry_type(type)
173
+ raise "unrecognized format: #{type}" unless SIZES.has_key?(type)
174
+ raise "unsupported format: #{type}" if SIZES[type].nil?
175
+ return true
176
+ end
177
+
178
+ def self.validate_definition_entry_count(count)
179
+ return true if count.empty? || count == '*'
180
+
181
+ begin
182
+ count = Integer(count)
183
+ rescue
184
+ raise "unsupported count: #{count}"
185
+ end
186
+ raise "unsupported count: #{count}" if count < 0
187
+ end
188
+
189
+ def self.valid_definition_entry_modifier?(modifier)
190
+ MODIFIERS.include? modifier
191
+ end
192
+
193
+ def self.validate_definition_endian_modifier(modifier, type)
194
+ if ENDIAN_MODIFIERS.include? modifier
195
+ raise "unsupported type attribute #{type} for endian modifier #{modifier}" unless ENDIAN_FORMATS.include? type
196
+ return true
197
+ end
198
+ false
199
+ end
200
+
201
+ def self.get_size(definition)
202
+ size = 0
203
+ definition.each_slice(2) do |format, _|
204
+ type, count = format[0, 1], format[1..-1]
205
+ modifier, modcount = count[0, 1], count[1..-1]
206
+ count = modcount if valid_definition_entry_modifier?(modifier)
207
+ count = count.empty? ? 1 : count.to_i
208
+ size +=
209
+ if BIT_FORMATS.include?(type)
210
+ (count / 8.0).ceil
211
+ elsif NIBBLE_FORMATS.include?(type)
212
+ (count / 2.0).ceil
213
+ else
214
+ count * SIZES[type]
215
+ end
216
+ end
217
+ size
218
+ end
219
+
220
+ def self.prep_decode(definition)
221
+ formats = ""
222
+ names = []
223
+ definition.each_slice(2) do |format, name|
224
+ formats << format
225
+ names << name
226
+ end
227
+ return formats, names
228
+ end
229
+ end
@@ -0,0 +1,3 @@
1
+ class Bistro
2
+ VERSION = "2.1.0"
3
+ end
@@ -0,0 +1,133 @@
1
+ describe Bistro do
2
+ STRUCT_DEF = [
3
+ 'Q', :quad,
4
+ 'L', 'long',
5
+ 'S', :short,
6
+ 'C', nil,
7
+ 'b5', :binary,
8
+ 'a0', 'none',
9
+ 'a', nil,
10
+ 'a2', 'bc',
11
+ ]
12
+ STRUCT_DEF_SIZE = 19
13
+
14
+ STRUCT_DEF_HASH = {
15
+ :quad => 'Q',
16
+ 'long' => 'L',
17
+ :short => 'S',
18
+ nil => 'C',
19
+ :binary => 'b5',
20
+ 'none' => 'a0',
21
+ :unused => 'a',
22
+ 'bc' => 'a2',
23
+ }
24
+
25
+ STRUCT_DEF_ASTERISK = ['a*', :word]
26
+ STRUCT_DEF_ASTERISK_SIZE = 0 # '*' is ignored
27
+
28
+ STRUCT_DEF_UNRECOGNIZED_FORMAT = ['D', nil]
29
+ STRUCT_DEF_UNSUPPORTED_FORMAT = ['U', nil]
30
+ STRUCT_DEF_UNSUPPORTED_COUNT_NEG = ['a-1', nil]
31
+ STRUCT_DEF_UNSUPPORTED_COUNT_INV = ['aX', nil]
32
+
33
+ STRUCT_ENCODED_STR = "\000\111\222\333\444\555\666\777\000\111\222\333\000\111\000\0320BC".force_encoding("ASCII-8BIT")
34
+ STRUCT_DECODED_HASH = {
35
+ :quad => 18_426_034_930_503_010_560,
36
+ "long" => 3_683_797_248,
37
+ :short => 18_688,
38
+ :binary => "01011",
39
+ "bc" => "BC",
40
+ "none" => ""
41
+ }
42
+ STRUCT_DECODED_HASH2 = STRUCT_DECODED_HASH.merge(:unused => "0")
43
+
44
+ it('.new') { expect { Bistro.new }.not_to raise_error }
45
+ it('.new with definition') { expect { Bistro.new(STRUCT_DEF) }.not_to raise_error }
46
+ it('.new with definition with *') { expect { Bistro.new(STRUCT_DEF_ASTERISK) }.not_to raise_error }
47
+ it('.new with Hash defintion') { expect { Bistro.new(STRUCT_DEF_HASH) }.not_to raise_error }
48
+ it '.new with another Bistro' do
49
+ s = Bistro.new(STRUCT_DEF)
50
+ s2 = Bistro.new(s)
51
+ expect(s2).to eq(s)
52
+ expect(s2).not_to equal(s)
53
+ end
54
+
55
+ it('.new with unrecognized format') { expect { Bistro.new(STRUCT_DEF_UNRECOGNIZED_FORMAT) }.to raise_error(RuntimeError) }
56
+ it('.new with unsupported format') { expect { Bistro.new(STRUCT_DEF_UNSUPPORTED_FORMAT) }.to raise_error(RuntimeError) }
57
+ it('.new with unsupported negative count') { expect { Bistro.new(STRUCT_DEF_UNSUPPORTED_COUNT_NEG) }.to raise_error(RuntimeError) }
58
+ it('.new with unsupported invalid count') { expect { Bistro.new(STRUCT_DEF_UNSUPPORTED_COUNT_INV) }.to raise_error(RuntimeError) }
59
+
60
+ it('#definition=') { expect { Bistro.new.definition = STRUCT_DEF }.not_to raise_error }
61
+ it('#definition= with definition with *') { expect { Bistro.new.definition = STRUCT_DEF_ASTERISK }.not_to raise_error }
62
+ it('#definition= with Hash defintion') { expect { Bistro.new.definition = STRUCT_DEF_HASH }.not_to raise_error }
63
+
64
+ it('#definition= with unrecognized format') { expect { Bistro.new.definition = STRUCT_DEF_UNRECOGNIZED_FORMAT }.to raise_error(RuntimeError) }
65
+ it('#definition= with unsupported format') { expect { Bistro.new.definition = STRUCT_DEF_UNSUPPORTED_FORMAT }.to raise_error(RuntimeError) }
66
+ it('#definition= with unsupported negative count') { expect { Bistro.new.definition = STRUCT_DEF_UNSUPPORTED_COUNT_NEG }.to raise_error(RuntimeError) }
67
+ it('#definition= with unsupported invalid count') { expect { Bistro.new.definition = STRUCT_DEF_UNSUPPORTED_COUNT_INV }.to raise_error(RuntimeError) }
68
+
69
+ it('#size') { expect(Bistro.new(STRUCT_DEF).size).to eq(STRUCT_DEF_SIZE) }
70
+ it('#size with definition with *') { expect(Bistro.new(STRUCT_DEF_ASTERISK).size).to eq(STRUCT_DEF_ASTERISK_SIZE) }
71
+
72
+ it '#decode' do
73
+ expect(Bistro.new(STRUCT_DEF).decode(STRUCT_ENCODED_STR)).to eq(STRUCT_DECODED_HASH)
74
+ end
75
+
76
+ it '#decode with definition with *' do
77
+ expect(Bistro.new(STRUCT_DEF_ASTERISK).decode("Testing")).to eq(:word => "Testing")
78
+ end
79
+
80
+ it '#decode with Hash defintion' do
81
+ expect(Bistro.new(STRUCT_DEF_HASH).decode(STRUCT_ENCODED_STR)).to eq(STRUCT_DECODED_HASH2)
82
+ end
83
+
84
+ it '#decode against multiple records' do
85
+ expect(Bistro.new(STRUCT_DEF).decode(STRUCT_ENCODED_STR * 10, 10)).to eq([STRUCT_DECODED_HASH] * 10)
86
+ end
87
+
88
+ it '#encode' do
89
+ expect(Bistro.new(STRUCT_DEF).encode(STRUCT_DECODED_HASH)).to eq(STRUCT_ENCODED_STR.force_encoding("ASCII-8BIT"))
90
+ end
91
+
92
+ it '#encode with definition with *' do
93
+ expect(Bistro.new(STRUCT_DEF_ASTERISK).encode(:word => "Testing")).to eq("Testing")
94
+ end
95
+
96
+ it '#encode against multiple records' do
97
+ s = Bistro.new(STRUCT_DEF).encode([STRUCT_DECODED_HASH] * 10)
98
+ expect(s).to eq(STRUCT_ENCODED_STR.force_encoding("ASCII-8BIT") * 10)
99
+ end
100
+
101
+ it '#== against another Bistro' do
102
+ expect(Bistro.new(STRUCT_DEF)).to eq(Bistro.new(STRUCT_DEF))
103
+ expect(Bistro.new(STRUCT_DEF)).not_to eq(Bistro.new(STRUCT_DEF_ASTERISK))
104
+ end
105
+
106
+ it '#each will iterate over definition' do
107
+ dup_def = []
108
+ Bistro.new(STRUCT_DEF).each { |field, name| dup_def << field << name }
109
+ expect(dup_def).to eq(STRUCT_DEF)
110
+ end
111
+
112
+ context "old style methods" do
113
+ after(:each) { Bistro.clear_structs_by_definition_cache }
114
+
115
+ it '#sizeof' do
116
+ expect(Bistro.sizeof(STRUCT_DEF)).to eq(STRUCT_DEF_SIZE)
117
+ # Do it twice for consistency reasons
118
+ expect(Bistro.sizeof(STRUCT_DEF)).to eq(STRUCT_DEF_SIZE)
119
+ end
120
+
121
+ it '#decode' do
122
+ expect(Bistro.decode(STRUCT_ENCODED_STR, STRUCT_DEF)).to eq(STRUCT_DECODED_HASH)
123
+ # Do it twice for consistency reasons
124
+ expect(Bistro.decode(STRUCT_ENCODED_STR, STRUCT_DEF)).to eq(STRUCT_DECODED_HASH)
125
+ end
126
+
127
+ it '#encode' do
128
+ expect(Bistro.encode(STRUCT_DECODED_HASH, STRUCT_DEF)).to eq(STRUCT_ENCODED_STR.force_encoding("ASCII-8BIT"))
129
+ # Do it twice for consistency reasons
130
+ expect(Bistro.encode(STRUCT_DECODED_HASH, STRUCT_DEF)).to eq(STRUCT_ENCODED_STR.force_encoding("ASCII-8BIT"))
131
+ end
132
+ end
133
+ end
Binary file
@@ -0,0 +1,203 @@
1
+ describe Bistro do
2
+ BIG_STRUCT_DEF = [
3
+ 'Q>', :quad,
4
+ 'L>', 'long',
5
+ 'S>', :short,
6
+ 'C', nil,
7
+ 'b5', :binary,
8
+ 'a0', 'none',
9
+ 'a', nil,
10
+ 'a2', 'bc',
11
+ ]
12
+ BIG_STRUCT_DEF_SIZE = 19
13
+
14
+ BIG_E_QUAD_STRUCT_DEF = ['Q>2', :quad]
15
+ BIG_E_QUAD_DEF_SIZE = 16
16
+
17
+ BIG_STRUCT_DEF_STAR = ['i>*', :word]
18
+ BIG_STRUCT_DEF_STAR_SIZE = 0 # '*' is ignored
19
+
20
+ BIG_STRUCT_DEF_UNRECOG_ENDIAN_FMT = ['Y>', nil]
21
+ BIG_STRUCT_DEF_UNSUPPORTED_ENDIAN_ATTRIBUTE = ['A>', nil]
22
+ BIG_STRUCT_DEF_INVALID_ENDIAN_MODIFIER = ['Q_', nil]
23
+
24
+ LIL_STRUCT_DEF = [
25
+ 'Q<', :quad,
26
+ 'L<', 'long',
27
+ 'S<', :short,
28
+ 'C', nil,
29
+ 'b5', :binary,
30
+ 'a0', 'none',
31
+ 'a', nil,
32
+ 'a2', 'bc',
33
+ ]
34
+ LIL_STRUCT_DEF_SIZE = 19
35
+
36
+ LIL_E_QUAD_STRUCT_DEF = ['Q<2', :quad]
37
+ LIL_E_QUAD_DEF_SIZE = 16
38
+
39
+ LIL_STRUCT_DEF_STAR = ['i<*', :word]
40
+ LIL_STRUCT_DEF_STAR_SIZE = 0 # '*' is ignored
41
+
42
+ LIL_STRUCT_DEF_UNRECOG_ENDIAN_FMT = ['Y<', nil]
43
+ LIL_STRUCT_DEF_UNSUPPORTED_ENDIAN_ATTRIBUTE = ['A<', nil]
44
+
45
+ END_STRUCT_ENCODED_STR = "\000\111\222\333\444\555\666\777\000\111\222\333\000\111\000\0320BC"
46
+
47
+ LIL_ENDIAN_STRUCT_DECODED_HASH =
48
+ {
49
+ :quad => 18_426_034_930_503_010_560,
50
+ "long" => 3_683_797_248,
51
+ :short => 18_688,
52
+ :binary => "01011",
53
+ "none" => "",
54
+ "bc" => "BC",
55
+ }
56
+
57
+ BIG_ENDIAN_STRUCT_DECODED_HASH =
58
+ {
59
+ :quad => 20_709_143_206_541_055,
60
+ "long" => 4_821_723,
61
+ :short => 73,
62
+ :binary => "01011",
63
+ "none" => "",
64
+ "bc" => "BC"
65
+ }
66
+
67
+ it('.new') { expect { Bistro.new }.not_to raise_error }
68
+ it('.new with big definition') { expect { Bistro.new(BIG_STRUCT_DEF) }.not_to raise_error }
69
+ it('.new with little definition') { expect { Bistro.new(LIL_STRUCT_DEF) }.not_to raise_error }
70
+ it('.new with big definition with *') { expect { Bistro.new(BIG_STRUCT_DEF_STAR) }.not_to raise_error }
71
+ it('.new with little definition with *') { expect { Bistro.new(LIL_STRUCT_DEF_STAR) }.not_to raise_error }
72
+ it '.new with another big endian Bistro' do
73
+ s = Bistro.new(BIG_STRUCT_DEF)
74
+ s2 = Bistro.new(s)
75
+ expect(s2).to eq(s)
76
+ expect(s2).not_to equal(s)
77
+ end
78
+
79
+ it '.new with another little endian Bistro' do
80
+ s = Bistro.new(LIL_STRUCT_DEF)
81
+ s2 = Bistro.new(s)
82
+ expect(s2).to eq(s)
83
+ expect(s2).not_to equal(s)
84
+ end
85
+
86
+ it '.new big endian Bistro with a little endian one' do
87
+ s = Bistro.new(BIG_STRUCT_DEF)
88
+ s2 = Bistro.new(LIL_STRUCT_DEF)
89
+ expect(s2).not_to eq(s)
90
+ expect(s2).not_to equal(s)
91
+ end
92
+
93
+ it('.new with unrecognized big e format') do
94
+ expect { Bistro.new(BIG_STRUCT_DEF_UNRECOG_ENDIAN_FMT) }
95
+ .to raise_error(RuntimeError)
96
+ end
97
+
98
+ it('.new with unsupported big e attribute') do
99
+ expect { Bistro.new(BIG_STRUCT_DEF_UNSUPPORTED_ENDIAN_ATTRIBUTE) }
100
+ .to raise_error(RuntimeError)
101
+ end
102
+
103
+ it('.new with invalid endian modifier') do
104
+ expect { Bistro.new(BIG_STRUCT_DEF_INVALID_ENDIAN_MODIFIER) }
105
+ .to raise_error(RuntimeError)
106
+ end
107
+
108
+ it('.new with unrecognized little e format') do
109
+ expect { Bistro.new(LIL_STRUCT_DEF_UNRECOG_ENDIAN_FMT) }
110
+ .to raise_error(RuntimeError)
111
+ end
112
+
113
+ it('.new with unsupported little e attribute') do
114
+ expect { Bistro.new(LIL_STRUCT_DEF_UNSUPPORTED_ENDIAN_ATTRIBUTE) }
115
+ .to raise_error(RuntimeError)
116
+ end
117
+
118
+ it('#definition= with definition with *') do
119
+ expect { Bistro.new.definition = BIG_STRUCT_DEF_STAR }
120
+ .not_to raise_error
121
+ end
122
+
123
+ it('#definition= with unrecognized big e format') do
124
+ expect { Bistro.new.definition = BIG_STRUCT_DEF_UNRECOG_ENDIAN_FMT }
125
+ .to raise_error(RuntimeError)
126
+ end
127
+ it('#definition= with unsupported big e attribute') do
128
+ expect { Bistro.new.definition = BIG_STRUCT_DEF_UNSUPPORTED_ENDIAN_ATTRIBUTE }
129
+ .to raise_error(RuntimeError)
130
+ end
131
+ it('#definition= with unrecognized little e format') do
132
+ expect { Bistro.new.definition = LIL_STRUCT_DEF_UNRECOG_ENDIAN_FMT }
133
+ .to raise_error(RuntimeError)
134
+ end
135
+ it('#definition= with unsupported little e attribute') do
136
+ expect { Bistro.new.definition = LIL_STRUCT_DEF_UNSUPPORTED_ENDIAN_ATTRIBUTE }
137
+ .to raise_error(RuntimeError)
138
+ end
139
+
140
+ it('#size') { expect(Bistro.new(BIG_STRUCT_DEF).size).to eq(BIG_STRUCT_DEF_SIZE) }
141
+ it('#size') { expect(Bistro.new(LIL_STRUCT_DEF).size).to eq(LIL_STRUCT_DEF_SIZE) }
142
+ it('#size with definition with *') { expect(Bistro.new(BIG_STRUCT_DEF_STAR).size).to eq(BIG_STRUCT_DEF_STAR_SIZE) }
143
+ it('#size with definition with *') { expect(Bistro.new(LIL_STRUCT_DEF_STAR).size).to eq(LIL_STRUCT_DEF_STAR_SIZE) }
144
+
145
+ it '#decode with different endian definitions' do
146
+ expect(Bistro.new(BIG_STRUCT_DEF).decode(END_STRUCT_ENCODED_STR))
147
+ .not_to equal(Bistro.new(LIL_STRUCT_DEF).decode(END_STRUCT_ENCODED_STR))
148
+ end
149
+ it '#encode with different endian definitions' do
150
+ big = Bistro.new(BIG_STRUCT_DEF).encode(BIG_ENDIAN_STRUCT_DECODED_HASH)
151
+ little = Bistro.new(LIL_STRUCT_DEF).encode(LIL_ENDIAN_STRUCT_DECODED_HASH)
152
+ expect(big).not_to equal(little)
153
+ end
154
+
155
+ it '#decode little endian struct' do
156
+ expect(Bistro.new(LIL_STRUCT_DEF).decode(END_STRUCT_ENCODED_STR)).to eq(LIL_ENDIAN_STRUCT_DECODED_HASH)
157
+ end
158
+ it '#decode big endian struct' do
159
+ expect(Bistro.new(BIG_STRUCT_DEF).decode(END_STRUCT_ENCODED_STR)).to eq(BIG_ENDIAN_STRUCT_DECODED_HASH)
160
+ end
161
+
162
+ it '#== against another little endian Bistro' do
163
+ expect(Bistro.new(LIL_STRUCT_DEF)).to eq(Bistro.new(LIL_STRUCT_DEF))
164
+ end
165
+ it '#== big endian against another little endian Bistro' do
166
+ expect(Bistro.new(BIG_STRUCT_DEF)).not_to eq(Bistro.new(LIL_STRUCT_DEF))
167
+ end
168
+ it '#== against another big endian Bistro' do
169
+ expect(Bistro.new(BIG_STRUCT_DEF)).to eq(Bistro.new(BIG_STRUCT_DEF))
170
+ end
171
+
172
+ it '#each will iterate over definition' do
173
+ dup_def = []
174
+ Bistro.new(BIG_STRUCT_DEF).each { |field, name| dup_def << field << name }
175
+ expect(dup_def).to eq(BIG_STRUCT_DEF)
176
+ end
177
+
178
+ it '#each will iterate over definition' do
179
+ dup_def = []
180
+ Bistro.new(LIL_STRUCT_DEF).each { |field, name| dup_def << field << name }
181
+ expect(dup_def).to eq(LIL_STRUCT_DEF)
182
+ end
183
+
184
+ context "old style methods" do
185
+ after(:each) { Bistro.clear_structs_by_definition_cache }
186
+
187
+ it '#sizeof' do
188
+ expect(Bistro.sizeof(BIG_STRUCT_DEF)).to eq(BIG_STRUCT_DEF_SIZE)
189
+ end
190
+ # Do it twice for consistency reasons
191
+ it '#sizeof' do
192
+ expect(Bistro.sizeof(BIG_STRUCT_DEF)).to eq(BIG_STRUCT_DEF_SIZE)
193
+ end
194
+
195
+ it '#sizeof' do
196
+ expect(Bistro.sizeof(LIL_STRUCT_DEF)).to eq(LIL_STRUCT_DEF_SIZE)
197
+ end
198
+ # Do it twice for consistency reasons
199
+ it '#sizeof' do
200
+ expect(Bistro.sizeof(LIL_STRUCT_DEF)).to eq(LIL_STRUCT_DEF_SIZE)
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,44 @@
1
+ describe Bistro do
2
+ let(:gif_header) do
3
+ Bistro.new([
4
+ "a3", :magic,
5
+ "a3", :version,
6
+ "S", :width,
7
+ "S", :height,
8
+ "a", :flags,
9
+ "C", :bg_color_index,
10
+ "C", :pixel_aspect_ratio
11
+ ])
12
+ end
13
+
14
+ it "read a gif header" do
15
+ filename = File.join(File.dirname(__FILE__), %w(data test.gif))
16
+ decoded_header_hash = {
17
+ :magic => "GIF",
18
+ :version => "89a",
19
+ :width => 16,
20
+ :height => 16,
21
+ :flags => "\x80",
22
+ :bg_color_index => 0,
23
+ :pixel_aspect_ratio => 0,
24
+ }
25
+ encoded_header = gif_header.encode(decoded_header_hash)
26
+ header_size = gif_header.size
27
+ header = File.open(filename, "rb") { |f| f.read(header_size) }
28
+ expect(encoded_header).to eq(header)
29
+ end
30
+
31
+ it "write a gif header" do
32
+ header = gif_header.encode(
33
+ :magic => "GIF",
34
+ :version => "89a",
35
+ :width => 16,
36
+ :height => 16,
37
+ :flags => "\x80",
38
+ :bg_color_index => 0,
39
+ :pixel_aspect_ratio => 0
40
+ )
41
+
42
+ expect(header).to eq("GIF89a\x10\x00\x10\x00\x80\x00\x00".force_encoding("ASCII-8BIT"))
43
+ end
44
+ end
@@ -0,0 +1,79 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, make a
10
+ # separate helper file that requires this one and then use it only in the specs
11
+ # that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # The settings below are suggested to provide a good initial experience
19
+ # with RSpec, but feel free to customize to your heart's content.
20
+
21
+ # These two settings work together to allow you to limit a spec run
22
+ # to individual examples or groups you care about by tagging them with
23
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
24
+ # get run.
25
+ config.filter_run :focus
26
+ config.run_all_when_everything_filtered = true
27
+
28
+ # Many RSpec users commonly either run the entire suite or an individual
29
+ # file, and it's useful to allow more verbose output when running an
30
+ # individual spec file.
31
+ if config.files_to_run.one?
32
+ # Use the documentation formatter for detailed output,
33
+ # unless a formatter has already been configured
34
+ # (e.g. via a command-line flag).
35
+ config.default_formatter = 'doc'
36
+ end
37
+
38
+ # Print the 10 slowest examples and example groups at the
39
+ # end of the spec run, to help surface which specs are running
40
+ # particularly slow.
41
+ # config.profile_examples = 10
42
+
43
+ # Run specs in random order to surface order dependencies. If you find an
44
+ # order dependency and want to debug it, you can fix the order by providing
45
+ # the seed, which is printed after each run.
46
+ # --seed 1234
47
+ config.order = :random
48
+
49
+ # Seed global randomization in this process using the `--seed` CLI option.
50
+ # Setting this allows you to use `--seed` to deterministically reproduce
51
+ # test failures related to randomization by passing the same `--seed` value
52
+ # as the one that triggered the failure.
53
+ Kernel.srand config.seed
54
+
55
+ # rspec-expectations config goes here. You can use an alternate
56
+ # assertion/expectation library such as wrong or the stdlib/minitest
57
+ # assertions if you prefer.
58
+ config.expect_with :rspec do |expectations|
59
+ # Enable only the newer, non-monkey-patching expect syntax.
60
+ # For more details, see:
61
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
62
+ expectations.syntax = :expect
63
+ end
64
+
65
+ # rspec-mocks config goes here. You can use an alternate test double
66
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
67
+ config.mock_with :rspec do |mocks|
68
+ # Enable only the newer, non-monkey-patching expect syntax.
69
+ # For more details, see:
70
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
71
+ mocks.syntax = :expect
72
+
73
+ # Prevents you from mocking or stubbing a method that does not exist on
74
+ # a real object. This is generally recommended.
75
+ mocks.verify_partial_doubles = true
76
+ end
77
+ end
78
+
79
+ require 'bistro'
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bistro
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dev Null Productions
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Bistro is a class for dealing with BInary STRuctured data
56
+ email:
57
+ - devnullproductions@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".rspec"
63
+ - LICENSE.txt
64
+ - README.md
65
+ - lib/bistro.rb
66
+ - lib/bistro/version.rb
67
+ - spec/bistro_spec.rb
68
+ - spec/data/test.gif
69
+ - spec/endian_spec.rb
70
+ - spec/gif_spec.rb
71
+ - spec/spec_helper.rb
72
+ homepage: http://github.com/DevNullProd/bistro
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.9.3
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.7.6
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Bistro is a class for dealing with BInary STRuctured data.
96
+ test_files:
97
+ - spec/bistro_spec.rb
98
+ - spec/data/test.gif
99
+ - spec/endian_spec.rb
100
+ - spec/gif_spec.rb
101
+ - spec/spec_helper.rb
102
+ - ".rspec"