1 #region Copyright notice and license
2 
3 // Copyright 2019 The gRPC Authors
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #endregion
18 
19 using System;
20 using System.Buffers;
21 using System.Runtime.InteropServices;
22 using Grpc.Core;
23 using Grpc.Core.Logging;
24 using Grpc.Core.Utils;
25 
26 namespace Grpc.Core.Internal
27 {
28     /// <summary>
29     /// Represents grpc_slice_buffer with some extra utility functions to allow
30     /// writing data to it using the <c>IBufferWriter</c> interface.
31     /// </summary>
32     internal class SliceBufferSafeHandle : SafeHandleZeroIsInvalid, IBufferWriter<byte>
33     {
34         const int DefaultTailSpaceSize = 4096;  // default buffer to allocate if no size hint is provided
35         static readonly NativeMethods Native = NativeMethods.Get();
36         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<SliceBufferSafeHandle>();
37 
38         public static readonly SliceBufferSafeHandle NullInstance = new SliceBufferSafeHandle();
39 
40         private IntPtr tailSpacePtr;
41         private int tailSpaceLen;
42 
43         private SliceMemoryManager memoryManagerLazy;
44 
SliceBufferSafeHandle()45         private SliceBufferSafeHandle()
46         {
47         }
48 
Create()49         public static SliceBufferSafeHandle Create()
50         {
51             return Native.grpcsharp_slice_buffer_create();
52         }
53 
54         public IntPtr Handle
55         {
56             get
57             {
58                 return handle;
59             }
60         }
61 
Advance(int count)62         public void Advance(int count)
63         {
64             GrpcPreconditions.CheckArgument(count >= 0);
65             GrpcPreconditions.CheckArgument(tailSpacePtr != IntPtr.Zero || count == 0);
66             GrpcPreconditions.CheckArgument(tailSpaceLen >= count);
67             tailSpaceLen = tailSpaceLen - count;
68             tailSpacePtr += count;
69             memoryManagerLazy?.Reset();
70         }
71 
72         // provides access to the "tail space" of this buffer.
73         // Use GetSpan when possible for better efficiency.
GetMemory(int sizeHint = 0)74         public Memory<byte> GetMemory(int sizeHint = 0)
75         {
76             EnsureBufferSpace(sizeHint);
77             if (memoryManagerLazy == null)
78             {
79                 memoryManagerLazy = new SliceMemoryManager();
80             }
81             memoryManagerLazy.Reset(new Slice(tailSpacePtr, tailSpaceLen));
82             return memoryManagerLazy.Memory;
83         }
84 
85         // provides access to the "tail space" of this buffer.
GetSpan(int sizeHint = 0)86         public unsafe Span<byte> GetSpan(int sizeHint = 0)
87         {
88             EnsureBufferSpace(sizeHint);
89             return new Span<byte>(tailSpacePtr.ToPointer(), tailSpaceLen);
90         }
91 
Complete()92         public void Complete()
93         {
94             AdjustTailSpace(0);
95         }
96 
97         // resets the data contained by this slice buffer
Reset()98         public void Reset()
99         {
100             // deletes all the data in the slice buffer
101             tailSpacePtr = IntPtr.Zero;
102             tailSpaceLen = 0;
103             memoryManagerLazy?.Reset();
104             Native.grpcsharp_slice_buffer_reset_and_unref(this);
105         }
106 
107         // copies the content of the slice buffer to a newly allocated byte array
108         // Note that this method has a relatively high overhead and should maily be used for testing.
ToByteArray()109         public byte[] ToByteArray()
110         {
111             ulong sliceCount = Native.grpcsharp_slice_buffer_slice_count(this).ToUInt64();
112 
113             Slice[] slices = new Slice[sliceCount];
114             int totalLen = 0;
115             for (int i = 0; i < (int) sliceCount; i++)
116             {
117                 Native.grpcsharp_slice_buffer_slice_peek(this, new UIntPtr((ulong) i), out UIntPtr sliceLen, out IntPtr dataPtr);
118                 slices[i] = new Slice(dataPtr, (int) sliceLen.ToUInt64());
119                 totalLen += (int) sliceLen.ToUInt64();
120 
121             }
122             var result = new byte[totalLen];
123             int offset = 0;
124             for (int i = 0; i < (int) sliceCount; i++)
125             {
126                 slices[i].ToSpanUnsafe().CopyTo(result.AsSpan(offset, slices[i].Length));
127                 offset += slices[i].Length;
128             }
129             GrpcPreconditions.CheckState(totalLen == offset);
130             return result;
131         }
132 
EnsureBufferSpace(int sizeHint)133         private void EnsureBufferSpace(int sizeHint)
134         {
135             GrpcPreconditions.CheckArgument(sizeHint >= 0);
136             if (sizeHint == 0)
137             {
138                 // if no hint is provided, keep the available space within some "reasonable" boundaries.
139                 // This is quite a naive approach which could use some fine-tuning, but currently in most case we know
140                 // the required buffer size in advance anyway, so this approach seems good enough for now.
141                 if (tailSpaceLen < DefaultTailSpaceSize / 2)
142                 {
143                     AdjustTailSpace(DefaultTailSpaceSize);
144                 }
145             }
146             else if (tailSpaceLen < sizeHint)
147             {
148                 // if hint is provided, always make sure we provide at least that much space
149                 AdjustTailSpace(sizeHint);
150             }
151         }
152 
153         // make sure there's exactly requestedSize bytes of continguous buffer space at the end of this slice buffer
AdjustTailSpace(int requestedSize)154         private void AdjustTailSpace(int requestedSize)
155         {
156             GrpcPreconditions.CheckArgument(requestedSize >= 0);
157             tailSpacePtr = Native.grpcsharp_slice_buffer_adjust_tail_space(this, new UIntPtr((ulong) tailSpaceLen), new UIntPtr((ulong) requestedSize));
158             tailSpaceLen = requestedSize;
159         }
ReleaseHandle()160         protected override bool ReleaseHandle()
161         {
162             Native.grpcsharp_slice_buffer_destroy(handle);
163             return true;
164         }
165     }
166 }
167