@@ -24,8 +24,9 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
2424 }
2525
2626 private void TestCases ( Span < byte > input , PoisonPagePlacement poison )
27- {
27+ {
2828 TestBase64 ( input , poison ) ;
29+ TestBase64Chars ( input , poison ) ;
2930 TestToStringToCharArray ( input , Base64FormattingOptions . None ) ;
3031 TestToStringToCharArray ( input , Base64FormattingOptions . InsertLineBreaks ) ;
3132 }
@@ -129,6 +130,167 @@ private void TestBase64(Span<byte> input, PoisonPagePlacement poison)
129130 Assert . Equal ( OperationStatus . InvalidData , Base64 . DecodeFromUtf8InPlace ( input , out int inPlaceDecoded ) ) ;
130131 }
131132 }
133+
134+ { // Test new simplified UTF-8 APIs
135+ // Test EncodeToUtf8 returning byte[]
136+ byte [ ] encodedArray = Base64 . EncodeToUtf8 ( input ) ;
137+ Assert . Equal ( true , maxEncodedLength >= encodedArray . Length && maxEncodedLength - 2 <= encodedArray . Length ) ;
138+
139+ // Test EncodeToUtf8 returning int
140+ encoderDest . Clear ( ) ;
141+ int charsWritten = Base64 . EncodeToUtf8 ( input , encoderDest ) ;
142+ Assert . SequenceEqual ( encodedArray . AsSpan ( ) , encoderDest . Slice ( 0 , charsWritten ) ) ;
143+
144+ // Test TryEncodeToUtf8
145+ encoderDest . Clear ( ) ;
146+ Assert . Equal ( true , Base64 . TryEncodeToUtf8 ( input , encoderDest , out int tryCharsWritten ) ) ;
147+ Assert . Equal ( charsWritten , tryCharsWritten ) ;
148+ Assert . SequenceEqual ( encodedArray . AsSpan ( ) , encoderDest . Slice ( 0 , tryCharsWritten ) ) ;
149+
150+ // Test DecodeFromUtf8 returning byte[]
151+ byte [ ] decodedArray = Base64 . DecodeFromUtf8 ( encodedArray ) ;
152+ Assert . SequenceEqual ( input , decodedArray . AsSpan ( ) ) ;
153+
154+ // Test DecodeFromUtf8 returning int
155+ decoderDest . Clear ( ) ;
156+ int bytesWritten = Base64 . DecodeFromUtf8 ( encodedArray , decoderDest ) ;
157+ Assert . Equal ( input . Length , bytesWritten ) ;
158+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , bytesWritten ) ) ;
159+
160+ // Test TryDecodeFromUtf8
161+ decoderDest . Clear ( ) ;
162+ Assert . Equal ( true , Base64 . TryDecodeFromUtf8 ( encodedArray , decoderDest , out int tryBytesWritten ) ) ;
163+ Assert . Equal ( input . Length , tryBytesWritten ) ;
164+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , tryBytesWritten ) ) ;
165+
166+ // Test TryEncodeToUtf8InPlace
167+ using PooledBoundedMemory < byte > inPlaceBuffer = PooledBoundedMemory < byte > . Rent ( maxEncodedLength , poison ) ;
168+ Span < byte > inPlaceDest = inPlaceBuffer . Span ;
169+ input . CopyTo ( inPlaceDest ) ;
170+ Assert . Equal ( true , Base64 . TryEncodeToUtf8InPlace ( inPlaceDest , input . Length , out int inPlaceWritten ) ) ;
171+ Assert . SequenceEqual ( encodedArray . AsSpan ( ) , inPlaceDest . Slice ( 0 , inPlaceWritten ) ) ;
172+
173+ // Test GetEncodedLength matches GetMaxEncodedToUtf8Length
174+ Assert . Equal ( Base64 . GetMaxEncodedToUtf8Length ( input . Length ) , Base64 . GetEncodedLength ( input . Length ) ) ;
175+
176+ // Test GetMaxDecodedLength matches GetMaxDecodedFromUtf8Length
177+ Assert . Equal ( Base64 . GetMaxDecodedFromUtf8Length ( maxEncodedLength ) , Base64 . GetMaxDecodedLength ( maxEncodedLength ) ) ;
178+ }
179+ }
180+
181+ private static void TestBase64Chars ( Span < byte > input , PoisonPagePlacement poison )
182+ {
183+ int encodedLength = Base64 . GetEncodedLength ( input . Length ) ;
184+ int maxDecodedLength = Base64 . GetMaxDecodedLength ( encodedLength ) ;
185+
186+ using PooledBoundedMemory < char > destPoisoned = PooledBoundedMemory < char > . Rent ( encodedLength , poison ) ;
187+ using PooledBoundedMemory < byte > decoderDestPoisoned = PooledBoundedMemory < byte > . Rent ( maxDecodedLength , poison ) ;
188+
189+ Span < char > encoderDest = destPoisoned . Span ;
190+ Span < byte > decoderDest = decoderDestPoisoned . Span ;
191+
192+ { // IsFinalBlock = true
193+ OperationStatus status = Base64 . EncodeToChars ( input , encoderDest , out int bytesConsumed , out int charsEncoded ) ;
194+
195+ Assert . Equal ( OperationStatus . Done , status ) ;
196+ Assert . Equal ( input . Length , bytesConsumed ) ;
197+ Assert . Equal ( encodedLength , charsEncoded ) ;
198+
199+ string encodedString = Base64 . EncodeToString ( input ) ;
200+ Assert . Equal ( encodedString , new string ( encoderDest . Slice ( 0 , charsEncoded ) ) ) ;
201+
202+ status = Base64 . DecodeFromChars ( encoderDest . Slice ( 0 , charsEncoded ) , decoderDest , out int charsRead , out int bytesDecoded ) ;
203+
204+ Assert . Equal ( OperationStatus . Done , status ) ;
205+ Assert . Equal ( input . Length , bytesDecoded ) ;
206+ Assert . Equal ( charsEncoded , charsRead ) ;
207+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
208+ }
209+
210+ { // IsFinalBlock = false
211+ encoderDest . Clear ( ) ;
212+ decoderDest . Clear ( ) ;
213+ OperationStatus status = Base64 . EncodeToChars ( input , encoderDest , out int bytesConsumed , out int charsEncoded , isFinalBlock : false ) ;
214+ Span < char > decodeInput = encoderDest . Slice ( 0 , charsEncoded ) ;
215+
216+ if ( input . Length % 3 == 0 )
217+ {
218+ Assert . Equal ( OperationStatus . Done , status ) ;
219+ Assert . Equal ( input . Length , bytesConsumed ) ;
220+ Assert . Equal ( encodedLength , charsEncoded ) ;
221+
222+ status = Base64 . DecodeFromChars ( decodeInput , decoderDest , out int charsRead , out int bytesDecoded , isFinalBlock : false ) ;
223+
224+ Assert . Equal ( OperationStatus . Done , status ) ;
225+ Assert . Equal ( input . Length , bytesDecoded ) ;
226+ Assert . Equal ( charsEncoded , charsRead ) ;
227+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
228+ }
229+ else
230+ {
231+ Assert . Equal ( OperationStatus . NeedMoreData , status ) ;
232+ Assert . Equal ( true , input . Length / 3 * 4 == charsEncoded ) ;
233+
234+ status = Base64 . DecodeFromChars ( decodeInput , decoderDest , out int charsRead , out int bytesDecoded , isFinalBlock : false ) ;
235+
236+ if ( decodeInput . Length % 4 == 0 )
237+ {
238+ Assert . Equal ( OperationStatus . Done , status ) ;
239+ Assert . Equal ( bytesConsumed , bytesDecoded ) ;
240+ Assert . Equal ( charsEncoded , charsRead ) ;
241+ }
242+ else
243+ {
244+ Assert . Equal ( OperationStatus . NeedMoreData , status ) ;
245+ }
246+
247+ Assert . SequenceEqual ( input . Slice ( 0 , bytesDecoded ) , decoderDest . Slice ( 0 , bytesDecoded ) ) ;
248+ }
249+ }
250+
251+ { // Test array-returning and int-returning overloads
252+ char [ ] encodedChars = Base64 . EncodeToChars ( input ) ;
253+ Assert . Equal ( encodedLength , encodedChars . Length ) ;
254+
255+ encoderDest . Clear ( ) ;
256+ int charsWritten = Base64 . EncodeToChars ( input , encoderDest ) ;
257+ Assert . Equal ( encodedLength , charsWritten ) ;
258+ Assert . SequenceEqual ( encodedChars . AsSpan ( ) , encoderDest . Slice ( 0 , charsWritten ) ) ;
259+
260+ byte [ ] decodedBytes = Base64 . DecodeFromChars ( encodedChars ) ;
261+ Assert . SequenceEqual ( input , decodedBytes . AsSpan ( ) ) ;
262+
263+ decoderDest . Clear ( ) ;
264+ int bytesWritten = Base64 . DecodeFromChars ( encodedChars , decoderDest ) ;
265+ Assert . Equal ( input . Length , bytesWritten ) ;
266+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , bytesWritten ) ) ;
267+ }
268+
269+ { // Test Try* variants
270+ encoderDest . Clear ( ) ;
271+ Assert . Equal ( true , Base64 . TryEncodeToChars ( input , encoderDest , out int charsWritten ) ) ;
272+ Assert . Equal ( encodedLength , charsWritten ) ;
273+
274+ decoderDest . Clear ( ) ;
275+ Assert . Equal ( true , Base64 . TryDecodeFromChars ( encoderDest . Slice ( 0 , charsWritten ) , decoderDest , out int bytesWritten ) ) ;
276+ Assert . Equal ( input . Length , bytesWritten ) ;
277+ Assert . SequenceEqual ( input , decoderDest . Slice ( 0 , bytesWritten ) ) ;
278+ }
279+
280+ { // Decode the random chars directly (as chars, from the input bytes interpreted as UTF-16)
281+ // Create a char span from the input bytes for testing decode with random data
282+ if ( input . Length >= 2 )
283+ {
284+ ReadOnlySpan < char > inputChars = System . Runtime . InteropServices . MemoryMarshal . Cast < byte , char > ( input ) ;
285+ decoderDest . Clear ( ) ;
286+
287+ // Try decoding - may succeed or fail depending on if input is valid base64
288+ OperationStatus status = Base64 . DecodeFromChars ( inputChars , decoderDest , out int charsConsumed , out int bytesDecoded ) ;
289+ // Just verify we don't crash - the result depends on input validity
290+ Assert . Equal ( true , status == OperationStatus . Done || status == OperationStatus . InvalidData ||
291+ status == OperationStatus . NeedMoreData || status == OperationStatus . DestinationTooSmall ) ;
292+ }
293+ }
132294 }
133295
134296 private static void TestToStringToCharArray ( Span < byte > input , Base64FormattingOptions options )
0 commit comments