/* * 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. */ package org.f1x.v1; import org.f1x.api.FixVersion; import org.f1x.api.message.MessageBuilder; import org.f1x.api.message.Tools; import org.f1x.api.message.fields.FixTags; import org.f1x.api.session.SessionID; import org.f1x.io.OutputChannel; import org.f1x.store.MessageStore; import org.f1x.util.AsciiUtils; import org.f1x.util.format.IntFormatter; import org.f1x.util.format.TimestampFormatter; import java.io.IOException; /** Assembles message using header information and MessageBuilder. Not thread safe. * The following header fields are used: * <ul> * <li>BodyLength</li> * <li>MsgType</li> * <li>MsgSeqNum</li> * <li>SenderCompID,SenderSubID</li> * <li>TargetCompID,TargetSubID</li> * <li>SendingTime</li> * </ul> */ final class RawMessageAssembler { private final static byte SOH = 1; private final TimestampFormatter timestampFormatter = TimestampFormatter.createUTCTimestampFormatter(); //TODO Reuse instance kept by MessageBuilder? private final byte [] BEGIN_STRING; private final boolean isSendRequiresConnect; private final byte [] buffer; RawMessageAssembler(FixVersion version, int maxMessageSize, boolean sendRequiresConnect) { buffer = new byte[maxMessageSize]; isSendRequiresConnect = sendRequiresConnect; BEGIN_STRING = AsciiUtils.getBytes("" + FixTags.BeginString + '=' + version.getBeginString() + (char) SOH); System.arraycopy(BEGIN_STRING, 0, buffer, 0, BEGIN_STRING.length); } void send(SessionID sessionID, int msgSeqNum, MessageBuilder messageBuilder, MessageStore messageStore, long sendingTime, OutputChannel out) throws IOException { if (isSendRequiresConnect && out == null) throw new IllegalStateException("Not connected"); int offset = BEGIN_STRING.length; final CharSequence msgType = messageBuilder.getMessageType(); final CharSequence senderSubId = sessionID.getSenderSubId(); final CharSequence targetSubId = sessionID.getTargetSubId(); // BodyLength is the number of characters in the message following the BodyLength field up to, and including, the delimiter immediately preceding the CheckSum tag ("10=") int bodyLength = (4 + msgType.length()) + (4 + IntFormatter.stringSize(msgSeqNum)) + (4 + TimestampFormatter.DATE_TIME_LENGTH) + (4 + sessionID.getSenderCompId().length()) + // T O D O: Pre-compute and keep in session ID? (4 + sessionID.getTargetCompId().length()) + messageBuilder.getLength(); if (senderSubId != null) bodyLength += 4 + senderSubId.length(); if (targetSubId != null) bodyLength += 4 + targetSubId.length(); // Standard Header tags offset = setIntField(FixTags.BodyLength, bodyLength, buffer, offset); offset = setTextField(FixTags.MsgType, msgType, buffer, offset); offset = setIntField(FixTags.MsgSeqNum, msgSeqNum, buffer, offset); offset = setTextField(FixTags.SenderCompID, sessionID.getSenderCompId(), buffer, offset); if (senderSubId != null) offset = setTextField(FixTags.SenderSubID, senderSubId, buffer, offset); offset = setUtcTimestampField(FixTags.SendingTime, sendingTime, buffer, offset); offset = setTextField(FixTags.TargetCompID, sessionID.getTargetCompId(), buffer, offset); if (targetSubId != null) offset = setTextField(FixTags.TargetSubID, targetSubId, buffer, offset); // Message-specific and custom tags offset = messageBuilder.output(buffer, offset); // Standard footer int checkSum = Tools.calcCheckSum(buffer, offset); //T O D O: Let MessageBuilder accumulate payload checksum as we build each message offset = set3DigitIntField(FixTags.CheckSum, checkSum, buffer, offset); try { if (out != null) out.write(buffer, 0, offset); } finally { if (messageStore != null) messageStore.put(msgSeqNum, buffer, 0, offset); } } private static int setTextField(int tagNo, CharSequence value, byte [] buffer, int offset) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; for (int i=0; i < value.length(); i++) buffer[offset++] = (byte)value.charAt(i); buffer[offset++] = SOH; return offset; } private static int setIntField(int tagNo, int value, byte [] buffer, int offset) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = IntFormatter.format(value, buffer, offset); buffer[offset++] = SOH; return offset; } private static int set3DigitIntField(int tagNo, int value, byte [] buffer, int offset) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = IntFormatter.format3digits(value, buffer, offset); buffer[offset++] = SOH; return offset; } private int setUtcTimestampField(int tagNo, long value, byte[] buffer, int offset) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = timestampFormatter.formatDateTime(value, buffer, offset); buffer[offset++] = SOH; return offset; } }