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