bistro 2.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,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"