messagepack 1.0.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.
- checksums.yaml +7 -0
- data/README.adoc +773 -0
- data/Rakefile +8 -0
- data/docs/Gemfile +7 -0
- data/docs/README.md +85 -0
- data/docs/_config.yml +137 -0
- data/docs/_guides/index.adoc +14 -0
- data/docs/_guides/io-streaming.adoc +226 -0
- data/docs/_guides/migration.adoc +218 -0
- data/docs/_guides/performance.adoc +189 -0
- data/docs/_pages/buffer.adoc +85 -0
- data/docs/_pages/extension-types.adoc +117 -0
- data/docs/_pages/factory-pattern.adoc +115 -0
- data/docs/_pages/index.adoc +20 -0
- data/docs/_pages/serialization.adoc +159 -0
- data/docs/_pages/streaming.adoc +97 -0
- data/docs/_pages/symbol-extension.adoc +69 -0
- data/docs/_pages/timestamp-extension.adoc +88 -0
- data/docs/_references/api.adoc +360 -0
- data/docs/_references/extensions.adoc +198 -0
- data/docs/_references/format.adoc +301 -0
- data/docs/_references/index.adoc +14 -0
- data/docs/_tutorials/extension-types.adoc +170 -0
- data/docs/_tutorials/getting-started.adoc +165 -0
- data/docs/_tutorials/index.adoc +14 -0
- data/docs/_tutorials/thread-safety.adoc +157 -0
- data/docs/index.adoc +77 -0
- data/docs/lychee.toml +42 -0
- data/lib/messagepack/bigint.rb +131 -0
- data/lib/messagepack/buffer.rb +534 -0
- data/lib/messagepack/core_ext.rb +34 -0
- data/lib/messagepack/error.rb +24 -0
- data/lib/messagepack/extensions/base.rb +55 -0
- data/lib/messagepack/extensions/registry.rb +154 -0
- data/lib/messagepack/extensions/symbol.rb +38 -0
- data/lib/messagepack/extensions/timestamp.rb +110 -0
- data/lib/messagepack/extensions/value.rb +38 -0
- data/lib/messagepack/factory.rb +349 -0
- data/lib/messagepack/format.rb +99 -0
- data/lib/messagepack/packer.rb +702 -0
- data/lib/messagepack/symbol.rb +4 -0
- data/lib/messagepack/time.rb +29 -0
- data/lib/messagepack/timestamp.rb +4 -0
- data/lib/messagepack/unpacker.rb +1418 -0
- data/lib/messagepack/version.rb +5 -0
- data/lib/messagepack.rb +81 -0
- metadata +94 -0
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'buffer'
|
|
4
|
+
require_relative 'format'
|
|
5
|
+
|
|
6
|
+
module Messagepack
|
|
7
|
+
# Unpacker deserializes MessagePack binary data into Ruby objects.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# unpacker = Messagepack::Unpacker.new
|
|
11
|
+
# unpacker.feed(data)
|
|
12
|
+
# obj = unpacker.read
|
|
13
|
+
#
|
|
14
|
+
# Or for one-shot operations:
|
|
15
|
+
# obj = Messagepack::Unpacker.new.full_unpack(data)
|
|
16
|
+
#
|
|
17
|
+
class Unpacker
|
|
18
|
+
STACK_CAPACITY = 128
|
|
19
|
+
|
|
20
|
+
# Disable dup and clone as they have weird semantics
|
|
21
|
+
undef_method :dup
|
|
22
|
+
undef_method :clone
|
|
23
|
+
|
|
24
|
+
# Sentinel to distinguish "no data" from "nil value"
|
|
25
|
+
UNAVAILABLE = Object.new
|
|
26
|
+
|
|
27
|
+
attr_reader :buffer, :symbolize_keys, :freeze_objects, :allow_unknown_ext, :optimized_symbols_parsing, :frozen
|
|
28
|
+
|
|
29
|
+
# Predicate methods for boolean options
|
|
30
|
+
def symbolize_keys?
|
|
31
|
+
@symbolize_keys
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def freeze?
|
|
35
|
+
@freeze_objects
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def allow_unknown_ext?
|
|
39
|
+
@allow_unknown_ext
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def optimized_symbols_parsing?
|
|
43
|
+
@optimized_symbols_parsing
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(io = nil, symbolize_keys: false, freeze: false,
|
|
47
|
+
allow_unknown_ext: false, optimized_symbols_parsing: false, **kwargs)
|
|
48
|
+
@buffer = BinaryBuffer.new(io)
|
|
49
|
+
@symbolize_keys = symbolize_keys
|
|
50
|
+
@freeze_objects = freeze
|
|
51
|
+
@allow_unknown_ext = allow_unknown_ext
|
|
52
|
+
@optimized_symbols_parsing = optimized_symbols_parsing
|
|
53
|
+
@ext_registry = ExtensionRegistry::Unpacker.new
|
|
54
|
+
@frozen = false # Custom frozen flag for pool
|
|
55
|
+
|
|
56
|
+
@stack = []
|
|
57
|
+
@last_object = nil
|
|
58
|
+
@head_byte = nil
|
|
59
|
+
@head_byte_consumed = false # Track if we've already consumed the format byte
|
|
60
|
+
@fed_data = false # Track if any data has been fed
|
|
61
|
+
|
|
62
|
+
# Partial read state for strings/binaries
|
|
63
|
+
@partial_read = nil # Stores {type: :str8/:str16/:etc, length: n, buffer: ""}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Set the extension registry for this unpacker.
|
|
67
|
+
# Used internally by Factory to inject custom type registrations.
|
|
68
|
+
#
|
|
69
|
+
# @param registry [ExtensionRegistry::Unpacker] The extension registry to use
|
|
70
|
+
#
|
|
71
|
+
def extension_registry=(registry)
|
|
72
|
+
@ext_registry = registry
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Mark this unpacker as frozen for pool use.
|
|
76
|
+
# Prevents type registration when used from a pool.
|
|
77
|
+
#
|
|
78
|
+
def freeze_for_pool
|
|
79
|
+
@frozen = true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Feed more data for streaming.
|
|
83
|
+
def feed(data)
|
|
84
|
+
@buffer.feed(data)
|
|
85
|
+
@fed_data = true # Mark that data has been fed
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
alias << feed
|
|
90
|
+
|
|
91
|
+
# Read one complete object.
|
|
92
|
+
# Returns nil if not enough data is available.
|
|
93
|
+
def read
|
|
94
|
+
result = nil
|
|
95
|
+
|
|
96
|
+
loop do
|
|
97
|
+
# If we have a partial read in progress, try to complete it
|
|
98
|
+
if @partial_read
|
|
99
|
+
obj = complete_partial_read
|
|
100
|
+
if obj.equal?(UNAVAILABLE)
|
|
101
|
+
# Still need more data
|
|
102
|
+
return nil
|
|
103
|
+
else
|
|
104
|
+
# Partial read complete, process and return
|
|
105
|
+
@partial_read = nil
|
|
106
|
+
obj = process_object(obj)
|
|
107
|
+
next if obj.equal?(UNAVAILABLE)
|
|
108
|
+
result = obj
|
|
109
|
+
break
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Read the format byte
|
|
114
|
+
# For non-IO case, use peek_byte to avoid consuming before we know we have enough data
|
|
115
|
+
# For IO case, use read_byte to trigger reading from the IO
|
|
116
|
+
if @buffer.io
|
|
117
|
+
# Always use read_byte for IO (it will trigger ensure_readable)
|
|
118
|
+
@head_byte ||= @buffer.read_byte
|
|
119
|
+
@head_byte_consumed = true
|
|
120
|
+
else
|
|
121
|
+
# Non-IO case: use peek_byte
|
|
122
|
+
@head_byte ||= @buffer.peek_byte
|
|
123
|
+
@head_byte_consumed = false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# If no format byte is available, handle EOF or IO case
|
|
127
|
+
if @head_byte.nil?
|
|
128
|
+
if @buffer.io
|
|
129
|
+
# For IO case, check if we've consumed all data
|
|
130
|
+
# If buffer is empty and we've read all bytes from IO, return nil (not an error)
|
|
131
|
+
if @buffer.bytes_available == 0 && @buffer.instance_variable_get(:@position) >= @buffer.instance_variable_get(:@length)
|
|
132
|
+
# IO is fully consumed, return nil
|
|
133
|
+
return nil
|
|
134
|
+
elsif !@fed_data
|
|
135
|
+
# Couldn't read from IO on first attempt (empty IO)
|
|
136
|
+
raise EOFError, "no data available"
|
|
137
|
+
else
|
|
138
|
+
# Already tried reading from IO, return nil
|
|
139
|
+
return nil
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
# No IO and no data
|
|
143
|
+
if !@fed_data
|
|
144
|
+
raise EOFError, "no data available"
|
|
145
|
+
else
|
|
146
|
+
return nil
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
format_byte = @head_byte
|
|
152
|
+
|
|
153
|
+
# Check if we have enough bytes to parse this format
|
|
154
|
+
# Note: For IO case, format byte is already consumed, so we need one less byte
|
|
155
|
+
needed_bytes = needed_bytes_for_format(format_byte)
|
|
156
|
+
if needed_bytes
|
|
157
|
+
# Adjust for already-consumed format byte
|
|
158
|
+
needed_bytes -= 1 if @head_byte_consumed
|
|
159
|
+
unless @buffer.bytes_available >= needed_bytes
|
|
160
|
+
return nil
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
# needed_bytes_for_format returned false (error case)
|
|
164
|
+
return nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Consume the format byte if we peeked at it
|
|
168
|
+
unless @head_byte_consumed
|
|
169
|
+
@buffer.read_byte
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Clear @head_byte before dispatching to avoid re-reading the same byte
|
|
173
|
+
@head_byte = nil
|
|
174
|
+
@head_byte_consumed = false
|
|
175
|
+
|
|
176
|
+
obj = dispatch_read(format_byte)
|
|
177
|
+
|
|
178
|
+
# If we couldn't read anything and have no stack, return nil
|
|
179
|
+
if obj.equal?(UNAVAILABLE) && @stack.empty? && @partial_read.nil?
|
|
180
|
+
return nil
|
|
181
|
+
elsif obj.equal?(UNAVAILABLE)
|
|
182
|
+
# We're in the middle of reading a container, continue loop
|
|
183
|
+
next
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Process the object we just read (or completed nested container)
|
|
187
|
+
obj = process_object(obj)
|
|
188
|
+
|
|
189
|
+
# If process_object returned UNAVAILABLE (need more elements for container),
|
|
190
|
+
# continue the loop to try reading more data from the buffer
|
|
191
|
+
if obj.equal?(UNAVAILABLE)
|
|
192
|
+
# Check if we have more data in the buffer to continue reading
|
|
193
|
+
# For IO streams, peek_byte might return nil even if more data is available
|
|
194
|
+
# (because peek_byte doesn't trigger ensure_readable), so also check for IO
|
|
195
|
+
if @buffer.peek_byte || @buffer.io
|
|
196
|
+
# Have more data in buffer, or have IO to try reading from
|
|
197
|
+
next
|
|
198
|
+
end
|
|
199
|
+
# No more data and no IO, return nil to wait for more
|
|
200
|
+
return nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# If we got a result and stack is now empty, we're done
|
|
204
|
+
if !obj.nil? && @stack.empty?
|
|
205
|
+
result = obj
|
|
206
|
+
break
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
result
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Get the number of bytes needed to parse the given format byte
|
|
214
|
+
# Returns nil if the format is unknown
|
|
215
|
+
def needed_bytes_for_format(format_byte)
|
|
216
|
+
available = @buffer.bytes_available
|
|
217
|
+
|
|
218
|
+
# For each format type, determine the total bytes needed (format + data)
|
|
219
|
+
needed_bytes = case format_byte
|
|
220
|
+
when 0x00..0x7f # positive fixnum
|
|
221
|
+
1 # Format byte is the value
|
|
222
|
+
when 0xe0..0xff # negative fixnum
|
|
223
|
+
1 # Format byte is the value
|
|
224
|
+
when 0xa0..0xbf # fixstr
|
|
225
|
+
1 + (format_byte & 0x1f) # Format byte + length in lower 5 bits
|
|
226
|
+
when 0x90..0x9f # fixarray
|
|
227
|
+
1 # Format byte (elements are parsed separately)
|
|
228
|
+
when 0x80..0x8f # fixmap
|
|
229
|
+
1 # Format byte (elements are parsed separately)
|
|
230
|
+
when 0xc0 # nil
|
|
231
|
+
1
|
|
232
|
+
when 0xc2 # false
|
|
233
|
+
1
|
|
234
|
+
when 0xc3 # true
|
|
235
|
+
1
|
|
236
|
+
when 0xc4 # bin8
|
|
237
|
+
# Need to peek at length byte (after format byte)
|
|
238
|
+
return nil if available < 2
|
|
239
|
+
buffer_data = @buffer.to_s
|
|
240
|
+
length = buffer_data.getbyte(1)
|
|
241
|
+
return nil if length.nil?
|
|
242
|
+
1 + 1 + length # format + length + data
|
|
243
|
+
when 0xc5 # bin16
|
|
244
|
+
return nil if available < 3
|
|
245
|
+
buffer_data = @buffer.to_s
|
|
246
|
+
length = buffer_data.byteslice(1, 2).unpack1('n') rescue 0
|
|
247
|
+
1 + 2 + length
|
|
248
|
+
when 0xc6 # bin32
|
|
249
|
+
return nil if available < 5
|
|
250
|
+
buffer_data = @buffer.to_s
|
|
251
|
+
length = buffer_data.byteslice(1, 4).unpack1('N') rescue 0
|
|
252
|
+
1 + 4 + length
|
|
253
|
+
when 0xc7 # ext8
|
|
254
|
+
return nil if available < 2
|
|
255
|
+
buffer_data = @buffer.to_s
|
|
256
|
+
length = buffer_data.getbyte(1) # Length of payload
|
|
257
|
+
return nil if length.nil?
|
|
258
|
+
1 + 1 + 1 + length # format + length + type + payload
|
|
259
|
+
when 0xc8 # ext16
|
|
260
|
+
return nil if available < 3
|
|
261
|
+
buffer_data = @buffer.to_s
|
|
262
|
+
length = buffer_data.byteslice(1, 2).unpack1('n') rescue 0
|
|
263
|
+
1 + 2 + 1 + length
|
|
264
|
+
when 0xc9 # ext32
|
|
265
|
+
return nil if available < 5
|
|
266
|
+
buffer_data = @buffer.to_s
|
|
267
|
+
length = buffer_data.byteslice(1, 4).unpack1('N') rescue 0
|
|
268
|
+
1 + 4 + 1 + length
|
|
269
|
+
when 0xca # float32
|
|
270
|
+
1 + 4 # format + 4 data bytes
|
|
271
|
+
when 0xcb # float64
|
|
272
|
+
1 + 8 # format + 8 data bytes
|
|
273
|
+
when 0xcc # uint8
|
|
274
|
+
1 + 1 # format + 1 data byte
|
|
275
|
+
when 0xcd # uint16
|
|
276
|
+
1 + 2 # format + 2 data bytes
|
|
277
|
+
when 0xce # uint32
|
|
278
|
+
1 + 4 # format + 4 data bytes
|
|
279
|
+
when 0xcf # uint64
|
|
280
|
+
1 + 8 # format + 8 data bytes
|
|
281
|
+
when 0xd0 # int8
|
|
282
|
+
1 + 1 # format + 1 data byte
|
|
283
|
+
when 0xd1 # int16
|
|
284
|
+
1 + 2 # format + 2 data bytes
|
|
285
|
+
when 0xd2 # int32
|
|
286
|
+
1 + 4 # format + 4 data bytes
|
|
287
|
+
when 0xd3 # int64
|
|
288
|
+
1 + 8 # format + 8 data bytes
|
|
289
|
+
when 0xd4 # fixext1
|
|
290
|
+
1 + 1 # format + 1 type + 1 data
|
|
291
|
+
when 0xd5 # fixext2
|
|
292
|
+
1 + 1 + 2
|
|
293
|
+
when 0xd6 # fixext4
|
|
294
|
+
1 + 1 + 4
|
|
295
|
+
when 0xd7 # fixext8
|
|
296
|
+
1 + 1 + 8
|
|
297
|
+
when 0xd8 # fixext16
|
|
298
|
+
1 + 1 + 16
|
|
299
|
+
when 0xd9 # str8
|
|
300
|
+
return nil if available < 2
|
|
301
|
+
buffer_data = @buffer.to_s
|
|
302
|
+
length = buffer_data.getbyte(1)
|
|
303
|
+
return nil if length.nil?
|
|
304
|
+
1 + 1 + length
|
|
305
|
+
when 0xda # str16
|
|
306
|
+
return nil if available < 3
|
|
307
|
+
buffer_data = @buffer.to_s
|
|
308
|
+
length = buffer_data.byteslice(1, 2).unpack1('n') rescue 0
|
|
309
|
+
1 + 2 + length
|
|
310
|
+
when 0xdb # str32
|
|
311
|
+
return nil if available < 5
|
|
312
|
+
buffer_data = @buffer.to_s
|
|
313
|
+
length = buffer_data.byteslice(1, 4).unpack1('N') rescue 0
|
|
314
|
+
1 + 4 + length
|
|
315
|
+
when 0xdc # array16
|
|
316
|
+
1 + 2 # format + 2 bytes count
|
|
317
|
+
when 0xdd # array32
|
|
318
|
+
1 + 4
|
|
319
|
+
when 0xde # map16
|
|
320
|
+
1 + 2
|
|
321
|
+
when 0xdf # map32
|
|
322
|
+
1 + 4
|
|
323
|
+
else
|
|
324
|
+
raise MalformedFormatError, "Unknown format byte: 0x#{format_byte.to_s(16)}"
|
|
325
|
+
end
|
|
326
|
+
# Returns the number of bytes needed for this format
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Complete a partial read (string/binary that spanned multiple feed calls)
|
|
330
|
+
def complete_partial_read
|
|
331
|
+
type = @partial_read[:type]
|
|
332
|
+
length = @partial_read[:length]
|
|
333
|
+
|
|
334
|
+
case type
|
|
335
|
+
when :str8, :bin8
|
|
336
|
+
data = @buffer.read_bytes(length)
|
|
337
|
+
if data.nil?
|
|
338
|
+
UNAVAILABLE
|
|
339
|
+
else
|
|
340
|
+
@partial_read = nil
|
|
341
|
+
data.force_encoding(type == :str8 ? Encoding::UTF_8 : Encoding::BINARY)
|
|
342
|
+
end
|
|
343
|
+
when :str16, :bin16
|
|
344
|
+
data = @buffer.read_bytes(length)
|
|
345
|
+
if data.nil?
|
|
346
|
+
UNAVAILABLE
|
|
347
|
+
else
|
|
348
|
+
@partial_read = nil
|
|
349
|
+
data.force_encoding(type == :str16 ? Encoding::UTF_8 : Encoding::BINARY)
|
|
350
|
+
end
|
|
351
|
+
when :str32, :bin32
|
|
352
|
+
data = @buffer.read_bytes(length)
|
|
353
|
+
if data.nil?
|
|
354
|
+
UNAVAILABLE
|
|
355
|
+
else
|
|
356
|
+
@partial_read = nil
|
|
357
|
+
data.force_encoding(type == :str32 ? Encoding::UTF_8 : Encoding::BINARY)
|
|
358
|
+
end
|
|
359
|
+
when :fixstr
|
|
360
|
+
data = @buffer.read_bytes(length)
|
|
361
|
+
if data.nil?
|
|
362
|
+
UNAVAILABLE
|
|
363
|
+
else
|
|
364
|
+
@partial_read = nil
|
|
365
|
+
data.force_encoding(Encoding::UTF_8)
|
|
366
|
+
end
|
|
367
|
+
when :fixext1, :fixext2, :fixext4, :fixext8, :fixext16
|
|
368
|
+
ext_size = { fixext1: 1, fixext2: 2, fixext4: 4, fixext8: 8, fixext16: 16 }[type]
|
|
369
|
+
payload = @buffer.read_bytes(ext_size)
|
|
370
|
+
if payload.nil?
|
|
371
|
+
UNAVAILABLE
|
|
372
|
+
else
|
|
373
|
+
@partial_read = nil
|
|
374
|
+
ExtensionValue.new(@partial_read[:ext_type], payload)
|
|
375
|
+
end
|
|
376
|
+
when :ext8, :ext16, :ext32
|
|
377
|
+
payload = @buffer.read_bytes(length)
|
|
378
|
+
if payload.nil?
|
|
379
|
+
UNAVAILABLE
|
|
380
|
+
else
|
|
381
|
+
@partial_read = nil
|
|
382
|
+
ExtensionValue.new(@partial_read[:ext_type], payload)
|
|
383
|
+
end
|
|
384
|
+
else
|
|
385
|
+
UNAVAILABLE
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Process an object that was just read or a completed nested container
|
|
390
|
+
def process_object(obj)
|
|
391
|
+
# Keep processing while we have nested context
|
|
392
|
+
while !@stack.empty?
|
|
393
|
+
frame = @stack.last
|
|
394
|
+
|
|
395
|
+
case frame.type
|
|
396
|
+
when :array
|
|
397
|
+
frame.object << obj
|
|
398
|
+
frame.count -= 1
|
|
399
|
+
if frame.count == 0
|
|
400
|
+
# Array complete, pop and return for parent processing
|
|
401
|
+
obj = complete_array(@stack.pop.object)
|
|
402
|
+
# Continue to add this to parent frame
|
|
403
|
+
next
|
|
404
|
+
else
|
|
405
|
+
# Need more elements for this array
|
|
406
|
+
return UNAVAILABLE
|
|
407
|
+
end
|
|
408
|
+
when :map_key
|
|
409
|
+
# Convert string keys to symbols if symbolize_keys is enabled
|
|
410
|
+
if @symbolize_keys && obj.is_a?(String)
|
|
411
|
+
obj = obj.to_sym
|
|
412
|
+
end
|
|
413
|
+
frame.key = obj
|
|
414
|
+
frame.type = :map_value
|
|
415
|
+
return UNAVAILABLE # Need to read the value
|
|
416
|
+
when :map_value
|
|
417
|
+
# obj is the value, frame.key is the key
|
|
418
|
+
frame.object[frame.key] = obj
|
|
419
|
+
frame.count -= 1
|
|
420
|
+
if frame.count == 0
|
|
421
|
+
# Map complete, pop and return for parent processing
|
|
422
|
+
obj = complete_map(@stack.pop.object)
|
|
423
|
+
# Continue to add this to parent frame
|
|
424
|
+
next
|
|
425
|
+
else
|
|
426
|
+
frame.type = :map_key
|
|
427
|
+
return UNAVAILABLE # Need to read next key
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Stack is empty, this is the final result
|
|
433
|
+
# Apply freeze option if enabled
|
|
434
|
+
if @freeze_objects
|
|
435
|
+
obj = freeze_object(obj)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
obj
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Freeze an object and all its contents recursively.
|
|
442
|
+
def freeze_object(obj)
|
|
443
|
+
return obj if obj.frozen?
|
|
444
|
+
|
|
445
|
+
case obj
|
|
446
|
+
when String
|
|
447
|
+
# Use -'' syntax to create frozen/deduped string
|
|
448
|
+
-obj
|
|
449
|
+
when Array
|
|
450
|
+
obj.each_with_index do |item, i|
|
|
451
|
+
obj[i] = freeze_object(item)
|
|
452
|
+
end
|
|
453
|
+
obj.freeze
|
|
454
|
+
when Hash
|
|
455
|
+
# First, recursively freeze all keys and values (before freezing the hash)
|
|
456
|
+
new_hash = {}
|
|
457
|
+
obj.each do |k, v|
|
|
458
|
+
frozen_key = freeze_object(k)
|
|
459
|
+
frozen_value = freeze_object(v)
|
|
460
|
+
new_hash[frozen_key] = frozen_value
|
|
461
|
+
end
|
|
462
|
+
# Replace the hash contents with the frozen version
|
|
463
|
+
obj.clear
|
|
464
|
+
new_hash.each { |k, v| obj[k] = v }
|
|
465
|
+
obj.freeze
|
|
466
|
+
else
|
|
467
|
+
obj
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Read and return array header count.
|
|
472
|
+
def read_array_header
|
|
473
|
+
byte = @buffer.read_byte
|
|
474
|
+
raise UnexpectedTypeError, "no data" if byte.nil?
|
|
475
|
+
|
|
476
|
+
count = case
|
|
477
|
+
when Format.fixarray?(byte)
|
|
478
|
+
Format.fixarray_count(byte)
|
|
479
|
+
when byte == Format::ARRAY16
|
|
480
|
+
n = @buffer.read_big_endian_uint16
|
|
481
|
+
raise UnexpectedTypeError, "unexpected EOF" if n.nil?
|
|
482
|
+
n
|
|
483
|
+
when byte == Format::ARRAY32
|
|
484
|
+
n = @buffer.read_big_endian_uint32
|
|
485
|
+
raise UnexpectedTypeError, "unexpected EOF" if n.nil?
|
|
486
|
+
n
|
|
487
|
+
else
|
|
488
|
+
raise UnexpectedTypeError, "unexpected format (byte=0x#{byte.to_s(16)})"
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
count
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Read and return map header count.
|
|
495
|
+
def read_map_header
|
|
496
|
+
byte = @buffer.read_byte
|
|
497
|
+
raise UnexpectedTypeError, "no data" if byte.nil?
|
|
498
|
+
|
|
499
|
+
count = case
|
|
500
|
+
when Format.fixmap?(byte)
|
|
501
|
+
Format.fixmap_count(byte)
|
|
502
|
+
when byte == Format::MAP16
|
|
503
|
+
n = @buffer.read_big_endian_uint16
|
|
504
|
+
raise UnexpectedTypeError, "unexpected EOF" if n.nil?
|
|
505
|
+
n
|
|
506
|
+
when byte == Format::MAP32
|
|
507
|
+
n = @buffer.read_big_endian_uint32
|
|
508
|
+
raise UnexpectedTypeError, "unexpected EOF" if n.nil?
|
|
509
|
+
n
|
|
510
|
+
else
|
|
511
|
+
raise UnexpectedTypeError, "unexpected format (byte=0x#{byte.to_s(16)})"
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
count
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Iterate over all objects in the buffer.
|
|
518
|
+
def each
|
|
519
|
+
return enum_for(__method__) unless block_given?
|
|
520
|
+
|
|
521
|
+
while obj = read
|
|
522
|
+
yield obj
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Read single object and reset.
|
|
527
|
+
def full_unpack
|
|
528
|
+
obj = read
|
|
529
|
+
# Check for extra bytes after the deserialized object
|
|
530
|
+
# Only check if not reading from an IO (which could have more data)
|
|
531
|
+
if !@buffer.io && !@buffer.empty?
|
|
532
|
+
raise MalformedFormatError, "#{@buffer.bytes_available} extra bytes after the deserialized object"
|
|
533
|
+
end
|
|
534
|
+
reset
|
|
535
|
+
obj
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
alias unpack full_unpack
|
|
539
|
+
|
|
540
|
+
# Reset unpacker state.
|
|
541
|
+
def reset
|
|
542
|
+
@buffer.reset
|
|
543
|
+
@stack.clear
|
|
544
|
+
@last_object = nil
|
|
545
|
+
@head_byte = nil
|
|
546
|
+
@head_byte_consumed = false
|
|
547
|
+
@partial_read = nil
|
|
548
|
+
@frozen = false
|
|
549
|
+
self
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Feed data and iterate over all objects in the buffer.
|
|
553
|
+
#
|
|
554
|
+
# @param data [String] Binary data to feed
|
|
555
|
+
# @yield [Object] Each unpacked object
|
|
556
|
+
# @return [Enumerator] Enumerator if no block given
|
|
557
|
+
#
|
|
558
|
+
def feed_each(data)
|
|
559
|
+
return enum_for(__method__, data) unless block_given?
|
|
560
|
+
|
|
561
|
+
feed(data)
|
|
562
|
+
each { |obj| yield obj }
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
# Skip one complete object without deserializing it.
|
|
566
|
+
#
|
|
567
|
+
# @return [self]
|
|
568
|
+
# @raise [EOFError] if no data is available
|
|
569
|
+
# @raise [StackError] if stack depth exceeds capacity
|
|
570
|
+
# @raise [MalformedFormatError] if invalid format byte encountered
|
|
571
|
+
#
|
|
572
|
+
def skip
|
|
573
|
+
# Check if we have data
|
|
574
|
+
@head_byte ||= @buffer.read_byte
|
|
575
|
+
raise EOFError, "no data available" if @head_byte.nil?
|
|
576
|
+
|
|
577
|
+
# Use a temporary stack to track nesting during skip
|
|
578
|
+
temp_stack = []
|
|
579
|
+
byte = @head_byte
|
|
580
|
+
|
|
581
|
+
loop do
|
|
582
|
+
# Check stack depth
|
|
583
|
+
if temp_stack.length > STACK_CAPACITY
|
|
584
|
+
raise StackError, "stack depth too deep"
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
case byte
|
|
588
|
+
when 0x00..0x7f # Positive fixint
|
|
589
|
+
# Single byte, done if stack empty
|
|
590
|
+
break if temp_stack.empty?
|
|
591
|
+
# Otherwise, we're inside a container
|
|
592
|
+
temp_stack[-1] -= 1
|
|
593
|
+
break if temp_stack[-1] == 0
|
|
594
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
595
|
+
|
|
596
|
+
when 0xe0..0xff # Negative fixint
|
|
597
|
+
# Single byte, done if stack empty
|
|
598
|
+
break if temp_stack.empty?
|
|
599
|
+
temp_stack[-1] -= 1
|
|
600
|
+
break if temp_stack[-1] == 0
|
|
601
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
602
|
+
|
|
603
|
+
when 0xc0 # nil
|
|
604
|
+
break if temp_stack.empty?
|
|
605
|
+
temp_stack[-1] -= 1
|
|
606
|
+
break if temp_stack[-1] == 0
|
|
607
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
608
|
+
|
|
609
|
+
when 0xc2 # false
|
|
610
|
+
break if temp_stack.empty?
|
|
611
|
+
temp_stack[-1] -= 1
|
|
612
|
+
break if temp_stack[-1] == 0
|
|
613
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
614
|
+
|
|
615
|
+
when 0xc3 # true
|
|
616
|
+
break if temp_stack.empty?
|
|
617
|
+
temp_stack[-1] -= 1
|
|
618
|
+
break if temp_stack[-1] == 0
|
|
619
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
620
|
+
|
|
621
|
+
when 0xcc # uint8
|
|
622
|
+
@buffer.skip_bytes(1)
|
|
623
|
+
break if temp_stack.empty?
|
|
624
|
+
temp_stack[-1] -= 1
|
|
625
|
+
break if temp_stack[-1] == 0
|
|
626
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
627
|
+
|
|
628
|
+
when 0xcd # uint16
|
|
629
|
+
@buffer.skip_bytes(2)
|
|
630
|
+
break if temp_stack.empty?
|
|
631
|
+
temp_stack[-1] -= 1
|
|
632
|
+
break if temp_stack[-1] == 0
|
|
633
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
634
|
+
|
|
635
|
+
when 0xce # uint32
|
|
636
|
+
@buffer.skip_bytes(4)
|
|
637
|
+
break if temp_stack.empty?
|
|
638
|
+
temp_stack[-1] -= 1
|
|
639
|
+
break if temp_stack[-1] == 0
|
|
640
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
641
|
+
|
|
642
|
+
when 0xcf # uint64
|
|
643
|
+
@buffer.skip_bytes(8)
|
|
644
|
+
break if temp_stack.empty?
|
|
645
|
+
temp_stack[-1] -= 1
|
|
646
|
+
break if temp_stack[-1] == 0
|
|
647
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
648
|
+
|
|
649
|
+
when 0xd0 # int8
|
|
650
|
+
@buffer.skip_bytes(1)
|
|
651
|
+
break if temp_stack.empty?
|
|
652
|
+
temp_stack[-1] -= 1
|
|
653
|
+
break if temp_stack[-1] == 0
|
|
654
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
655
|
+
|
|
656
|
+
when 0xd1 # int16
|
|
657
|
+
@buffer.skip_bytes(2)
|
|
658
|
+
break if temp_stack.empty?
|
|
659
|
+
temp_stack[-1] -= 1
|
|
660
|
+
break if temp_stack[-1] == 0
|
|
661
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
662
|
+
|
|
663
|
+
when 0xd2 # int32
|
|
664
|
+
@buffer.skip_bytes(4)
|
|
665
|
+
break if temp_stack.empty?
|
|
666
|
+
temp_stack[-1] -= 1
|
|
667
|
+
break if temp_stack[-1] == 0
|
|
668
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
669
|
+
|
|
670
|
+
when 0xd3 # int64
|
|
671
|
+
@buffer.skip_bytes(8)
|
|
672
|
+
break if temp_stack.empty?
|
|
673
|
+
temp_stack[-1] -= 1
|
|
674
|
+
break if temp_stack[-1] == 0
|
|
675
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
676
|
+
|
|
677
|
+
when 0xca # float32
|
|
678
|
+
@buffer.skip_bytes(4)
|
|
679
|
+
break if temp_stack.empty?
|
|
680
|
+
temp_stack[-1] -= 1
|
|
681
|
+
break if temp_stack[-1] == 0
|
|
682
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
683
|
+
|
|
684
|
+
when 0xcb # float64
|
|
685
|
+
@buffer.skip_bytes(8)
|
|
686
|
+
break if temp_stack.empty?
|
|
687
|
+
temp_stack[-1] -= 1
|
|
688
|
+
break if temp_stack[-1] == 0
|
|
689
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
690
|
+
|
|
691
|
+
when 0xa0..0xbf # fixstr
|
|
692
|
+
length = byte & 0x1f
|
|
693
|
+
@buffer.skip_bytes(length)
|
|
694
|
+
break if temp_stack.empty?
|
|
695
|
+
temp_stack[-1] -= 1
|
|
696
|
+
break if temp_stack[-1] == 0
|
|
697
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
698
|
+
|
|
699
|
+
when 0xd9 # str8
|
|
700
|
+
length = @buffer.read_byte
|
|
701
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
702
|
+
@buffer.skip_bytes(length)
|
|
703
|
+
break if temp_stack.empty?
|
|
704
|
+
temp_stack[-1] -= 1
|
|
705
|
+
break if temp_stack[-1] == 0
|
|
706
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
707
|
+
|
|
708
|
+
when 0xda # str16
|
|
709
|
+
length = @buffer.read_big_endian_uint16
|
|
710
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
711
|
+
@buffer.skip_bytes(length)
|
|
712
|
+
break if temp_stack.empty?
|
|
713
|
+
temp_stack[-1] -= 1
|
|
714
|
+
break if temp_stack[-1] == 0
|
|
715
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
716
|
+
|
|
717
|
+
when 0xdb # str32
|
|
718
|
+
length = @buffer.read_big_endian_uint32
|
|
719
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
720
|
+
@buffer.skip_bytes(length)
|
|
721
|
+
break if temp_stack.empty?
|
|
722
|
+
temp_stack[-1] -= 1
|
|
723
|
+
break if temp_stack[-1] == 0
|
|
724
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
725
|
+
|
|
726
|
+
when 0xc4 # bin8
|
|
727
|
+
length = @buffer.read_byte
|
|
728
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
729
|
+
@buffer.skip_bytes(length)
|
|
730
|
+
break if temp_stack.empty?
|
|
731
|
+
temp_stack[-1] -= 1
|
|
732
|
+
break if temp_stack[-1] == 0
|
|
733
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
734
|
+
|
|
735
|
+
when 0xc5 # bin16
|
|
736
|
+
length = @buffer.read_big_endian_uint16
|
|
737
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
738
|
+
@buffer.skip_bytes(length)
|
|
739
|
+
break if temp_stack.empty?
|
|
740
|
+
temp_stack[-1] -= 1
|
|
741
|
+
break if temp_stack[-1] == 0
|
|
742
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
743
|
+
|
|
744
|
+
when 0xc6 # bin32
|
|
745
|
+
length = @buffer.read_big_endian_uint32
|
|
746
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
747
|
+
@buffer.skip_bytes(length)
|
|
748
|
+
break if temp_stack.empty?
|
|
749
|
+
temp_stack[-1] -= 1
|
|
750
|
+
break if temp_stack[-1] == 0
|
|
751
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
752
|
+
|
|
753
|
+
when 0xd4..0xd8 # fixext
|
|
754
|
+
sizes = { 0xd4 => 1, 0xd5 => 2, 0xd6 => 4, 0xd7 => 8, 0xd8 => 16 }
|
|
755
|
+
length = sizes[byte]
|
|
756
|
+
@buffer.skip_bytes(1 + length) # type + payload
|
|
757
|
+
break if temp_stack.empty?
|
|
758
|
+
temp_stack[-1] -= 1
|
|
759
|
+
break if temp_stack[-1] == 0
|
|
760
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
761
|
+
|
|
762
|
+
when 0xc7 # ext8
|
|
763
|
+
length = @buffer.read_byte
|
|
764
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
765
|
+
@buffer.skip_bytes(1 + length) # type + payload
|
|
766
|
+
break if temp_stack.empty?
|
|
767
|
+
temp_stack[-1] -= 1
|
|
768
|
+
break if temp_stack[-1] == 0
|
|
769
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
770
|
+
|
|
771
|
+
when 0xc8 # ext16
|
|
772
|
+
length = @buffer.read_big_endian_uint16
|
|
773
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
774
|
+
@buffer.skip_bytes(2 + length) # type + payload
|
|
775
|
+
break if temp_stack.empty?
|
|
776
|
+
temp_stack[-1] -= 1
|
|
777
|
+
break if temp_stack[-1] == 0
|
|
778
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
779
|
+
|
|
780
|
+
when 0xc9 # ext32
|
|
781
|
+
length = @buffer.read_big_endian_uint32
|
|
782
|
+
raise EOFError, "unexpected end of data" if length.nil?
|
|
783
|
+
@buffer.skip_bytes(4 + length) # type + payload
|
|
784
|
+
break if temp_stack.empty?
|
|
785
|
+
temp_stack[-1] -= 1
|
|
786
|
+
break if temp_stack[-1] == 0
|
|
787
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
788
|
+
|
|
789
|
+
when 0x90..0x9f # fixarray
|
|
790
|
+
count = byte & 0x0f
|
|
791
|
+
if count > 0
|
|
792
|
+
temp_stack.push(count)
|
|
793
|
+
else
|
|
794
|
+
break if temp_stack.empty?
|
|
795
|
+
temp_stack[-1] -= 1
|
|
796
|
+
break if temp_stack[-1] == 0
|
|
797
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
when 0xdc # array16
|
|
801
|
+
count = @buffer.read_big_endian_uint16
|
|
802
|
+
raise EOFError, "unexpected end of data" if count.nil?
|
|
803
|
+
if count > 0
|
|
804
|
+
temp_stack.push(count)
|
|
805
|
+
else
|
|
806
|
+
break if temp_stack.empty?
|
|
807
|
+
temp_stack[-1] -= 1
|
|
808
|
+
break if temp_stack[-1] == 0
|
|
809
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
when 0xdd # array32
|
|
813
|
+
count = @buffer.read_big_endian_uint32
|
|
814
|
+
raise EOFError, "unexpected end of data" if count.nil?
|
|
815
|
+
if count > 0
|
|
816
|
+
temp_stack.push(count)
|
|
817
|
+
else
|
|
818
|
+
break if temp_stack.empty?
|
|
819
|
+
temp_stack[-1] -= 1
|
|
820
|
+
break if temp_stack[-1] == 0
|
|
821
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
when 0x80..0x8f # fixmap
|
|
825
|
+
count = byte & 0x0f
|
|
826
|
+
if count > 0
|
|
827
|
+
temp_stack.push(count * 2) # Map has key-value pairs
|
|
828
|
+
else
|
|
829
|
+
break if temp_stack.empty?
|
|
830
|
+
temp_stack[-1] -= 1
|
|
831
|
+
break if temp_stack[-1] == 0
|
|
832
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
when 0xde # map16
|
|
836
|
+
count = @buffer.read_big_endian_uint16
|
|
837
|
+
raise EOFError, "unexpected end of data" if count.nil?
|
|
838
|
+
if count > 0
|
|
839
|
+
temp_stack.push(count * 2)
|
|
840
|
+
else
|
|
841
|
+
break if temp_stack.empty?
|
|
842
|
+
temp_stack[-1] -= 1
|
|
843
|
+
break if temp_stack[-1] == 0
|
|
844
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
when 0xdf # map32
|
|
848
|
+
count = @buffer.read_big_endian_uint32
|
|
849
|
+
raise EOFError, "unexpected end of data" if count.nil?
|
|
850
|
+
if count > 0
|
|
851
|
+
temp_stack.push(count * 2)
|
|
852
|
+
else
|
|
853
|
+
break if temp_stack.empty?
|
|
854
|
+
temp_stack[-1] -= 1
|
|
855
|
+
break if temp_stack[-1] == 0
|
|
856
|
+
temp_stack.pop if temp_stack[-1] == 0
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
else
|
|
860
|
+
raise MalformedFormatError, "unknown format byte: 0x#{byte.to_s(16)}"
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
# Read next byte for next iteration
|
|
864
|
+
byte = @buffer.read_byte
|
|
865
|
+
break if byte.nil? && temp_stack.empty?
|
|
866
|
+
raise EOFError, "unexpected end of data" if byte.nil?
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
# Clear the head byte since we've consumed it during skip
|
|
870
|
+
@head_byte = nil
|
|
871
|
+
self
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
# Skip nil value or return false if next object is not nil.
|
|
875
|
+
#
|
|
876
|
+
# @return [Boolean] true if skipped nil, false otherwise
|
|
877
|
+
# @raise [EOFError] if no data is available
|
|
878
|
+
#
|
|
879
|
+
def skip_nil
|
|
880
|
+
@head_byte ||= @buffer.read_byte
|
|
881
|
+
raise EOFError, "no data available" if @head_byte.nil?
|
|
882
|
+
|
|
883
|
+
if @head_byte == Format::NIL
|
|
884
|
+
@head_byte = nil
|
|
885
|
+
return true
|
|
886
|
+
else
|
|
887
|
+
# Not a nil, don't consume the byte (it will be used by next read)
|
|
888
|
+
return false
|
|
889
|
+
end
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
# Extension type registration
|
|
893
|
+
|
|
894
|
+
def register_type(type_id, klass = nil, unpacker_proc = nil, &block)
|
|
895
|
+
raise FrozenError, "can't modify frozen Messagepack::Unpacker" if @frozen
|
|
896
|
+
|
|
897
|
+
# Handle multiple calling patterns:
|
|
898
|
+
# register_type(type_id) { |data| ... }
|
|
899
|
+
# register_type(type_id, klass) { |data| ... }
|
|
900
|
+
# register_type(type_id, klass, :method_name)
|
|
901
|
+
# register_type(type_id, klass, proc)
|
|
902
|
+
|
|
903
|
+
if block_given?
|
|
904
|
+
unpacker_proc = block
|
|
905
|
+
elsif unpacker_proc.is_a?(Symbol)
|
|
906
|
+
# Convert symbol to proc (use a local variable to avoid capture issues)
|
|
907
|
+
method_name = unpacker_proc
|
|
908
|
+
unpacker_proc = ->(data) { klass.send(method_name, data) }
|
|
909
|
+
elsif klass.is_a?(Symbol)
|
|
910
|
+
# register_type(type_id, :method_name) pattern
|
|
911
|
+
unpacker_proc = ->(data) { Object.send(klass, data) }
|
|
912
|
+
klass = nil
|
|
913
|
+
elsif klass&.respond_to?(:call)
|
|
914
|
+
# klass is actually a proc
|
|
915
|
+
unpacker_proc = klass
|
|
916
|
+
klass = nil
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
@ext_registry.register(type_id, klass, unpacker_proc)
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
def registered_types
|
|
923
|
+
@ext_registry.registered_types
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
def type_registered?(klass_or_type)
|
|
927
|
+
@ext_registry.type_registered?(klass_or_type)
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
private
|
|
931
|
+
|
|
932
|
+
# StackFrame class for tracking nested structures
|
|
933
|
+
class StackFrame
|
|
934
|
+
attr_accessor :count, :type, :object, :key
|
|
935
|
+
|
|
936
|
+
def initialize(type, count, object = nil)
|
|
937
|
+
@type = type
|
|
938
|
+
@count = count
|
|
939
|
+
@object = object
|
|
940
|
+
@key = nil
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
# Type dispatch based on first byte
|
|
945
|
+
def dispatch_read(byte)
|
|
946
|
+
return UNAVAILABLE if byte.nil?
|
|
947
|
+
|
|
948
|
+
case byte
|
|
949
|
+
when 0x00..0x7f
|
|
950
|
+
read_positive_fixnum(byte)
|
|
951
|
+
when 0xe0..0xff
|
|
952
|
+
read_negative_fixnum(byte)
|
|
953
|
+
when 0xa0..0xbf
|
|
954
|
+
read_fixstr(byte)
|
|
955
|
+
when 0x90..0x9f
|
|
956
|
+
read_fixarray(byte)
|
|
957
|
+
when 0x80..0x8f
|
|
958
|
+
read_fixmap(byte)
|
|
959
|
+
when 0xc0
|
|
960
|
+
read_nil
|
|
961
|
+
when 0xc2
|
|
962
|
+
read_false
|
|
963
|
+
when 0xc3
|
|
964
|
+
read_true
|
|
965
|
+
when 0xc4
|
|
966
|
+
read_bin8
|
|
967
|
+
when 0xc5
|
|
968
|
+
read_bin16
|
|
969
|
+
when 0xc6
|
|
970
|
+
read_bin32
|
|
971
|
+
when 0xc7
|
|
972
|
+
read_ext8
|
|
973
|
+
when 0xc8
|
|
974
|
+
read_ext16
|
|
975
|
+
when 0xc9
|
|
976
|
+
read_ext32
|
|
977
|
+
when 0xca
|
|
978
|
+
read_float32
|
|
979
|
+
when 0xcb
|
|
980
|
+
read_float64
|
|
981
|
+
when 0xcc
|
|
982
|
+
read_uint8
|
|
983
|
+
when 0xcd
|
|
984
|
+
read_uint16
|
|
985
|
+
when 0xce
|
|
986
|
+
read_uint32
|
|
987
|
+
when 0xcf
|
|
988
|
+
read_uint64
|
|
989
|
+
when 0xd0
|
|
990
|
+
read_int8
|
|
991
|
+
when 0xd1
|
|
992
|
+
read_int16
|
|
993
|
+
when 0xd2
|
|
994
|
+
read_int32
|
|
995
|
+
when 0xd3
|
|
996
|
+
read_int64
|
|
997
|
+
when 0xd4
|
|
998
|
+
read_fixext1
|
|
999
|
+
when 0xd5
|
|
1000
|
+
read_fixext2
|
|
1001
|
+
when 0xd6
|
|
1002
|
+
read_fixext4
|
|
1003
|
+
when 0xd7
|
|
1004
|
+
read_fixext8
|
|
1005
|
+
when 0xd8
|
|
1006
|
+
read_fixext16
|
|
1007
|
+
when 0xd9
|
|
1008
|
+
read_str8
|
|
1009
|
+
when 0xda
|
|
1010
|
+
read_str16
|
|
1011
|
+
when 0xdb
|
|
1012
|
+
read_str32
|
|
1013
|
+
when 0xdc
|
|
1014
|
+
read_array16
|
|
1015
|
+
when 0xdd
|
|
1016
|
+
read_array32
|
|
1017
|
+
when 0xde
|
|
1018
|
+
read_map16
|
|
1019
|
+
when 0xdf
|
|
1020
|
+
read_map32
|
|
1021
|
+
else
|
|
1022
|
+
raise MalformedFormatError, "Unknown format byte: 0x#{byte.to_s(16)}"
|
|
1023
|
+
end
|
|
1024
|
+
end
|
|
1025
|
+
|
|
1026
|
+
# Primitive type readers
|
|
1027
|
+
|
|
1028
|
+
def read_nil
|
|
1029
|
+
nil
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
def read_false
|
|
1033
|
+
false
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
def read_true
|
|
1037
|
+
true
|
|
1038
|
+
end
|
|
1039
|
+
|
|
1040
|
+
def read_positive_fixnum(byte)
|
|
1041
|
+
byte
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
def read_negative_fixnum(byte)
|
|
1045
|
+
Format.negative_fixnum_value(byte)
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
def read_uint8
|
|
1049
|
+
n = @buffer.read_byte
|
|
1050
|
+
return UNAVAILABLE if n.nil?
|
|
1051
|
+
n
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
def read_uint16
|
|
1055
|
+
@buffer.read_big_endian_uint16 || UNAVAILABLE
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
def read_uint32
|
|
1059
|
+
@buffer.read_big_endian_uint32 || UNAVAILABLE
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
def read_uint64
|
|
1063
|
+
@buffer.read_big_endian_uint64 || UNAVAILABLE
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
def read_int8
|
|
1067
|
+
n = @buffer.read_byte
|
|
1068
|
+
return UNAVAILABLE if n.nil?
|
|
1069
|
+
# Convert to signed 8-bit
|
|
1070
|
+
n >= 128 ? n - 256 : n
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
def read_int16
|
|
1074
|
+
n = @buffer.read_big_endian_uint16
|
|
1075
|
+
return UNAVAILABLE if n.nil?
|
|
1076
|
+
# Convert to signed 16-bit
|
|
1077
|
+
n >= 32768 ? n - 65536 : n
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
def read_int32
|
|
1081
|
+
n = @buffer.read_big_endian_uint32
|
|
1082
|
+
return UNAVAILABLE if n.nil?
|
|
1083
|
+
# Convert to signed 32-bit
|
|
1084
|
+
n >= 2**31 ? n - 2**32 : n
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
def read_int64
|
|
1088
|
+
@buffer.read_big_endian_int64 || UNAVAILABLE
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
def read_float32
|
|
1092
|
+
@buffer.read_float32 || UNAVAILABLE
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1095
|
+
def read_float64
|
|
1096
|
+
@buffer.read_float64 || UNAVAILABLE
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
# String and binary readers
|
|
1100
|
+
|
|
1101
|
+
def read_fixstr(byte)
|
|
1102
|
+
length = Format.fixstr_length(byte)
|
|
1103
|
+
data = @buffer.read_bytes(length)
|
|
1104
|
+
if data.nil?
|
|
1105
|
+
# Store partial read state
|
|
1106
|
+
@partial_read = { type: :fixstr, length: length - 0, buffer: "" }
|
|
1107
|
+
UNAVAILABLE
|
|
1108
|
+
else
|
|
1109
|
+
data.force_encoding(Encoding::UTF_8)
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
def read_str8
|
|
1114
|
+
length = @buffer.read_byte
|
|
1115
|
+
return UNAVAILABLE if length.nil?
|
|
1116
|
+
data = @buffer.read_bytes(length)
|
|
1117
|
+
if data.nil?
|
|
1118
|
+
# Store partial read state
|
|
1119
|
+
@partial_read = { type: :str8, length: length, buffer: "" }
|
|
1120
|
+
UNAVAILABLE
|
|
1121
|
+
else
|
|
1122
|
+
data.force_encoding(Encoding::UTF_8)
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def read_str16
|
|
1127
|
+
length = @buffer.read_big_endian_uint16
|
|
1128
|
+
return UNAVAILABLE if length.nil?
|
|
1129
|
+
data = @buffer.read_bytes(length)
|
|
1130
|
+
if data.nil?
|
|
1131
|
+
# Store partial read state
|
|
1132
|
+
@partial_read = { type: :str16, length: length, buffer: "" }
|
|
1133
|
+
UNAVAILABLE
|
|
1134
|
+
else
|
|
1135
|
+
data.force_encoding(Encoding::UTF_8)
|
|
1136
|
+
end
|
|
1137
|
+
end
|
|
1138
|
+
|
|
1139
|
+
def read_str32
|
|
1140
|
+
length = @buffer.read_big_endian_uint32
|
|
1141
|
+
return UNAVAILABLE if length.nil?
|
|
1142
|
+
data = @buffer.read_bytes(length)
|
|
1143
|
+
if data.nil?
|
|
1144
|
+
# Store partial read state
|
|
1145
|
+
@partial_read = { type: :str32, length: length, buffer: "" }
|
|
1146
|
+
UNAVAILABLE
|
|
1147
|
+
else
|
|
1148
|
+
data.force_encoding(Encoding::UTF_8)
|
|
1149
|
+
end
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
def read_bin8
|
|
1153
|
+
length = @buffer.read_byte
|
|
1154
|
+
return UNAVAILABLE if length.nil?
|
|
1155
|
+
data = @buffer.read_bytes(length)
|
|
1156
|
+
if data.nil?
|
|
1157
|
+
# Store partial read state
|
|
1158
|
+
@partial_read = { type: :bin8, length: length, buffer: "" }
|
|
1159
|
+
UNAVAILABLE
|
|
1160
|
+
else
|
|
1161
|
+
data || UNAVAILABLE
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
def read_bin16
|
|
1166
|
+
length = @buffer.read_big_endian_uint16
|
|
1167
|
+
return UNAVAILABLE if length.nil?
|
|
1168
|
+
data = @buffer.read_bytes(length)
|
|
1169
|
+
if data.nil?
|
|
1170
|
+
# Store partial read state
|
|
1171
|
+
@partial_read = { type: :bin16, length: length, buffer: "" }
|
|
1172
|
+
UNAVAILABLE
|
|
1173
|
+
else
|
|
1174
|
+
data || UNAVAILABLE
|
|
1175
|
+
end
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
def read_bin32
|
|
1179
|
+
length = @buffer.read_big_endian_uint32
|
|
1180
|
+
return UNAVAILABLE if length.nil?
|
|
1181
|
+
data = @buffer.read_bytes(length)
|
|
1182
|
+
if data.nil?
|
|
1183
|
+
# Store partial read state
|
|
1184
|
+
@partial_read = { type: :bin32, length: length, buffer: "" }
|
|
1185
|
+
UNAVAILABLE
|
|
1186
|
+
else
|
|
1187
|
+
data || UNAVAILABLE
|
|
1188
|
+
end
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
# Array readers
|
|
1192
|
+
|
|
1193
|
+
def read_fixarray(byte)
|
|
1194
|
+
count = Format.fixarray_count(byte)
|
|
1195
|
+
start_array(count)
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
def read_array16
|
|
1199
|
+
count = @buffer.read_big_endian_uint16
|
|
1200
|
+
return UNAVAILABLE if count.nil?
|
|
1201
|
+
start_array(count)
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
def read_array32
|
|
1205
|
+
count = @buffer.read_big_endian_uint32
|
|
1206
|
+
return UNAVAILABLE if count.nil?
|
|
1207
|
+
start_array(count)
|
|
1208
|
+
end
|
|
1209
|
+
|
|
1210
|
+
def start_array(count)
|
|
1211
|
+
if count == 0
|
|
1212
|
+
[]
|
|
1213
|
+
else
|
|
1214
|
+
@stack.push(StackFrame.new(:array, count, []))
|
|
1215
|
+
UNAVAILABLE # Signal that we started a container
|
|
1216
|
+
end
|
|
1217
|
+
end
|
|
1218
|
+
|
|
1219
|
+
def complete_array(array)
|
|
1220
|
+
if @freeze_objects
|
|
1221
|
+
freeze_object(array)
|
|
1222
|
+
else
|
|
1223
|
+
array
|
|
1224
|
+
end
|
|
1225
|
+
end
|
|
1226
|
+
|
|
1227
|
+
# Map readers
|
|
1228
|
+
|
|
1229
|
+
def read_fixmap(byte)
|
|
1230
|
+
count = Format.fixmap_count(byte)
|
|
1231
|
+
start_map(count)
|
|
1232
|
+
end
|
|
1233
|
+
|
|
1234
|
+
def read_map16
|
|
1235
|
+
count = @buffer.read_big_endian_uint16
|
|
1236
|
+
return UNAVAILABLE if count.nil?
|
|
1237
|
+
start_map(count)
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
def read_map32
|
|
1241
|
+
count = @buffer.read_big_endian_uint32
|
|
1242
|
+
return UNAVAILABLE if count.nil?
|
|
1243
|
+
start_map(count)
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
def start_map(count)
|
|
1247
|
+
if count == 0
|
|
1248
|
+
result = @symbolize_keys ? {} : {}
|
|
1249
|
+
result.freeze if @freeze_objects
|
|
1250
|
+
result
|
|
1251
|
+
else
|
|
1252
|
+
@stack.push(StackFrame.new(:map_key, count, @symbolize_keys ? {} : {}))
|
|
1253
|
+
UNAVAILABLE # Signal that we started a container
|
|
1254
|
+
end
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1257
|
+
def complete_map(hash)
|
|
1258
|
+
if @freeze_objects
|
|
1259
|
+
freeze_object(hash)
|
|
1260
|
+
else
|
|
1261
|
+
hash
|
|
1262
|
+
end
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
# Extension readers
|
|
1266
|
+
|
|
1267
|
+
def read_fixext1
|
|
1268
|
+
type = @buffer.read_byte
|
|
1269
|
+
return UNAVAILABLE if type.nil?
|
|
1270
|
+
type = type >= 128 ? type - 256 : type
|
|
1271
|
+
payload = @buffer.read_bytes(1)
|
|
1272
|
+
if payload.nil?
|
|
1273
|
+
# Store partial read state
|
|
1274
|
+
@partial_read = { type: :fixext1, ext_type: type, length: 1 }
|
|
1275
|
+
UNAVAILABLE
|
|
1276
|
+
else
|
|
1277
|
+
handle_extension(type, payload)
|
|
1278
|
+
end
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1281
|
+
def read_fixext2
|
|
1282
|
+
type = @buffer.read_byte
|
|
1283
|
+
return UNAVAILABLE if type.nil?
|
|
1284
|
+
type = type >= 128 ? type - 256 : type
|
|
1285
|
+
payload = @buffer.read_bytes(2)
|
|
1286
|
+
if payload.nil?
|
|
1287
|
+
# Store partial read state
|
|
1288
|
+
@partial_read = { type: :fixext2, ext_type: type, length: 2 }
|
|
1289
|
+
UNAVAILABLE
|
|
1290
|
+
else
|
|
1291
|
+
handle_extension(type, payload)
|
|
1292
|
+
end
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
def read_fixext4
|
|
1296
|
+
type = @buffer.read_byte
|
|
1297
|
+
return UNAVAILABLE if type.nil?
|
|
1298
|
+
type = type >= 128 ? type - 256 : type
|
|
1299
|
+
payload = @buffer.read_bytes(4)
|
|
1300
|
+
if payload.nil?
|
|
1301
|
+
# Store partial read state
|
|
1302
|
+
@partial_read = { type: :fixext4, ext_type: type, length: 4 }
|
|
1303
|
+
UNAVAILABLE
|
|
1304
|
+
else
|
|
1305
|
+
handle_extension(type, payload)
|
|
1306
|
+
end
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
def read_fixext8
|
|
1310
|
+
type = @buffer.read_byte
|
|
1311
|
+
return UNAVAILABLE if type.nil?
|
|
1312
|
+
type = type >= 128 ? type - 256 : type
|
|
1313
|
+
payload = @buffer.read_bytes(8)
|
|
1314
|
+
if payload.nil?
|
|
1315
|
+
# Store partial read state
|
|
1316
|
+
@partial_read = { type: :fixext8, ext_type: type, length: 8 }
|
|
1317
|
+
UNAVAILABLE
|
|
1318
|
+
else
|
|
1319
|
+
handle_extension(type, payload)
|
|
1320
|
+
end
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
def read_fixext16
|
|
1324
|
+
type = @buffer.read_byte
|
|
1325
|
+
return UNAVAILABLE if type.nil?
|
|
1326
|
+
type = type >= 128 ? type - 256 : type
|
|
1327
|
+
payload = @buffer.read_bytes(16)
|
|
1328
|
+
if payload.nil?
|
|
1329
|
+
# Store partial read state
|
|
1330
|
+
@partial_read = { type: :fixext16, ext_type: type, length: 16 }
|
|
1331
|
+
UNAVAILABLE
|
|
1332
|
+
else
|
|
1333
|
+
handle_extension(type, payload)
|
|
1334
|
+
end
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
def read_ext8
|
|
1338
|
+
length = @buffer.read_byte
|
|
1339
|
+
return UNAVAILABLE if length.nil?
|
|
1340
|
+
type = @buffer.read_byte
|
|
1341
|
+
return UNAVAILABLE if type.nil?
|
|
1342
|
+
type = type >= 128 ? type - 256 : type
|
|
1343
|
+
payload = @buffer.read_bytes(length)
|
|
1344
|
+
if payload.nil?
|
|
1345
|
+
# Store partial read state
|
|
1346
|
+
@partial_read = { type: :ext8, ext_type: type, length: length }
|
|
1347
|
+
UNAVAILABLE
|
|
1348
|
+
else
|
|
1349
|
+
handle_extension(type, payload)
|
|
1350
|
+
end
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
def read_ext16
|
|
1354
|
+
length = @buffer.read_big_endian_uint16
|
|
1355
|
+
return UNAVAILABLE if length.nil?
|
|
1356
|
+
type = @buffer.read_byte
|
|
1357
|
+
return UNAVAILABLE if type.nil?
|
|
1358
|
+
type = type >= 128 ? type - 256 : type
|
|
1359
|
+
payload = @buffer.read_bytes(length)
|
|
1360
|
+
if payload.nil?
|
|
1361
|
+
# Store partial read state
|
|
1362
|
+
@partial_read = { type: :ext16, ext_type: type, length: length }
|
|
1363
|
+
UNAVAILABLE
|
|
1364
|
+
else
|
|
1365
|
+
handle_extension(type, payload)
|
|
1366
|
+
end
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
def read_ext32
|
|
1370
|
+
length = @buffer.read_big_endian_uint32
|
|
1371
|
+
return UNAVAILABLE if length.nil?
|
|
1372
|
+
type = @buffer.read_byte
|
|
1373
|
+
return UNAVAILABLE if type.nil?
|
|
1374
|
+
type = type >= 128 ? type - 256 : type
|
|
1375
|
+
payload = @buffer.read_bytes(length)
|
|
1376
|
+
if payload.nil?
|
|
1377
|
+
# Store partial read state
|
|
1378
|
+
@partial_read = { type: :ext32, ext_type: type, length: length }
|
|
1379
|
+
UNAVAILABLE
|
|
1380
|
+
else
|
|
1381
|
+
handle_extension(type, payload)
|
|
1382
|
+
end
|
|
1383
|
+
end
|
|
1384
|
+
|
|
1385
|
+
def handle_extension(type, payload)
|
|
1386
|
+
klass, unpacker_proc, flags = @ext_registry.lookup(type)
|
|
1387
|
+
|
|
1388
|
+
if klass && unpacker_proc
|
|
1389
|
+
# Check if this is a recursive unpacker
|
|
1390
|
+
if flags && (flags & 0x01) != 0
|
|
1391
|
+
# Recursive unpacker - create a temporary unpacker with the payload
|
|
1392
|
+
temp_unpacker = Unpacker.new
|
|
1393
|
+
temp_unpacker.feed(payload)
|
|
1394
|
+
# Share the extension registry so nested extensions work
|
|
1395
|
+
temp_unpacker.instance_variable_set(:@ext_registry, @ext_registry)
|
|
1396
|
+
# Convert Symbol to method call if needed
|
|
1397
|
+
if unpacker_proc.is_a?(Symbol)
|
|
1398
|
+
klass.send(unpacker_proc, temp_unpacker)
|
|
1399
|
+
else
|
|
1400
|
+
unpacker_proc.call(temp_unpacker)
|
|
1401
|
+
end
|
|
1402
|
+
else
|
|
1403
|
+
# Non-recursive - pass the payload directly
|
|
1404
|
+
# Convert Symbol to method call if needed
|
|
1405
|
+
if unpacker_proc.is_a?(Symbol)
|
|
1406
|
+
klass.send(unpacker_proc, payload)
|
|
1407
|
+
else
|
|
1408
|
+
unpacker_proc.call(payload)
|
|
1409
|
+
end
|
|
1410
|
+
end
|
|
1411
|
+
elsif @allow_unknown_ext
|
|
1412
|
+
ExtensionValue.new(type, payload)
|
|
1413
|
+
else
|
|
1414
|
+
raise UnknownExtTypeError, "Unknown extension type: #{type}"
|
|
1415
|
+
end
|
|
1416
|
+
end
|
|
1417
|
+
end
|
|
1418
|
+
end
|