@@ -1004,6 +1004,89 @@ public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_ActualDestination
10041004 Assert . Equal ( new byte [ ] { 1 , 2 , 3 , 4 } , destination4 ) ;
10051005 }
10061006
1007+ [ Theory ]
1008+ [ InlineData ( "AQ\r \n Q=" ) ]
1009+ [ InlineData ( "AQ\r \n Q=\r \n " ) ]
1010+ [ InlineData ( "AQ Q=" ) ]
1011+ [ InlineData ( "AQ\t Q=" ) ]
1012+ public void DecodingWithWhiteSpaceSplitFinalQuantumAndIsFinalBlockFalse ( string base64String )
1013+ {
1014+ // When a final quantum (containing padding) is split by whitespace and isFinalBlock=false,
1015+ // the decoder should not consume any bytes, allowing the caller to retry with isFinalBlock=true
1016+ ReadOnlySpan < byte > base64Data = Encoding . ASCII . GetBytes ( base64String ) ;
1017+ var output = new byte [ 10 ] ;
1018+
1019+ // First call with isFinalBlock=false should consume 0 bytes
1020+ OperationStatus status = Base64 . DecodeFromUtf8 ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1021+ Assert . Equal ( 0 , bytesConsumed ) ;
1022+ Assert . Equal ( 0 , bytesWritten ) ;
1023+ Assert . Equal ( OperationStatus . InvalidData , status ) ;
1024+
1025+ // Second call with isFinalBlock=true should succeed
1026+ status = Base64 . DecodeFromUtf8 ( base64Data , output , out bytesConsumed , out bytesWritten , isFinalBlock : true ) ;
1027+ Assert . Equal ( OperationStatus . Done , status ) ;
1028+ Assert . Equal ( base64Data . Length , bytesConsumed ) ;
1029+ Assert . Equal ( 2 , bytesWritten ) ; // "AQQ=" decodes to 2 bytes: {1, 4}
1030+ Assert . Equal ( new byte [ ] { 1 , 4 } , output [ ..2 ] ) ;
1031+ }
1032+
1033+ [ Fact ]
1034+ public void DecodingCompleteQuantumWithIsFinalBlockFalse ( )
1035+ {
1036+ // Complete quantum without padding should be decoded even when isFinalBlock=false
1037+ ReadOnlySpan < byte > base64Data = "AAAA"u8 ;
1038+ var output = new byte [ 10 ] ;
1039+
1040+ OperationStatus status = Base64 . DecodeFromUtf8 ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1041+ Assert . Equal ( OperationStatus . Done , status ) ;
1042+ Assert . Equal ( 4 , bytesConsumed ) ;
1043+ Assert . Equal ( 3 , bytesWritten ) ;
1044+ }
1045+
1046+ [ Fact ]
1047+ public void DecodingPaddedQuantumWithIsFinalBlockFalse ( )
1048+ {
1049+ // Quantum with padding should not be decoded when isFinalBlock=false
1050+ ReadOnlySpan < byte > base64Data = "AAA="u8 ;
1051+ var output = new byte [ 10 ] ;
1052+
1053+ OperationStatus status = Base64 . DecodeFromUtf8 ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1054+ Assert . Equal ( OperationStatus . InvalidData , status ) ;
1055+ Assert . Equal ( 0 , bytesConsumed ) ;
1056+ Assert . Equal ( 0 , bytesWritten ) ;
1057+ }
1058+
1059+ [ Theory ]
1060+ [ InlineData ( "AQIDBAUG AQ\r \n Q=" , 9 , 6 , "AQ\r \n Q=" ) ] // Two complete blocks, then whitespace-split final quantum
1061+ [ InlineData ( "AQID BAUG AQ\r \n Q=" , 10 , 6 , "AQ\r \n Q=" ) ] // Two blocks with space, then whitespace-split final quantum
1062+ [ InlineData ( "AQIDBAUG\r \n AQID AQ\r \n Q=" , 15 , 9 , "AQ\r \n Q=" ) ] // Multiple blocks with various whitespace patterns
1063+ public void DecodingWithValidDataBeforeWhiteSpaceSplitFinalQuantum ( string base64String , int expectedBytesConsumedFirstCall , int expectedBytesWrittenFirstCall , string expectedRemainingAfterFirstCall )
1064+ {
1065+ // When there's valid data before a whitespace-split final quantum and isFinalBlock=false,
1066+ // verify the streaming scenario works correctly
1067+ ReadOnlySpan < byte > base64Data = Encoding . ASCII . GetBytes ( base64String ) ;
1068+ var output = new byte [ 100 ] ;
1069+
1070+ // First call with isFinalBlock=false should decode the valid complete blocks and stop before the incomplete final quantum
1071+ OperationStatus status = Base64 . DecodeFromUtf8 ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1072+
1073+ Assert . Equal ( OperationStatus . InvalidData , status ) ;
1074+ Assert . Equal ( expectedBytesConsumedFirstCall , bytesConsumed ) ;
1075+ Assert . Equal ( expectedBytesWrittenFirstCall , bytesWritten ) ;
1076+
1077+ // Verify that only the final block remains
1078+ ReadOnlySpan < byte > remaining = base64Data . Slice ( bytesConsumed ) ;
1079+ string remainingString = Encoding . ASCII . GetString ( remaining ) ;
1080+ Assert . Equal ( expectedRemainingAfterFirstCall , remainingString ) ;
1081+
1082+ // Verify we can complete decoding by retrying with the FULL input and isFinalBlock=true
1083+ Array . Clear ( output , 0 , output . Length ) ;
1084+ status = Base64 . DecodeFromUtf8 ( base64Data , output , out bytesConsumed , out bytesWritten , isFinalBlock : true ) ;
1085+ Assert . Equal ( OperationStatus . Done , status ) ;
1086+ Assert . Equal ( base64Data . Length , bytesConsumed ) ;
1087+ Assert . True ( bytesWritten > 0 , "Should have decoded data" ) ;
1088+ }
1089+
10071090 [ Fact ]
10081091 public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_TrailingWhiteSpacesAreConsumed ( )
10091092 {
@@ -1020,5 +1103,88 @@ public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_TrailingWhiteSpac
10201103 Assert . Equal ( destination . Length , written ) ;
10211104 Assert . Equal ( new byte [ ] { 240 , 159 , 141 , 137 , 240 , 159 } , destination ) ;
10221105 }
1106+
1107+ [ Theory ]
1108+ [ InlineData ( "AQ\r \n Q=" ) ]
1109+ [ InlineData ( "AQ\r \n Q=\r \n " ) ]
1110+ [ InlineData ( "AQ Q=" ) ]
1111+ [ InlineData ( "AQ\t Q=" ) ]
1112+ public void DecodingFromCharsWithWhiteSpaceSplitFinalQuantumAndIsFinalBlockFalse ( string base64String )
1113+ {
1114+ // When a final quantum (containing padding) is split by whitespace and isFinalBlock=false,
1115+ // the decoder should not consume any bytes, allowing the caller to retry with isFinalBlock=true
1116+ ReadOnlySpan < char > base64Data = base64String . AsSpan ( ) ;
1117+ var output = new byte [ 10 ] ;
1118+
1119+ // First call with isFinalBlock=false should consume 0 bytes
1120+ OperationStatus status = Base64 . DecodeFromChars ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1121+ Assert . Equal ( 0 , bytesConsumed ) ;
1122+ Assert . Equal ( 0 , bytesWritten ) ;
1123+ Assert . Equal ( OperationStatus . InvalidData , status ) ;
1124+
1125+ // Second call with isFinalBlock=true should succeed
1126+ status = Base64 . DecodeFromChars ( base64Data , output , out bytesConsumed , out bytesWritten , isFinalBlock : true ) ;
1127+ Assert . Equal ( OperationStatus . Done , status ) ;
1128+ Assert . Equal ( base64Data . Length , bytesConsumed ) ;
1129+ Assert . Equal ( 2 , bytesWritten ) ; // "AQQ=" decodes to 2 bytes: {1, 4}
1130+ Assert . Equal ( new byte [ ] { 1 , 4 } , output [ ..2 ] ) ;
1131+ }
1132+
1133+ [ Fact ]
1134+ public void DecodingFromCharsCompleteQuantumWithIsFinalBlockFalse ( )
1135+ {
1136+ // Complete quantum without padding should be decoded even when isFinalBlock=false
1137+ ReadOnlySpan < char > base64Data = "AAAA" . AsSpan ( ) ;
1138+ var output = new byte [ 10 ] ;
1139+
1140+ OperationStatus status = Base64 . DecodeFromChars ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1141+ Assert . Equal ( OperationStatus . Done , status ) ;
1142+ Assert . Equal ( 4 , bytesConsumed ) ;
1143+ Assert . Equal ( 3 , bytesWritten ) ;
1144+ }
1145+
1146+ [ Fact ]
1147+ public void DecodingFromCharsPaddedQuantumWithIsFinalBlockFalse ( )
1148+ {
1149+ // Quantum with padding should not be decoded when isFinalBlock=false
1150+ ReadOnlySpan < char > base64Data = "AAA=" . AsSpan ( ) ;
1151+ var output = new byte [ 10 ] ;
1152+
1153+ OperationStatus status = Base64 . DecodeFromChars ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1154+ Assert . Equal ( OperationStatus . InvalidData , status ) ;
1155+ Assert . Equal ( 0 , bytesConsumed ) ;
1156+ Assert . Equal ( 0 , bytesWritten ) ;
1157+ }
1158+
1159+ [ Theory ]
1160+ [ InlineData ( "AQIDBAUG AQ\r \n Q=" , 9 , 6 , "AQ\r \n Q=" ) ] // Two complete blocks, then whitespace-split final quantum
1161+ [ InlineData ( "AQID BAUG AQ\r \n Q=" , 10 , 6 , "AQ\r \n Q=" ) ] // Two blocks with space, then whitespace-split final quantum
1162+ [ InlineData ( "AQIDBAUG\r \n AQID AQ\r \n Q=" , 15 , 9 , "AQ\r \n Q=" ) ] // Multiple blocks with various whitespace patterns
1163+ public void DecodingFromCharsWithValidDataBeforeWhiteSpaceSplitFinalQuantum ( string base64String , int expectedBytesConsumedFirstCall , int expectedBytesWrittenFirstCall , string expectedRemainingAfterFirstCall )
1164+ {
1165+ // When there's valid data before a whitespace-split final quantum and isFinalBlock=false,
1166+ // verify the streaming scenario works correctly
1167+ ReadOnlySpan < char > base64Data = base64String . AsSpan ( ) ;
1168+ var output = new byte [ 100 ] ;
1169+
1170+ // First call with isFinalBlock=false should decode the valid complete blocks and stop before the incomplete final quantum
1171+ OperationStatus status = Base64 . DecodeFromChars ( base64Data , output , out int bytesConsumed , out int bytesWritten , isFinalBlock : false ) ;
1172+
1173+ Assert . Equal ( OperationStatus . InvalidData , status ) ;
1174+ Assert . Equal ( expectedBytesConsumedFirstCall , bytesConsumed ) ;
1175+ Assert . Equal ( expectedBytesWrittenFirstCall , bytesWritten ) ;
1176+
1177+ // Verify that only the final block remains
1178+ ReadOnlySpan < char > remaining = base64Data . Slice ( bytesConsumed ) ;
1179+ string remainingString = new string ( remaining ) ;
1180+ Assert . Equal ( expectedRemainingAfterFirstCall , remainingString ) ;
1181+
1182+ // Verify we can complete decoding by retrying with the FULL input and isFinalBlock=true
1183+ Array . Clear ( output , 0 , output . Length ) ;
1184+ status = Base64 . DecodeFromChars ( base64Data , output , out bytesConsumed , out bytesWritten , isFinalBlock : true ) ;
1185+ Assert . Equal ( OperationStatus . Done , status ) ;
1186+ Assert . Equal ( base64Data . Length , bytesConsumed ) ;
1187+ Assert . True ( bytesWritten > 0 , "Should have decoded data" ) ;
1188+ }
10231189 }
10241190}
0 commit comments