Skip to content

Commit 45a2f10

Browse files
committed
Documentation and minor changes.
1 parent ad27690 commit 45a2f10

File tree

11 files changed

+536
-50
lines changed

11 files changed

+536
-50
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
SPEC = FIX44
2-
BIN = uf-test
2+
BIN = fullfix-test
33

44
.PHONY: release debug release32
55
release debug release32 : $(BIN)

README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
# FullFIX
22

3+
###### Platform: Linux
4+
5+
###### Licence: BSD 3-clause
6+
37
FullFIX is a library for parsing Financial Information eXchange (FIX) messages.
48
The main goal of the project is to produce the fastest software solution for FIX protocol.
9+
The library is written entirely in C for better portability.
510
The parser does not rely on any library apart from `libc`, it does not require any
611
special memory allocator and it does not impose any I/O or threading model.
7-
The library is written entirely in C for better portability. Unlike some other well known solutions,
8-
in this parser the FIX specification gets converted to C code at compile time to achieve the best performance.
12+
Unlike some other well known solutions, in this parser the FIX specification
13+
gets converted to C code at compile time to achieve the best performance.
14+
15+
_Supported FIX protocol versions_: up to and including v4.4.
916

1017
### Performance
1118

12-
The numbers below are achieved on my 5 years old laptop with Core i5-430M 2.25GHz processor,
13-
on modern production hardware the results will probably be better.
19+
The numbers below have been achieved on my 5 years old laptop with Core i5-430M 2.25GHz processor.
20+
On modern production hardware the results will probably be better.
1421

15-
Compiler: gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
22+
_Compiler:_ gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
1623

17-
OS: Linux Mint 17.2 64bit
24+
_OS:_ Linux Mint 17.2 64bit
1825

19-
FIX message type | FIX specification | Validation | Time to parse one message (average from 100 runs)
26+
FIX message type | FIX specification | Validation | Average time to parse one message
2027
----------------------------------|------------------------------------------|------------|--------------------------------------------------
2128
NewOrderSingle('D') | Hand-coded spec. for this message only | No | 0.335 µs/msg
2229
NewOrderSingle('D') | Hand-coded spec. for this message only | Yes | 0.571 µs/msg
2330
NewOrderSingle('D') | Compiled full spec. for FIX.4.4 | Yes | 0.754 µs/msg
2431
MarketDataIncrementalRefresh('X') | Hand-coded spec. for this message only | Yes | 1.294 µs/msg
2532
MarketDataIncrementalRefresh('X') | Compiled full spec. for FIX.4.4 | Yes | 1.435 µs/msg
26-
27-
###### Platform: Linux
28-
29-
###### Licence: BSD 3-clause

doc/doc.md

Lines changed: 429 additions & 0 deletions
Large diffs are not rendered by default.

doc/faq.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
### Frequently Asked Questions
2+
**Q.**: _This is just a parser, where is the FIX builder / composer?_
3+
4+
**A.**: In its simplest form, composing a FIX message is mostly a `sprintf()`
5+
exercise, and as such is probably not worth being included into the library.
6+
More advanced FIX builder would include an API to add individual tag/value pairs and
7+
to convert the result to a plain string. Although this would be useful, there
8+
would be certain overhead associated with it, which would impact the performance.
9+
Another open issue is the amount of validation that needs to be done in this
10+
composer / builder. In general, the correct code will produce the correct FIX messages,
11+
which means there will be no need to validate the result. The same time, such a
12+
validation would be useful for debugging. This leads to the idea of debug-only
13+
validation, possibly in the spirit of `assert()` macro. Overall, at the moment
14+
the FIX message builder / composer is an open question.
15+
16+
**Q.**: _How about support for FIX protocol version 5.0?_
17+
18+
**A.**: For some reason, in the version 5.0 they introduced two layers to the
19+
protocol: session layer and application layer. In my opinion, the declared benefits of the
20+
split are by far outweighed by the complications of the implementation. Supporting
21+
the two layers will require one more pass over the input data and one more layer of the
22+
specification, which will certainly hurt the performance. The FIX protocol
23+
used to be good for quick exchange of very focused financial data
24+
and assumed a reasonably simple implementation. Not any more. I think the authors of the
25+
standard should at some point at least attempt to implement their protocol in
26+
the real code to assess on the quality of some of their decisions. That does not mean
27+
the support cannot be added to this library, it all depends on if there is a substantial
28+
amount of interest in such a support.

include/fix.h

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ const fix_error_details* get_fix_group_error_details(const fix_group* const grou
175175
// tag as string
176176
fix_error get_fix_tag_as_string(const fix_group* const group, unsigned tag, fix_string* const result);
177177

178+
// copy tag as string
179+
fix_error copy_fix_tag_as_string(const fix_group* const group, unsigned tag, char** const result);
180+
178181
// tag as group
179182
fix_error get_fix_tag_as_group(const fix_group* const group, unsigned tag, fix_group** const result);
180183

@@ -190,27 +193,27 @@ fix_error get_fix_tag_as_double(const fix_group* const group, unsigned tag, doub
190193
// tag as boolean
191194
fix_error get_fix_tag_as_boolean(const fix_group* const group, unsigned tag, bool* const result);
192195

193-
// tag as UTCTimestamp
196+
// tag as utc_timestamp
194197
typedef struct
195198
{
196199
unsigned short year;
197200
unsigned char month, day, hour, minute, second;
198201
unsigned short millisecond;
199-
} UTC_timestamp;
202+
} utc_timestamp;
200203

201-
fix_error get_fix_tag_as_UTC_timestamp(const fix_group* const group, unsigned tag, UTC_timestamp* const result);
204+
fix_error get_fix_tag_as_utc_timestamp(const fix_group* const group, unsigned tag, utc_timestamp* const result);
202205

203-
// tag as TZTimestamp
206+
// tag as tz_timestamp
204207
typedef struct
205208
{
206-
UTC_timestamp utc;
209+
utc_timestamp utc;
207210
short offset_minutes;
208-
} TZ_Timestamp;
211+
} tz_timestamp;
209212

210-
fix_error get_fix_tag_as_TZ_timestamp(const fix_group* const group, unsigned tag, TZ_Timestamp* const result);
213+
fix_error get_fix_tag_as_tz_timestamp(const fix_group* const group, unsigned tag, tz_timestamp* const result);
211214

212215
// tag as LocalMktDate
213-
fix_error get_fix_tag_as_LocalMktDate(const fix_group* const group, unsigned tag, UTC_timestamp* const result);
216+
fix_error get_fix_tag_as_LocalMktDate(const fix_group* const group, unsigned tag, utc_timestamp* const result);
214217

215218
// tag as FIX version (for ApplVerID, DefaultApplVerID and RefApplVerID)
216219
typedef enum
@@ -238,15 +241,14 @@ fix_error get_fix_tag_as_fix_version(const fix_group* const group, unsigned tag,
238241
double* : get_fix_tag_as_double, \
239242
bool* : get_fix_tag_as_boolean, \
240243
char* : get_fix_tag_as_char, \
241-
UTC_timestamp* : get_fix_tag_as_UTC_timestamp, \
242244
fix_group** : get_fix_tag_as_group \
243245
fix_version* : get_fix_tag_as_fix_version \
244246
)((g), (t), (p))
245247
#endif
246248

247249
// utilities -------------------------------------------------------------------------------------
248-
// UTC_timestamp to struct timeval converter
249-
fix_error UTC_timestamp_to_timeval(const UTC_timestamp* const utc, struct timeval* const result);
250+
// utc_timestamp to struct timeval converter
251+
fix_error utc_timestamp_to_timeval(const utc_timestamp* const utc, struct timeval* const result);
250252

251253
// error message string from fix_error_details
252254
const char* compose_fix_error_message(const fix_error_details* const details) NOINLINE;

src/converters.c

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,30 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3232

3333
#define RETURN(r) return group->error->code = (r)
3434

35+
// copy tag as string
36+
fix_error copy_fix_tag_as_string(const fix_group* const group, unsigned tag, char** const result)
37+
{
38+
fix_string value;
39+
const fix_error err = get_fix_tag_as_string(group, tag, &value);
40+
41+
if(err != FE_OK)
42+
return err;
43+
44+
if(result)
45+
{
46+
const size_t n = fix_string_length(value);
47+
char* const p = *result = malloc(n + 1);
48+
49+
if(!p)
50+
RETURN( FE_OUT_OF_MEMORY );
51+
52+
memcpy(p, value.begin, n);
53+
*(p + n) = 0;
54+
}
55+
56+
return FE_OK;
57+
}
58+
3559
// tag as long integer
3660
fix_error get_fix_tag_as_long(const fix_group* const group, unsigned tag, long* const result)
3761
{
@@ -225,7 +249,7 @@ fix_error get_fix_tag_as_boolean(const fix_group* const group, unsigned tag, boo
225249

226250
// helper to read the 'YYYYMMDD' part of the time-stamp
227251
static
228-
fix_error read_date_part(const fix_group* const group, fix_string* const ps, UTC_timestamp* const ts)
252+
fix_error read_date_part(const fix_group* const group, fix_string* const ps, utc_timestamp* const ts)
229253
{
230254
// format 'YYYYMMDD', where YYYY = 0000-9999, MM = 01-12, DD = 01-31
231255
const char* s = ps->begin;
@@ -252,7 +276,7 @@ fix_error read_date_part(const fix_group* const group, fix_string* const ps, UTC
252276

253277
// helper to read 'HH:MM:SS' part of the time-stamp
254278
static
255-
fix_error read_time_part(const fix_group* const group, fix_string* const ps, UTC_timestamp* const ts)
279+
fix_error read_time_part(const fix_group* const group, fix_string* const ps, utc_timestamp* const ts)
256280
{
257281
// format 'HH:MM:SS.sss', where HH = 00-23, MM = 00-59, SS = 00-60 (60 only if UTC leap second).
258282
const char* s = ps->begin;
@@ -283,7 +307,7 @@ fix_error read_time_part(const fix_group* const group, fix_string* const ps, UTC
283307

284308
// helper to read 'HH:MM:SS.sss' part of the time-stamp
285309
static
286-
fix_error read_time_ms_part(const fix_group* const group, fix_string* const ps, UTC_timestamp* const ts)
310+
fix_error read_time_ms_part(const fix_group* const group, fix_string* const ps, utc_timestamp* const ts)
287311
{
288312
fix_error err = read_time_part(group, ps, ts);
289313

@@ -308,7 +332,7 @@ fix_error read_time_ms_part(const fix_group* const group, fix_string* const ps,
308332

309333
// helper to read both date and time parts of the time-stamp string
310334
static
311-
fix_error read_timestamp_part(const fix_group* const group, fix_string* const ps, UTC_timestamp* const ts)
335+
fix_error read_timestamp_part(const fix_group* const group, fix_string* const ps, utc_timestamp* const ts)
312336
{
313337
fix_error err = read_date_part(group, ps, ts);
314338

@@ -324,8 +348,8 @@ fix_error read_timestamp_part(const fix_group* const group, fix_string* const ps
324348
return FE_OK;
325349
}
326350

327-
// tag as UTCTimestamp
328-
fix_error get_fix_tag_as_UTC_timestamp(const fix_group* const group, unsigned tag, UTC_timestamp* const result)
351+
// tag as utc_timestamp
352+
fix_error get_fix_tag_as_utc_timestamp(const fix_group* const group, unsigned tag, utc_timestamp* const result)
329353
{
330354
// from the spec:
331355
// string field representing Time/date combination represented in UTC (Universal Time Coordinated, also known as "GMT")
@@ -340,7 +364,7 @@ fix_error get_fix_tag_as_UTC_timestamp(const fix_group* const group, unsigned ta
340364
if(err != FE_OK)
341365
return err;
342366

343-
UTC_timestamp ts;
367+
utc_timestamp ts;
344368

345369
err = read_timestamp_part(group, &value, &ts);
346370

@@ -356,8 +380,8 @@ fix_error get_fix_tag_as_UTC_timestamp(const fix_group* const group, unsigned ta
356380
return FE_OK;
357381
}
358382

359-
// tag as TZTimestamp
360-
fix_error get_fix_tag_as_TZ_timestamp(const fix_group* const group, unsigned tag, TZ_Timestamp* const result)
383+
// tag as tz_timestamp
384+
fix_error get_fix_tag_as_tz_timestamp(const fix_group* const group, unsigned tag, tz_timestamp* const result)
361385
{
362386
// from the spec:
363387
// string field representing a time/date combination representing local time with an offset to UTC to allow
@@ -371,7 +395,7 @@ fix_error get_fix_tag_as_TZ_timestamp(const fix_group* const group, unsigned tag
371395
if(err != FE_OK)
372396
return err;
373397

374-
TZ_Timestamp ts;
398+
tz_timestamp ts;
375399

376400
// date
377401
err = read_date_part(group, &value, &ts.utc);
@@ -432,7 +456,7 @@ fix_error get_fix_tag_as_TZ_timestamp(const fix_group* const group, unsigned tag
432456
}
433457

434458
// tag as LocalMktDate
435-
fix_error get_fix_tag_as_LocalMktDate(const fix_group* const group, unsigned tag, UTC_timestamp* const result)
459+
fix_error get_fix_tag_as_LocalMktDate(const fix_group* const group, unsigned tag, utc_timestamp* const result)
436460
{
437461
// from the spec (FIX.5.0SP2_EP194):
438462
// string field representing a Date of Local Market (as oppose to UTC) in YYYYMMDD format.

src/utils.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ void set_fatal_error(fix_parser* const parser, fix_error code)
7070
details->context = EMPTY_STR;
7171
}
7272

73-
// UTC_timestamp to struct timespec converter
74-
fix_error UTC_timestamp_to_timeval(const UTC_timestamp* const utc, struct timeval* const result)
73+
// utc_timestamp to struct timespec converter
74+
fix_error utc_timestamp_to_timeval(const utc_timestamp* const utc, struct timeval* const result)
7575
{
7676
if(!utc
7777
|| utc->year > 9999

test/test_utils.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fix_string make_n_copies_of_multiple_messages(size_t n, const fix_string src[],
8787
return (fix_string){ msg, msg + len };
8888
}
8989

90-
bool equal_utc_timestamps(const UTC_timestamp* ts1, const UTC_timestamp* ts2)
90+
bool equal_utc_timestamps(const utc_timestamp* ts1, const utc_timestamp* ts2)
9191
{
9292
return ts1->year == ts2->year
9393
&& ts1->month == ts2->month
@@ -194,11 +194,11 @@ bool valid_boolean(fix_group* group, unsigned tag, const bool expected)
194194
return true;
195195
}
196196

197-
bool valid_timestamp(fix_group* group, unsigned tag, const UTC_timestamp* expected)
197+
bool valid_timestamp(fix_group* group, unsigned tag, const utc_timestamp* expected)
198198
{
199-
UTC_timestamp value = { 0, 0, 0, 0, 0, 0, 0 };
199+
utc_timestamp value = { 0, 0, 0, 0, 0, 0, 0 };
200200

201-
GET_TAG(group, tag, value, get_fix_tag_as_UTC_timestamp);
201+
GET_TAG(group, tag, value, get_fix_tag_as_utc_timestamp);
202202
ENSURE(equal_utc_timestamps(&value, expected), "Tag %u - value mismatch: timestamps", tag);
203203
return true;
204204
}
@@ -405,7 +405,7 @@ bool valid_simple_message(fix_group* const group)
405405

406406
return valid_long(group, 34, 215)
407407
&& valid_string(group, 49, CONST_LIT("CLIENT12"))
408-
&& valid_timestamp(group, 52, &(UTC_timestamp){ 2010, 2, 25, 19, 41, 57, 316 })
408+
&& valid_timestamp(group, 52, &(utc_timestamp){ 2010, 2, 25, 19, 41, 57, 316 })
409409
&& valid_char(group, 56, 'B')
410410
&& valid_string(group, 1, CONST_LIT("Marcel"))
411411
&& valid_long(group, 11, 13346)
@@ -414,7 +414,7 @@ bool valid_simple_message(fix_group* const group)
414414
&& valid_long(group, 44, 5)
415415
&& valid_long(group, 54, 1)
416416
&& valid_long(group, 59, 0)
417-
&& valid_timestamp(group, 60, &(UTC_timestamp){ 2010, 2, 25, 19, 39, 52, 20 });
417+
&& valid_timestamp(group, 60, &(utc_timestamp){ 2010, 2, 25, 19, 39, 52, 20 });
418418
}
419419

420420
bool valid_message_with_groups(fix_group* const group)
@@ -427,7 +427,7 @@ bool valid_message_with_groups(fix_group* const group)
427427
bool ret = valid_char(group, 49, 'A')
428428
&& valid_char(group, 56, 'B')
429429
&& valid_long(group, 34, 12)
430-
&& valid_timestamp(group, 52, &(UTC_timestamp){ 2010, 3, 18, 3, 21, 11, 364 })
430+
&& valid_timestamp(group, 52, &(utc_timestamp){ 2010, 3, 18, 3, 21, 11, 364 })
431431
&& valid_char(group, 262, 'A');
432432

433433
if(!ret)

test/test_utils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
5252
void* check_ptr(void* p);
5353
fix_string make_n_copies(size_t n, const fix_string src);
5454
fix_string make_n_copies_of_multiple_messages(size_t n, const fix_string src[], size_t n_src);
55-
bool equal_utc_timestamps(const UTC_timestamp* ts1, const UTC_timestamp* ts2);
55+
bool equal_utc_timestamps(const utc_timestamp* ts1, const utc_timestamp* ts2);
5656
void print_times(const char* test_name, size_t num_messages,
5757
const struct timespec* start, const struct timespec* stop);
5858

@@ -76,7 +76,7 @@ bool valid_long(fix_group* group, unsigned tag, const long expected);
7676
bool valid_double(fix_group* group, unsigned tag, const double expected);
7777
bool valid_char(fix_group* group, unsigned tag, const char expected);
7878
bool valid_boolean(fix_group* group, unsigned tag, const bool expected);
79-
bool valid_timestamp(fix_group* group, unsigned tag, const UTC_timestamp* expected);
79+
bool valid_timestamp(fix_group* group, unsigned tag, const utc_timestamp* expected);
8080

8181
// parser invocations -------------------------------------------------------------------------
8282
// message function

test/utils_test.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3333
#include <string.h>
3434

3535
static
36-
bool test_UTC_timestamp_to_timeval()
36+
bool test_utc_timestamp_to_timeval()
3737
{
3838
struct tm now; // in UTC
3939
const time_t t = time(NULL);
@@ -44,7 +44,7 @@ bool test_UTC_timestamp_to_timeval()
4444

4545
strftime(orig, sizeof(orig), "%F %T", &now);
4646

47-
UTC_timestamp utc =
47+
utc_timestamp utc =
4848
{
4949
.year = now.tm_year + 1900,
5050
.month = now.tm_mon + 1,
@@ -57,7 +57,7 @@ bool test_UTC_timestamp_to_timeval()
5757

5858
struct timeval result;
5959

60-
ENSURE(UTC_timestamp_to_timeval(&utc, &result) == FE_OK, "Failed conversion to timeval");
60+
ENSURE(utc_timestamp_to_timeval(&utc, &result) == FE_OK, "Failed conversion to timeval");
6161
ENSURE(strftime(converted, sizeof(converted), "%F %T", gmtime(&result.tv_sec)) > 0, "Failed to convert resulting time_t");
6262
ENSURE(strcmp(converted, orig) == 0, "Time mismatch: original \"%s\", converted \"%s\"", orig, converted);
6363
ENSURE(utc.millisecond * 1000 == result.tv_usec, "Millisecond value mismatch");
@@ -69,6 +69,6 @@ void utils_test()
6969
{
7070
puts("# Utils tests:");
7171

72-
test_UTC_timestamp_to_timeval();
72+
test_utc_timestamp_to_timeval();
7373
}
7474

tools/perf-stat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ if num_runs <= 0 or num_runs > 1000:
5252
# test invocation
5353
def test_results():
5454
try:
55-
output = subprocess.check_output('./uf-test', universal_newlines = True)
55+
output = subprocess.check_output('./fullfix-test', universal_newlines = True)
5656
except OSError as e:
5757
die(str(e))
5858
prefix = '-- '

0 commit comments

Comments
 (0)