/*
* 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.util.AsciiUtils;
import org.f1x.util.ByteRingReader;
public class BodyLengthParser {
private static final int CHECKSUM_FIELD_LENGTH = 7; // Checksum(10) is always expressed using 3 digits and includes terminating SOH byte: "10=123|"
private static final byte SOH = (byte) 1; // FIX field separator
private static final byte WILDCARD = '*';
private static final byte [] EXPECTED_HEADER = AsciiUtils.getBytes("8=FIX.*.*\0019=");
private static final int EXPECTED_HEADER_LENGTH = EXPECTED_HEADER.length;
/** @return size of message remaining to read */
public static int getRemainingMessageSize(ByteRingReader reader) {
validateMessagePrefix (reader); // cost ~20 nanos
int bodyLength = parseBodyLength(reader);
return (bodyLength + CHECKSUM_FIELD_LENGTH) - reader.getRemainingLength(); // reader.getRemainingLength() is positioned at the first byte after SOH separator that follows BodyLength
}
/** Ensures that message starts with BeginString and BodyLength tags */
static void validateMessagePrefix (ByteRingReader reader) {
for (int i=0; i < EXPECTED_HEADER_LENGTH; i++) {
byte expectedByte = EXPECTED_HEADER[i];
byte actualByte = reader.next();
if (expectedByte != WILDCARD && expectedByte != actualByte)
throw InvalidFixMessageException.UNEXPECTED_MESSAGE_START;
}
}
/**
* BodyLength is always the second field in the message.
* Count the number of characters in the message following the BodyLength field up to, and including,
* the delimiter immediately preceding the CheckSum tag ("10=").
*/
static int parseBodyLength(ByteRingReader reader) {
int result = 0;
byte b = reader.next();
if (b == SOH)
throw InvalidFixMessageException.EMPTY_BODY_LENGTH_TAG;
do {
if (b >= '0' && b <= '9')
result = 10*result + (b - '0');
else
throw InvalidFixMessageException.INVALID_BODY_LENGTH_TAG;
} while ( (b = reader.next()) != SOH);
return result;
}
private static final class InvalidFixMessageException extends IllegalArgumentException {
/** Pre-allocated exception to avoid garbage generation */
public static final InvalidFixMessageException INVALID_BODY_LENGTH_TAG = new InvalidFixMessageException("Message BodyLength is invalid");
public static final InvalidFixMessageException EMPTY_BODY_LENGTH_TAG = new InvalidFixMessageException("Message BodyLength is empty");
public static final InvalidFixMessageException UNEXPECTED_MESSAGE_START = new InvalidFixMessageException("Message must begin with BeginString and BodyLength tags");
private InvalidFixMessageException(String message) { super(message); }
@Override
public Throwable fillInStackTrace()
{
return this;
}
}
}