@@ -30,8 +30,9 @@ private class ToBase64CharTransform : ICryptoTransform
30
30
{
31
31
private readonly ToBase64Transform _base64Transform = new ToBase64Transform ( ) ;
32
32
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
+
35
36
public bool CanTransformMultipleBlocks => _base64Transform . CanTransformMultipleBlocks ;
36
37
public bool CanReuseTransform => _base64Transform . CanReuseTransform ;
37
38
@@ -51,20 +52,52 @@ public int TransformBlock(
51
52
{
52
53
int bytesToProcess = Math . Min ( InputBlockSize , inputCount - inputProcessed ) ;
53
54
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
+
54
85
// 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 ;
57
88
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
59
90
int base64BytesWritten = _base64Transform . TransformBlock (
60
91
inputBuffer , inputOffset + inputProcessed , bytesToProcess ,
61
92
outputBuffer , base64OutputStart ) ;
62
93
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 ) ;
64
96
for ( int i = 0 ; i < base64BytesWritten ; i ++ )
65
97
{
66
- char base64Char = ( char ) outputBuffer [ base64OutputStart + i ] ;
67
- outputSpan [ i ] = base64Char ;
98
+ // Expand each ascii 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 ] ) ;
68
101
}
69
102
70
103
inputProcessed += bytesToProcess ;
@@ -81,12 +114,10 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input
81
114
82
115
// Expand each Base64 byte to two bytes in the output buffer
83
116
byte [ ] outputBuffer = new byte [ base64Buffer . Length * 2 ] ;
84
- Span < char > outputSpan = MemoryMarshal . Cast < byte , char > ( outputBuffer . AsSpan ( ) ) ;
85
117
for ( int i = 0 ; i < base64Buffer . Length ; i ++ )
86
118
{
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 ] ) ;
90
121
}
91
122
92
123
return outputBuffer ;
@@ -108,8 +139,12 @@ public static void WriteOnSingleLine(XElement assembliesElement)
108
139
xmlWriter . Flush ( ) ;
109
140
cryptoStream . FlushFinalBlock ( ) ;
110
141
142
+ // guaranteed to succeed with the MemoryStream() constructor
111
143
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 ) ;
113
148
114
149
// Output the result
115
150
Console . WriteLine ( $ "STARTRESULTXML { charData . Length } { charData } ENDRESULTXML") ;
0 commit comments