@@ -23,6 +23,7 @@ all_tests() ->
2323 array_with_extra_input ,
2424 array32_count_exceeds_data ,
2525 array_of_zero_width_elements ,
26+ array_of_described_zero_width_elements_unsupported ,
2627 unsupported_type ,
2728 peek_described_section_total_sizes ,
2829 peek_described_section ,
@@ -53,8 +54,10 @@ roundtrip(_Config) ->
5354 {binary , <<" outer value" >>}},
5455 {array , ubyte , [{ubyte , 1 }, {ubyte , 255 }]},
5556 {array , boolean , [true , false , true ]},
56- {array , null , [null , null , null ]},
57- {array , null , []},
57+ % % Zero-width array elements (e.g. null repeated N times) are
58+ % % represented opaquely rather than expanded into N terms.
59+ {as_is , 16#f0 , <<5 :32 , 3 :32 , 16#40 >>},
60+ {as_is , 16#f0 , <<5 :32 , 0 :32 , 16#40 >>},
5861 true ,
5962 {list , [{utf8 , <<" hi" >>},
6063 {described ,
@@ -90,42 +93,72 @@ array_with_extra_input(_Config) ->
9093 ? assertExit (Expected , amqp10_binary_parser :parse_many (Bin , [])).
9194
9295array32_count_exceeds_data (_Config ) ->
93- Count = 16#FFFFFFFF ,
94- Type = 16#45 ,
95- ArrayPayload = <<Count :32 , Type >>,
96- Size = byte_size (ArrayPayload ),
97- Bin = <<16#f0 , Size :32 , ArrayPayload /binary >>,
98- ? assertExit (
99- {failed_to_parse_array_count_exceeds_limit , Type , Count },
100- amqp10_binary_parser :parse (Bin )),
101- % % Also verify smaller mismatches are caught: 10 elements but only 3 data bytes
102- SmallType = 16#50 ,
103- SmallPayload = <<10 :32 , SmallType , 1 , 2 , 3 >>,
104- SmallSize = byte_size (SmallPayload ),
105- SmallBin = <<16#f0 , SmallSize :32 , SmallPayload /binary >>,
106- ? assertExit (
107- {failed_to_parse_array_count_exceeds_input , SmallType , 10 , 3 },
108- amqp10_binary_parser :parse (SmallBin )).
109-
96+ TypeArray32 = 16#f0 ,
97+ TypeUByte = 16#50 ,
98+ % % 10 elements declared but only 3 data bytes available.
99+ Count = 10 ,
100+ Payload = <<Count :32 , TypeUByte , 1 , 2 , 3 >>,
101+ Bin = <<TypeArray32 , (byte_size (Payload )):32 , Payload /binary >>,
102+ ? assertExit ({failed_to_parse_array_count_exceeds_input , TypeUByte , Count , 3 },
103+ amqp10_binary_parser :parse (Bin )).
104+
105+ % % Zero-width array elements (null, booleans, uint0, ulong0, list0) cost zero
106+ % % octets on the wire no matter how large Count is. Rather than materializing
107+ % % Count terms (CWE-770: a small frame could otherwise amplify into gigabytes
108+ % % of heap), the parser represents such an array as an opaque, constant-size value.
110109array_of_zero_width_elements (_Config ) ->
111- % % Array8 (0xe0), Size=2, Count=10, Null Constructor=0x40 (width 0)
112- {Parsed40 , 4 } = amqp10_binary_parser :parse (<<16#e0 , 2 , 10 , 16#40 >>),
113- ? assertEqual ({array , null , lists :duplicate (10 , null )}, Parsed40 ),
114-
115- {Parsed41 , 4 } = amqp10_binary_parser :parse (<<16#e0 , 2 , 10 , 16#41 >>),
116- ? assertEqual ({array , boolean , lists :duplicate (10 , true )}, Parsed41 ),
117-
118- {Parsed42 , 4 } = amqp10_binary_parser :parse (<<16#e0 , 2 , 10 , 16#42 >>),
119- ? assertEqual ({array , boolean , lists :duplicate (10 , false )}, Parsed42 ),
120-
121- {Parsed43 , 4 } = amqp10_binary_parser :parse (<<16#e0 , 2 , 10 , 16#43 >>),
122- ? assertEqual ({array , uint , lists :duplicate (10 , {uint , 0 })}, Parsed43 ),
123-
124- {Parsed44 , 4 } = amqp10_binary_parser :parse (<<16#e0 , 2 , 10 , 16#44 >>),
125- ? assertEqual ({array , ulong , lists :duplicate (10 , {ulong , 0 })}, Parsed44 ),
126-
127- {Parsed45 , 4 } = amqp10_binary_parser :parse (<<16#e0 , 2 , 10 , 16#45 >>),
128- ? assertEqual ({array , list , lists :duplicate (10 , {list , []})}, Parsed45 ).
110+ TypeArray32 = 16#f0 ,
111+ TypeArray8 = 16#e0 ,
112+
113+ Check32 = fun (Type , Count ) ->
114+ Encoded = <<5 :32 , Count :32 , Type >>,
115+ Bin = <<TypeArray32 , Encoded /binary >>,
116+ {Parsed , ParsedSize } = amqp10_binary_parser :parse (Bin ),
117+ ? assertEqual ({as_is , TypeArray32 , Encoded }, Parsed ),
118+ ? assertEqual (byte_size (Bin ), ParsedSize ),
119+ % % Re-encoding reproduces the exact original bytes.
120+ ? assertEqual (Bin , iolist_to_binary (amqp10_binary_generator :generate (Parsed )))
121+ end ,
122+ [Check32 (Type , 10 ) || Type <- [16#40 , 16#41 , 16#42 , 16#43 , 16#44 , 16#45 ]],
123+
124+ % % No limit on Count: an absurd 32-bit count is represented exactly as cheaply as a small one.
125+ Check32 (16#40 , 16#FFFFFFFF ),
126+
127+ % % Array8 form (0xe0) is parsed just as cheaply, and the selector is
128+ % % preserved rather than normalized to array32: re-encoding reproduces
129+ % % the exact original 4 bytes, not an inflated 10-byte array32 form.
130+ Check8 = fun (Type , Count ) ->
131+ Encoded = <<2 , Count , Type >>,
132+ Bin = <<TypeArray8 , Encoded /binary >>,
133+ {Parsed , ParsedSize } = amqp10_binary_parser :parse (Bin ),
134+ ? assertEqual ({as_is , TypeArray8 , Encoded }, Parsed ),
135+ ? assertEqual (byte_size (Bin ), ParsedSize ),
136+ ? assertEqual (Bin , iolist_to_binary (amqp10_binary_generator :generate (Parsed )))
137+ end ,
138+ [Check8 (Type , 10 ) || Type <- [16#40 , 16#41 , 16#42 , 16#43 , 16#44 , 16#45 ]],
139+ Check8 (16#40 , 255 ),
140+
141+ % % Nested as an element of an outer array of arrays, the preserved array8
142+ % % selector must be upgraded to array32 framing: RabbitMQ's generator
143+ % % always uses the large (32-bit) form for nested array elements (see
144+ % % constructor/1: "use large array type for all nested arrays").
145+ {Inner8 , _ } = amqp10_binary_parser :parse (<<TypeArray8 , 2 :8 , 10 :8 , 16#40 >>),
146+ OuterBin = iolist_to_binary (amqp10_binary_generator :generate ({array , array , [Inner8 ]})),
147+ {{array , array , [InnerParsedBack ]}, _ } = amqp10_binary_parser :parse (OuterBin ),
148+ ? assertEqual ({as_is , TypeArray32 , <<5 :32 , 10 :32 , 16#40 >>}, InnerParsedBack ).
149+
150+ % % An array of described zero-width elements (e.g. a described null repeated
151+ % % Count times) carries no information beyond its count and descriptor. This
152+ % % combination isn't supported: it is rejected cleanly instead of being
153+ % % expanded (unbounded memory) or given a bespoke opaque representation.
154+ array_of_described_zero_width_elements_unsupported (_Config ) ->
155+ % % ?DESCRIBED, utf8 "URL" descriptor, null (zero-width) element type.
156+ DescribedNull = <<0 , 16#a1 , 3 , " URL" , 16#40 >>,
157+ Count = 16#FFFFFFFF ,
158+ CountAndV = <<Count :32 , DescribedNull /binary >>,
159+ Bin = <<16#f0 , (byte_size (CountAndV )):32 , CountAndV /binary >>,
160+ ? assertExit ({array_of_described_zero_width_elements_unsupported , Count },
161+ amqp10_binary_parser :parse (Bin )).
129162
130163unsupported_type (_Config ) ->
131164 UnsupportedType = 16#02 ,
0 commit comments