Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions simplefix/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class RawLengthNotNumberError(ParsingError, ValueError):
"""Raw length value could not be converted to integer."""


class RawDataNotFollowedByFieldSeparator(ParsingError):
"""Raw data field isn't followed by SOH field separator."""


class FieldOrderError(ParsingError):
"""Field not found where required by standard."""

Expand Down
17 changes: 11 additions & 6 deletions simplefix/parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#! /usr/bin/env python
########################################################################
# SimpleFIX
# Copyright (C) 2016-2023, David Arnold.
# Copyright (C) 2016-2026, David Arnold.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -262,7 +262,7 @@ def get_buffer(self):
"""Return a reference to the internal buffer."""
return self.buf

def get_message(self):
def get_message(self): # skipcq: PY-R1000
"""Process the accumulated buffer and return the first message.

If the buffer starts with FIX fields other than BeginString
Expand Down Expand Up @@ -299,12 +299,17 @@ def get_message(self):
raise errors.TagNotNumberError(*e.args)

if tag in self.raw_data_tags and self.raw_len > 0:
# Try to extract the data value.
if self.raw_len > len(self.buf) - point:
# The buffer doesn't yet contain all the raw data,
# so wait for more to be added.
# Try to extract the data value (and its SOH,
# although that's then ignored)
if self.raw_len + 1 > len(self.buf) - point:
# The buffer doesn't yet contain all the raw data
# plus the following SOH, so wait for more to be added.
break

# Check for SOH after raw data.
if self.buf[point + self.raw_len] != SOH_BYTE:
raise errors.RawDataNotFollowedByFieldSeparator(tag_string)

# We've got enough buffer to extract the raw data value.
value = self.buf[point:point + self.raw_len]
self.buf = self.buf[point + self.raw_len + 1:]
Expand Down
44 changes: 34 additions & 10 deletions test/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#! /usr/bin/env python
########################################################################
# SimpleFIX
# Copyright (C) 2016-2023, David Arnold.
# Copyright (C) 2016-2026, David Arnold.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand All @@ -23,33 +23,28 @@
#
########################################################################

import sys
import unittest

from simplefix import FixMessage, FixParser, SOH_STR, errors


def make_str(s):
if sys.version_info.major == 2:
return bytes(s)

return bytes(s, 'ASCII')


# Python 2.6's unittest.TestCase doesn't have assertIsNone()
def test_none(_, other): # skipcq: PYL-R1719
"""Check for None."""
return other is None


# Python 2.6's unittest.TestCase doesn't have assertIsNotNone()
def test_not_none(_, other): # skipcq: PYL-R1719
"""Check is not None."""
return other is not None


class ParserTests(unittest.TestCase):

"""Tests for FIX tag-value parser."""

def setUp(self):
"""Initialize the test suite."""
if not hasattr(self, "assertIsNotNone"):
ParserTests.assertIsNotNone = test_not_none
if not hasattr(self, "assertIsNone"):
Expand Down Expand Up @@ -553,6 +548,35 @@ def test_begin_string_config(self):
else:
self.fail("These keywords together should fail validation.")

def test_raw_data_ending_on_packet_boundary(self):
"""Check parsing when raw data field ends on a packet boundary."""
b1 = b"8=FIX.4.2" + SOH_STR + \
b"9=169" + SOH_STR + \
b"35=A" + SOH_STR + \
b"52=20171213-01:41:08.063" + SOH_STR + \
b"49=HelloWorld" + SOH_STR + \
b"56=1234" + SOH_STR + \
b"34=1" + SOH_STR + \
b"95=6" + SOH_STR + \
b"96=ABC=DE"

# Packet boundary between end of data and SOH, as in
# https://github.com/da4089/simplefix/issues/64

b2 = SOH_STR + \
b"98=0" + SOH_STR + \
b"108=30" + SOH_STR + \
b"10=166" + SOH_STR

parser = FixParser()
parser.append_buffer(b1)
msg = parser.get_message()
self.assertIsNone(msg)

parser.append_buffer(b2)
msg = parser.get_message()
self.assertIsNotNone(msg)


# b"2018-05-06 12:34:56.789 RECV "

Expand Down
Loading