blob: 01964d9a07d3aa5519d28dacd0fa2ac7fbe82870 [file] [log] [blame]
Jan Tattermuschc8555612020-05-28 11:14:29 +02001#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2008 Google Inc. All rights reserved.
Jan Tattermuschc8555612020-05-28 11:14:29 +02004//
Joshua Haberman8c451772023-09-08 17:13:13 -07005// 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 Tattermuschc8555612020-05-28 11:14:29 +02008#endregion
9
10using System;
11using System.Buffers;
Jan Tattermuschc8555612020-05-28 11:14:29 +020012using System.Runtime.CompilerServices;
13using System.Security;
14
15namespace 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 Tattermusch5fc49bd2020-05-28 17:30:07 +020026 public CodedOutputStream CodedOutputStream => codedOutputStream;
Jan Tattermuschf9d90192020-05-28 15:58:34 +020027
Jan Tattermuschc8555612020-05-28 11:14:29 +020028 /// <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 Tattermusch90391032020-06-03 15:32:20 +020041 /// Initialize an instance with a buffer writer.
Jan Tattermuschc8555612020-05-28 11:14:29 +020042 /// 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 Tattermuschf9d90192020-05-28 15:58:34 +020046 public static void Initialize(IBufferWriter<byte> bufferWriter, out WriteBufferHelper instance, out Span<byte> buffer)
Jan Tattermuschc8555612020-05-28 11:14:29 +020047 {
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 Tattermusch90391032020-06-03 15:32:20 +020053 /// <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 Tattermuschc8555612020-05-28 11:14:29 +020060 {
Jan Tattermusch90391032020-06-03 15:32:20 +020061 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 Tattermusch80780bd2020-06-23 16:53:05 +020096 [MethodImpl(MethodImplOptions.NoInlining)]
Jan Tattermusch90391032020-06-03 15:32:20 +020097 public static void RefreshBuffer(ref Span<byte> buffer, ref WriterInternalState state)
98 {
99 if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
Jan Tattermuschc8555612020-05-28 11:14:29 +0200100 {
101 // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
Jan Tattermusch90391032020-06-03 15:32:20 +0200102 state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
Jan Tattermusch3cdc1072020-06-03 14:11:44 +0200103 // reset position, limit stays the same because we are reusing the codedOutputStream's internal buffer.
Jan Tattermuschc8555612020-05-28 11:14:29 +0200104 state.position = 0;
105 }
Jan Tattermusch90391032020-06-03 15:32:20 +0200106 else if (state.writeBufferHelper.bufferWriter != null)
Jan Tattermuschc8555612020-05-28 11:14:29 +0200107 {
108 // commit the bytes and get a new buffer to write to.
Jan Tattermusch90391032020-06-03 15:32:20 +0200109 state.writeBufferHelper.bufferWriter.Advance(state.position);
Jan Tattermuschc8555612020-05-28 11:14:29 +0200110 state.position = 0;
Jan Tattermusch90391032020-06-03 15:32:20 +0200111 buffer = state.writeBufferHelper.bufferWriter.GetSpan();
Jan Tattermusch3cdc1072020-06-03 14:11:44 +0200112 state.limit = buffer.Length;
Jan Tattermuschc8555612020-05-28 11:14:29 +0200113 }
114 else
115 {
116 // We're writing to a single buffer.
117 throw new CodedOutputStream.OutOfSpaceException();
118 }
119 }
120
Jan Tattermusch90391032020-06-03 15:32:20 +0200121 [MethodImpl(MethodImplOptions.AggressiveInlining)]
122 public static void Flush(ref Span<byte> buffer, ref WriterInternalState state)
Jan Tattermuschc8555612020-05-28 11:14:29 +0200123 {
Jan Tattermusch90391032020-06-03 15:32:20 +0200124 if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
Jan Tattermuschc8555612020-05-28 11:14:29 +0200125 {
126 // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
Jan Tattermusch90391032020-06-03 15:32:20 +0200127 state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
Jan Tattermuschc8555612020-05-28 11:14:29 +0200128 state.position = 0;
129 }
Jan Tattermusch90391032020-06-03 15:32:20 +0200130 else if (state.writeBufferHelper.bufferWriter != null)
Jan Tattermuschc8555612020-05-28 11:14:29 +0200131 {
Jan Tattermusch19c0d732020-06-08 17:03:51 +0200132 // 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 Tattermusch90391032020-06-03 15:32:20 +0200135 state.writeBufferHelper.bufferWriter.Advance(state.position);
Jan Tattermuschc8555612020-05-28 11:14:29 +0200136 state.position = 0;
Jan Tattermusch3cdc1072020-06-03 14:11:44 +0200137 state.limit = 0;
Jan Tattermuschc8555612020-05-28 11:14:29 +0200138 buffer = default; // invalidate the current buffer
Jan Tattermuschc8555612020-05-28 11:14:29 +0200139 }
140 }
141 }
142}