@@ -41,23 +41,45 @@ namespace RabbitMQ.Client.Impl
41
41
{
42
42
internal class WireFormatting
43
43
{
44
- public static decimal AmqpToDecimal ( byte scale , uint unsignedMantissa )
44
+ // * DESCRIPTION TAKEN FROM MS REFERENCE SOURCE *
45
+ // https://github.com/microsoft/referencesource/blob/master/mscorlib/system/decimal.cs
46
+ // The lo, mid, hi, and flags fields contain the representation of the
47
+ // Decimal value. The lo, mid, and hi fields contain the 96-bit integer
48
+ // part of the Decimal. Bits 0-15 (the lower word) of the flags field are
49
+ // unused and must be zero; bits 16-23 contain must contain a value between
50
+ // 0 and 28, indicating the power of 10 to divide the 96-bit integer part
51
+ // by to produce the Decimal value; bits 24-30 are unused and must be zero;
52
+ // and finally bit 31 indicates the sign of the Decimal value, 0 meaning
53
+ // positive and 1 meaning negative.
54
+ readonly struct DecimalData
55
+ {
56
+ public readonly uint Flags ;
57
+ public readonly uint Hi ;
58
+ public readonly uint Lo ;
59
+ public readonly uint Mid ;
60
+
61
+ internal DecimalData ( uint flags , uint hi , uint lo , uint mid )
62
+ {
63
+ Flags = flags ;
64
+ Hi = hi ;
65
+ Lo = lo ;
66
+ Mid = mid ;
67
+ }
68
+ }
69
+
70
+ private static UTF8Encoding UTF8 = new UTF8Encoding ( ) ;
71
+
72
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
73
+ public static decimal ReadDecimal ( ReadOnlySpan < byte > span )
45
74
{
46
75
if ( scale > 28 )
47
76
{
48
- throw new SyntaxErrorException ( $ "Unrepresentable AMQP decimal table field: scale= { scale } " ) ;
77
+ ThrowInvalidDecimalScale ( scale ) ;
49
78
}
50
79
51
- return new decimal (
52
- // The low 32 bits of a 96-bit integer
53
- lo : ( int ) ( unsignedMantissa & 0x7FFFFFFF ) ,
54
- // The middle 32 bits of a 96-bit integer.
55
- mid : 0 ,
56
- // The high 32 bits of a 96-bit integer.
57
- hi : 0 ,
58
- isNegative : ( unsignedMantissa & 0x80000000 ) != 0 ,
59
- // A power of 10 ranging from 0 to 28.
60
- scale : scale ) ;
80
+ uint unsignedMantissa = NetworkOrderDeserializer . ReadUInt32 ( span . Slice ( 1 ) ) ;
81
+ var data = new DecimalData ( ( ( uint ) ( scale << 16 ) ) | unsignedMantissa & 0x80000000 , 0 , unsignedMantissa & 0x7FFFFFFF , 0 ) ;
82
+ return Unsafe . As < DecimalData , decimal > ( ref data ) ;
61
83
}
62
84
63
85
public static void DecimalToAmqp ( decimal value , out byte scale , out int mantissa )
@@ -274,11 +296,37 @@ public static int GetArrayByteCount(IList val)
274
296
return byteCount ;
275
297
}
276
298
299
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
300
+ #if NETCOREAPP
301
+ public static int GetByteCount ( ReadOnlySpan < char > val ) => val . IsEmpty ? 0 : UTF8 . GetByteCount ( val ) ;
302
+ #else
303
+ public static int GetByteCount ( string val ) => string . IsNullOrEmpty ( val ) ? 0 : UTF8 . GetByteCount ( val ) ;
304
+ #endif
305
+
306
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
277
307
public static int WriteDecimal ( Span < byte > span , decimal value )
278
308
{
279
- DecimalToAmqp ( value , out byte scale , out int mantissa ) ;
280
- span [ 0 ] = scale ;
281
- return 1 + WriteLong ( span . Slice ( 1 ) , ( uint ) mantissa ) ;
309
+ // Cast the decimal to our struct to avoid the decimal.GetBits allocations.
310
+ DecimalData data = Unsafe . As < decimal , DecimalData > ( ref value ) ;
311
+ // According to the documentation :-
312
+ // - word 0: low-order "mantissa"
313
+ // - word 1, word 2: medium- and high-order "mantissa"
314
+ // - word 3: mostly reserved; "exponent" and sign bit
315
+ // In one way, this is broader than AMQP: the mantissa is larger.
316
+ // In another way, smaller: the exponent ranges 0-28 inclusive.
317
+ // We need to be careful about the range of word 0, too: we can
318
+ // only take 31 bits worth of it, since the sign bit needs to
319
+ // fit in there too.
320
+ if ( data . Mid != 0 || // mantissa extends into middle word
321
+ data . Hi != 0 || // mantissa extends into top word
322
+ data . Lo < 0 ) // mantissa extends beyond 31 bits
323
+ {
324
+ return ThrowWireFormattingException ( value ) ;
325
+ }
326
+
327
+ span [ 0 ] = ( byte ) ( ( data . Flags >> 16 ) & 0xFF ) ;
328
+ WriteLong ( span . Slice ( 1 ) , ( data . Flags & 0b1000_0000_0000_0000_0000_0000_0000_0000 ) | ( data . Lo & 0b0111_1111_1111_1111_1111_1111_1111_1111 ) ) ;
329
+ return 5 ;
282
330
}
283
331
284
332
public static int WriteFieldValue ( Span < byte > span , object value )
@@ -558,5 +606,30 @@ public static int WriteTimestamp(Span<byte> span, AmqpTimestamp val)
558
606
// See also MethodArgumentReader.ReadTimestamp and AmqpTimestamp itself
559
607
return WriteLonglong ( span , ( ulong ) val . UnixTime ) ;
560
608
}
609
+
610
+ public static int ThrowArgumentOutOfRangeException ( int orig , int expected )
611
+ {
612
+ throw new ArgumentOutOfRangeException ( "span" , $ "Span has not enough space ({ orig } instead of { expected } )") ;
613
+ }
614
+
615
+ public static int ThrowArgumentOutOfRangeException ( string val , int maxLength )
616
+ {
617
+ throw new ArgumentOutOfRangeException ( nameof ( val ) , val , $ "Value exceeds the maximum allowed length of { maxLength } bytes.") ;
618
+ }
619
+
620
+ public static int ThrowSyntaxErrorException ( uint byteCount )
621
+ {
622
+ throw new SyntaxErrorException ( $ "Long string too long; byte length={ byteCount } , max={ int . MaxValue } ") ;
623
+ }
624
+
625
+ private static int ThrowWireFormattingException ( decimal value )
626
+ {
627
+ throw new WireFormattingException ( "Decimal overflow in AMQP encoding" , value ) ;
628
+ }
629
+
630
+ private static decimal ThrowInvalidDecimalScale ( int scale )
631
+ {
632
+ throw new SyntaxErrorException ( $ "Unrepresentable AMQP decimal table field: scale={ scale } ") ;
633
+ }
561
634
}
562
635
}
0 commit comments