FixAntenna/NetCore/FixEngine/PreparedMessageUtil.cs (515 lines of code) (raw):
// Copyright (c) 2021 EPAM Systems
//
// 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.
using System;
using System.Collections.Generic;
using Epam.FixAntenna.Constants.Fixt11;
using Epam.FixAntenna.NetCore.Common.Logging;
using Epam.FixAntenna.NetCore.Configuration;
using Epam.FixAntenna.NetCore.Helpers;
using Epam.FixAntenna.NetCore.Message;
using Epam.FixAntenna.NetCore.Message.Format;
namespace Epam.FixAntenna.NetCore.FixEngine
{
public class PreparedMessageUtil
{
public const int BodylengthFieldLength = 3;
public const int BodylengthFieldDefaultMax = 999;
public readonly int SeqnumFieldLength = 5;
private const int CheckSumFieldLength = 3;
public static readonly byte[] EmptyBytes = new byte[0];
protected internal static readonly ILog Log = LogFactory.GetLog(typeof(PreparedMessageUtil));
private static int _timeStampLength;
private readonly bool _includeLastProcessed;
private readonly SessionParameters _sessionParameters;
/// <summary>
/// Default constructor with <see cref="SessionParameters"/> object. This parameters will be used for building new PreparedMessage
/// objects.
/// </summary>
/// <param name="sessionParameters"> SessionParameters object </param>
public PreparedMessageUtil(SessionParameters sessionParameters)
{
_sessionParameters = sessionParameters;
_includeLastProcessed = sessionParameters.IsNeedToIncludeLastProcessed();
var sendingTimeFormatter =
FixDateFormatterFactory.GetSendingTimeFormatter(sessionParameters.FixVersion);
_timeStampLength = sendingTimeFormatter.GetFormattedStringLength(DateTimeOffset.Now);
var seqNumLen = _sessionParameters.Configuration.GetPropertyAsInt(Config.SeqNumLength, 1, 10, true, "Wrong value in parameter SeqNumLength. The padding is disabled.");
// don't know why by it was 5 as default
if (seqNumLen > 5) SeqnumFieldLength = seqNumLen;
}
/// <summary>
/// Builds FixMessage object from exist template. Instance is received from pool.
/// </summary>
/// <param name="message"> </param>
/// <param name="userStructure">
/// @return </param>
/// <exception cref="PreparedMessageException">
/// </exception>
public virtual FixMessage PrepareMessage(FixMessage message, MessageStructure userStructure)
{
return PrepareMessage(message, userStructure, true);
}
/// <summary>
/// Builds FixMessage object from exist template.
/// </summary>
/// <param name="message"> </param>
/// <param name="userStructure">
/// @return </param>
/// <exception cref="PreparedMessageException">
/// </exception>
public virtual FixMessage PrepareMessage(FixMessage message, MessageStructure userStructure, bool fromPool)
{
var msgType = new TagValue();
message.LoadTagValue(Tags.MsgType, msgType);
var res = new byte[msgType.Length];
Array.Copy(msgType.Buffer, msgType.Offset, res, 0, msgType.Length);
return PrepareMessage(message, res, userStructure);
}
/// <summary>
/// Builds FixMessage object with specified type, message structure and prefilled header information
/// </summary>
/// <param name="msgTypeStr"> message type </param>
/// <param name="userStructure"> message structure </param>
public virtual FixMessage PrepareMessage(string msgTypeStr, MessageStructure userStructure)
{
return PrepareMessage(msgTypeStr, userStructure, true);
}
/// <summary>
/// Builds FixMessage object with specified type, message structure and prefilled header information
/// </summary>
/// <param name="msgTypeStr"> message type </param>
/// <param name="userStructure"> message structure </param>
public virtual FixMessage PrepareMessage(string msgTypeStr, MessageStructure userStructure, bool fromPool)
{
var msgType = msgTypeStr.AsByteArray();
return PrepareMessage(msgType, userStructure, fromPool);
}
/// <summary>
/// Builds FixMessage object with specified type, message structure and prefilled header information
/// </summary>
/// <param name="msgType"> message type </param>
/// <param name="userStructure"> message structure </param>
public virtual FixMessage PrepareMessage(byte[] msgType, MessageStructure userStructure)
{
return PrepareMessage(msgType, userStructure, true);
}
/// <summary>
/// Builds FixMessage object with specified type, message structure and prefilled header information
/// </summary>
/// <param name="msgType"> message type </param>
/// <param name="userStructure"> message structure </param>
public virtual FixMessage PrepareMessage(byte[] msgType, MessageStructure userStructure, bool fromPool)
{
//Create complete structure
var ms = PrepareFullMessageStructure(null, msgType, userStructure);
var length = CalculateLength(ms);
var list = FixMessageFactory.NewInstance(fromPool, true);
list.IsPreparedMessage = true;
var msgBuffer = new byte[length];
msgBuffer.Fill((byte)' ');
list.SetBuffer(msgBuffer, 0, length);
FillMessage(list, msgBuffer, ms);
FillHeaderAndTrailer(msgType, list, ms);
return list;
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from exist template with specified message structure and prefilled
/// header and trailer.
/// </summary>
/// <param name="template"> </param>
/// <param name="msgType"> </param>
/// <param name="userStructure"> </param>
/// <exception cref="PreparedMessageException"> </exception>
public virtual FixMessage PrepareMessage(FixMessage template, string msgType,
MessageStructure userStructure)
{
return PrepareMessage(template, msgType, userStructure, true);
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from exist template with specified message structure and prefilled
/// header and trailer.
/// </summary>
/// <param name="template"> </param>
/// <param name="msgType"> </param>
/// <param name="userStructure"> </param>
/// <exception cref="PreparedMessageException"> </exception>
public virtual FixMessage PrepareMessage(FixMessage template, string msgType,
MessageStructure userStructure, bool fromPool)
{
return PrepareMessage(template, msgType.AsByteArray(), userStructure, fromPool);
}
public virtual FixMessage PrepareMessage(FixMessage template, byte[] msgType,
MessageStructure userStructure)
{
return PrepareMessage(template, msgType, userStructure, true);
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from exist template with specified message type, message structure
/// and prefilled header and trailer.
/// </summary>
/// <param name="template"> FixMessage object </param>
/// <param name="userStructure"> template structure </param>
/// <param name="msgType"> type of the template </param>
/// <exception cref="PreparedMessageException"> exception </exception>
public virtual FixMessage PrepareMessage(FixMessage template, byte[] msgType,
MessageStructure userStructure, bool fromPool)
{
var ms = PrepareFullMessageStructure(template, msgType, userStructure);
foreach (var field in template)
{
if (!ms.ContainsTagId(field.TagId))
{
throw new PreparedMessageException("MessageStructure doesn't contain all necessary fields " +
"uniquetempvar.");
}
}
var headerTagSet = PrepareHeaderTagSet(msgType);
var length = CalculateLength(ms);
var list = FixMessageFactory.NewInstance(fromPool, true);
list.IsPreparedMessage = true;
var msgBuffer = new byte[length];
msgBuffer.Fill((byte)' ');
list.SetBuffer(msgBuffer, 0, length);
var offset = 0;
var size = ms.Size;
var templateValue = new TagValue();
var templateCopy = template.DeepClone(true, false);
for (var i = 0; i < size; i++)
{
var tag = ms.GetTagId(i);
var tagLength = ms.GetLength(i);
var type = ms.GetType(i);
var templateValuePresent = false;
if (templateCopy.IsTagExists(tag))
{
templateValue = templateCopy.GetTag(tag);
templateCopy.RemoveTag(tag);
templateValuePresent = true;
}
if (MessageStructure.VariableLength != tagLength)
{
offset += FixTypes.FormatInt(tag, msgBuffer, offset);
msgBuffer[offset++] = (byte)'=';
list.AddPrepared(tag, offset, tagLength);
offset += tagLength;
msgBuffer[offset++] = (byte)'\u0001';
if (!headerTagSet.Contains(tag) && templateValuePresent)
{
if (type == ValueType.Double)
{
try
{
var v = FixTypes.ParseFloat(templateValue.Buffer, templateValue.Offset,
templateValue.Length);
//TODO: fix precision
list.SetAtIndex(i, v, tagLength);
}
catch (Exception)
{
Log.Warn("Can't init tag " + tag + " for prepared message. Template value '" +
StringHelper.NewString(templateValue.Buffer, templateValue.Offset,
templateValue.Length) + "' isn't a double");
}
}
else if (type == ValueType.Long)
{
try
{
var v = FixTypes.ParseInt(templateValue.Buffer, templateValue.Offset,
templateValue.Length);
list.SetAtIndex(i, v);
}
catch (Exception)
{
Log.Warn("Can't init tag " + tag + " for prepared message. Template value '" +
StringHelper.NewString(templateValue.Buffer, templateValue.Offset,
templateValue.Length) + "' isn't a number");
}
}
else
{
list.SetAtIndex(i, templateValue);
}
}
}
else
{
if (templateValuePresent)
{
list.AddTag(tag, templateValue.Buffer, templateValue.Offset, templateValue.Length);
}
else
{
list.AddTag(tag, EmptyBytes);
}
}
}
FillHeaderAndTrailer(msgType, list, ms);
return list;
}
public virtual MessageStructure PrepareFullMessageStructure(FixMessage template, byte[] msgType,
MessageStructure userStructure)
{
var ms = new MessageStructure();
AddHeaderStructure(msgType, ms);
if (template != null)
{
BuildStructureFromFixMessage(ms, template, userStructure);
UpdateBodyLengthStructure(ms, template);
}
else
{
ms.Merge(userStructure);
}
AddTrailerStructure(ms);
return ms;
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from String object.
/// </summary>
/// <param name="message"> </param>
/// <param name="structure"> </param>
/// <exception cref="PreparedMessageException"> </exception>
public virtual FixMessage PrepareMessageFromString(byte[] message, MessageStructure structure)
{
return PrepareMessageFromString(message, structure, true);
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from String object.
/// </summary>
/// <param name="message"> </param>
/// <param name="structure"> </param>
/// <exception cref="PreparedMessageException"> </exception>
public virtual FixMessage PrepareMessageFromString(byte[] message, MessageStructure structure, bool fromPool)
{
return PrepareMessage(RawFixUtil.GetFixMessage(message), structure, fromPool);
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from String object.
/// </summary>
/// <param name="message"> message string </param>
/// <param name="structure"> message structure object </param>
/// <param name="type"> message type </param>
/// <exception cref="PreparedMessageException"> exception </exception>
public virtual FixMessage PrepareMessageFromString(byte[] message, string type, MessageStructure structure)
{
return PrepareMessageFromString(message, type, structure, true);
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from String object.
/// </summary>
/// <param name="message"> message string </param>
/// <param name="structure"> message structure object </param>
/// <param name="type"> message type </param>
/// <exception cref="PreparedMessageException"> exception </exception>
public virtual FixMessage PrepareMessageFromString(byte[] message, string type, MessageStructure structure,
bool fromPool)
{
return PrepareMessageFromString(message, type.AsByteArray(), structure, fromPool);
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from String object.
/// </summary>
/// <param name="message"> </param>
/// <param name="type"> </param>
/// <param name="structure"> </param>
/// <exception cref="PreparedMessageException"> </exception>
public virtual FixMessage PrepareMessageFromString(byte[] message, byte[] type, MessageStructure structure)
{
return PrepareMessageFromString(message, type, structure, true);
}
/// <summary>
/// Builds <see cref="FixMessage"/> object from String object.
/// </summary>
/// <param name="message"> </param>
/// <param name="type"> </param>
/// <param name="structure"> </param>
/// <exception cref="PreparedMessageException"> </exception>
public virtual FixMessage PrepareMessageFromString(byte[] message, byte[] type, MessageStructure structure,
bool fromPool)
{
return PrepareMessage(RawFixUtil.GetFixMessage(message), type, structure);
}
private int CalculateLength(MessageStructure ms)
{
var msgLength = 0;
var size = ms.Size;
for (var i = 0; i < size; i++)
{
var tagLength = ms.GetLength(i);
if (MessageStructure.VariableLength != tagLength)
{
var tagId = ms.GetTagId(i);
msgLength += FixTypes.FormatIntLength(tagId) + tagLength + 2;
}
}
return msgLength;
}
private void FillMessage(FixMessage list, byte[] msgBuffer, MessageStructure ms)
{
var offset = 0;
var size = ms.Size;
for (var i = 0; i < size; i++)
{
var tagLength = ms.GetLength(i);
var tag = ms.GetTagId(i);
if (MessageStructure.VariableLength != tagLength)
{
offset += FixTypes.FormatInt(tag, msgBuffer, offset);
msgBuffer[offset++] = (byte)'=';
list.AddPrepared(tag, offset, tagLength);
offset += tagLength;
msgBuffer[offset++] = (byte)'\u0001';
}
else
{
list.AddTag(tag, EmptyBytes);
}
}
}
/// <exception cref="PreparedMessageException"> </exception>
private MessageStructure BuildStructureFromFixMessage(MessageStructure dest, FixMessage message,
MessageStructure structure)
{
IList<int> userTagIds = new List<int>(structure.TagIds);
IList<int> userLengths = new List<int>(structure.Lengths);
IList<ValueType> userTypes = new List<ValueType>(structure.Types);
var markers = new int[dest.Size];
markers.Fill(0);
foreach (var tag in message)
{
var tagId = tag.TagId;
var destPos = -1;
do
{
destPos = dest.IndexOf(tagId);
} while (destPos >= 0 && destPos < markers.Length && markers[destPos] > 0);
if (destPos >= 0 && destPos < markers.Length)
{
//tag found in original set - need update
markers[destPos] = 1;
if (userTagIds.Contains(tagId))
{
var index = userTagIds.IndexOf(tagId);
var length = userLengths[index];
var type = userTypes[index];
dest.SetLengthAtIndex(destPos, length);
dest.SetTypeAtIndex(destPos, type);
userTagIds.RemoveAt(index);
userLengths.RemoveAt(index);
userTypes.RemoveAt(index);
}
}
else
{
//tag not found or it was just added - need adding
if (userTagIds.Contains(tagId))
{
var index = userTagIds.IndexOf(tagId);
var length = userLengths[index];
var type = userTypes[index];
dest.Reserve(tagId, length, type);
userTagIds.RemoveAt(index);
userLengths.RemoveAt(index);
userTypes.RemoveAt(index);
}
else
{
dest.Reserve(tagId, tag.Length);
}
}
}
if (userTagIds.Count > 0)
{
throw new PreparedMessageException("There are reserved fields which are absent in sample message: " +
string.Join(", ", userTagIds));
}
return dest;
}
private void FillHeaderAndTrailer(byte[] msgType, FixMessage msg, MessageStructure ms)
{
msg.Set(Tags.BeginString, _sessionParameters.FixVersion.MessageVersion);
msg.Set(Tags.MsgType, msgType);
msg.Set(Tags.SenderCompID, _sessionParameters.SenderCompId);
msg.Set(Tags.TargetCompID, _sessionParameters.TargetCompId);
SafeSetValue(msg, Tags.SenderSubID, _sessionParameters.SenderSubId);
SafeSetValue(msg, Tags.TargetSubID, _sessionParameters.TargetSubId);
SafeSetValue(msg, Tags.SenderLocationID, _sessionParameters.SenderLocationId);
SafeSetValue(msg, Tags.TargetLocationID, _sessionParameters.TargetLocationId);
foreach (var field in _sessionParameters.UserDefinedFields)
{
msg.Set(field.TagId, field.Buffer, field.Offset, field.Length);
}
if (RawFixUtil.IsLogon(msgType))
{
msg.Set(Tags.EncryptMethod, "0");
msg.Set(Tags.HeartBtInt, Convert.ToString(_sessionParameters.HeartbeatInterval));
foreach (var field in _sessionParameters.OutgoingLoginMessage)
{
msg.Set(field.TagId, field.Buffer, field.Offset, field.Length);
}
}
msg.Set(Tags.BodyLength, msg.CalculateBodyLength());
}
private void SafeSetValue(FixMessage msg, int tagId, string value)
{
if (!ReferenceEquals(value, null))
{
msg.Set(tagId, value);
}
}
private void AddHeaderStructure(byte[] msgType, MessageStructure ms)
{
var senderCompId = _sessionParameters.SenderCompId;
if (ReferenceEquals(senderCompId, null))
{
throw new ArgumentException("SenderCompId can't be null");
}
var targetCompId = _sessionParameters.TargetCompId;
if (ReferenceEquals(targetCompId, null))
{
throw new ArgumentException("TargetCompId can't be null");
}
var insetOffset = 0;
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.BeginString,
_sessionParameters.FixVersion.MessageVersion.Length, ValueType.String);
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.BodyLength, BodylengthFieldLength, ValueType.Long);
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.MsgType, msgType.Length, ValueType.String);
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.MsgSeqNum, SeqnumFieldLength, ValueType.Long);
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.SenderCompID, senderCompId.Length);
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.TargetCompID, targetCompId.Length);
//optional params
insetOffset = SafeReserve(ms, insetOffset, Tags.SenderSubID, _sessionParameters.SenderSubId);
insetOffset = SafeReserve(ms, insetOffset, Tags.TargetSubID, _sessionParameters.TargetSubId);
insetOffset = SafeReserve(ms, insetOffset, Tags.SenderLocationID, _sessionParameters.SenderLocationId);
insetOffset = SafeReserve(ms, insetOffset, Tags.TargetLocationID, _sessionParameters.TargetLocationId);
foreach (var field in _sessionParameters.UserDefinedFields)
{
insetOffset = ReserveIfAbsent(ms, insetOffset, field.TagId, field.Length);
}
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.SendingTime, _timeStampLength, ValueType.Date);
if (_includeLastProcessed)
{
// add 369 tag if needed
var currentLength = GetBytesLength(_sessionParameters.IncomingSequenceNumber - 1);
var seqNumFieldLength = Math.Max(currentLength, SeqnumFieldLength);
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.LastMsgSeqNumProcessed, seqNumFieldLength,
ValueType.Long);
}
if (RawFixUtil.IsLogon(msgType))
{
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.EncryptMethod, 1);
insetOffset = ReserveIfAbsent(ms, insetOffset, Tags.HeartBtInt,
GetBytesLength(_sessionParameters.HeartbeatInterval), ValueType.Long);
foreach (var field in _sessionParameters.OutgoingLoginMessage)
{
insetOffset = ReserveIfAbsent(ms, insetOffset, field.TagId, field.Length);
}
}
}
private ISet<int> PrepareHeaderTagSet(byte[] msgType)
{
ISet<int> res = new HashSet<int>();
res.Add(Tags.BeginString);
res.Add(Tags.BodyLength);
res.Add(Tags.MsgType);
res.Add(Tags.MsgSeqNum);
res.Add(Tags.SenderCompID);
res.Add(Tags.TargetCompID);
res.Add(Tags.SendingTime);
if (!ReferenceEquals(_sessionParameters.SenderSubId, null))
{
res.Add(Tags.SenderSubID);
}
if (!ReferenceEquals(_sessionParameters.TargetSubId, null))
{
res.Add(Tags.TargetSubID);
}
if (!ReferenceEquals(_sessionParameters.SenderLocationId, null))
{
res.Add(Tags.SenderLocationID);
}
if (!ReferenceEquals(_sessionParameters.TargetLocationId, null))
{
res.Add(Tags.TargetLocationID);
}
var message = _sessionParameters.UserDefinedFields;
var size = message.Count;
for (var i = 0; i < size; i++)
{
res.Add(message.GetTagIdAtIndex(i));
}
if (_includeLastProcessed)
{
res.Add(Tags.LastMsgSeqNumProcessed);
}
if (RawFixUtil.IsLogon(msgType))
{
res.Add(Tags.EncryptMethod);
res.Add(Tags.HeartBtInt);
var outgoingLoginFixMessage = _sessionParameters.OutgoingLoginMessage;
var logonFieldsSize = outgoingLoginFixMessage.Count;
for (var i = 0; i < logonFieldsSize; i++)
{
res.Add(outgoingLoginFixMessage.GetTagIdAtIndex(i));
}
}
return res;
}
private MessageStructure AddTrailerStructure(MessageStructure ms)
{
ReserveIfAbsent(ms, ms.Size, Tags.CheckSum, CheckSumFieldLength, ValueType.Long);
return ms;
}
private MessageStructure UpdateBodyLengthStructure(MessageStructure ms, FixMessage template)
{
var msgBl = template.CalculateBodyLength();
if (msgBl > BodylengthFieldDefaultMax)
{
var length = 1;
var tempValue = msgBl;
while ((tempValue /= 10) > 0)
{
length++;
}
ReserveIfAbsent(ms, ms.IndexOfTag(Tags.BodyLength), Tags.BodyLength, length, ValueType.Long);
}
return ms;
}
private int SafeReserve(MessageStructure msg, int pos, int tagId, string param)
{
if (!ReferenceEquals(param, null))
{
return ReserveIfAbsent(msg, pos, tagId, param.Length);
}
return pos;
}
private int ReserveIfAbsent(MessageStructure ms, int pos, int tagId, int length)
{
return ReserveIfAbsent(ms, pos, tagId, length, ValueType.ByteArray);
}
private int ReserveIfAbsent(MessageStructure ms, int pos, int tagId, int length, ValueType type)
{
if (!ms.ContainsTagId(tagId))
{
ms.Reserve(pos, tagId, length, type);
return ++pos;
}
ms.SetLength(tagId, length);
ms.SetType(tagId, type);
return ms.IndexOfTag(tagId);
}
/// <summary>
/// returns size of serialized long in bytes
/// </summary>
/// <param name="num"> number </param>
/// <returns> number of bytes </returns>
public static int GetBytesLength(long num)
{
var size = 1;
if (num < 0)
{
size++;
num = -num;
}
while ((num /= 10) > 0)
{
size++;
}
return size;
}
/// <seealso cref="FixTypes.FormatUInt(long)"> </seealso>
internal static byte[] FormatUInt(long val)
{
return FixTypes.FormatUInt(val);
}
internal static byte[] FormatInt(long val)
{
return FixTypes.FormatInt(val);
}
/// <seealso cref="FixTypes.ParseInt(byte[], int, int)"> </seealso>
internal static int ParseInt(byte[] buffer, int offset, int length)
{
return (int)FixTypes.ParseInt(buffer, offset, length);
}
}
}