@@ -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
@@ -47,6 +48,7 @@ public int TransformBlock(
47
48
int totalBytesWritten = 0 ;
48
49
int inputProcessed = 0 ;
49
50
51
+ Debug . Assert ( outputOffset % 2 == 0 , $ "{ nameof ( outputOffset ) } must be a multiple 2") ;
50
52
while ( inputProcessed < inputCount )
51
53
{
52
54
int bytesToProcess = Math . Min ( InputBlockSize , inputCount - inputProcessed ) ;
@@ -55,16 +57,48 @@ public int TransformBlock(
55
57
int base64OutputStart = outputOffset + totalBytesWritten + OutputBlockSize / 2 ;
56
58
int base64OutputLength = _base64Transform . OutputBlockSize ;
57
59
58
- // Apply Base64 transformation directly to the second half of the output buffer
60
+ // write Base64 transformation directly to the second half of the output buffer
59
61
int base64BytesWritten = _base64Transform . TransformBlock (
60
62
inputBuffer , inputOffset + inputProcessed , bytesToProcess ,
61
63
outputBuffer , base64OutputStart ) ;
62
64
63
- var outputSpan = MemoryMarshal . Cast < byte , char > ( outputBuffer . AsSpan ( outputOffset + totalBytesWritten , OutputBlockSize ) ) ;
65
+ /*
66
+ Input Buffer ("hi mom"):
67
+ +-----+-----+-----+-----+-----+-----+
68
+ | 'h' | 'i' | ' ' | 'm' | 'o' | 'm' |
69
+ +-----+-----+-----+-----+-----+-----+
70
+ |104 |105 | 32 |109 |111 |109 |
71
+ +-----+-----+-----+-----+-----+-----+
72
+
73
+ Base64 Encoding Process:
74
+ - 'hi ' -> 'aGkg'
75
+ - 'mom' -> 'bW9t'
76
+
77
+ Base64 Encoded Output:
78
+ | |base64Written | | base64Written |
79
+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
80
+ | \0 | \0 | \0 | \0 |'a' |'G' |'k' |'g' | \0 | \0 | \0 | \0 |'b' |'W' |'9' |'t' |
81
+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
82
+ | 0 | 0 | 0 | 0 | 97 | 71 |107 |103 | 0 | 0 | 0 | 0 | 98 | 87 | 57 |116 |
83
+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
84
+
85
+ Expanded Output Buffer (UTF-16 Encoding):
86
+ | outputChars | outputChars |
87
+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
88
+ | \0 |'a' | \0 |'G' | \0 |'k' | \0 |'g' | \0 |'b' | \0 |'W' | \0 |'9' | \0 |'t' |
89
+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
90
+ | 0 | 97 | 0 | 71 | 0 |107 | 0 |103 | 0 | 98 | 0 | 87 | 0 | 57 | 0 |116 |
91
+ +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
92
+
93
+ */
94
+
95
+ var base64Written = outputBuffer . AsSpan ( base64OutputStart , base64BytesWritten ) ;
96
+ var outputChars = MemoryMarshal . Cast < byte , char > ( outputBuffer . AsSpan ( outputOffset + totalBytesWritten , OutputBlockSize ) ) ;
64
97
for ( int i = 0 ; i < base64BytesWritten ; i ++ )
65
98
{
66
- char base64Char = ( char ) outputBuffer [ base64OutputStart + i ] ;
67
- outputSpan [ i ] = base64Char ;
99
+ // Expand each acsii byte to a char write it in the same logical position
100
+ // as a char in outputChars eventually filling the output buffer
101
+ outputChars [ i ] = ( char ) base64Written [ i ] ;
68
102
}
69
103
70
104
inputProcessed += bytesToProcess ;
@@ -84,9 +118,8 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input
84
118
Span < char > outputSpan = MemoryMarshal . Cast < byte , char > ( outputBuffer . AsSpan ( ) ) ;
85
119
for ( int i = 0 ; i < base64Buffer . Length ; i ++ )
86
120
{
87
- // Convert each byte to a char
88
- char base64Char = ( char ) base64Buffer [ i ] ;
89
- outputSpan [ i ] = base64Char ;
121
+ // Convert each ascii byte to a char
122
+ outputSpan [ i ] = ( char ) base64Buffer [ i ]
90
123
}
91
124
92
125
return outputBuffer ;
@@ -108,8 +141,12 @@ public static void WriteOnSingleLine(XElement assembliesElement)
108
141
xmlWriter . Flush ( ) ;
109
142
cryptoStream . FlushFinalBlock ( ) ;
110
143
144
+ // guranteed to succeed with the MemoryStream() constructor
111
145
ms . TryGetBuffer ( out var bytes ) ;
112
- var charData = MemoryMarshal . Cast < byte , char > ( bytes ) ;
146
+ // we went to a lot of trouble to put characters in the final buffer
147
+ // so that we can avoid a copy here and pass the span directly to the
148
+ // string interpolation logic.
149
+ Span < char > charData = MemoryMarshal . Cast < byte , char > ( bytes ) ;
113
150
114
151
// Output the result
115
152
Console . WriteLine ( $ "STARTRESULTXML { charData . Length } { charData } ENDRESULTXML") ;
0 commit comments