197 lines
7.7 KiB
C#
197 lines
7.7 KiB
C#
using Sphagnum.Common.Messaging.Contracts;
|
|
using Sphagnum.Common.Messaging.Contracts.Messages;
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
|
|
namespace Sphagnum.Common.Messaging.Utils
|
|
{
|
|
/// <summary>
|
|
/// Порядок передачи:
|
|
/// 1. MessageSize, 4 байта
|
|
/// 2. MessageType, 1 байт
|
|
/// 3. MessageFlags, 2 байта
|
|
/// 4. Id сообщения, если есть, 16 байт
|
|
/// 5. ExchangeNameLength, если есть, 1 байт
|
|
/// 6. ExchangeName, если есть, ExchangeNameLength байт, Utf8
|
|
/// 7. RoutingKey, если есть, 3 байта
|
|
/// 8. PayloadSize, если есть - 4 байта
|
|
/// 9. Payload, если есть, PayloadSize байт
|
|
/// </summary>
|
|
internal static class MessageParser
|
|
{
|
|
public static Message UnpackMessage(byte[] bytes)
|
|
{
|
|
if ((MessageType)bytes[4] != MessageType.Common)
|
|
{
|
|
throw new ArgumentException("Uncorrect message type! 1 (MessageType.Common) expected!");
|
|
}
|
|
var exchangeName = GetExchangeName(bytes);
|
|
var routingKey = GetRoutingKey(bytes);
|
|
var payload = GetPayload(bytes);
|
|
var id = GetMessageId(bytes);
|
|
return new Message(id, exchangeName, routingKey, payload);
|
|
}
|
|
public static byte[] PackMessage(Message message)
|
|
{
|
|
if (string.IsNullOrEmpty(message.Exchange) || string.IsNullOrWhiteSpace(message.Exchange))
|
|
{
|
|
throw new ArgumentException("Bad exchange name!");
|
|
}
|
|
else if (Encoding.UTF8.GetByteCount(message.Exchange) > 255)
|
|
{
|
|
throw new ArgumentException("Exchange name in UTF8 encoding must allocate < 256 bytes!");
|
|
}
|
|
|
|
var flags = MessageFlags.HasExchange;
|
|
int count = 23;
|
|
if (message.Payload.Length > 0)
|
|
{
|
|
flags |= MessageFlags.HasPayload;
|
|
count += message.Payload.Length;
|
|
count += 4;
|
|
}
|
|
if (!message.RoutingKey.IsEmpry)
|
|
{
|
|
flags |= MessageFlags.HasRoutingKey;
|
|
count += 3;
|
|
}
|
|
|
|
var exchangeNameBytes = Encoding.UTF8.GetBytes(message.Exchange);// todo перевести на более оптимальный метод, не аллоцирующий лишнего.
|
|
count += exchangeNameBytes.Length;
|
|
count++;
|
|
return Pack(message, flags, count);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static Guid GetMessageId(byte[] bytes)
|
|
{
|
|
var slice = bytes.AsSpan(7, 16);
|
|
return new Guid(slice);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static byte[] Pack(Message message, MessageFlags flags, int count)
|
|
{
|
|
var result = new byte[count];
|
|
result[4] = (byte)MessageType.Common;
|
|
var shift = 5;
|
|
BitConverter.TryWriteBytes(result.AsSpan(shift), (ushort)flags);//2. flags
|
|
shift += 2;
|
|
message.MessageId.TryWriteBytes(result.AsSpan(shift));//3. id
|
|
shift += 16;
|
|
if ((flags & MessageFlags.HasExchange) == MessageFlags.HasExchange)
|
|
{
|
|
var exchangeBytes = Encoding.UTF8.GetBytes(message.Exchange);
|
|
BitConverter.TryWriteBytes(result.AsSpan(shift), (byte)message.Exchange.Length);//4. ExchangeNameLength
|
|
shift += 1;
|
|
exchangeBytes.CopyTo(result.AsSpan(shift));//5. ExchangeName
|
|
shift += exchangeBytes.Length;
|
|
}
|
|
if ((flags & MessageFlags.HasRoutingKey) == MessageFlags.HasRoutingKey)//6. RoutingKey
|
|
{
|
|
result[shift] = message.RoutingKey.Part1;
|
|
shift++;
|
|
result[shift] = message.RoutingKey.Part2;
|
|
shift++;
|
|
result[shift] = message.RoutingKey.Part3;
|
|
shift++;
|
|
}
|
|
if ((flags & MessageFlags.HasPayload) == MessageFlags.HasPayload)
|
|
{
|
|
BitConverter.TryWriteBytes(result.AsSpan(shift), message.Payload.Length);//7. PayloadSize
|
|
shift += 4;
|
|
message.Payload.CopyTo(result.AsMemory(shift));//8. Payload
|
|
}
|
|
BitConverter.TryWriteBytes(result.AsSpan(0, 4), result.Length);
|
|
return result;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static byte[] GetPayload(Span<byte> bytes)
|
|
{
|
|
var result = Array.Empty<byte>();
|
|
if (HasPayload(bytes))
|
|
{
|
|
var shift = 23;
|
|
if (HasExchange(bytes))//todo проверить бенчмарком, как работает инлайн
|
|
{
|
|
shift += bytes[23];
|
|
shift++;
|
|
}
|
|
if (HasKey(bytes))
|
|
{
|
|
shift += 3;
|
|
}
|
|
|
|
var payloadSize = BitConverter.ToInt32(bytes[shift..]);
|
|
if (payloadSize > 0)
|
|
{
|
|
result = bytes.Slice(shift + 4, payloadSize).ToArray();
|
|
}
|
|
}
|
|
return result;
|
|
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static MessageType GetMessageType(Span<byte> bytes)
|
|
{
|
|
return (MessageType)bytes[4];
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool HasKey(Span<byte> bytes)
|
|
{
|
|
var value = BitConverter.ToUInt16(bytes.Slice(5, 2));
|
|
return ((MessageFlags)value & MessageFlags.HasRoutingKey) == MessageFlags.HasRoutingKey;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool HasPayload(Span<byte> bytes)
|
|
{
|
|
var value = BitConverter.ToUInt16(bytes.Slice(5, 2));
|
|
return ((MessageFlags)value & MessageFlags.HasPayload) == MessageFlags.HasPayload;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static bool HasExchange(Span<byte> bytes)
|
|
{
|
|
var value = BitConverter.ToUInt16(bytes.Slice(5, 2));
|
|
return ((MessageFlags)value & MessageFlags.HasExchange) == MessageFlags.HasExchange;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static string GetExchangeName(Span<byte> bytes)
|
|
{
|
|
var hasExchange = HasExchange(bytes);
|
|
if (!hasExchange)
|
|
{
|
|
throw new ArgumentException("bytes must contains exchange name!");
|
|
}
|
|
return Encoding.UTF8.GetString(bytes.Slice(24, bytes[23]));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static RoutingKey GetRoutingKey(Span<byte> bytes)
|
|
{
|
|
var length = bytes[23];
|
|
RoutingKey key;
|
|
if (HasKey(bytes))
|
|
{
|
|
var routingKeyShift = 23 + length + 1;
|
|
|
|
var routingKeyPart1 = bytes[routingKeyShift];
|
|
var routingKeyPart2 = bytes[routingKeyShift + 1];
|
|
var routingKeyPart3 = bytes[routingKeyShift + 2];
|
|
key = new RoutingKey(routingKeyPart1, routingKeyPart2, routingKeyPart3);
|
|
}
|
|
else
|
|
{
|
|
key = new RoutingKey();
|
|
}
|
|
return key;
|
|
}
|
|
}
|
|
}
|