/*
* 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.io.parsers;
import org.f1x.api.FixParserException;
import org.f1x.util.AsciiUtils;
import org.f1x.util.TestUtils;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
public class Test_SimpleMessageScanner {
private final TestSimpleMessageScanner scanner = new TestSimpleMessageScanner(49, 56);
@Test
public void testSimpleGoodMessage() throws Exception {
assertParsed("8=FIX.4.2|9=63|35=A|34=1|49=EZ|52=20131004-02:27:25.762|56=SERVER|98=0|108=30|10=098|", "49=EZ, 56=SERVER");
}
/** Verify that parser returns correct number of bytes parsed */
@Test
public void testMoreThanOneMessage() throws Exception {
assertMessageEnd("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=55|...", 104);
}
@Test
public void testMissingBodyLenTag() throws Exception {
assertScannerFailed("8=FIX.4.4|1=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|", "Missing BodyLength(9) tag"); // tag 1 instead of tag 9
}
@Test
public void testSplitLogonMessage() throws Exception {
assertParsed ("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|", "49=CLIENT, 56=SERVER");
// now let's try various cases where buffer does not contain entire message
assertMessageTruncated("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", 1); // missing final SOH
assertMessageTruncated("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|", 7); // missing CheckSum(10) field
assertMessageTruncated("8=FIX.4.4|9=82|35=A|34=1|49=CLIENT|52=20140101-10:10:10.100|51= just for increasing |", 16); // missing everything starting from tag TargetCompID(56)
assertMessageTruncated("8=FIX.4.2|9=63|35=A|34=1|49=EZ|51= just for increasing ", 22);
assertScannerFailed("8=FIX.4.2|9=63", "Message is too small");
}
@Test
public void testMissingTags() throws Exception {
// what if both tags we are looking for are missing?
assertParsed("8=FIX.4.2|9=47|35=A|34=1|52=20131004-02:27:25.762|98=0|108=30|10=000|", "");
assertParsed("8=FIX.4.2|9=53|35=A|34=1|52=20131004-02:27:25.762|56=SERVER|98=0|108=30|10=000|", "56=SERVER");
assertParsed("8=FIX.4.2|9=53|35=A|34=1|49=EZ|52=20131004-02:27:25.762|98=0|108=30|10=XXX|", "49=EZ");
}
@Test
public void testSmallMessage () {
assertScannerFailed("", "Message is too small");
assertScannerFailed(" ", "Message is too small");
assertScannerFailed("8=FIX", "Message is too small");
assertScannerFailed("8=FIX.4.4|", "Message is too small");
}
@Test
public void testNotAFixMessage () {
assertScannerFailed("I Don't know what is this but not a FIX message. Just for increasing", "Not a FIX message");
}
@Test
public void testBadTagNum () {
assertScannerFailed("8=FIX.4.2|9=63|ABC=A|34=1|49=EZ|52=20131004-02:27:25.762|56=SERVER|98=0|108=30|10=098|", "Expecting a number");
}
@Test
public void testBadBodyLength () {
assertScannerFailed("8=FIX.4.2|9=ABC|35=A|34=1|49=EZ|52=20131004-02:27:25.762|56=SERVER|98=0|108=30|10=098|", "Expecting a number");
}
@Test
public void testDuplicateTag () throws IOException {
assertScannerFailed("8=FIX.4.2|9=53|35=A|34=1|49=EZ|52=20131004-02:27:25.762|49=EZ|56=SERVER|10=098|", "Invalid Logon message: duplicate tag 49 or missing BodyLength(9) tag");
assertScannerFailed("8=FIX.4.2|9=61|35=A|34=1|49=EZ|52=20131004-02:27:25.762|56=SERVER|56=SERVER|10=098|", "Invalid Logon message: duplicate tag 56 or missing BodyLength(9) tag");
}
@Test
public void testInterruptedParsing () throws IOException {
SingleTagMessageScanner scanner = new SingleTagMessageScanner(49);
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|8=FIX...";
byte [] msgBytes = AsciiUtils.getBytes(message.replace('|', '\u0001'));
try {
int result = scanner.parse(msgBytes, 0, msgBytes.length, null);
if (result <= 0)
Assert.fail("Message is expected to be complete but buffer is too small (missing " + (-result) + " bytes)");
String actualTagValue = new String (msgBytes, scanner.tagValueStart, scanner.tagValueLen, "US-ASCII");
Assert.assertEquals("CLIENT", actualTagValue);
String nextMessage = message.substring(result);
Assert.assertEquals("8=FIX...", nextMessage);
} catch (FixParserException e) {
e.printStackTrace();
Assert.fail("Unexpected parser error: " + e.getMessage());
}
}
/// Helpers
private void assertScannerFailed(String message, String expectedError) {
try {
int offset = parse(message);
if (offset > 0)
Assert.fail("Scanner was expected to fail but it didn't: " + message);
else
Assert.fail("Scanner was expected to fail with exception but it reported truncated message instead (Missing " + (-offset) + " bytes)");
} catch (Exception e) {
Assert.assertEquals(expectedError, e.getMessage());
}
}
private void assertMessageTruncated(String message, int expectedMissingByteCount) {
int actualMissingByteCount = parse(message);
Assert.assertTrue("Parser didn't detect that message is truncated", actualMissingByteCount < 0);
Assert.assertEquals("Truncated message tail size", expectedMissingByteCount, -actualMissingByteCount);
}
private void assertMessageEnd(String message, int expectedByteCount) {
int actualByteCount = parse(message);
if (actualByteCount <= 0)
Assert.fail("Message was supposed to parse normally but instead parser reported missing " + (-actualByteCount) + " bytes");
Assert.assertEquals("Parsed byte count", expectedByteCount, actualByteCount);
}
private static final int WRAPPER = 3;
public static byte [] wrap (String message, int wrapSize) {
byte [] msgBytes = AsciiUtils.getBytes(message.replace('|', '\u0001'));
return TestUtils.wrap(msgBytes, wrapSize);
}
private void assertParsed(String message, String extractedTags) throws Exception {
try {
int offset = parse(message);
if (offset <= 0)
Assert.fail("Message is expected to be complete but buffer is too small (missing " + (-offset) + " bytes)");
} catch (FixParserException e) {
e.printStackTrace();
Assert.fail("Unexpected parser error: " + e.getMessage());
}
byte [] msgBytes = wrap(message, WRAPPER);
StringBuilder sb = new StringBuilder();
for (int i=0; i < scanner.tagsToWatch.length; i++) {
if (scanner.tagValueStart[i] != -1) {
if (sb.length() > 0)
sb.append(", ");
sb.append(scanner.tagsToWatch[i]);
sb.append('=');
sb.append(new String (msgBytes, scanner.tagValueStart[i], scanner.tagValueLen[i], "US-ASCII"));
}
}
Assert.assertEquals(extractedTags, sb.toString());
}
private int parse(String message) throws SimpleMessageScanner.MessageFormatException {
byte [] msgBytes = AsciiUtils.getBytes(message.replace('|', '\u0001'));
int result = scanner.parse(wrap(message, WRAPPER), WRAPPER, msgBytes.length, null);
if (result >= 0) {
Assert.assertTrue(result > WRAPPER);
result = result - WRAPPER;
}
return result;
}
class TestSimpleMessageScanner extends SimpleMessageScanner<Object> {
final int [] tagsToWatch;
final int [] tagValueStart;
final int [] tagValueLen;
TestSimpleMessageScanner(int... tagsToWatch) {
this.tagsToWatch = tagsToWatch;
this.tagValueStart = new int [tagsToWatch.length];
this.tagValueLen = new int [tagsToWatch.length];
}
@Override
public int parse(byte[] buffer, int start, int len, Object cookie) throws MessageFormatException {
//tagsLeft = tagsToWatch.length;
Arrays.fill(tagValueStart, -1);
Arrays.fill(tagValueLen, -1);
return super.parse(buffer, start, len, cookie);
}
@Override
protected boolean onTagNumber(int tagNum, Object cookie) throws FixParserException {
return (indexOf(tagNum) >= 0);
}
@Override
protected boolean onTagValue(int tagNum, byte[] message, int tagValueStart, int tagValueLen, Object cookie) throws FixParserException {
int currentTagIndex = indexOf(tagNum);
assert currentTagIndex != -1;
if (this.tagValueStart[currentTagIndex] != -1)
throw new FixParserException("Invalid Logon message: duplicate tag " + tagNum + " or missing BodyLength(9) tag");
this.tagValueStart[currentTagIndex] = tagValueStart;
this.tagValueLen[currentTagIndex] = tagValueLen;
return true; // continue parsing
}
private int indexOf(int tagNum) {
for (int i=0; i < tagsToWatch.length; i++) {
if (tagsToWatch[i] == tagNum)
return i;
}
return -1;
}
}
class SingleTagMessageScanner extends SimpleMessageScanner<Object> {
final int tagToWatch;
int tagValueStart;
int tagValueLen;
SingleTagMessageScanner(int tagToWatch) {
this.tagToWatch = tagToWatch;
}
@Override
protected boolean onTagNumber(int tagNum, Object cookie) throws FixParserException {
return tagToWatch == tagNum;
}
@Override
protected boolean onTagValue(int tagNum, byte[] message, int tagValueStart, int tagValueLen, Object cookie) throws FixParserException {
this.tagValueStart = tagValueStart;
this.tagValueLen = tagValueLen;
return false; // stop parsing
}
}
}