blob: abf3119cd2a2bd971dd5bbb591e35e3d7cd4fdc9 [file] [log] [blame]
Baptiste Assmanne138dda2020-10-22 15:39:03 +02001/*
2 * Financial Information eXchange Protocol
3 *
4 * Copyright 2020 Baptiste Assmann <bedis9@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <haproxy/intops.h>
14#include <haproxy/fix.h>
15/*
16 * Return the corresponding numerical tag id if <str> looks like a valid FIX
17 * protocol tag ID. Otherwise, 0 is returned (0 is an invalid id).
18 *
19 * If <version> is given, it must be one of a defined FIX version string (see
20 * FIX_X_Y macros). In this case, the function will also check tag ID ranges. If
21 * no <version> is provided, any strictly positive integer is valid.
22 *
23 * tag ID range depends on FIX protocol version:
24 * - FIX.4.0: 1-140
25 * - FIX.4.1: 1-211
26 * - FIX.4.2: 1-446
27 * - FIX.4.3: 1-659
28 * - FIX.4.4: 1-956
29 * - FIX.5.0: 1-1139
30 * - FIX.5.0SP1: 1-1426
31 * - FIX.5.0SP2: 1-1621
32 * range 10000 to 19999 is for "user defined tags"
33 */
34unsigned int fix_check_id(const struct ist str, const struct ist version) {
35 const char *s, *end;
36 unsigned int ret;
37
38 s = istptr(str);
39 end = istend(str);
40 ret = read_uint(&s, end);
41
42 /* we did not consume all characters from <str>, this is an error */
43 if (s != end)
44 return 0;
45
46 /* field ID can't be 0 */
47 if (ret == 0)
48 return 0;
49
50 /* we can leave now if version was not provided */
51 if (!isttest(version))
52 return ret;
53
54 /* we can leave now if this is a "user defined tag id" */
55 if (ret >= 10000 && ret <= 19999)
56 return ret;
57
58 /* now perform checking per FIX version */
59 if (istissame(FIX_4_0, version) && (ret <= 140))
60 return ret;
61 else if (istissame(FIX_4_1, version) && (ret <= 211))
62 return ret;
63 else if (istissame(FIX_4_2, version) && (ret <= 446))
64 return ret;
65 else if (istissame(FIX_4_3, version) && (ret <= 659))
66 return ret;
67 else if (istissame(FIX_4_4, version) && (ret <= 956))
68 return ret;
69 /* version string is the same for all 5.0 versions, so we can only take
70 * into consideration the biggest range
71 */
72 else if (istissame(FIX_5_0, version) && (ret <= 1621))
73 return ret;
74
75 return 0;
76}
77
78/*
79 * Parse a FIX message <msg> and performs following sanity checks:
80 *
81 * - checks tag ids and values are not empty
82 * - checks tag ids are numerical value
83 * - checks the first tag is BeginString with a valid version
84 * - checks the second tag is BodyLength with the right body length
85 * - checks the third tag is MsgType
86 * - checks the last tag is CheckSum with a valid checksum
87 *
88 * Returns:
89 * FIX_INVALID_MESSAGE if the message is invalid
90 * FIX_NEED_MORE_DATA if we need more data to fully validate the message
91 * FIX_VALID_MESSAGE if the message looks valid
92 */
93int fix_validate_message(const struct ist msg)
94{
95 struct ist parser, version;
96 unsigned int tagnum, bodylen;
97 unsigned char checksum;
98 char *body;
99 int ret = FIX_INVALID_MESSAGE;
100
101 if (istlen(msg) < FIX_MSG_MINSIZE) {
102 ret = FIX_NEED_MORE_DATA;
103 goto end;
104 }
105
106 /* parsing the whole message to compute the checksum and check all tag
107 * ids are properly set. Here we are sure to have the 2 first tags. Thus
108 * the version and the body length can be checked.
109 */
110 parser = msg;
111 version = IST_NULL;
112 checksum = tagnum = bodylen = 0;
113 body = NULL;
114 while (istlen(parser) > 0) {
115 struct ist tag, value;
116 unsigned int tagid;
117 const char *p, *end;
118
119 /* parse the tag ID and its value and perform first sanity checks */
120 value = iststop(istfind(parser, '='), FIX_DELIMITER);
121
122 /* end of value not found */
123 if (istend(value) == istend(parser)) {
124 ret = FIX_NEED_MORE_DATA;
125 goto end;
126 }
Ilya Shipitsinf38a0182020-12-21 01:16:17 +0500127 /* empty tag or empty value are forbidden */
Baptiste Assmanne138dda2020-10-22 15:39:03 +0200128 if (istptr(parser) == istptr(value) ||!istlen(value))
129 goto end;
130
131 /* value points on '='. get the tag and skip '=' */
132 tag = ist2(istptr(parser), istptr(value) - istptr(parser));
133 value = istnext(value);
134
135 /* Check the tag id */
136 tagid = fix_check_id(tag, version);
137 if (!tagid)
138 goto end;
139 tagnum++;
140
141 if (tagnum == 1) {
142 /* the first tag must be BeginString */
143 if (tagid != FIX_TAG_BeginString)
144 goto end;
145
146 version = fix_version(value);
147 if (!isttest(version))
148 goto end;
149 }
150 else if (tagnum == 2) {
151 /* the second tag must be bodyLength */
152 if (tagid != FIX_TAG_BodyLength)
153 goto end;
154
155 p = istptr(value);
156 end = istend(value);
157 bodylen = read_uint(&p, end);
158
159 /* we did not consume all characters from <str> or no body, this is an error.
160 * There is at least the message type in the body.
161 */
162 if (p != end || !bodylen)
163 goto end;
164
165 body = istend(value) + 1;
166 }
167 else if (tagnum == 3) {
168 /* the third tag must be MsgType */
169 if (tagid != FIX_TAG_MsgType)
170 goto end;
171 }
172 else if (tagnum > 3 && tagid == FIX_TAG_CheckSum) {
173 /* CheckSum tag should be the last one and is not taken into account
174 * to compute the checksum itself and the body length. The value is
175 * a three-octet representation of the checksum decimal value.
176 */
177 if (bodylen != istptr(parser) - body)
178 goto end;
179
180 if (istlen(value) != 3)
181 goto end;
182 if (checksum != strl2ui(istptr(value), istlen(value)))
183 goto end;
184
185 /* End of the message, exit from the loop */
186 ret = FIX_VALID_MESSAGE;
187 goto end;
188 }
189
190 /* compute checksum of tag=value<delim> */
191 for (p = istptr(tag) ; p < istend(tag) ; ++p)
192 checksum += *p;
193 checksum += '=';
194 for (p = istptr(value) ; p < istend(value) ; ++p)
195 checksum += *p;
196 checksum += FIX_DELIMITER;
197
198 /* move the parser after the value and its delimiter */
199 parser = istadv(parser, istlen(tag) + istlen(value) + 2);
200 }
201
202 if (body) {
203 /* We start to read the body but we don't reached the checksum tag */
204 ret = FIX_NEED_MORE_DATA;
205 }
206
207 end:
208 return ret;
209}
210
211
212/*
213 * Iter on a FIX message <msg> and return the value of <tagid>.
214 *
215 * Returns the corresponding value if <tagid> is found. If <tagid> is not found
216 * because more data are required, the message with a length set to 0 is
217 * returned. If <tagid> is not found in the message or if the message is
218 * invalid, IST_NULL is returned.
219 *
220 * Note: Only simple sanity checks are performed on tags and values (not empty).
221 *
222 * the tag looks like
223 * <tagid>=<value>FIX_DELIMITER with <tag> and <value> not empty
224 */
225struct ist fix_tag_value(const struct ist msg, unsigned int tagid)
226{
227 struct ist parser, t, v;
228 unsigned int id;
229
230 parser = msg;
231 while (istlen(parser) > 0) {
232 v = iststop(istfind(parser, '='), FIX_DELIMITER);
233
234 /* delimiter not found, need more data */
235 if (istend(v) == istend(parser))
236 break;
237
238 /* empty tag or empty value, invalid */
239 if (istptr(parser) == istptr(v) || !istlen(v))
240 goto not_found_or_invalid;
241
242 t = ist2(istptr(parser), istptr(v) - istptr(parser));
243 v = istnext(v);
244
245 id = fix_check_id(t, IST_NULL);
246 if (!id)
247 goto not_found_or_invalid;
248 if (id == tagid) {
Ilya Shipitsinc6ecf562021-08-07 14:41:56 +0500249 /* <tagId> found, return the corresponding value */
Baptiste Assmanne138dda2020-10-22 15:39:03 +0200250 return v;
251 }
252
253 /* CheckSum tag is the last one, no <tagid> found */
254 if (id == FIX_TAG_CheckSum)
255 goto not_found_or_invalid;
256
257 parser = istadv(parser, istlen(t) + istlen(v) + 2);
258 }
259 /* not enough data to find <tagid> */
260 return ist2(istptr(msg), 0);
261
262 not_found_or_invalid:
263 return IST_NULL;
264}