| File: FormatterBinaryWriter.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.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace Microsoft.AspNetCore.OutputCaching; internal ref struct FormatterBinaryWriter { // this is effectively a cut-down re-implementation of BinaryWriter // from https://github.com/dotnet/runtime/blob/3689fbec921418e496962dc0ee252bdc9eafa3de/src/libraries/System.Private.CoreLib/src/System/IO/BinaryWriter.cs // and is byte-compatible; however, instead of working against a Stream, we work against a IBufferWriter<byte> // // note it also has APIs for writing raw BLOBs private readonly IBufferWriter<byte> target; private int offset, length; private ref byte root; public FormatterBinaryWriter(IBufferWriter<byte> target) { ArgumentNullException.ThrowIfNull(target); this.target = target; root = ref Unsafe.NullRef<byte>(); // no buffer initially offset = length = 0; DebugAssertValid(); } private Span<byte> AvailableBuffer { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { DebugAssertValid(); return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref root, offset), length - offset); } } [Conditional("DEBUG")] private void DebugAssertValid() { Debug.Assert(target is not null); if (Unsafe.IsNullRef(ref root)) { // no buffer; expect all zeros Debug.Assert(length == 0 && offset == 0); } else { // have buffer; expect valid offset and positive length Debug.Assert(offset >= 0 && offset <= length); Debug.Assert(length > 0); } } // Writes a byte to this stream. The current position of the stream is // advanced by one. // public void Write(byte value) { if (offset < length) { Unsafe.Add(ref root, offset++) = value; } else { SlowWrite(value); } DebugAssertValid(); } public void Write(string value) => Write(value, 0); internal void Write(string value, int lengthShift) { ArgumentNullException.ThrowIfNull(value); if (value.Length == 0) { Write(0); // length prefix return; } var bytes = Encoding.UTF8.GetByteCount(value); Write7BitEncodedInt(bytes << lengthShift); // length prefix if (bytes <= length - offset) { var actual = Encoding.UTF8.GetBytes(value, AvailableBuffer); Debug.Assert(actual == bytes); offset += bytes; } else { Flush(); // get the encoding to do the heavy lifting directly var actual = Encoding.UTF8.GetBytes(value, target); Debug.Assert(actual == bytes); } DebugAssertValid(); } private void RequestNewBuffer() { Flush(); var span = target.GetSpan(1024); // fairly arbitrary non-trivial buffer; we can explore larger if useful if (span.IsEmpty) { Throw(); } offset = 0; length = span.Length; root = ref MemoryMarshal.GetReference(span); DebugAssertValid(); static void Throw() => throw new InvalidOperationException("Unable to acquire non-empty write buffer"); } public void Flush() // commits the current buffer and leave in a buffer-free state { if (!Unsafe.IsNullRef(ref root)) { target.Advance(offset); length = offset = 0; root = ref Unsafe.NullRef<byte>(); } DebugAssertValid(); } private void SlowWrite(byte value) { RequestNewBuffer(); Unsafe.Add(ref root, offset++) = value; } public void Write7BitEncodedInt(int value) { uint uValue = (uint)value; // Write out an int 7 bits at a time. The high bit of the byte, // when on, tells reader to continue reading more bytes. // // Using the constants 0x7F and ~0x7F below offers smaller // codegen than using the constant 0x80. while (uValue > 0x7Fu) { Write((byte)(uValue | ~0x7Fu)); uValue >>= 7; } Write((byte)uValue); } public void Write7BitEncodedInt64(long value) { ulong uValue = (ulong)value; // Write out an int 7 bits at a time. The high bit of the byte, // when on, tells reader to continue reading more bytes. // // Using the constants 0x7F and ~0x7F below offers smaller // codegen than using the constant 0x80. while (uValue > 0x7Fu) { Write((byte)((uint)uValue | ~0x7Fu)); uValue >>= 7; } Write((byte)uValue); } public void WriteRaw(scoped ReadOnlySpan<byte> value) { if (value.IsEmpty) { } // nothing to do else if ((offset + value.Length) <= length) { value.CopyTo(AvailableBuffer); offset += value.Length; } else { SlowWriteRaw(value); } DebugAssertValid(); } private void SlowWriteRaw(scoped ReadOnlySpan<byte> value) { do { RequestNewBuffer(); var available = AvailableBuffer; var toWrite = Math.Min(value.Length, available.Length); value.Slice(start: 0, length: toWrite).CopyTo(available); offset += toWrite; value = value.Slice(start: toWrite); } while (!value.IsEmpty); } }