1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // 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 8 #endregion 9 10 using System; 11 using System.Buffers; 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 26 public CodedOutputStream CodedOutputStream => codedOutputStream; 27 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)] InitializeGoogle.Protobuf.WriteBufferHelper34 public static void Initialize(CodedOutputStream codedOutputStream, out WriteBufferHelper instance) 35 { 36 instance.bufferWriter = null; 37 instance.codedOutputStream = codedOutputStream; 38 } 39 40 /// <summary> 41 /// Initialize an instance with a buffer writer. 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)] InitializeGoogle.Protobuf.WriteBufferHelper46 public static void Initialize(IBufferWriter<byte> bufferWriter, out WriteBufferHelper instance, out Span<byte> buffer) 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 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)] InitializeNonRefreshableGoogle.Protobuf.WriteBufferHelper59 public static void InitializeNonRefreshable(out WriteBufferHelper instance) 60 { 61 instance.bufferWriter = null; 62 instance.codedOutputStream = null; 63 } 64 65 /// <summary> 66 /// Verifies that SpaceLeft returns zero. 67 /// </summary> 68 [MethodImpl(MethodImplOptions.AggressiveInlining)] CheckNoSpaceLeftGoogle.Protobuf.WriteBufferHelper69 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)] GetSpaceLeftGoogle.Protobuf.WriteBufferHelper82 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 96 [MethodImpl(MethodImplOptions.NoInlining)] RefreshBufferGoogle.Protobuf.WriteBufferHelper97 public static void RefreshBuffer(ref Span<byte> buffer, ref WriterInternalState state) 98 { 99 if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null) 100 { 101 // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical. 102 state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position); 103 // reset position, limit stays the same because we are reusing the codedOutputStream's internal buffer. 104 state.position = 0; 105 } 106 else if (state.writeBufferHelper.bufferWriter != null) 107 { 108 // commit the bytes and get a new buffer to write to. 109 state.writeBufferHelper.bufferWriter.Advance(state.position); 110 state.position = 0; 111 buffer = state.writeBufferHelper.bufferWriter.GetSpan(); 112 state.limit = buffer.Length; 113 } 114 else 115 { 116 // We're writing to a single buffer. 117 throw new CodedOutputStream.OutOfSpaceException(); 118 } 119 } 120 121 [MethodImpl(MethodImplOptions.AggressiveInlining)] FlushGoogle.Protobuf.WriteBufferHelper122 public static void Flush(ref Span<byte> buffer, ref WriterInternalState state) 123 { 124 if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null) 125 { 126 // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical. 127 state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position); 128 state.position = 0; 129 } 130 else if (state.writeBufferHelper.bufferWriter != null) 131 { 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. 135 state.writeBufferHelper.bufferWriter.Advance(state.position); 136 state.position = 0; 137 state.limit = 0; 138 buffer = default; // invalidate the current buffer 139 } 140 } 141 } 142 }