• 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 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using System;
34 using System.Buffers;
35 using System.IO;
36 using System.Runtime.CompilerServices;
37 using System.Security;
38 
39 namespace Google.Protobuf
40 {
41     /// <summary>
42     /// Abstraction for writing to a steam / IBufferWriter
43     /// </summary>
44     [SecuritySafeCritical]
45     internal struct WriteBufferHelper
46     {
47         private IBufferWriter<byte> bufferWriter;
48         private CodedOutputStream codedOutputStream;
49 
50         public CodedOutputStream CodedOutputStream => codedOutputStream;
51 
52         /// <summary>
53         /// Initialize an instance with a coded output stream.
54         /// This approach is faster than using a constructor because the instance to initialize is passed by reference
55         /// and we can write directly into it without copying.
56         /// </summary>
57         [MethodImpl(MethodImplOptions.AggressiveInlining)]
InitializeGoogle.Protobuf.WriteBufferHelper58         public static void Initialize(CodedOutputStream codedOutputStream, out WriteBufferHelper instance)
59         {
60             instance.bufferWriter = null;
61             instance.codedOutputStream = codedOutputStream;
62         }
63 
64         /// <summary>
65         /// Initialize an instance with a buffer writer.
66         /// This approach is faster than using a constructor because the instance to initialize is passed by reference
67         /// and we can write directly into it without copying.
68         /// </summary>
69         [MethodImpl(MethodImplOptions.AggressiveInlining)]
InitializeGoogle.Protobuf.WriteBufferHelper70         public static void Initialize(IBufferWriter<byte> bufferWriter, out WriteBufferHelper instance, out Span<byte> buffer)
71         {
72             instance.bufferWriter = bufferWriter;
73             instance.codedOutputStream = null;
74             buffer = default;  // TODO: initialize the initial buffer so that the first write is not via slowpath.
75         }
76 
77         /// <summary>
78         /// Initialize an instance with a buffer represented by a single span (i.e. buffer cannot be refreshed)
79         /// This approach is faster than using a constructor because the instance to initialize is passed by reference
80         /// and we can write directly into it without copying.
81         /// </summary>
82         [MethodImpl(MethodImplOptions.AggressiveInlining)]
InitializeNonRefreshableGoogle.Protobuf.WriteBufferHelper83         public static void InitializeNonRefreshable(out WriteBufferHelper instance)
84         {
85             instance.bufferWriter = null;
86             instance.codedOutputStream = null;
87         }
88 
89         /// <summary>
90         /// Verifies that SpaceLeft returns zero.
91         /// </summary>
92         [MethodImpl(MethodImplOptions.AggressiveInlining)]
CheckNoSpaceLeftGoogle.Protobuf.WriteBufferHelper93         public static void CheckNoSpaceLeft(ref WriterInternalState state)
94         {
95             if (GetSpaceLeft(ref state) != 0)
96             {
97                 throw new InvalidOperationException("Did not write as much data as expected.");
98             }
99         }
100 
101         /// <summary>
102         /// If writing to a flat array, returns the space left in the array. Otherwise,
103         /// throws an InvalidOperationException.
104         /// </summary>
105         [MethodImpl(MethodImplOptions.AggressiveInlining)]
GetSpaceLeftGoogle.Protobuf.WriteBufferHelper106         public static int GetSpaceLeft(ref WriterInternalState state)
107         {
108             if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream == null && state.writeBufferHelper.bufferWriter == null)
109             {
110                 return state.limit - state.position;
111             }
112             else
113             {
114                 throw new InvalidOperationException(
115                     "SpaceLeft can only be called on CodedOutputStreams that are " +
116                         "writing to a flat array or when writing to a single span.");
117             }
118         }
119 
120         [MethodImpl(MethodImplOptions.NoInlining)]
RefreshBufferGoogle.Protobuf.WriteBufferHelper121         public static void RefreshBuffer(ref Span<byte> buffer, ref WriterInternalState state)
122         {
123             if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
124             {
125                 // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
126                 state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
127                 // reset position, limit stays the same because we are reusing the codedOutputStream's internal buffer.
128                 state.position = 0;
129             }
130             else if (state.writeBufferHelper.bufferWriter != null)
131             {
132                 // commit the bytes and get a new buffer to write to.
133                 state.writeBufferHelper.bufferWriter.Advance(state.position);
134                 state.position = 0;
135                 buffer = state.writeBufferHelper.bufferWriter.GetSpan();
136                 state.limit = buffer.Length;
137             }
138             else
139             {
140                 // We're writing to a single buffer.
141                 throw new CodedOutputStream.OutOfSpaceException();
142             }
143         }
144 
145         [MethodImpl(MethodImplOptions.AggressiveInlining)]
FlushGoogle.Protobuf.WriteBufferHelper146         public static void Flush(ref Span<byte> buffer, ref WriterInternalState state)
147         {
148             if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
149             {
150                 // because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
151                 state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
152                 state.position = 0;
153             }
154             else if (state.writeBufferHelper.bufferWriter != null)
155             {
156                 // calling Advance invalidates the current buffer and we must not continue writing to it,
157                 // so we set the current buffer to point to an empty Span. If any subsequent writes happen,
158                 // the first subsequent write will trigger refresing of the buffer.
159                 state.writeBufferHelper.bufferWriter.Advance(state.position);
160                 state.position = 0;
161                 state.limit = 0;
162                 buffer = default;  // invalidate the current buffer
163             }
164         }
165     }
166 }