1 /*
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  */
fix_check_id(const struct ist str,const struct ist version)34 unsigned 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  */
fix_validate_message(const struct ist msg)93 int 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 		}
127 		/* empty tag or empty value are forbidden */
128 		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  */
fix_tag_value(const struct ist msg,unsigned int tagid)225 struct 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) {
249 			/* <tagId> found, return the corresponding value */
250 			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 }
265