Fixed Base64 EncodeToUtf8 / DecodeFromUtf8 return states (Done | NeedMoreData) (...
authorGünther Foidl <gue@korporal.at>
Mon, 27 Jan 2020 19:18:14 +0000 (20:18 +0100)
committerLevi Broderick <GrabYourPitchforks@users.noreply.github.com>
Mon, 27 Jan 2020 19:18:14 +0000 (11:18 -0800)
src/libraries/System.Memory/src/System/Buffers/Text/Base64Decoder.cs
src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs
src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs
src/libraries/System.Memory/tests/Base64/Base64EncoderUnitTests.cs
src/libraries/System.Memory/tests/Base64/Base64TestHelper.cs
src/libraries/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Base64Transforms.cs

index ffb660a..1a2e941 100644 (file)
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
@@ -17,21 +17,23 @@ namespace System.Buffers.Text
     public static partial class Base64
     {
         /// <summary>
-        /// Decode the span of UTF-8 encoded text represented as base 64 into binary data.
+        /// Decode the span of UTF-8 encoded text represented as base64 into binary data.
         /// If the input is not a multiple of 4, it will decode as much as it can, to the closest multiple of 4.
         /// </summary>
-        /// <param name="utf8">The input span which contains UTF-8 encoded text in base 64 that needs to be decoded.</param>
+        /// <param name="utf8">The input span which contains UTF-8 encoded text in base64 that needs to be decoded.</param>
         /// <param name="bytes">The output span which contains the result of the operation, i.e. the decoded binary data.</param>
         /// <param name="bytesConsumed">The number of input bytes consumed during the operation. This can be used to slice the input for subsequent calls, if necessary.</param>
         /// <param name="bytesWritten">The number of bytes written into the output span. This can be used to slice the output for subsequent calls, if necessary.</param>
-        /// <param name="isFinalBlock">True (default) when the input span contains the entire data to decode.
-        /// Set to false only if it is known that the input span contains partial data with more data to follow.</param>
+        /// <param name="isFinalBlock"><see langword="true"/> (default) when the input span contains the entire data to encode.
+        /// Set to <see langword="true"/> when the source buffer contains the entirety of the data to encode.
+        /// Set to <see langword="false"/> if this method is being called in a loop and if more input data may follow.
+        /// At the end of the loop, call this (potentially with an empty source buffer) passing <see langword="true"/>.</param>
         /// <returns>It returns the OperationStatus enum values:
         /// - Done - on successful processing of the entire input span
         /// - DestinationTooSmall - if there is not enough space in the output span to fit the decoded input
-        /// - NeedMoreData - only if isFinalBlock is false and the input is not a multiple of 4, otherwise the partial input would be considered as InvalidData
-        /// - InvalidData - if the input contains bytes outside of the expected base 64 range, or if it contains invalid/more than two padding characters,
-        ///   or if the input is incomplete (i.e. not a multiple of 4) and isFinalBlock is true.
+        /// - NeedMoreData - only if <paramref name="isFinalBlock"/> is false and the input is not a multiple of 4, otherwise the partial input would be considered as InvalidData
+        /// - InvalidData - if the input contains bytes outside of the expected base64 range, or if it contains invalid/more than two padding characters,
+        ///   or if the input is incomplete (i.e. not a multiple of 4) and <paramref name="isFinalBlock"/> is <see langword="true"/>.
         /// </returns>
         public static unsafe OperationStatus DecodeFromUtf8(ReadOnlySpan<byte> utf8, Span<byte> bytes, out int bytesConsumed, out int bytesWritten, bool isFinalBlock = true)
         {
@@ -123,6 +125,10 @@ namespace System.Buffers.Text
                 {
                     if (isFinalBlock)
                         goto InvalidDataExit;
+
+                    if (src == srcBytes + utf8.Length)
+                        goto DoneExit;
+
                     goto NeedMoreDataExit;
                 }
 
index 7825881..ba67dc4 100644 (file)
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
@@ -19,19 +19,21 @@ namespace System.Buffers.Text
     public static partial class Base64
     {
         /// <summary>
-        /// Encode the span of binary data into UTF-8 encoded text represented as base 64.
+        /// Encode the span of binary data into UTF-8 encoded text represented as base64.
         /// </summary>
         /// <param name="bytes">The input span which contains binary data that needs to be encoded.</param>
-        /// <param name="utf8">The output span which contains the result of the operation, i.e. the UTF-8 encoded text in base 64.</param>
+        /// <param name="utf8">The output span which contains the result of the operation, i.e. the UTF-8 encoded text in base64.</param>
         /// <param name="bytesConsumed">The number of input bytes consumed during the operation. This can be used to slice the input for subsequent calls, if necessary.</param>
         /// <param name="bytesWritten">The number of bytes written into the output span. This can be used to slice the output for subsequent calls, if necessary.</param>
-        /// <param name="isFinalBlock">True (default) when the input span contains the entire data to encode.
-        /// Set to false only if it is known that the input span contains partial data with more data to follow.</param>
+        /// <param name="isFinalBlock"><see langword="true"/> (default) when the input span contains the entire data to encode.
+        /// Set to <see langword="true"/> when the source buffer contains the entirety of the data to encode.
+        /// Set to <see langword="false"/> if this method is being called in a loop and if more input data may follow.
+        /// At the end of the loop, call this (potentially with an empty source buffer) passing <see langword="true"/>.</param>
         /// <returns>It returns the OperationStatus enum values:
         /// - Done - on successful processing of the entire input span
         /// - DestinationTooSmall - if there is not enough space in the output span to fit the encoded input
-        /// - NeedMoreData - only if isFinalBlock is false, otherwise the output is padded if the input is not a multiple of 3
-        /// It does not return InvalidData since that is not possible for base 64 encoding.
+        /// - NeedMoreData - only if <paramref name="isFinalBlock"/> is <see langword="false"/>, otherwise the output is padded if the input is not a multiple of 3
+        /// It does not return InvalidData since that is not possible for base64 encoding.
         /// </returns>
         public static unsafe OperationStatus EncodeToUtf8(ReadOnlySpan<byte> bytes, Span<byte> utf8, out int bytesConsumed, out int bytesWritten, bool isFinalBlock = true)
         {
@@ -100,7 +102,12 @@ namespace System.Buffers.Text
                     goto DestinationTooSmallExit;
 
                 if (!isFinalBlock)
+                {
+                    if (src == srcEnd)
+                        goto DoneExit;
+
                     goto NeedMoreData;
+                }
 
                 if (src + 1 == srcEnd)
                 {
index 8d6de7a..9522c43 100644 (file)
@@ -15,17 +15,17 @@ namespace System.Buffers.Text.Tests
             var rnd = new Random(42);
             for (int i = 0; i < 10; i++)
             {
-                int numBytes = rnd.Next(100, 1000 * 1000);
-                while (numBytes % 4 != 0)
+                int numBytes;
+                do
                 {
                     numBytes = rnd.Next(100, 1000 * 1000);
-                }
+                } while (numBytes % 4 != 0);    // ensure we have a valid length
+
                 Span<byte> source = new byte[numBytes];
                 Base64TestHelper.InitalizeDecodableBytes(source, numBytes);
 
                 Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                Assert.Equal(OperationStatus.Done,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+                Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
                 Assert.Equal(source.Length, consumed);
                 Assert.Equal(decodedBytes.Length, decodedByteCount);
                 Assert.True(Base64TestHelper.VerifyDecodingCorrectness(source.Length, decodedBytes.Length, source, decodedBytes));
@@ -33,16 +33,93 @@ namespace System.Buffers.Text.Tests
         }
 
         [Fact]
-        public void DecodeEmptySpan()
+        public void BasicDecodingInvalidInputLength()
+        {
+            var rnd = new Random(42);
+            for (int i = 0; i < 10; i++)
+            {
+                int numBytes;
+                do
+                {
+                    numBytes = rnd.Next(100, 1000 * 1000);
+                } while (numBytes % 4 == 0);    // ensure we have a invalid length
+
+                Span<byte> source = new byte[numBytes];
+                Base64TestHelper.InitalizeDecodableBytes(source, numBytes);
+
+                Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
+                int expectedConsumed = numBytes / 4 * 4;    // decode input up to the closest multiple of 4
+                int expectedDecoded = expectedConsumed / 4 * 3;
+
+                Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+                Assert.Equal(expectedConsumed, consumed);
+                Assert.Equal(expectedDecoded, decodedByteCount);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedDecoded, source, decodedBytes));
+            }
+        }
+
+        [Fact]
+        public void BasicDecodingWithFinalBlockFalse()
+        {
+            var rnd = new Random(42);
+            for (int i = 0; i < 10; i++)
+            {
+                int numBytes;
+                do
+                {
+                    numBytes = rnd.Next(100, 1000 * 1000);
+                } while (numBytes % 4 != 0);    // ensure we have a valid length
+
+                Span<byte> source = new byte[numBytes];
+                Base64TestHelper.InitalizeDecodableBytes(source, numBytes);
+
+                Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
+                int expectedConsumed = source.Length / 4 * 4; // only consume closest multiple of four since isFinalBlock is false
+
+                Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
+                Assert.Equal(expectedConsumed, consumed);
+                Assert.Equal(decodedBytes.Length, decodedByteCount);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
+            }
+        }
+
+        [Fact]
+        public void BasicDecodingWithFinalBlockFalseInvalidInputLength()
+        {
+            var rnd = new Random(42);
+            for (int i = 0; i < 10; i++)
+            {
+                int numBytes;
+                do
+                {
+                    numBytes = rnd.Next(100, 1000 * 1000);
+                } while (numBytes % 4 == 0);    // ensure we have a invalid length
+
+                Span<byte> source = new byte[numBytes];
+                Base64TestHelper.InitalizeDecodableBytes(source, numBytes);
+
+                Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
+                int expectedConsumed = source.Length / 4 * 4; // only consume closest multiple of four since isFinalBlock is false
+                int expectedDecoded = expectedConsumed / 4 * 3;
+
+                Assert.Equal(OperationStatus.NeedMoreData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
+                Assert.Equal(expectedConsumed, consumed);
+                Assert.Equal(expectedDecoded, decodedByteCount);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
+            }
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void DecodeEmptySpan(bool isFinalBlock)
         {
             Span<byte> source = Span<byte>.Empty;
             Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
 
-            Assert.Equal(OperationStatus.Done,
-                Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
-            Assert.Equal(source.Length, consumed);
-            Assert.Equal(decodedBytes.Length, decodedByteCount);
-            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(source.Length, decodedBytes.Length, source, decodedBytes));
+            Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock));
+            Assert.Equal(0, consumed);
+            Assert.Equal(0, decodedByteCount);
         }
 
         [Fact]
@@ -52,36 +129,131 @@ namespace System.Buffers.Text.Tests
             Span<byte> decodedBytes = Guid.NewGuid().ToByteArray();
             Base64.EncodeToUtf8(decodedBytes, source, out int _, out int _);
 
-            Assert.Equal(OperationStatus.Done,
-                Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+            Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
             Assert.Equal(24, consumed);
             Assert.Equal(16, decodedByteCount);
             Assert.True(Base64TestHelper.VerifyDecodingCorrectness(source.Length, decodedBytes.Length, source, decodedBytes));
         }
 
         [Fact]
-        public void BasicDecodingWithFinalBlockFalse()
+        public void DecodingOutputTooSmall()
         {
-            var rnd = new Random(42);
-            for (int i = 0; i < 10; i++)
+            for (int numBytes = 5; numBytes < 20; numBytes++)
             {
-                int numBytes = rnd.Next(100, 1000 * 1000);
-                while (numBytes % 4 != 0)
-                {
-                    numBytes = rnd.Next(100, 1000 * 1000);
-                }
                 Span<byte> source = new byte[numBytes];
                 Base64TestHelper.InitalizeDecodableBytes(source, numBytes);
 
-                Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                int expectedConsumed = source.Length / 4 * 4; // only consume closest multiple of four since isFinalBlock is false
+                Span<byte> decodedBytes = new byte[3];
+                int consumed, written;
+                if (numBytes % 4 == 0)
+                {
+                    Assert.True(OperationStatus.DestinationTooSmall ==
+                        Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out written), "Number of Input Bytes: " + numBytes);
+                }
+                else
+                {
+                    Assert.True(OperationStatus.InvalidData ==
+                        Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out written), "Number of Input Bytes: " + numBytes);
+                }
+                int expectedConsumed = 4;
+                Assert.Equal(expectedConsumed, consumed);
+                Assert.Equal(decodedBytes.Length, written);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
+            }
+
+            // Output too small even with padding characters in the input
+            {
+                Span<byte> source = new byte[12];
+                Base64TestHelper.InitalizeDecodableBytes(source);
+                source[10] = Base64TestHelper.EncodingPad;
+                source[11] = Base64TestHelper.EncodingPad;
 
-                Assert.Equal(OperationStatus.NeedMoreData,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
+                Span<byte> decodedBytes = new byte[6];
+                Assert.Equal(OperationStatus.DestinationTooSmall, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int written));
+                int expectedConsumed = 8;
                 Assert.Equal(expectedConsumed, consumed);
-                Assert.Equal(decodedBytes.Length, decodedByteCount);
+                Assert.Equal(decodedBytes.Length, written);
                 Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
             }
+
+            {
+                Span<byte> source = new byte[12];
+                Base64TestHelper.InitalizeDecodableBytes(source);
+                source[11] = Base64TestHelper.EncodingPad;
+
+                Span<byte> decodedBytes = new byte[7];
+                Assert.Equal(OperationStatus.DestinationTooSmall, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int written));
+                int expectedConsumed = 8;
+                Assert.Equal(expectedConsumed, consumed);
+                Assert.Equal(6, written);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, 6, source, decodedBytes));
+            }
+        }
+
+        [Fact]
+        public void DecodingOutputTooSmallWithFinalBlockFalse()
+        {
+            for (int numBytes = 8; numBytes < 20; numBytes++)
+            {
+                Span<byte> source = new byte[numBytes];
+                Base64TestHelper.InitalizeDecodableBytes(source, numBytes);
+
+                Span<byte> decodedBytes = new byte[4];
+                int consumed, written;
+                Assert.True(OperationStatus.DestinationTooSmall ==
+                    Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out written, isFinalBlock: false), "Number of Input Bytes: " + numBytes);
+                int expectedConsumed = 4;
+                int expectedWritten = 3;
+                Assert.Equal(expectedConsumed, consumed);
+                Assert.Equal(expectedWritten, written);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes));
+            }
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void DecodingOutputTooSmallRetry(bool isFinalBlock)
+        {
+            Span<byte> source = new byte[1000];
+            Base64TestHelper.InitalizeDecodableBytes(source);
+
+            int outputSize = 240;
+            int requiredSize = Base64.GetMaxDecodedFromUtf8Length(source.Length);
+
+            Span<byte> decodedBytes = new byte[outputSize];
+            Assert.Equal(OperationStatus.DestinationTooSmall, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock));
+            int expectedConsumed = decodedBytes.Length / 3 * 4;
+            Assert.Equal(expectedConsumed, consumed);
+            Assert.Equal(decodedBytes.Length, decodedByteCount);
+            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
+
+            decodedBytes = new byte[requiredSize - outputSize];
+            source = source.Slice(consumed);
+            Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out decodedByteCount, isFinalBlock));
+            expectedConsumed = decodedBytes.Length / 3 * 4;
+            Assert.Equal(expectedConsumed, consumed);
+            Assert.Equal(decodedBytes.Length, decodedByteCount);
+            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
+        }
+
+        [Theory]
+        [InlineData("AQ==", 1)]
+        [InlineData("AQI=", 2)]
+        [InlineData("AQID", 3)]
+        [InlineData("AQIDBA==", 4)]
+        [InlineData("AQIDBAU=", 5)]
+        [InlineData("AQIDBAUG", 6)]
+        public void BasicDecodingWithFinalBlockTrueKnownInputDone(string inputString, int expectedWritten)
+        {
+            Span<byte> source = Encoding.ASCII.GetBytes(inputString);
+            Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
+
+            int expectedConsumed = inputString.Length;
+            Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+            Assert.Equal(expectedConsumed, consumed);
+            Assert.Equal(expectedWritten, decodedByteCount);
+            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes));
         }
 
         [Theory]
@@ -90,72 +262,70 @@ namespace System.Buffers.Text.Tests
         [InlineData("AQI", 0, 0)]
         [InlineData("AQIDBA", 4, 3)]
         [InlineData("AQIDBAU", 4, 3)]
-        [InlineData("AQID", 4, 3)]
-        [InlineData("AQIDBAUG", 8, 6)]
-        public void BasicDecodingWithFinalBlockFalseKnownInputNeedMoreData(string inputString, int expectedConsumed, int expectedWritten)
+        public void BasicDecodingWithFinalBlockTrueKnownInputInvalid(string inputString, int expectedConsumed, int expectedWritten)
         {
             Span<byte> source = Encoding.ASCII.GetBytes(inputString);
             Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
 
-            Assert.Equal(OperationStatus.NeedMoreData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
+            Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
             Assert.Equal(expectedConsumed, consumed);
             Assert.Equal(expectedWritten, decodedByteCount); // expectedWritten == decodedBytes.Length
             Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
         }
 
         [Theory]
-        [InlineData("AQ==", 0, 0)]
-        [InlineData("AQI=", 0, 0)]
-        [InlineData("AQIDBA==", 4, 3)]
-        [InlineData("AQIDBAU=", 4, 3)]
-        public void BasicDecodingWithFinalBlockFalseKnownInputInvalid(string inputString, int expectedConsumed, int expectedWritten)
+        [InlineData("AQID", 3)]
+        [InlineData("AQIDBAUG", 6)]
+        public void BasicDecodingWithFinalBlockFalseKnownInputDone(string inputString, int expectedWritten)
         {
             Span<byte> source = Encoding.ASCII.GetBytes(inputString);
             Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
 
-            Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
+            int expectedConsumed = inputString.Length;
+            Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
             Assert.Equal(expectedConsumed, consumed);
-            Assert.Equal(expectedWritten, decodedByteCount);
-            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes));
+            Assert.Equal(expectedWritten, decodedByteCount); // expectedWritten == decodedBytes.Length
+            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
         }
 
         [Theory]
         [InlineData("A", 0, 0)]
         [InlineData("AQ", 0, 0)]
         [InlineData("AQI", 0, 0)]
+        [InlineData("AQIDB", 4, 3)]
         [InlineData("AQIDBA", 4, 3)]
         [InlineData("AQIDBAU", 4, 3)]
-        public void BasicDecodingWithFinalBlockTrueKnownInputInvalid(string inputString, int expectedConsumed, int expectedWritten)
+        public void BasicDecodingWithFinalBlockFalseKnownInputNeedMoreData(string inputString, int expectedConsumed, int expectedWritten)
         {
             Span<byte> source = Encoding.ASCII.GetBytes(inputString);
             Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
 
-            Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+            Assert.Equal(OperationStatus.NeedMoreData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
             Assert.Equal(expectedConsumed, consumed);
             Assert.Equal(expectedWritten, decodedByteCount); // expectedWritten == decodedBytes.Length
             Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
         }
 
         [Theory]
-        [InlineData("AQ==", 4, 1)]
-        [InlineData("AQI=", 4, 2)]
-        [InlineData("AQID", 4, 3)]
-        [InlineData("AQIDBA==", 8, 4)]
-        [InlineData("AQIDBAU=", 8, 5)]
-        [InlineData("AQIDBAUG", 8, 6)]
-        public void BasicDecodingWithFinalBlockTrueKnownInputDone(string inputString, int expectedConsumed, int expectedWritten)
+        [InlineData("AQ==", 0, 0)]
+        [InlineData("AQI=", 0, 0)]
+        [InlineData("AQIDBA==", 4, 3)]
+        [InlineData("AQIDBAU=", 4, 3)]
+        public void BasicDecodingWithFinalBlockFalseKnownInputInvalid(string inputString, int expectedConsumed, int expectedWritten)
         {
             Span<byte> source = Encoding.ASCII.GetBytes(inputString);
             Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
 
-            Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+            Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock: false));
             Assert.Equal(expectedConsumed, consumed);
             Assert.Equal(expectedWritten, decodedByteCount);
             Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes));
         }
 
-        [Fact]
-        public void DecodingInvalidBytes()
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void DecodingInvalidBytes(bool isFinalBlock)
         {
             // Invalid Bytes:
             // 0-42
@@ -174,14 +344,13 @@ namespace System.Buffers.Text.Tests
                 for (int i = 0; i < invalidBytes.Length; i++)
                 {
                     // Don't test padding (byte 61 i.e. '='), which is tested in DecodingInvalidBytesPadding
-                    if (invalidBytes[i] == Base64TestHelper.s_encodingPad)
+                    if (invalidBytes[i] == Base64TestHelper.EncodingPad)
                         continue;
 
                     // replace one byte with an invalid input
                     source[j] = invalidBytes[i];
 
-                    Assert.Equal(OperationStatus.InvalidData,
-                        Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+                    Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock));
 
                     if (j < 4)
                     {
@@ -197,29 +366,30 @@ namespace System.Buffers.Text.Tests
                 }
             }
 
-            // Input that is not a multiple of 4 is considered invalid
+            // Input that is not a multiple of 4 is considered invalid, if isFinalBlock = true
+            if (isFinalBlock)
             {
                 Span<byte> source = new byte[7] { 50, 50, 50, 50, 80, 80, 80 }; // incomplete input - "2222PPP"
                 Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                Assert.Equal(OperationStatus.InvalidData,
-                        Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+                Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
                 Assert.Equal(4, consumed);
                 Assert.Equal(3, decodedByteCount);
                 Assert.True(Base64TestHelper.VerifyDecodingCorrectness(4, 3, source, decodedBytes));
             }
         }
 
-        [Fact]
-        public void DecodingInvalidBytesPadding()
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void DecodingInvalidBytesPadding(bool isFinalBlock)
         {
             // Only last 2 bytes can be padding, all other occurrence of padding is invalid
             for (int j = 0; j < 7; j++)
             {
                 Span<byte> source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; // valid input - "2222PPPP"
                 Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                source[j] = Base64TestHelper.s_encodingPad;
-                Assert.Equal(OperationStatus.InvalidData,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+                source[j] = Base64TestHelper.EncodingPad;
+                Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock));
 
                 if (j < 4)
                 {
@@ -238,10 +408,9 @@ namespace System.Buffers.Text.Tests
             {
                 Span<byte> source = new byte[] { 50, 50, 50, 50, 80, 42, 42, 42 };
                 Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                source[6] = Base64TestHelper.s_encodingPad;
-                source[7] = Base64TestHelper.s_encodingPad; // invalid input - "2222P*=="
-                Assert.Equal(OperationStatus.InvalidData,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
+                source[6] = Base64TestHelper.EncodingPad;
+                source[7] = Base64TestHelper.EncodingPad; // invalid input - "2222P*=="
+                Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock));
 
                 Assert.Equal(4, consumed);
                 Assert.Equal(3, decodedByteCount);
@@ -249,125 +418,44 @@ namespace System.Buffers.Text.Tests
 
                 source = new byte[] { 50, 50, 50, 50, 80, 42, 42, 42 };
                 decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                source[7] = Base64TestHelper.s_encodingPad; // invalid input - "2222PP**="
-                Assert.Equal(OperationStatus.InvalidData,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out decodedByteCount));
+                source[7] = Base64TestHelper.EncodingPad; // invalid input - "2222PP**="
+                Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out decodedByteCount, isFinalBlock));
 
                 Assert.Equal(4, consumed);
                 Assert.Equal(3, decodedByteCount);
                 Assert.True(Base64TestHelper.VerifyDecodingCorrectness(4, 3, source, decodedBytes));
             }
 
-            // The last byte or the last 2 bytes being the padding character is valid
+            // The last byte or the last 2 bytes being the padding character is valid, if isFinalBlock = true
             {
                 Span<byte> source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 };
                 Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                source[6] = Base64TestHelper.s_encodingPad;
-                source[7] = Base64TestHelper.s_encodingPad; // valid input - "2222PP=="
-                Assert.Equal(OperationStatus.Done,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
-
-                Assert.Equal(source.Length, consumed);
-                Assert.Equal(4, decodedByteCount);
-                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(source.Length, 4, source, decodedBytes));
+                source[6] = Base64TestHelper.EncodingPad;
+                source[7] = Base64TestHelper.EncodingPad; // valid input - "2222PP=="
 
-                source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 };
-                decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
-                source[7] = Base64TestHelper.s_encodingPad; // valid input - "2222PPP="
-                Assert.Equal(OperationStatus.Done,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out decodedByteCount));
-
-                Assert.Equal(source.Length, consumed);
-                Assert.Equal(5, decodedByteCount);
-                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(source.Length, 5, source, decodedBytes));
-            }
-        }
+                OperationStatus expectedStatus = isFinalBlock ? OperationStatus.Done : OperationStatus.InvalidData;
+                int expectedConsumed = isFinalBlock ? source.Length : 4;
+                int expectedWritten = isFinalBlock ? 4 : 3;
 
-        [Fact]
-        public void DecodingOutputTooSmall()
-        {
-            for (int numBytes = 5; numBytes < 20; numBytes++)
-            {
-                Span<byte> source = new byte[numBytes];
-                Base64TestHelper.InitalizeDecodableBytes(source, numBytes);
-
-                Span<byte> decodedBytes = new byte[3];
-                int consumed, written;
-                if (numBytes % 4 != 0)
-                {
-                    Assert.True(OperationStatus.InvalidData ==
-                        Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out written), "Number of Input Bytes: " + numBytes);
-                }
-                else
-                {
-                    Assert.True(OperationStatus.DestinationTooSmall ==
-                        Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out written), "Number of Input Bytes: " + numBytes);
-                }
-                int expectedConsumed = 4;
-                Assert.Equal(expectedConsumed, consumed);
-                Assert.Equal(decodedBytes.Length, written);
-                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
-            }
-
-            // Output too small even with padding characters in the input
-            {
-                Span<byte> source = new byte[12];
-                Base64TestHelper.InitalizeDecodableBytes(source);
-                source[10] = Base64TestHelper.s_encodingPad;
-                source[11] = Base64TestHelper.s_encodingPad;
-
-                Span<byte> decodedBytes = new byte[6];
-                Assert.Equal(OperationStatus.DestinationTooSmall,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int written));
-                int expectedConsumed = 8;
+                Assert.Equal(expectedStatus, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount, isFinalBlock));
                 Assert.Equal(expectedConsumed, consumed);
-                Assert.Equal(decodedBytes.Length, written);
-                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
-            }
+                Assert.Equal(expectedWritten, decodedByteCount);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes));
 
-            {
-                Span<byte> source = new byte[12];
-                Base64TestHelper.InitalizeDecodableBytes(source);
-                source[11] = Base64TestHelper.s_encodingPad;
+                source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 };
+                decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)];
+                source[7] = Base64TestHelper.EncodingPad; // valid input - "2222PPP="
 
-                Span<byte> decodedBytes = new byte[7];
-                Assert.Equal(OperationStatus.DestinationTooSmall,
-                    Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int written));
-                int expectedConsumed = 8;
+                expectedConsumed = isFinalBlock ? source.Length : 4;
+                expectedWritten = isFinalBlock ? 5 : 3;
+                Assert.Equal(expectedStatus, Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out decodedByteCount, isFinalBlock));
                 Assert.Equal(expectedConsumed, consumed);
-                Assert.Equal(6, written);
-                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, 6, source, decodedBytes));
+                Assert.Equal(expectedWritten, decodedByteCount);
+                Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes));
             }
         }
 
         [Fact]
-        public void DecodingOutputTooSmallRetry()
-        {
-            Span<byte> source = new byte[1000];
-            Base64TestHelper.InitalizeDecodableBytes(source);
-
-            int outputSize = 240;
-            int requiredSize = Base64.GetMaxDecodedFromUtf8Length(source.Length);
-
-            Span<byte> decodedBytes = new byte[outputSize];
-            Assert.Equal(OperationStatus.DestinationTooSmall,
-                Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount));
-            int expectedConsumed = decodedBytes.Length / 3 * 4;
-            Assert.Equal(expectedConsumed, consumed);
-            Assert.Equal(decodedBytes.Length, decodedByteCount);
-            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
-
-            decodedBytes = new byte[requiredSize - outputSize];
-            source = source.Slice(consumed);
-            Assert.Equal(OperationStatus.Done,
-                Base64.DecodeFromUtf8(source, decodedBytes, out consumed, out decodedByteCount));
-            expectedConsumed = decodedBytes.Length / 3 * 4;
-            Assert.Equal(expectedConsumed, consumed);
-            Assert.Equal(decodedBytes.Length, decodedByteCount);
-            Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, decodedBytes.Length, source, decodedBytes));
-        }
-
-        [Fact]
         public void GetMaxDecodedLength()
         {
             Span<byte> sourceEmpty = Span<byte>.Empty;
@@ -451,7 +539,7 @@ namespace System.Buffers.Text.Tests
                     Span<byte> buffer = new byte[8] { 50, 50, 50, 50, 80, 80, 80, 80 }; // valid input - "2222PPPP"
 
                     // Don't test padding (byte 61 i.e. '='), which is tested in DecodeInPlaceInvalidBytesPadding
-                    if (invalidBytes[i] == Base64TestHelper.s_encodingPad)
+                    if (invalidBytes[i] == Base64TestHelper.EncodingPad)
                         continue;
 
                     // replace one byte with an invalid input
@@ -488,7 +576,7 @@ namespace System.Buffers.Text.Tests
             for (int j = 0; j < 7; j++)
             {
                 Span<byte> buffer = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; // valid input - "2222PPPP"
-                buffer[j] = Base64TestHelper.s_encodingPad;
+                buffer[j] = Base64TestHelper.EncodingPad;
                 string sourceString = Encoding.ASCII.GetString(buffer.Slice(0, 4).ToArray());
 
                 Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten));
@@ -508,8 +596,8 @@ namespace System.Buffers.Text.Tests
             // Invalid input with valid padding
             {
                 Span<byte> buffer = new byte[] { 50, 50, 50, 50, 80, 42, 42, 42 };
-                buffer[6] = Base64TestHelper.s_encodingPad;
-                buffer[7] = Base64TestHelper.s_encodingPad; // invalid input - "2222P*=="
+                buffer[6] = Base64TestHelper.EncodingPad;
+                buffer[7] = Base64TestHelper.EncodingPad; // invalid input - "2222P*=="
                 string sourceString = Encoding.ASCII.GetString(buffer.Slice(0, 4).ToArray());
                 Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten));
                 Assert.Equal(3, bytesWritten);
@@ -519,7 +607,7 @@ namespace System.Buffers.Text.Tests
 
             {
                 Span<byte> buffer = new byte[] { 50, 50, 50, 50, 80, 42, 42, 42 };
-                buffer[7] = Base64TestHelper.s_encodingPad; // invalid input - "2222P**="
+                buffer[7] = Base64TestHelper.EncodingPad; // invalid input - "2222P**="
                 string sourceString = Encoding.ASCII.GetString(buffer.Slice(0, 4).ToArray());
                 Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten));
                 Assert.Equal(3, bytesWritten);
@@ -530,8 +618,8 @@ namespace System.Buffers.Text.Tests
             // The last byte or the last 2 bytes being the padding character is valid
             {
                 Span<byte> buffer = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 };
-                buffer[6] = Base64TestHelper.s_encodingPad;
-                buffer[7] = Base64TestHelper.s_encodingPad; // valid input - "2222PP=="
+                buffer[6] = Base64TestHelper.EncodingPad;
+                buffer[7] = Base64TestHelper.EncodingPad; // valid input - "2222PP=="
                 string sourceString = Encoding.ASCII.GetString(buffer.ToArray());
                 Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten));
                 Assert.Equal(4, bytesWritten);
@@ -541,7 +629,7 @@ namespace System.Buffers.Text.Tests
 
             {
                 Span<byte> buffer = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 };
-                buffer[7] = Base64TestHelper.s_encodingPad; // valid input - "2222PPP="
+                buffer[7] = Base64TestHelper.EncodingPad; // valid input - "2222PPP="
                 string sourceString = Encoding.ASCII.GetString(buffer.ToArray());
                 Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten));
                 Assert.Equal(5, bytesWritten);
index 1628f17..7fbb0ee 100644 (file)
@@ -31,14 +31,12 @@ namespace System.Buffers.Text.Tests
                 string expectedText = Convert.ToBase64String(bytes, 0, value + 1);
                 Assert.Equal(expectedText, encodedText);
 
-                if (encodedBytes.Length % 4 == 0)
-                {
-                    Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(encodedBytes.Length)];
-                    Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(encodedBytes, decodedBytes, out consumed, out int decodedByteCount));
-                    Assert.Equal(encodedBytes.Length, consumed);
-                    Assert.Equal(sourceBytes.Length, decodedByteCount);
-                    Assert.True(sourceBytes.SequenceEqual(decodedBytes.Slice(0, decodedByteCount)));
-                }
+                Assert.Equal(0, encodedBytes.Length % 4);
+                Span<byte> decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(encodedBytes.Length)];
+                Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(encodedBytes, decodedBytes, out consumed, out int decodedByteCount));
+                Assert.Equal(encodedBytes.Length, consumed);
+                Assert.Equal(sourceBytes.Length, decodedByteCount);
+                Assert.True(sourceBytes.SequenceEqual(decodedBytes.Slice(0, decodedByteCount)));
             }
         }
 
@@ -61,63 +59,6 @@ namespace System.Buffers.Text.Tests
         }
 
         [Fact]
-        public void EncodeEmptySpan()
-        {
-            Span<byte> source = Span<byte>.Empty;
-            Span<byte> encodedBytes = new byte[Base64.GetMaxEncodedToUtf8Length(source.Length)];
-
-            Assert.Equal(OperationStatus.Done, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount));
-            Assert.Equal(source.Length, consumed);
-            Assert.Equal(encodedBytes.Length, encodedBytesCount);
-            Assert.True(Base64TestHelper.VerifyEncodingCorrectness(source.Length, encodedBytes.Length, source, encodedBytes));
-        }
-
-        [Fact]
-        [OuterLoop]
-        public void EncodeTooLargeSpan()
-        {
-
-            if (IntPtr.Size < 8)
-                return;
-
-            bool allocatedFirst = false;
-            bool allocatedSecond = false;
-            IntPtr memBlockFirst = IntPtr.Zero;
-            IntPtr memBlockSecond = IntPtr.Zero;
-
-            // int.MaxValue - (int.MaxValue % 4) => 2147483644, largest multiple of 4 less than int.MaxValue
-            // CLR default limit of 2 gigabytes (GB).
-            // 1610612734, larger than MaximumEncodeLength, requires output buffer of size 2147483648 (which is > int.MaxValue)
-            const int sourceCount = (int.MaxValue >> 2) * 3 + 1;
-            const int encodedCount = 2000000000;
-
-            try
-            {
-                allocatedFirst = AllocationHelper.TryAllocNative((IntPtr)sourceCount, out memBlockFirst);
-                allocatedSecond = AllocationHelper.TryAllocNative((IntPtr)encodedCount, out memBlockSecond);
-                if (allocatedFirst && allocatedSecond)
-                {
-                    unsafe
-                    {
-                        var source = new Span<byte>(memBlockFirst.ToPointer(), sourceCount);
-                        var encodedBytes = new Span<byte>(memBlockSecond.ToPointer(), encodedCount);
-
-                        Assert.Equal(OperationStatus.DestinationTooSmall, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount));
-                        Assert.Equal((encodedBytes.Length >> 2) * 3, consumed); // encoding 1500000000 bytes fits into buffer of 2000000000 bytes
-                        Assert.Equal(encodedBytes.Length, encodedBytesCount);
-                    }
-                }
-            }
-            finally
-            {
-                if (allocatedFirst)
-                    AllocationHelper.ReleaseNative(ref memBlockFirst);
-                if (allocatedSecond)
-                    AllocationHelper.ReleaseNative(ref memBlockSecond);
-            }
-        }
-
-        [Fact]
         public void BasicEncodingWithFinalBlockFalse()
         {
             var rnd = new Random(42);
@@ -126,11 +67,14 @@ namespace System.Buffers.Text.Tests
                 int numBytes = rnd.Next(100, 1000 * 1000);
                 Span<byte> source = new byte[numBytes];
                 Base64TestHelper.InitalizeBytes(source, numBytes);
+
                 Span<byte> encodedBytes = new byte[Base64.GetMaxEncodedToUtf8Length(source.Length)];
                 int expectedConsumed = source.Length / 3 * 3; // only consume closest multiple of three since isFinalBlock is false
                 int expectedWritten = source.Length / 3 * 4;
 
-                Assert.Equal(OperationStatus.NeedMoreData, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock: false));
+                // The constant random seed guarantees that both states are tested.
+                OperationStatus expectedStatus = numBytes % 3 == 0 ? OperationStatus.Done : OperationStatus.NeedMoreData;
+                Assert.Equal(expectedStatus, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock: false));
                 Assert.Equal(expectedConsumed, consumed);
                 Assert.Equal(expectedWritten, encodedBytesCount);
                 Assert.True(Base64TestHelper.VerifyEncodingCorrectness(expectedConsumed, expectedWritten, source, encodedBytes));
@@ -138,15 +82,18 @@ namespace System.Buffers.Text.Tests
         }
 
         [Theory]
-        [InlineData(1, "", 0, 0)]
-        [InlineData(2, "", 0, 0)]
-        [InlineData(3, "AQID", 3, 4)]
-        [InlineData(4, "AQID", 3, 4)]
-        [InlineData(5, "AQID", 3, 4)]
-        [InlineData(6, "AQIDBAUG", 6, 8)]
-        [InlineData(7, "AQIDBAUG", 6, 8)]
-        public void BasicEncodingWithFinalBlockFalseKnownInput(int numBytes, string expectedText, int expectedConsumed, int expectedWritten)
+        [InlineData(1, "AQ==")]
+        [InlineData(2, "AQI=")]
+        [InlineData(3, "AQID")]
+        [InlineData(4, "AQIDBA==")]
+        [InlineData(5, "AQIDBAU=")]
+        [InlineData(6, "AQIDBAUG")]
+        [InlineData(7, "AQIDBAUGBw==")]
+        public void BasicEncodingWithFinalBlockTrueKnownInput(int numBytes, string expectedText)
         {
+            int expectedConsumed = numBytes;
+            int expectedWritten = expectedText.Length;
+
             Span<byte> source = new byte[numBytes];
             for (int i = 0; i < numBytes; i++)
             {
@@ -154,7 +101,7 @@ namespace System.Buffers.Text.Tests
             }
             Span<byte> encodedBytes = new byte[Base64.GetMaxEncodedToUtf8Length(source.Length)];
 
-            Assert.Equal(OperationStatus.NeedMoreData, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock: false));
+            Assert.Equal(OperationStatus.Done, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock: true));
             Assert.Equal(expectedConsumed, consumed);
             Assert.Equal(expectedWritten, encodedBytesCount);
 
@@ -163,14 +110,14 @@ namespace System.Buffers.Text.Tests
         }
 
         [Theory]
-        [InlineData(1, "AQ==", 1, 4)]
-        [InlineData(2, "AQI=", 2, 4)]
+        [InlineData(1, "", 0, 0)]
+        [InlineData(2, "", 0, 0)]
         [InlineData(3, "AQID", 3, 4)]
-        [InlineData(4, "AQIDBA==", 4, 8)]
-        [InlineData(5, "AQIDBAU=", 5, 8)]
+        [InlineData(4, "AQID", 3, 4)]
+        [InlineData(5, "AQID", 3, 4)]
         [InlineData(6, "AQIDBAUG", 6, 8)]
-        [InlineData(7, "AQIDBAUGBw==", 7, 12)]
-        public void BasicEncodingWithFinalBlockTrueKnownInput(int numBytes, string expectedText, int expectedConsumed, int expectedWritten)
+        [InlineData(7, "AQIDBAUG", 6, 8)]
+        public void BasicEncodingWithFinalBlockFalseKnownInput(int numBytes, string expectedText, int expectedConsumed, int expectedWritten)
         {
             Span<byte> source = new byte[numBytes];
             for (int i = 0; i < numBytes; i++)
@@ -179,7 +126,8 @@ namespace System.Buffers.Text.Tests
             }
             Span<byte> encodedBytes = new byte[Base64.GetMaxEncodedToUtf8Length(source.Length)];
 
-            Assert.Equal(OperationStatus.Done, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock: true));
+            OperationStatus expectedStatus = numBytes % 3 == 0 ? OperationStatus.Done : OperationStatus.NeedMoreData;
+            Assert.Equal(expectedStatus, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock: false));
             Assert.Equal(expectedConsumed, consumed);
             Assert.Equal(expectedWritten, encodedBytesCount);
 
@@ -187,8 +135,23 @@ namespace System.Buffers.Text.Tests
             Assert.Equal(expectedText, encodedText);
         }
 
-        [Fact]
-        public void EncodingOutputTooSmall()
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void EncodeEmptySpan(bool isFinalBlock)
+        {
+            Span<byte> source = Span<byte>.Empty;
+            Span<byte> encodedBytes = new byte[Base64.GetMaxEncodedToUtf8Length(source.Length)];
+
+            Assert.Equal(OperationStatus.Done, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock));
+            Assert.Equal(0, consumed);
+            Assert.Equal(0, encodedBytesCount);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void EncodingOutputTooSmall(bool isFinalBlock)
         {
             for (int numBytes = 4; numBytes < 20; numBytes++)
             {
@@ -196,8 +159,7 @@ namespace System.Buffers.Text.Tests
                 Base64TestHelper.InitalizeBytes(source, numBytes);
 
                 Span<byte> encodedBytes = new byte[4];
-                Assert.Equal(OperationStatus.DestinationTooSmall,
-                    Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int written));
+                Assert.Equal(OperationStatus.DestinationTooSmall, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int written, isFinalBlock));
                 int expectedConsumed = 3;
                 Assert.Equal(expectedConsumed, consumed);
                 Assert.Equal(encodedBytes.Length, written);
@@ -205,8 +167,10 @@ namespace System.Buffers.Text.Tests
             }
         }
 
-        [Fact]
-        public void EncodingOutputTooSmallRetry()
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void EncodingOutputTooSmallRetry(bool isFinalBlock)
         {
             Span<byte> source = new byte[750];
             Base64TestHelper.InitalizeBytes(source);
@@ -215,8 +179,7 @@ namespace System.Buffers.Text.Tests
             int requiredSize = Base64.GetMaxEncodedToUtf8Length(source.Length);
 
             Span<byte> encodedBytes = new byte[outputSize];
-            Assert.Equal(OperationStatus.DestinationTooSmall,
-                Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int written));
+            Assert.Equal(OperationStatus.DestinationTooSmall, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int written, isFinalBlock));
             int expectedConsumed = encodedBytes.Length / 4 * 3;
             Assert.Equal(expectedConsumed, consumed);
             Assert.Equal(encodedBytes.Length, written);
@@ -224,14 +187,59 @@ namespace System.Buffers.Text.Tests
 
             encodedBytes = new byte[requiredSize - outputSize];
             source = source.Slice(consumed);
-            Assert.Equal(OperationStatus.Done,
-                Base64.EncodeToUtf8(source, encodedBytes, out consumed, out written));
+            Assert.Equal(OperationStatus.Done, Base64.EncodeToUtf8(source, encodedBytes, out consumed, out written, isFinalBlock));
             expectedConsumed = encodedBytes.Length / 4 * 3;
             Assert.Equal(expectedConsumed, consumed);
             Assert.Equal(encodedBytes.Length, written);
             Assert.True(Base64TestHelper.VerifyEncodingCorrectness(expectedConsumed, encodedBytes.Length, source, encodedBytes));
         }
 
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        [OuterLoop]
+        public void EncodeTooLargeSpan(bool isFinalBlock)
+        {
+            if (!Environment.Is64BitProcess)
+                return;
+
+            bool allocatedFirst = false;
+            bool allocatedSecond = false;
+            IntPtr memBlockFirst = IntPtr.Zero;
+            IntPtr memBlockSecond = IntPtr.Zero;
+
+            // int.MaxValue - (int.MaxValue % 4) => 2147483644, largest multiple of 4 less than int.MaxValue
+            // CLR default limit of 2 gigabytes (GB).
+            // 1610612734, larger than MaximumEncodeLength, requires output buffer of size 2147483648 (which is > int.MaxValue)
+            const int sourceCount = (int.MaxValue >> 2) * 3 + 1;
+            const int encodedCount = 2000000000;
+
+            try
+            {
+                allocatedFirst = AllocationHelper.TryAllocNative((IntPtr)sourceCount, out memBlockFirst);
+                allocatedSecond = AllocationHelper.TryAllocNative((IntPtr)encodedCount, out memBlockSecond);
+                if (allocatedFirst && allocatedSecond)
+                {
+                    unsafe
+                    {
+                        var source = new Span<byte>(memBlockFirst.ToPointer(), sourceCount);
+                        var encodedBytes = new Span<byte>(memBlockSecond.ToPointer(), encodedCount);
+
+                        Assert.Equal(OperationStatus.DestinationTooSmall, Base64.EncodeToUtf8(source, encodedBytes, out int consumed, out int encodedBytesCount, isFinalBlock));
+                        Assert.Equal((encodedBytes.Length >> 2) * 3, consumed); // encoding 1500000000 bytes fits into buffer of 2000000000 bytes
+                        Assert.Equal(encodedBytes.Length, encodedBytesCount);
+                    }
+                }
+            }
+            finally
+            {
+                if (allocatedFirst)
+                    AllocationHelper.ReleaseNative(ref memBlockFirst);
+                if (allocatedSecond)
+                    AllocationHelper.ReleaseNative(ref memBlockSecond);
+            }
+        }
+
         [Fact]
         public void GetMaxEncodedLength()
         {
index 516cdf4..a308843 100644 (file)
@@ -45,23 +45,17 @@ namespace System.Buffers.Text.Tests
             -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
         };
 
-        public static readonly byte s_encodingPad = (byte)'=';              // '=', for padding
-
-        public static readonly sbyte s_invalidByte = -1;                    // Designating -1 for invalid bytes in the decoding map
+        public const byte EncodingPad = (byte)'=';      // '=', for padding
+        public const sbyte InvalidByte = -1;            // Designating -1 for invalid bytes in the decoding map
 
         public static byte[] InvalidBytes
         {
             get
             {
-                int[] indices = s_decodingMap.FindAllIndexOf(s_invalidByte);
-                // Workaroudn for indices.Cast<byte>().ToArray() since it throws
+                int[] indices = s_decodingMap.FindAllIndexOf(InvalidByte);
+                // Workaround for indices.Cast<byte>().ToArray() since it throws
                 // InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Byte'
-                byte[] bytes = new byte[indices.Length];
-                for (int i = 0; i < indices.Length; i++)
-                {
-                    bytes[i] = (byte)indices[i];
-                }
-                return bytes;
+                return indices.Select(i => (byte)i).ToArray();
             }
         }
 
@@ -101,7 +95,7 @@ namespace System.Buffers.Text.Tests
             sbyte[] data = new sbyte[256]; // 0 to byte.MaxValue (255)
             for (int i = 0; i < data.Length; i++)
             {
-                data[i] = s_invalidByte;
+                data[i] = InvalidByte;
             }
             for (int i = 0; i < s_characters.Length; i++)
             {
index 946a6a8..d1928e1 100644 (file)
@@ -46,7 +46,7 @@ namespace System.Security.Cryptography
                 ThrowHelper.ThrowCryptographicException();
             }
 
-            Debug.Assert(status == OperationStatus.NeedMoreData);
+            Debug.Assert(status == OperationStatus.Done);
             Debug.Assert(consumed == InputBlockSize);
 
             return written;