Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 1 | #region Copyright notice and license |
| 2 | // Protocol Buffers - Google's data interchange format |
| 3 | // Copyright 2008 Google Inc. All rights reserved. |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 4 | // |
Joshua Haberman | 8c45177 | 2023-09-08 17:13:13 -0700 | [diff] [blame] | 5 | // Use of this source code is governed by a BSD-style |
| 6 | // license that can be found in the LICENSE file or at |
| 7 | // https://developers.google.com/open-source/licenses/bsd |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 8 | #endregion |
| 9 | |
| 10 | using System; |
| 11 | using System.Buffers; |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 12 | using System.Runtime.CompilerServices; |
| 13 | using System.Security; |
| 14 | |
| 15 | namespace Google.Protobuf |
| 16 | { |
| 17 | /// <summary> |
| 18 | /// Abstraction for writing to a steam / IBufferWriter |
| 19 | /// </summary> |
| 20 | [SecuritySafeCritical] |
| 21 | internal struct WriteBufferHelper |
| 22 | { |
| 23 | private IBufferWriter<byte> bufferWriter; |
| 24 | private CodedOutputStream codedOutputStream; |
| 25 | |
Jan Tattermusch | 5fc49bd | 2020-05-28 17:30:07 +0200 | [diff] [blame] | 26 | public CodedOutputStream CodedOutputStream => codedOutputStream; |
Jan Tattermusch | f9d9019 | 2020-05-28 15:58:34 +0200 | [diff] [blame] | 27 | |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 28 | /// <summary> |
| 29 | /// Initialize an instance with a coded output stream. |
| 30 | /// This approach is faster than using a constructor because the instance to initialize is passed by reference |
| 31 | /// and we can write directly into it without copying. |
| 32 | /// </summary> |
| 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 34 | public static void Initialize(CodedOutputStream codedOutputStream, out WriteBufferHelper instance) |
| 35 | { |
| 36 | instance.bufferWriter = null; |
| 37 | instance.codedOutputStream = codedOutputStream; |
| 38 | } |
| 39 | |
| 40 | /// <summary> |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 41 | /// Initialize an instance with a buffer writer. |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 42 | /// This approach is faster than using a constructor because the instance to initialize is passed by reference |
| 43 | /// and we can write directly into it without copying. |
| 44 | /// </summary> |
| 45 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
Jan Tattermusch | f9d9019 | 2020-05-28 15:58:34 +0200 | [diff] [blame] | 46 | public static void Initialize(IBufferWriter<byte> bufferWriter, out WriteBufferHelper instance, out Span<byte> buffer) |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 47 | { |
| 48 | instance.bufferWriter = bufferWriter; |
| 49 | instance.codedOutputStream = null; |
| 50 | buffer = default; // TODO: initialize the initial buffer so that the first write is not via slowpath. |
| 51 | } |
| 52 | |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 53 | /// <summary> |
| 54 | /// Initialize an instance with a buffer represented by a single span (i.e. buffer cannot be refreshed) |
| 55 | /// This approach is faster than using a constructor because the instance to initialize is passed by reference |
| 56 | /// and we can write directly into it without copying. |
| 57 | /// </summary> |
| 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 59 | public static void InitializeNonRefreshable(out WriteBufferHelper instance) |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 60 | { |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 61 | instance.bufferWriter = null; |
| 62 | instance.codedOutputStream = null; |
| 63 | } |
| 64 | |
| 65 | /// <summary> |
| 66 | /// Verifies that SpaceLeft returns zero. |
| 67 | /// </summary> |
| 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 69 | public static void CheckNoSpaceLeft(ref WriterInternalState state) |
| 70 | { |
| 71 | if (GetSpaceLeft(ref state) != 0) |
| 72 | { |
| 73 | throw new InvalidOperationException("Did not write as much data as expected."); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | /// <summary> |
| 78 | /// If writing to a flat array, returns the space left in the array. Otherwise, |
| 79 | /// throws an InvalidOperationException. |
| 80 | /// </summary> |
| 81 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 82 | public static int GetSpaceLeft(ref WriterInternalState state) |
| 83 | { |
| 84 | if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream == null && state.writeBufferHelper.bufferWriter == null) |
| 85 | { |
| 86 | return state.limit - state.position; |
| 87 | } |
| 88 | else |
| 89 | { |
| 90 | throw new InvalidOperationException( |
| 91 | "SpaceLeft can only be called on CodedOutputStreams that are " + |
| 92 | "writing to a flat array or when writing to a single span."); |
| 93 | } |
| 94 | } |
| 95 | |
Jan Tattermusch | 80780bd | 2020-06-23 16:53:05 +0200 | [diff] [blame] | 96 | [MethodImpl(MethodImplOptions.NoInlining)] |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 97 | public static void RefreshBuffer(ref Span<byte> buffer, ref WriterInternalState state) |
| 98 | { |
| 99 | if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null) |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 100 | { |
| 101 | // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical. |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 102 | state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position); |
Jan Tattermusch | 3cdc107 | 2020-06-03 14:11:44 +0200 | [diff] [blame] | 103 | // reset position, limit stays the same because we are reusing the codedOutputStream's internal buffer. |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 104 | state.position = 0; |
| 105 | } |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 106 | else if (state.writeBufferHelper.bufferWriter != null) |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 107 | { |
| 108 | // commit the bytes and get a new buffer to write to. |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 109 | state.writeBufferHelper.bufferWriter.Advance(state.position); |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 110 | state.position = 0; |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 111 | buffer = state.writeBufferHelper.bufferWriter.GetSpan(); |
Jan Tattermusch | 3cdc107 | 2020-06-03 14:11:44 +0200 | [diff] [blame] | 112 | state.limit = buffer.Length; |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 113 | } |
| 114 | else |
| 115 | { |
| 116 | // We're writing to a single buffer. |
| 117 | throw new CodedOutputStream.OutOfSpaceException(); |
| 118 | } |
| 119 | } |
| 120 | |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 121 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 122 | public static void Flush(ref Span<byte> buffer, ref WriterInternalState state) |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 123 | { |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 124 | if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null) |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 125 | { |
| 126 | // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical. |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 127 | state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position); |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 128 | state.position = 0; |
| 129 | } |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 130 | else if (state.writeBufferHelper.bufferWriter != null) |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 131 | { |
Jan Tattermusch | 19c0d73 | 2020-06-08 17:03:51 +0200 | [diff] [blame] | 132 | // calling Advance invalidates the current buffer and we must not continue writing to it, |
| 133 | // so we set the current buffer to point to an empty Span. If any subsequent writes happen, |
| 134 | // the first subsequent write will trigger refresing of the buffer. |
Jan Tattermusch | 9039103 | 2020-06-03 15:32:20 +0200 | [diff] [blame] | 135 | state.writeBufferHelper.bufferWriter.Advance(state.position); |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 136 | state.position = 0; |
Jan Tattermusch | 3cdc107 | 2020-06-03 14:11:44 +0200 | [diff] [blame] | 137 | state.limit = 0; |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 138 | buffer = default; // invalidate the current buffer |
Jan Tattermusch | c855561 | 2020-05-28 11:14:29 +0200 | [diff] [blame] | 139 | } |
| 140 | } |
| 141 | } |
| 142 | } |