/*
* 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.SessionIDBean;
import org.f1x.api.message.MessageParser;
import org.f1x.io.EmptyOutputChannel;
import org.f1x.io.OutputChannel;
import org.f1x.util.AsciiUtils;
import org.f1x.io.PredefinedInputChannel;
import org.f1x.util.StoredTimeSource;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.ArrayList;
import java.util.List;
/** This test validates parser in case when FIX message is split between different TCP packets (Message requires multiple InputChannel.reads()) */
public class Test_FixCommunicatorSplitMessages {
private final OutputChannel out = new EmptyOutputChannel();
private final MessageCollectingTestFixCommunicator fix = new MessageCollectingTestFixCommunicator ();
private static class MessageCollectingTestFixCommunicator extends TestFixCommunicator{
private List<String> parsedMessages = new ArrayList<>();
public MessageCollectingTestFixCommunicator() {
super(new SessionIDBean("CLIENT", "SERVER"), StoredTimeSource.makeFromUTCTimestamp("20140101-10:10:10.100"));
}
@Override
protected void processInboundMessage(MessageParser parser, CharSequence msgType, int msgSeqNum) {
String message = MessageParser2String.convert(parser);
parsedMessages.add(message.replace('\u0001', '|'));
}
@Override
protected void errorProcessingMessage(String errorText, Exception e, boolean logStackTrace) {
if (e != ConnectionProblemException.NO_SOCKET_DATA)
throw new RuntimeException(errorText + ": " + e.getMessage());
}
}
@Rule
public final ExpectedException expectedException = ExpectedException.none();
@Test
public void testSingleMessageWithBufferLengthMoreMax() {
expectedException.expect(RuntimeException.class);
expectedException.expectMessage(CoreMatchers.equalTo("Protocol Error: Message is too large"));
String message = createMessageWithGivenLength(fix.getSettings().getMaxInboundMessageSize() + 1);
int halfOfMessage = message.length() / 2;
PredefinedInputChannel in = new PredefinedInputChannel(
message.substring(0, halfOfMessage),
message.substring(halfOfMessage)
);
fix.connect(in, out);
fix.processInboundMessages();
}
@Test
public void testSingleMessageWithoutMsgSeqNum(){
expectedException.expect(RuntimeException.class);
expectedException.expectMessage(CoreMatchers.equalTo("Protocol Error: No MsgSeqNum(34) in message"));
String message = "8=FIX.4.4|9=77|35=A|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|";
PredefinedInputChannel in = new PredefinedInputChannel (
message
);
fix.connect(in, out);
fix.processInboundMessages();
assertParsedMessages(message);
}
@Test
public void testSingleMessage() {
String message = "8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|";
PredefinedInputChannel in = new PredefinedInputChannel (
message
);
fix.connect(in, out);
fix.processInboundMessages();
assertParsedMessages(message);
}
@Test
public void testTwoMessages() {
String messageOne = "8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|";
String messageTwo = "8=FIX.4.4|9=67|35=1|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|112=TEST123|10=245|";
PredefinedInputChannel in = new PredefinedInputChannel (
messageOne,
messageTwo
);
fix.connect(in, out);
fix.processInboundMessages();
assertParsedMessages(
messageOne,
messageTwo
);
}
@Test
public void testSplitSingleMessage() {
String messagePartOne = "8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:1";
String messagePartTwo = "0:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|";
PredefinedInputChannel in = new PredefinedInputChannel (
messagePartOne, messagePartTwo
);
fix.connect(in, out);
fix.processInboundMessages();
assertParsedMessages(messagePartOne + messagePartTwo);
}
@Test
public void testSplitSecondMessage() {
PredefinedInputChannel in = new PredefinedInputChannel (
"8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|8=FIX.4.4|", // here we have beginning of the next message
"9=67|35=1|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|112=TEST123|10=245|"
);
fix.connect(in, out);
fix.processInboundMessages();
assertParsedMessages(
"8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|",
"8=FIX.4.4|9=67|35=1|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|112=TEST123|10=245|"
);
}
@Test
public void testSplitSecondMessageMultipleTimes() {
PredefinedInputChannel in = new PredefinedInputChannel (
"8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|8=FIX.4.4|", // here we have beginning of the next message
"9=67|35=1|3", "4=1|49=CLI", "ENT|52=2014", "0101-10:10:10", ".100|56=SERVE", "R|112=TEST123|10=245|"
);
fix.connect(in, out);
fix.processInboundMessages();
assertParsedMessages(
"8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|",
"8=FIX.4.4|9=67|35=1|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|112=TEST123|10=245|");
}
@Test
public void testLogonBuffer() {
String logonBuffer = "8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|8=FIX.4.4|9=67|35=1|34=1|49=CLI";
PredefinedInputChannel in = new PredefinedInputChannel (
"ENT|52=20140101-10:10:10.100|56=SERVER|112=TEST123|10=245|" // part of this message was read together with LOGON
);
fix.connect(in, out);
fix.processInboundMessages(AsciiUtils.getBytes(logonBuffer.replace('|', '\u0001')), logonBuffer.length());
assertParsedMessages(
"8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|98=0|108=30|141=Y|383=8192|10=080|",
"8=FIX.4.4|9=67|35=1|34=1|49=CLIENT|52=20140101-10:10:10.100|56=SERVER|112=TEST123|10=245|"
);
}
private static String createMessageWithGivenLength(int length) {
String beginString = "8=FIX.4.4|";
String bodyLengthPattern = "9=??????????|";
String checkSum = "10=000|";
String msgType = "35=A|";
String msgSeqNum = "34=1|";
int neededBodyLength = length - (beginString.length() + bodyLengthPattern.length() + checkSum.length());
String bodyLength = String.format("9=%010d|", neededBodyLength);
int accountValueLength = neededBodyLength - (msgType.length() + msgSeqNum.length() + "1=|".length());
String account = "1=" + new String(new byte[accountValueLength]) + '|';
String message = beginString + bodyLength + msgType + msgSeqNum + account + checkSum;
Assert.assertEquals(length, message.length());
return message;
}
private void assertParsedMessages (String ... expectedMessages) {
Assert.assertEquals("number of messages", expectedMessages.length, fix.parsedMessages.size());
for (int i=0; i < expectedMessages.length ; i++)
Assert.assertEquals(expectedMessages[i], fix.parsedMessages.get(i));
}
}