#region Copyright notice and license
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using Grpc.Core;
using Grpc.Core.Logging;
using Grpc.Core.Utils;
namespace Grpc.Core.Internal
{
///
/// Represents grpc_slice_buffer with some extra utility functions to allow
/// writing data to it using the IBufferWriter interface.
///
internal class SliceBufferSafeHandle : SafeHandleZeroIsInvalid, IBufferWriter
{
const int DefaultTailSpaceSize = 4096; // default buffer to allocate if no size hint is provided
static readonly NativeMethods Native = NativeMethods.Get();
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType();
public static readonly SliceBufferSafeHandle NullInstance = new SliceBufferSafeHandle();
private IntPtr tailSpacePtr;
private int tailSpaceLen;
private SliceMemoryManager memoryManagerLazy;
private SliceBufferSafeHandle()
{
}
public static SliceBufferSafeHandle Create()
{
return Native.grpcsharp_slice_buffer_create();
}
public IntPtr Handle
{
get
{
return handle;
}
}
public void Advance(int count)
{
GrpcPreconditions.CheckArgument(count >= 0);
GrpcPreconditions.CheckArgument(tailSpacePtr != IntPtr.Zero || count == 0);
GrpcPreconditions.CheckArgument(tailSpaceLen >= count);
tailSpaceLen = tailSpaceLen - count;
tailSpacePtr += count;
memoryManagerLazy?.Reset();
}
// provides access to the "tail space" of this buffer.
// Use GetSpan when possible for better efficiency.
public Memory GetMemory(int sizeHint = 0)
{
EnsureBufferSpace(sizeHint);
if (memoryManagerLazy == null)
{
memoryManagerLazy = new SliceMemoryManager();
}
memoryManagerLazy.Reset(new Slice(tailSpacePtr, tailSpaceLen));
return memoryManagerLazy.Memory;
}
// provides access to the "tail space" of this buffer.
public unsafe Span GetSpan(int sizeHint = 0)
{
EnsureBufferSpace(sizeHint);
return new Span(tailSpacePtr.ToPointer(), tailSpaceLen);
}
public void Complete()
{
AdjustTailSpace(0);
}
// resets the data contained by this slice buffer
public void Reset()
{
// deletes all the data in the slice buffer
tailSpacePtr = IntPtr.Zero;
tailSpaceLen = 0;
memoryManagerLazy?.Reset();
Native.grpcsharp_slice_buffer_reset_and_unref(this);
}
// copies the content of the slice buffer to a newly allocated byte array
// Note that this method has a relatively high overhead and should maily be used for testing.
public byte[] ToByteArray()
{
ulong sliceCount = Native.grpcsharp_slice_buffer_slice_count(this).ToUInt64();
Slice[] slices = new Slice[sliceCount];
int totalLen = 0;
for (int i = 0; i < (int) sliceCount; i++)
{
Native.grpcsharp_slice_buffer_slice_peek(this, new UIntPtr((ulong) i), out UIntPtr sliceLen, out IntPtr dataPtr);
slices[i] = new Slice(dataPtr, (int) sliceLen.ToUInt64());
totalLen += (int) sliceLen.ToUInt64();
}
var result = new byte[totalLen];
int offset = 0;
for (int i = 0; i < (int) sliceCount; i++)
{
slices[i].ToSpanUnsafe().CopyTo(result.AsSpan(offset, slices[i].Length));
offset += slices[i].Length;
}
GrpcPreconditions.CheckState(totalLen == offset);
return result;
}
private void EnsureBufferSpace(int sizeHint)
{
GrpcPreconditions.CheckArgument(sizeHint >= 0);
if (sizeHint == 0)
{
// if no hint is provided, keep the available space within some "reasonable" boundaries.
// This is quite a naive approach which could use some fine-tuning, but currently in most case we know
// the required buffer size in advance anyway, so this approach seems good enough for now.
if (tailSpaceLen < DefaultTailSpaceSize / 2)
{
AdjustTailSpace(DefaultTailSpaceSize);
}
}
else if (tailSpaceLen < sizeHint)
{
// if hint is provided, always make sure we provide at least that much space
AdjustTailSpace(sizeHint);
}
}
// make sure there's exactly requestedSize bytes of continguous buffer space at the end of this slice buffer
private void AdjustTailSpace(int requestedSize)
{
GrpcPreconditions.CheckArgument(requestedSize >= 0);
tailSpacePtr = Native.grpcsharp_slice_buffer_adjust_tail_space(this, new UIntPtr((ulong) tailSpaceLen), new UIntPtr((ulong) requestedSize));
tailSpaceLen = requestedSize;
}
protected override bool ReleaseHandle()
{
Native.grpcsharp_slice_buffer_destroy(handle);
return true;
}
}
}