@@ -30,8 +30,9 @@ private class ToBase64CharTransform : ICryptoTransform
3030 {
3131 private readonly ToBase64Transform _base64Transform = new ToBase64Transform ( ) ;
3232
33- public int InputBlockSize => _base64Transform . InputBlockSize ;
34- public int OutputBlockSize => _base64Transform . OutputBlockSize * 2 ;
33+ public int InputBlockSize => _base64Transform . InputBlockSize ; // 3 bytes of input
34+ public int OutputBlockSize => _base64Transform . OutputBlockSize * 2 ; // 4 bytes of base64 output * 2 for UTF-16 encoding
35+
3536 public bool CanTransformMultipleBlocks => _base64Transform . CanTransformMultipleBlocks ;
3637 public bool CanReuseTransform => _base64Transform . CanReuseTransform ;
3738
@@ -51,20 +52,52 @@ public int TransformBlock(
5152 {
5253 int bytesToProcess = Math . Min ( InputBlockSize , inputCount - inputProcessed ) ;
5354
55+ /*
56+ Input Buffer ("hi mom"):
57+ +-----+-----+-----+-----+-----+-----+
58+ | 'h' | 'i' | ' ' | 'm' | 'o' | 'm' |
59+ +-----+-----+-----+-----+-----+-----+
60+ |104 |105 | 32 |109 |111 |109 |
61+ +-----+-----+-----+-----+-----+-----+
62+
63+ Base64 Encoding Process:
64+ - 'hi ' -> 'aGkg'
65+ - 'mom' -> 'bW9t'
66+
67+ Base64 Encoded Output:
68+ | |base64Written | | base64Written |
69+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
70+ | \0 | \0 | \0 | \0 |'a' |'G' |'k' |'g' | \0 | \0 | \0 | \0 |'b' |'W' |'9' |'t' |
71+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
72+ | 0 | 0 | 0 | 0 | 97 | 71 |107 |103 | 0 | 0 | 0 | 0 | 98 | 87 | 57 |116 |
73+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
74+
75+ Expanded Output Buffer (UTF-16 Encoding):
76+ | outputChars | outputChars |
77+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
78+ | \0 |'a' | \0 |'G' | \0 |'k' | \0 |'g' | \0 |'b' | \0 |'W' | \0 |'9' | \0 |'t' |
79+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
80+ | 0 | 97 | 0 | 71 | 0 |107 | 0 |103 | 0 | 98 | 0 | 87 | 0 | 57 | 0 |116 |
81+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
82+
83+ */
84+
5485 // Calculate positions in the output buffer
55- int base64OutputStart = outputOffset + totalBytesWritten + OutputBlockSize / 2 ;
56- int base64OutputLength = _base64Transform . OutputBlockSize ;
86+ int outputStart = outputOffset + totalBytesWritten ;
87+ int base64OutputStart = outputStart + OutputBlockSize / 2 ;
5788
58- // Apply Base64 transformation directly to the second half of the output buffer
89+ // write Base64 transformation directly to the second half of the output buffer
5990 int base64BytesWritten = _base64Transform . TransformBlock (
6091 inputBuffer , inputOffset + inputProcessed , bytesToProcess ,
6192 outputBuffer , base64OutputStart ) ;
6293
63- var outputSpan = MemoryMarshal . Cast < byte , char > ( outputBuffer . AsSpan ( outputOffset + totalBytesWritten , OutputBlockSize ) ) ;
94+ var base64Written = outputBuffer . AsSpan ( base64OutputStart , base64BytesWritten ) ;
95+ var outputChars = outputBuffer . AsSpan ( outputStart , OutputBlockSize ) ;
6496 for ( int i = 0 ; i < base64BytesWritten ; i ++ )
6597 {
66- char base64Char = ( char ) outputBuffer [ base64OutputStart + i ] ;
67- outputSpan [ i ] = base64Char ;
98+ // Expand each acsii byte to a char write it in the same logical position
99+ // as a char in outputChars eventually filling the output buffer
100+ BitConverter . TryWriteBytes ( outputChars . Slice ( i * 2 ) , ( char ) base64Written [ i ] ) ;
68101 }
69102
70103 inputProcessed += bytesToProcess ;
@@ -81,12 +114,10 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input
81114
82115 // Expand each Base64 byte to two bytes in the output buffer
83116 byte [ ] outputBuffer = new byte [ base64Buffer . Length * 2 ] ;
84- Span < char > outputSpan = MemoryMarshal . Cast < byte , char > ( outputBuffer . AsSpan ( ) ) ;
85117 for ( int i = 0 ; i < base64Buffer . Length ; i ++ )
86118 {
87- // Convert each byte to a char
88- char base64Char = ( char ) base64Buffer [ i ] ;
89- outputSpan [ i ] = base64Char ;
119+ // Convert each ascii byte to a char
120+ BitConverter . TryWriteBytes ( outputBuffer . AsSpan ( i * 2 ) , ( char ) base64Buffer [ i ] ) ;
90121 }
91122
92123 return outputBuffer ;
@@ -108,8 +139,12 @@ public static void WriteOnSingleLine(XElement assembliesElement)
108139 xmlWriter . Flush ( ) ;
109140 cryptoStream . FlushFinalBlock ( ) ;
110141
142+ // guranteed to succeed with the MemoryStream() constructor
111143 ms . TryGetBuffer ( out var bytes ) ;
112- var charData = MemoryMarshal . Cast < byte , char > ( bytes ) ;
144+ // we went to a lot of trouble to put characters in the final buffer
145+ // so that we can avoid a copy here and pass the span directly to the
146+ // string interpolation logic.
147+ Span < char > charData = MemoryMarshal . Cast < byte , char > ( bytes ) ;
113148
114149 // Output the result
115150 Console . WriteLine ( $ "STARTRESULTXML { charData . Length } { charData } ENDRESULTXML") ;
0 commit comments