modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MsgPack/MsgPackWriter.cs (296 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
namespace Apache.Ignite.Internal.Proto.MsgPack;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using BinaryTuple;
using Buffers;
using Transactions;
/// <summary>
/// MsgPack writer. Wraps <see cref="PooledArrayBuffer"/>. Writer index is kept by the buffer, so this struct is readonly.
/// </summary>
internal readonly ref struct MsgPackWriter
{
private const int MaxFixPositiveInt = 127;
private const int MaxFixStringLength = 31;
private const int MinFixNegativeInt = -32;
private const int MaxFixMapCount = 15;
private const int MaxFixArrayCount = 15;
/// <summary>
/// Initializes a new instance of the <see cref="MsgPackWriter"/> struct.
/// </summary>
/// <param name="buf">Buffer.</param>
public MsgPackWriter(PooledArrayBuffer buf)
{
Buf = buf;
}
private PooledArrayBuffer Buf { get; }
/// <summary>
/// Writes an unsigned value to specified memory location and returns number of bytes written.
/// </summary>
/// <param name="span">Span.</param>
/// <param name="val">Value.</param>
/// <returns>Bytes written.</returns>
public static int WriteUnsigned(Span<byte> span, ulong val)
{
unchecked
{
if (val <= MaxFixPositiveInt)
{
span[0] = (byte)val;
return 1;
}
if (val <= byte.MaxValue)
{
span[0] = MsgPackCode.UInt8;
span[1] = (byte)val;
return 2;
}
if (val <= ushort.MaxValue)
{
span[0] = MsgPackCode.UInt16;
BinaryPrimitives.WriteUInt16BigEndian(span[1..], (ushort)val);
return 3;
}
if (val <= uint.MaxValue)
{
span[0] = MsgPackCode.UInt32;
BinaryPrimitives.WriteUInt32BigEndian(span[1..], (uint)val);
return 5;
}
span[0] = MsgPackCode.UInt64;
BinaryPrimitives.WriteUInt64BigEndian(span[1..], val);
return 9;
}
}
/// <summary>
/// Writes nil.
/// </summary>
public void WriteNil() => Buf.GetSpanAndAdvance(1)[0] = MsgPackCode.Nil;
/// <summary>
/// Writes bool.
/// </summary>
/// <param name="val">Value.</param>
public void Write(bool val) => Buf.GetSpanAndAdvance(1)[0] = val ? MsgPackCode.True : MsgPackCode.False;
/// <summary>
/// Writes a <see cref="Guid"/> as UUID (RFC #4122).
/// <para />
/// <see cref="Guid"/> uses a mixed-endian format which differs from UUID,
/// see https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding.
/// </summary>
/// <param name="val">Guid.</param>
public void Write(Guid val)
{
var span = Buf.GetSpanAndAdvance(18);
span[0] = MsgPackCode.FixExt16;
span[1] = (byte)ClientMessagePackType.Uuid;
UuidSerializer.Write(val, span[2..]);
}
/// <summary>
/// Writes string.
/// </summary>
/// <param name="val">Value.</param>
public void Write(string? val)
{
if (val == null)
{
WriteNil();
return;
}
var byteCount = ProtoCommon.StringEncoding.GetMaxByteCount(val.Length);
var bufferSize = byteCount + 5;
var span = Buf.GetSpan(bufferSize);
if (byteCount <= MaxFixStringLength)
{
var bytesWritten = ProtoCommon.StringEncoding.GetBytes(val, span[1..]);
span[0] = (byte)(MsgPackCode.MinFixStr | bytesWritten);
Buf.Advance(bytesWritten + 1);
}
else if (byteCount <= byte.MaxValue)
{
var bytesWritten = ProtoCommon.StringEncoding.GetBytes(val, span[2..]);
span[0] = MsgPackCode.Str8;
span[1] = unchecked((byte)bytesWritten);
Buf.Advance(bytesWritten + 2);
}
else if (byteCount <= ushort.MaxValue)
{
var bytesWritten = ProtoCommon.StringEncoding.GetBytes(val, span[3..]);
span[0] = MsgPackCode.Str16;
BinaryPrimitives.WriteUInt16BigEndian(span[1..], (ushort)bytesWritten);
Buf.Advance(bytesWritten + 3);
}
else
{
var bytesWritten = ProtoCommon.StringEncoding.GetBytes(val, span[5..]);
span[0] = MsgPackCode.Str32;
BinaryPrimitives.WriteUInt32BigEndian(span[1..], (uint)bytesWritten);
Buf.Advance(bytesWritten + 5);
}
}
/// <summary>
/// Writes long.
/// </summary>
/// <param name="val">Value.</param>
public void Write(long val)
{
if (val >= 0)
{
var span = Buf.GetSpan(9);
var written = WriteUnsigned(span, (ulong)val);
Buf.Advance(written);
}
else
{
if (val >= MinFixNegativeInt)
{
Buf.GetSpanAndAdvance(1)[0] = unchecked((byte)val);
}
else if (val >= sbyte.MinValue)
{
var span = Buf.GetSpanAndAdvance(2);
span[0] = MsgPackCode.Int8;
span[1] = unchecked((byte)val);
}
else if (val >= short.MinValue)
{
var span = Buf.GetSpanAndAdvance(3);
span[0] = MsgPackCode.Int16;
BinaryPrimitives.WriteInt16BigEndian(span[1..], (short)val);
}
else if (val >= int.MinValue)
{
var span = Buf.GetSpanAndAdvance(5);
span[0] = MsgPackCode.Int32;
BinaryPrimitives.WriteInt32BigEndian(span[1..], (int)val);
}
else
{
var span = Buf.GetSpanAndAdvance(9);
span[0] = MsgPackCode.Int64;
BinaryPrimitives.WriteInt64BigEndian(span[1..], val);
}
}
}
/// <summary>
/// Writes int.
/// </summary>
/// <param name="val">Value.</param>
public void Write(int val) => Write((long)val);
/// <summary>
/// Reserves space for a bit set.
/// </summary>
/// <param name="bitCount">Bit count.</param>
/// <returns>Span to write.</returns>
public Span<byte> WriteBitSet(int bitCount)
{
var byteCount = bitCount / 8 + 1;
WriteExtensionHeader((byte)ClientMessagePackType.Bitmask, byteCount);
var span = Buf.GetSpanAndAdvance(byteCount);
// Clear all bits to avoid random data from pooled array.
span.Clear();
return span;
}
/// <summary>
/// Writes extension format header.
/// </summary>
/// <param name="typeCode">Extension type code.</param>
/// <param name="dataLength">Data length.</param>
public void WriteExtensionHeader(byte typeCode, int dataLength)
{
switch (dataLength)
{
case 1:
var span = Buf.GetSpanAndAdvance(2);
span[0] = MsgPackCode.FixExt1;
span[1] = typeCode;
return;
case 2:
span = Buf.GetSpanAndAdvance(2);
span[0] = MsgPackCode.FixExt2;
span[1] = typeCode;
return;
case 4:
span = Buf.GetSpanAndAdvance(2);
span[0] = MsgPackCode.FixExt4;
span[1] = typeCode;
return;
case 8:
span = Buf.GetSpanAndAdvance(2);
span[0] = MsgPackCode.FixExt8;
span[1] = typeCode;
return;
case 16:
span = Buf.GetSpanAndAdvance(2);
span[0] = MsgPackCode.FixExt16;
span[1] = typeCode;
return;
default:
if (dataLength <= byte.MaxValue)
{
span = Buf.GetSpanAndAdvance(3);
span[0] = MsgPackCode.Ext8;
span[1] = unchecked((byte)dataLength);
span[2] = typeCode;
}
else if (dataLength <= ushort.MaxValue)
{
span = Buf.GetSpanAndAdvance(4);
span[0] = MsgPackCode.Ext16;
BinaryPrimitives.WriteUInt16BigEndian(span[1..], (ushort)dataLength);
span[3] = typeCode;
}
else
{
span = Buf.GetSpanAndAdvance(6);
span[0] = MsgPackCode.Ext32;
BinaryPrimitives.WriteUInt32BigEndian(span[1..], (uint)dataLength);
span[5] = typeCode;
}
break;
}
}
/// <summary>
/// Writes array header.
/// </summary>
/// <param name="count">Array element count.</param>
public void WriteArrayHeader(int count)
{
if (count <= MaxFixArrayCount)
{
Buf.GetSpanAndAdvance(1)[0] = (byte)(MsgPackCode.MinFixArray | count);
}
else if (count <= ushort.MaxValue)
{
var span = Buf.GetSpanAndAdvance(3);
span[0] = MsgPackCode.Array16;
BinaryPrimitives.WriteUInt16BigEndian(span[1..], (ushort)count);
}
else
{
var span = Buf.GetSpanAndAdvance(5);
span[0] = MsgPackCode.Array32;
BinaryPrimitives.WriteUInt32BigEndian(span[1..], (uint)count);
}
}
/// <summary>
/// Writes map header.
/// </summary>
/// <param name="count">Map element count.</param>
public void WriteMapHeader(int count)
{
if (count <= MaxFixMapCount)
{
Buf.GetSpanAndAdvance(1)[0] = (byte)(MsgPackCode.MinFixMap | count);
}
else if (count <= ushort.MaxValue)
{
var span = Buf.GetSpanAndAdvance(3);
span[0] = MsgPackCode.Map16;
BinaryPrimitives.WriteUInt16BigEndian(span[1..], (ushort)count);
}
else
{
var span = Buf.GetSpanAndAdvance(5);
span[0] = MsgPackCode.Map32;
BinaryPrimitives.WriteUInt32BigEndian(span[1..], (uint)count);
}
}
/// <summary>
/// Writes binary header.
/// </summary>
/// <param name="length">Binary payload length, in bytes.</param>
public void WriteBinaryHeader(int length)
{
if (length <= byte.MaxValue)
{
var span = Buf.GetSpanAndAdvance(2);
span[0] = MsgPackCode.Bin8;
span[1] = (byte)length;
}
else if (length <= ushort.MaxValue)
{
var span = Buf.GetSpanAndAdvance(3);
span[0] = MsgPackCode.Bin16;
BinaryPrimitives.WriteUInt16BigEndian(span[1..], (ushort)length);
}
else
{
var span = Buf.GetSpanAndAdvance(5);
span[0] = MsgPackCode.Bin32;
BinaryPrimitives.WriteUInt32BigEndian(span[1..], (uint)length);
}
}
/// <summary>
/// Writes binary data, including header.
/// </summary>
/// <param name="span">Data.</param>
public void Write(ReadOnlySpan<byte> span)
{
WriteBinaryHeader(span.Length);
span.CopyTo(Buf.GetSpanAndAdvance(span.Length));
}
/// <summary>
/// Writes a transaction.
/// </summary>
/// <param name="tx">Transaction.</param>
public void WriteTx(Transaction? tx)
{
if (tx == null)
{
WriteNil();
}
else
{
Write(tx.Id);
}
}
/// <summary>
/// Writes an array of objects with type codes.
/// </summary>
/// <param name="col">Array.</param>
public void WriteObjectCollectionAsBinaryTuple(ICollection<object?>? col)
{
if (col == null)
{
WriteNil();
return;
}
Write(col.Count);
if (col.Count == 0)
{
return;
}
using var builder = new BinaryTupleBuilder(col.Count * 3);
foreach (var obj in col)
{
builder.AppendObjectWithType(obj);
}
Write(builder.Build().Span);
}
}