/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Shared/test/Shared.Tests/Encoding/Int7BitEncodingUtilsTests.cs
355 строк
12 KB
Brennan
Fixup a few dataprotection and antiforgery edge cases (#66508)
29 апр 2026, 05:06
Не верифицирован
29 апр 2026, 05:06
5a0718f
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.AspNetCore.Shared.Tests; public class Int7BitEncodingUtilsTests { [Theory] [InlineData(0, 1)] [InlineData(1, 1)] [InlineData(0b0_1111111, 1)] [InlineData(0b1_0000000, 2)] [InlineData(0b1111111_1111111, 2)] [InlineData(0b1_0000000_0000000, 3)] [InlineData(0b1111111_1111111_1111111, 3)] [InlineData(0b1_0000000_0000000_0000000, 4)] [InlineData(0b1111111_1111111_1111111_1111111, 4)] [InlineData(0b1_0000000_0000000_0000000_0000000, 5)] [InlineData(uint.MaxValue, 5)] public void Measure7BitEncodedUIntLength_ReturnsExceptedLength(uint value, int expectedSize) { var actualSize = value.Measure7BitEncodedUIntLength(); Assert.Equal(expectedSize, actualSize); } [Theory] [InlineData(0, new byte[] { 0x00 })] [InlineData(1, new byte[] { 0x01 })] [InlineData(127, new byte[] { 0x7F })] [InlineData(128, new byte[] { 0x80, 0x01 })] [InlineData(255, new byte[] { 0xFF, 0x01 })] [InlineData(256, new byte[] { 0x80, 0x02 })] [InlineData(16383, new byte[] { 0xFF, 0x7F })] [InlineData(16384, new byte[] { 0x80, 0x80, 0x01 })] [InlineData(2097151, new byte[] { 0xFF, 0xFF, 0x7F })] [InlineData(2097152, new byte[] { 0x80, 0x80, 0x80, 0x01 })] [InlineData(268435455, new byte[] { 0xFF, 0xFF, 0xFF, 0x7F })] [InlineData(268435456, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 })] [InlineData(int.MaxValue, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 })] public void Read7BitEncodedInt_DecodesCorrectly(int expected, byte[] encoded) { ReadOnlySpan<byte> source = encoded; var bytesConsumed = source.Read7BitEncodedInt(out var value); Assert.Equal(expected, value); Assert.Equal(encoded.Length, bytesConsumed); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(127)] [InlineData(128)] [InlineData(255)] [InlineData(16383)] [InlineData(16384)] [InlineData(2097151)] [InlineData(2097152)] [InlineData(268435455)] [InlineData(268435456)] [InlineData(int.MaxValue)] public void Read7BitEncodedInt_RoundTripsWithWrite(int value) { Span<byte> buffer = stackalloc byte[5]; var bytesWritten = buffer.Write7BitEncodedInt(value); ReadOnlySpan<byte> source = buffer.Slice(0, bytesWritten); var bytesConsumed = source.Read7BitEncodedInt(out var decoded); Assert.Equal(value, decoded); Assert.Equal(bytesWritten, bytesConsumed); } [Fact] public void Read7BitEncodedInt_WithEmptySpan_ThrowsFormatException() { Assert.Throws<FormatException>(() => { var source = ReadOnlySpan<byte>.Empty; return source.Read7BitEncodedInt(out _); }); } [Fact] public void Read7BitEncodedInt_WithTruncatedData_ThrowsFormatException() { Assert.Throws<FormatException>(() => { // This represents the start of a multi-byte encoded value but is incomplete // 0x80 has continuation bit set, meaning more bytes should follow ReadOnlySpan<byte> source = [0x80]; return source.Read7BitEncodedInt(out _); }); } [Fact] public void Read7BitEncodedInt_WithOverflow_ThrowsFormatException() { Assert.Throws<FormatException>(() => { // 6 bytes with continuation bits set would overflow a 32-bit integer ReadOnlySpan<byte> source = [0x80, 0x80, 0x80, 0x80, 0x80, 0x01]; return source.Read7BitEncodedInt(out _); }); } [Fact] public void Read7BitEncodedInt_WithFifthByteOverflow_ThrowsFormatException() { // Regression test: the 5th byte must fit within 4 bits (32 - 28 = 4 bits remaining). // A 5th byte value > 0x0F (0b_1111) would overflow a 32-bit integer. // The correct behavior (matching BinaryReader/FormatterBinaryReader) is to throw, // and the previous implementation silently misdecoded the value. Assert.Throws<FormatException>(() => { // First 4 bytes use continuation bits, 5th byte = 0x10 (> 0x0F, overflows 32 bits) ReadOnlySpan<byte> source = [0x80, 0x80, 0x80, 0x80, 0x10]; return source.Read7BitEncodedInt(out _); }); } [Fact] public void Read7BitEncodedInt_WithExtraDataAfterValue_ConsumesOnlyNeededBytes() { // Value 127 followed by extra bytes ReadOnlySpan<byte> source = [0x7F, 0xFF, 0xFF]; var bytesConsumed = source.Read7BitEncodedInt(out var value); Assert.Equal(127, value); Assert.Equal(1, bytesConsumed); } [Theory] [InlineData("")] [InlineData("Hello")] [InlineData("Hello, World!")] [InlineData("UTF-8: \u00e9\u00e8\u00ea")] public void Read7BitEncodedString_DecodesCorrectly(string expected) { var stringBytes = System.Text.Encoding.UTF8.GetBytes(expected); var lengthBytes = new byte[5]; Span<byte> lengthSpan = lengthBytes; var lengthSize = lengthSpan.Write7BitEncodedInt(stringBytes.Length); var encodedBytes = new byte[lengthSize + stringBytes.Length]; Array.Copy(lengthBytes, 0, encodedBytes, 0, lengthSize); Array.Copy(stringBytes, 0, encodedBytes, lengthSize, stringBytes.Length); ReadOnlySpan<byte> source = encodedBytes; var bytesConsumed = source.Read7BitEncodedString(out var value); Assert.Equal(expected, value); Assert.Equal(encodedBytes.Length, bytesConsumed); } [Fact] public void Read7BitEncodedString_WithEmptyString_ReturnsEmptyAndConsumesLengthByte() { // Length of 0 ReadOnlySpan<byte> source = new byte[] { 0x00 }; var bytesConsumed = source.Read7BitEncodedString(out var value); Assert.Equal(string.Empty, value); Assert.Equal(1, bytesConsumed); } [Fact] public void Read7BitEncodedString_WithTruncatedStringData_ThrowsFormatException() { Assert.Throws<FormatException>(() => { // Length says 10 bytes, but only 3 bytes of data follow ReadOnlySpan<byte> source = [0x0A, 0x41, 0x42, 0x43]; return source.Read7BitEncodedString(out _); }); } [Fact] public void Read7BitEncodedString_WithMultiByteLengthPrefixAndTruncatedData_ThrowsFormatException() { Assert.Throws<FormatException>(() => { // Length prefix 0xC8 0x01 = 200 (multi-byte), but only 2 bytes of string data follow // Total: 4 bytes, but need 2 (prefix) + 200 (data) = 202 bytes ReadOnlySpan<byte> source = [0xC8, 0x01, 0x41, 0x42]; return source.Read7BitEncodedString(out _); }); } [Fact] public void Read7BitEncodedString_WithTruncatedLengthPrefix_ThrowsFormatException() { Assert.Throws<FormatException>(() => { // Continuation bit set but no more data ReadOnlySpan<byte> source = [0x80]; return source.Read7BitEncodedString(out _); }); } [Fact] public void Read7BitEncodedString_WithExtraDataAfterString_ConsumesOnlyNeededBytes() { // "Hi" (length 2) followed by extra bytes ReadOnlySpan<byte> source = new byte[] { 0x02, 0x48, 0x69, 0xFF, 0xFF }; var bytesConsumed = source.Read7BitEncodedString(out var value); Assert.Equal("Hi", value); Assert.Equal(3, bytesConsumed); // 1 byte length + 2 bytes string } [Fact] public void Read7BitEncodedString_WithMultiByteLengthPrefix_DecodesCorrectly() { // Create a string that requires a multi-byte length prefix (> 127 bytes) var longString = new string('A', 200); var stringBytes = System.Text.Encoding.UTF8.GetBytes(longString); var lengthBytes = new byte[5]; Span<byte> lengthSpan = lengthBytes; var lengthSize = lengthSpan.Write7BitEncodedInt(stringBytes.Length); Assert.True(lengthSize > 1); // Verify we're testing multi-byte length var encodedBytes = new byte[lengthSize + stringBytes.Length]; Array.Copy(lengthBytes, 0, encodedBytes, 0, lengthSize); Array.Copy(stringBytes, 0, encodedBytes, lengthSize, stringBytes.Length); ReadOnlySpan<byte> source = encodedBytes; var bytesConsumed = source.Read7BitEncodedString(out var value); Assert.Equal(longString, value); Assert.Equal(encodedBytes.Length, bytesConsumed); } [Theory] [InlineData("")] [InlineData("Hello")] [InlineData("Hello, World!")] [InlineData("UTF-8: \u00e9\u00e8\u00ea")] public void Write7BitEncodedString_EncodesCorrectly(string value) { var expectedByteCount = Int7BitEncodingUtils.Measure7BitEncodedStringLength(value); Span<byte> buffer = stackalloc byte[expectedByteCount]; var bytesWritten = buffer.Write7BitEncodedString(value); Assert.Equal(expectedByteCount, bytesWritten); // Verify by reading back ReadOnlySpan<byte> source = buffer.Slice(0, bytesWritten); var bytesConsumed = source.Read7BitEncodedString(out var decoded); Assert.Equal(value, decoded); Assert.Equal(bytesWritten, bytesConsumed); } [Fact] public void Write7BitEncodedString_WithNullString_WritesZeroLength() { Span<byte> buffer = stackalloc byte[10]; var bytesWritten = buffer.Write7BitEncodedString(null!); Assert.Equal(1, bytesWritten); Assert.Equal(0, buffer[0]); } [Fact] public void Write7BitEncodedString_WithEmptyString_WritesZeroLength() { Span<byte> buffer = stackalloc byte[10]; var bytesWritten = buffer.Write7BitEncodedString(string.Empty); Assert.Equal(1, bytesWritten); Assert.Equal(0, buffer[0]); } [Fact] public void Write7BitEncodedString_WithLongString_UsesMultiByteLengthPrefix() { var longString = new string('A', 200); var expectedByteCount = Int7BitEncodingUtils.Measure7BitEncodedStringLength(longString); var buffer = new byte[expectedByteCount]; var bytesWritten = buffer.AsSpan().Write7BitEncodedString(longString); Assert.Equal(expectedByteCount, bytesWritten); // Verify the length prefix is multi-byte (200 > 127) Assert.True((buffer[0] & 0x80) != 0); // Continuation bit set // Verify by reading back ReadOnlySpan<byte> source = buffer; var bytesConsumed = source.Read7BitEncodedString(out var decoded); Assert.Equal(longString, decoded); Assert.Equal(bytesWritten, bytesConsumed); } [Theory] [InlineData("", 1)] [InlineData("A", 2)] [InlineData("Hello", 6)] public void Measure7BitEncodedStringLength_ReturnsCorrectLength(string value, int expectedLength) { var actualLength = Int7BitEncodingUtils.Measure7BitEncodedStringLength(value); Assert.Equal(expectedLength, actualLength); } [Fact] public void Measure7BitEncodedStringLength_WithNullString_ReturnsOne() { var length = Int7BitEncodingUtils.Measure7BitEncodedStringLength(null!); Assert.Equal(1, length); } [Fact] public void Measure7BitEncodedStringLength_WithLongString_IncludesMultiByteLengthPrefix() { var longString = new string('A', 200); var length = Int7BitEncodingUtils.Measure7BitEncodedStringLength(longString); // 200 bytes for string + 2 bytes for length prefix (200 requires 2 bytes in 7-bit encoding) Assert.Equal(202, length); } [Fact] public void Measure7BitEncodedStringLength_WithUtf8String_CountsUtf8Bytes() { // Each of these characters is 2 bytes in UTF-8 var utf8String = "\u00e9\u00e8\u00ea"; // 3 chars, 6 bytes var length = Int7BitEncodingUtils.Measure7BitEncodedStringLength(utf8String); // 6 bytes for string + 1 byte for length prefix Assert.Equal(7, length); } }