summaryrefslogtreecommitdiffstats
path: root/src/fix.c
blob: abf3119cd2a2bd971dd5bbb591e35e3d7cd4fdc9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/*
 * Financial Information eXchange Protocol
 *
 * Copyright 2020 Baptiste Assmann <bedis9@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *
 */

#include <haproxy/intops.h>
#include <haproxy/fix.h>
/*
 * Return the corresponding numerical tag id if <str> looks like a valid FIX
 * protocol tag ID. Otherwise, 0 is returned (0 is an invalid id).
 *
 * If <version> is given, it must be one of a defined FIX version string (see
 * FIX_X_Y macros). In this case, the function will also check tag ID ranges. If
 * no <version> is provided, any strictly positive integer is valid.
 *
 * tag ID range depends on FIX protocol version:
 *    - FIX.4.0:    1-140
 *    - FIX.4.1:    1-211
 *    - FIX.4.2:    1-446
 *    - FIX.4.3:    1-659
 *    - FIX.4.4:    1-956
 *    - FIX.5.0:    1-1139
 *    - FIX.5.0SP1: 1-1426
 *    - FIX.5.0SP2: 1-1621
 * range 10000 to 19999 is for "user defined tags"
 */
unsigned int fix_check_id(const struct ist str, const struct ist version) {
	const char *s, *end;
	unsigned int ret;

	s = istptr(str);
	end = istend(str);
	ret = read_uint(&s, end);

	/* we did not consume all characters from <str>, this is an error */
	if (s != end)
		return 0;

	/* field ID can't be 0 */
	if (ret == 0)
		return 0;

	/* we can leave now if version was not provided */
	if (!isttest(version))
		return ret;

	/* we can leave now if this is a "user defined tag id" */
	if (ret >= 10000 && ret <= 19999)
		return ret;

	/* now perform checking per FIX version */
	if (istissame(FIX_4_0, version) && (ret <= 140))
		return ret;
	else if (istissame(FIX_4_1, version) && (ret <= 211))
		return ret;
	else if (istissame(FIX_4_2, version) && (ret <= 446))
		return ret;
	else if (istissame(FIX_4_3, version) && (ret <= 659))
		return ret;
	else if (istissame(FIX_4_4, version) && (ret <= 956))
		return ret;
	/* version string is the same for all 5.0 versions, so we can only take
	 * into consideration the biggest range
	 */
	else if (istissame(FIX_5_0, version) && (ret <= 1621))
		return ret;

	return 0;
}

/*
 * Parse a FIX message <msg> and performs following sanity checks:
 *
 *   - checks tag ids and values are not empty
 *   - checks tag ids are numerical value
 *   - checks the first tag is BeginString with a valid version
 *   - checks the second tag is BodyLength with the right body length
 *   - checks the third tag is MsgType
 *   - checks the last tag is CheckSum with a valid checksum
 *
 * Returns:
 *  FIX_INVALID_MESSAGE if the message is invalid
 *  FIX_NEED_MORE_DATA  if we need more data to fully validate the message
 *  FIX_VALID_MESSAGE   if the message looks valid
 */
int fix_validate_message(const struct ist msg)
{
	struct ist parser, version;
	unsigned int tagnum, bodylen;
	unsigned char checksum;
	char *body;
	int ret = FIX_INVALID_MESSAGE;

	if (istlen(msg) < FIX_MSG_MINSIZE) {
		ret = FIX_NEED_MORE_DATA;
		goto end;
	}

	/* parsing the whole message to compute the checksum and check all tag
	 * ids are properly set. Here we are sure to have the 2 first tags. Thus
	 * the version and the body length can be checked.
	 */
	parser = msg;
	version = IST_NULL;
	checksum = tagnum = bodylen = 0;
	body = NULL;
	while (istlen(parser) > 0) {
		struct ist tag, value;
		unsigned int tagid;
		const char *p, *end;

		/* parse the tag ID and its value and perform first sanity checks */
		value = iststop(istfind(parser, '='), FIX_DELIMITER);

		/* end of value not found */
		if (istend(value) == istend(parser)) {
			ret = FIX_NEED_MORE_DATA;
			goto end;
		}
		/* empty tag or empty value are forbidden */
		if (istptr(parser) == istptr(value) ||!istlen(value))
			goto end;

		/* value points on '='. get the tag and skip '=' */
		tag = ist2(istptr(parser), istptr(value) - istptr(parser));
		value = istnext(value);

		/* Check the tag id */
		tagid = fix_check_id(tag, version);
		if (!tagid)
			goto end;
		tagnum++;

		if (tagnum == 1) {
			/* the first tag must be BeginString */
			if (tagid != FIX_TAG_BeginString)
				goto end;

			version = fix_version(value);
			if (!isttest(version))
				goto end;
		}
		else if (tagnum == 2) {
			/* the second tag must be bodyLength */
			if (tagid != FIX_TAG_BodyLength)
				goto end;

			p = istptr(value);
			end = istend(value);
			bodylen = read_uint(&p, end);

			/* we did not consume all characters from <str> or no body, this is an error.
			 * There is at least the message type in the body.
			 */
			if (p != end || !bodylen)
				goto end;

			body = istend(value) + 1;
		}
		else if (tagnum == 3) {
			/* the third tag must be MsgType */
			if (tagid != FIX_TAG_MsgType)
				goto end;
		}
		else if (tagnum > 3 && tagid == FIX_TAG_CheckSum) {
			/* CheckSum tag should be the last one and is not taken into account
			 * to compute the checksum itself and the body length. The value is
			 * a three-octet representation of the checksum decimal value.
			 */
			if (bodylen != istptr(parser) - body)
				goto end;

			if (istlen(value) != 3)
				goto end;
			if (checksum != strl2ui(istptr(value), istlen(value)))
				goto end;

			/* End of the message, exit from the loop */
			ret = FIX_VALID_MESSAGE;
			goto end;
		}

		/* compute checksum of tag=value<delim> */
		for (p = istptr(tag) ; p < istend(tag) ; ++p)
			checksum += *p;
		checksum += '=';
		for (p = istptr(value) ; p < istend(value) ; ++p)
			checksum += *p;
		checksum += FIX_DELIMITER;

		/* move the parser after the value and its delimiter */
		parser = istadv(parser, istlen(tag) + istlen(value) + 2);
	}

	if (body) {
		/* We start to read the body but we don't reached the checksum tag */
		ret = FIX_NEED_MORE_DATA;
	}

  end:
	return ret;
}


/*
 * Iter on a FIX message <msg> and return the value of <tagid>.
 *
 * Returns the corresponding value if <tagid> is found. If <tagid> is not found
 * because more data are required, the message with a length set to 0 is
 * returned. If <tagid> is not found in the message or if the message is
 * invalid, IST_NULL is returned.
 *
 * Note: Only simple sanity checks are performed on tags and values (not empty).
 *
 * the tag looks like
 *   <tagid>=<value>FIX_DELIMITER with <tag> and <value> not empty
 */
struct ist fix_tag_value(const struct ist msg, unsigned int tagid)
{
	struct ist parser, t, v;
	unsigned int id;

	parser = msg;
	while (istlen(parser) > 0) {
		v  = iststop(istfind(parser, '='), FIX_DELIMITER);

		/* delimiter not found, need more data */
		if (istend(v) == istend(parser))
			break;

		/* empty tag or empty value, invalid */
		if (istptr(parser) == istptr(v) || !istlen(v))
			goto not_found_or_invalid;

		t = ist2(istptr(parser), istptr(v) - istptr(parser));
		v = istnext(v);

		id = fix_check_id(t, IST_NULL);
		if (!id)
			goto not_found_or_invalid;
		if (id == tagid) {
			/* <tagId> found, return the corresponding value */
			return v;
		}

		/* CheckSum tag is the last one, no <tagid> found */
		if (id == FIX_TAG_CheckSum)
			goto not_found_or_invalid;

		parser = istadv(parser, istlen(t) + istlen(v) + 2);
	}
	/* not enough data to find <tagid> */
	return ist2(istptr(msg), 0);

  not_found_or_invalid:
	return IST_NULL;
}