• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }