| File: FormatterBinaryReader.cs | Web Access |
| Project: src\aspnetcore\src\Middleware\OutputCaching\src\Microsoft.AspNetCore.OutputCaching.csproj (Microsoft.AspNetCore.OutputCaching) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace Microsoft.AspNetCore.OutputCaching; internal ref struct FormatterBinaryReader { // this is effectively a cut-down re-implementation of BinaryReader // from https://github.com/dotnet/runtime/blob/3689fbec921418e496962dc0ee252bdc9eafa3de/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs // and is byte-compatible; however, instead of working against a Stream, we work against a ReadOnlyMemory<byte> // // additionally, we add support for reading a string with length specified by the caller (rather than handled automatically), // and in-place (zero-copy) BLOB reads private readonly ReadOnlyMemory<byte> _original; // used to allow us to zero-copy chunks out of the payload private readonly ref byte _root; private readonly int _length; private int _offset; public bool IsEOF => _offset >= _length; public FormatterBinaryReader(ReadOnlyMemory<byte> content) { _original = content; _length = content.Length; _root = ref MemoryMarshal.GetReference(content.Span); } public byte ReadByte() { if (IsEOF) { ThrowEndOfStream(); } return Unsafe.Add(ref _root, _offset++); } public int Read7BitEncodedInt() { // Unlike writing, we can't delegate to the 64-bit read on // 64-bit platforms. The reason for this is that we want to // stop consuming bytes if we encounter an integer overflow. uint result = 0; byte byteReadJustNow; // Read the integer 7 bits at a time. The high bit // of the byte when on means to continue reading more bytes. // // There are two failure cases: we've read more than 5 bytes, // or the fifth byte is about to cause integer overflow. // This means that we can read the first 4 bytes without // worrying about integer overflow. const int MaxBytesWithoutOverflow = 4; for (int shift = 0; shift < MaxBytesWithoutOverflow * 7; shift += 7) { // ReadByte handles end of stream cases for us. byteReadJustNow = ReadByte(); result |= (byteReadJustNow & 0x7Fu) << shift; if (byteReadJustNow <= 0x7Fu) { return (int)result; // early exit } } // Read the 5th byte. Since we already read 28 bits, // the value of this byte must fit within 4 bits (32 - 28), // and it must not have the high bit set. byteReadJustNow = ReadByte(); if (byteReadJustNow > 0b_1111u) { ThrowOverflowException(); } result |= (uint)byteReadJustNow << (MaxBytesWithoutOverflow * 7); return (int)result; } public long Read7BitEncodedInt64() { ulong result = 0; byte byteReadJustNow; // Read the integer 7 bits at a time. The high bit // of the byte when on means to continue reading more bytes. // // There are two failure cases: we've read more than 10 bytes, // or the tenth byte is about to cause integer overflow. // This means that we can read the first 9 bytes without // worrying about integer overflow. const int MaxBytesWithoutOverflow = 9; for (int shift = 0; shift < MaxBytesWithoutOverflow * 7; shift += 7) { // ReadByte handles end of stream cases for us. byteReadJustNow = ReadByte(); result |= (byteReadJustNow & 0x7Ful) << shift; if (byteReadJustNow <= 0x7Fu) { return (long)result; // early exit } } // Read the 10th byte. Since we already read 63 bits, // the value of this byte must fit within 1 bit (64 - 63), // and it must not have the high bit set. byteReadJustNow = ReadByte(); if (byteReadJustNow > 0b_1u) { ThrowOverflowException(); } result |= (ulong)byteReadJustNow << (MaxBytesWithoutOverflow * 7); return (long)result; } public string ReadString() => ReadString(Read7BitEncodedInt()); public void SkipString() => Skip(Read7BitEncodedInt()); public string ReadString(int bytes) { ArgumentOutOfRangeException.ThrowIfNegative(bytes); if (_offset > _length - bytes) { ThrowEndOfStream(); } if (bytes == 0) { return ""; } var s = Encoding.UTF8.GetString(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref _root, _offset), bytes)); _offset += bytes; return s; } public void Skip(int bytes) { ArgumentOutOfRangeException.ThrowIfNegative(bytes); if (_offset > _length - bytes) { ThrowEndOfStream(); } _offset += bytes; } public ReadOnlySpan<byte> ReadBytesSpan(int count) { ArgumentOutOfRangeException.ThrowIfNegative(count); if (_offset > _length - count) { ThrowEndOfStream(); } if (count == 0) { return default; } var result = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref _root, _offset), count); _offset += count; return result; } public ReadOnlyMemory<byte> ReadBytesMemory(int count) { ArgumentOutOfRangeException.ThrowIfNegative(count); if (_offset > _length - count) { ThrowEndOfStream(); } if (count == 0) { return default; } var result = _original.Slice(_offset, count); _offset += count; return result; } [DoesNotReturn] private static void ThrowEndOfStream() => throw new EndOfStreamException(); [DoesNotReturn] private static void ThrowOverflowException() => throw new OverflowException(); }