Coverage Report

Created: 2026-07-16 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/fix.c
Line
Count
Source
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
 */
34
0
unsigned int fix_check_id(const struct ist str, const struct ist version) {
35
0
  const char *s, *end;
36
0
  unsigned int ret;
37
38
0
  s = istptr(str);
39
0
  end = istend(str);
40
0
  ret = read_uint(&s, end);
41
42
  /* we did not consume all characters from <str>, this is an error */
43
0
  if (s != end)
44
0
    return 0;
45
46
  /* field ID can't be 0 */
47
0
  if (ret == 0)
48
0
    return 0;
49
50
  /* we can leave now if version was not provided */
51
0
  if (!isttest(version))
52
0
    return ret;
53
54
  /* we can leave now if this is a "user defined tag id" */
55
0
  if (ret >= 10000 && ret <= 19999)
56
0
    return ret;
57
58
  /* now perform checking per FIX version */
59
0
  if (istissame(FIX_4_0, version) && (ret <= 140))
60
0
    return ret;
61
0
  else if (istissame(FIX_4_1, version) && (ret <= 211))
62
0
    return ret;
63
0
  else if (istissame(FIX_4_2, version) && (ret <= 446))
64
0
    return ret;
65
0
  else if (istissame(FIX_4_3, version) && (ret <= 659))
66
0
    return ret;
67
0
  else if (istissame(FIX_4_4, version) && (ret <= 956))
68
0
    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
0
  else if (istissame(FIX_5_0, version) && (ret <= 1621))
73
0
    return ret;
74
75
0
  return 0;
76
0
}
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
 */
93
int fix_validate_message(const struct ist msg)
94
0
{
95
0
  struct ist parser, version;
96
0
  unsigned int tagnum, bodylen;
97
0
  unsigned char checksum;
98
0
  char *body;
99
0
  int ret = FIX_INVALID_MESSAGE;
100
101
0
  if (istlen(msg) < FIX_MSG_MINSIZE) {
102
0
    ret = FIX_NEED_MORE_DATA;
103
0
    goto end;
104
0
  }
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
0
  parser = msg;
111
0
  version = IST_NULL;
112
0
  checksum = tagnum = bodylen = 0;
113
0
  body = NULL;
114
0
  while (istlen(parser) > 0) {
115
0
    struct ist tag, value;
116
0
    unsigned int tagid;
117
0
    const char *p, *end;
118
119
    /* parse the tag ID and its value and perform first sanity checks */
120
0
    value = iststop(istfind(parser, '='), FIX_DELIMITER);
121
122
    /* end of value not found */
123
0
    if (istend(value) == istend(parser)) {
124
0
      ret = FIX_NEED_MORE_DATA;
125
0
      goto end;
126
0
    }
127
    /* empty tag or empty value are forbidden */
128
0
    if (istptr(parser) == istptr(value) ||!istlen(value))
129
0
      goto end;
130
131
    /* value points on '='. get the tag and skip '=' */
132
0
    tag = ist2(istptr(parser), istptr(value) - istptr(parser));
133
0
    value = istnext(value);
134
135
    /* Check the tag id */
136
0
    tagid = fix_check_id(tag, version);
137
0
    if (!tagid)
138
0
      goto end;
139
0
    tagnum++;
140
141
0
    if (tagnum == 1) {
142
      /* the first tag must be BeginString */
143
0
      if (tagid != FIX_TAG_BeginString)
144
0
        goto end;
145
146
0
      version = fix_version(value);
147
0
      if (!isttest(version))
148
0
        goto end;
149
0
    }
150
0
    else if (tagnum == 2) {
151
      /* the second tag must be bodyLength */
152
0
      if (tagid != FIX_TAG_BodyLength)
153
0
        goto end;
154
155
0
      p = istptr(value);
156
0
      end = istend(value);
157
0
      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
0
      if (p != end || !bodylen)
163
0
        goto end;
164
165
0
      body = istend(value) + 1;
166
0
    }
167
0
    else if (tagnum == 3) {
168
      /* the third tag must be MsgType */
169
0
      if (tagid != FIX_TAG_MsgType)
170
0
        goto end;
171
0
    }
172
0
    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
0
      if (bodylen != istptr(parser) - body)
178
0
        goto end;
179
180
0
      if (istlen(value) != 3)
181
0
        goto end;
182
0
      if (checksum != strl2ui(istptr(value), istlen(value)))
183
0
        goto end;
184
185
      /* End of the message, exit from the loop */
186
0
      ret = FIX_VALID_MESSAGE;
187
0
      goto end;
188
0
    }
189
190
    /* compute checksum of tag=value<delim> */
191
0
    for (p = istptr(tag) ; p < istend(tag) ; ++p)
192
0
      checksum += *p;
193
0
    checksum += '=';
194
0
    for (p = istptr(value) ; p < istend(value) ; ++p)
195
0
      checksum += *p;
196
0
    checksum += FIX_DELIMITER;
197
198
    /* move the parser after the value and its delimiter */
199
0
    parser = istadv(parser, istlen(tag) + istlen(value) + 2);
200
0
  }
201
202
0
  if (body) {
203
    /* We start to read the body but we don't reached the checksum tag */
204
0
    ret = FIX_NEED_MORE_DATA;
205
0
  }
206
207
0
  end:
208
0
  return ret;
209
0
}
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
 */
225
struct ist fix_tag_value(const struct ist msg, unsigned int tagid)
226
0
{
227
0
  struct ist parser, t, v;
228
0
  unsigned int id;
229
230
0
  parser = msg;
231
0
  while (istlen(parser) > 0) {
232
0
    v  = iststop(istfind(parser, '='), FIX_DELIMITER);
233
234
    /* delimiter not found, need more data */
235
0
    if (istend(v) == istend(parser))
236
0
      break;
237
238
    /* empty tag or empty value, invalid */
239
0
    if (istptr(parser) == istptr(v) || !istlen(v))
240
0
      goto not_found_or_invalid;
241
242
0
    t = ist2(istptr(parser), istptr(v) - istptr(parser));
243
0
    v = istnext(v);
244
245
0
    id = fix_check_id(t, IST_NULL);
246
0
    if (!id)
247
0
      goto not_found_or_invalid;
248
0
    if (id == tagid) {
249
      /* <tagId> found, return the corresponding value */
250
0
      return v;
251
0
    }
252
253
    /* CheckSum tag is the last one, no <tagid> found */
254
0
    if (id == FIX_TAG_CheckSum)
255
0
      goto not_found_or_invalid;
256
257
0
    parser = istadv(parser, istlen(t) + istlen(v) + 2);
258
0
  }
259
  /* not enough data to find <tagid> */
260
0
  return ist2(istptr(msg), 0);
261
262
0
  not_found_or_invalid:
263
0
  return IST_NULL;
264
0
}