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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.adoc +773 -0
  3. data/Rakefile +8 -0
  4. data/docs/Gemfile +7 -0
  5. data/docs/README.md +85 -0
  6. data/docs/_config.yml +137 -0
  7. data/docs/_guides/index.adoc +14 -0
  8. data/docs/_guides/io-streaming.adoc +226 -0
  9. data/docs/_guides/migration.adoc +218 -0
  10. data/docs/_guides/performance.adoc +189 -0
  11. data/docs/_pages/buffer.adoc +85 -0
  12. data/docs/_pages/extension-types.adoc +117 -0
  13. data/docs/_pages/factory-pattern.adoc +115 -0
  14. data/docs/_pages/index.adoc +20 -0
  15. data/docs/_pages/serialization.adoc +159 -0
  16. data/docs/_pages/streaming.adoc +97 -0
  17. data/docs/_pages/symbol-extension.adoc +69 -0
  18. data/docs/_pages/timestamp-extension.adoc +88 -0
  19. data/docs/_references/api.adoc +360 -0
  20. data/docs/_references/extensions.adoc +198 -0
  21. data/docs/_references/format.adoc +301 -0
  22. data/docs/_references/index.adoc +14 -0
  23. data/docs/_tutorials/extension-types.adoc +170 -0
  24. data/docs/_tutorials/getting-started.adoc +165 -0
  25. data/docs/_tutorials/index.adoc +14 -0
  26. data/docs/_tutorials/thread-safety.adoc +157 -0
  27. data/docs/index.adoc +77 -0
  28. data/docs/lychee.toml +42 -0
  29. data/lib/messagepack/bigint.rb +131 -0
  30. data/lib/messagepack/buffer.rb +534 -0
  31. data/lib/messagepack/core_ext.rb +34 -0
  32. data/lib/messagepack/error.rb +24 -0
  33. data/lib/messagepack/extensions/base.rb +55 -0
  34. data/lib/messagepack/extensions/registry.rb +154 -0
  35. data/lib/messagepack/extensions/symbol.rb +38 -0
  36. data/lib/messagepack/extensions/timestamp.rb +110 -0
  37. data/lib/messagepack/extensions/value.rb +38 -0
  38. data/lib/messagepack/factory.rb +349 -0
  39. data/lib/messagepack/format.rb +99 -0
  40. data/lib/messagepack/packer.rb +702 -0
  41. data/lib/messagepack/symbol.rb +4 -0
  42. data/lib/messagepack/time.rb +29 -0
  43. data/lib/messagepack/timestamp.rb +4 -0
  44. data/lib/messagepack/unpacker.rb +1418 -0
  45. data/lib/messagepack/version.rb +5 -0
  46. data/lib/messagepack.rb +81 -0
  47. 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