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 { /// /// Порядок передачи: /// 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 байт /// 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 bytes) { var result = Array.Empty(); 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 bytes) { return (MessageType)bytes[4]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasKey(Span bytes) { var value = BitConverter.ToUInt16(bytes.Slice(5, 2)); return ((MessageFlags)value & MessageFlags.HasRoutingKey) == MessageFlags.HasRoutingKey; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasPayload(Span bytes) { var value = BitConverter.ToUInt16(bytes.Slice(5, 2)); return ((MessageFlags)value & MessageFlags.HasPayload) == MessageFlags.HasPayload; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasExchange(Span bytes) { var value = BitConverter.ToUInt16(bytes.Slice(5, 2)); return ((MessageFlags)value & MessageFlags.HasExchange) == MessageFlags.HasExchange; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetExchangeName(Span 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 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; } } }