Coverage Report

Created: 2026-08-14 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-fix.c
Line
Count
Source
1
/* packet-fix.c
2
 * Routines for Financial Information eXchange (FIX) Protocol dissection
3
 * Copyright 2000, PC Drew <drewpc@ibsncentral.com>
4
 *
5
 * Wireshark - Network traffic analyzer
6
 * By Gerald Combs <gerald@wireshark.org>
7
 * Copyright 1998 Gerald Combs
8
 *
9
 * SPDX-License-Identifier: GPL-2.0-or-later
10
 *
11
 * Documentation: http://www.fixprotocol.org/
12
 * Fields and messages from http://www.quickfixengine.org/ and http://sourceforge.net/projects/quickfix/files/ xml
13
 *
14
 */
15
16
#include "config.h"
17
18
#include <stdlib.h>
19
20
#include <epan/packet.h>
21
#include <epan/expert.h>
22
#include <epan/prefs.h>
23
24
#include <wsutil/strtoi.h>
25
26
#include "packet-tcp.h"
27
#include "packet-tls.h"
28
29
void proto_register_fix(void);
30
void proto_reg_handoff_fix(void);
31
32
typedef struct _fix_parameter {
33
    unsigned field_len;
34
    unsigned tag_len;
35
    unsigned value_offset;
36
    unsigned value_len;
37
    unsigned ctrla_offset;
38
} fix_parameter;
39
40
/* Initialize the protocol and registered fields */
41
static int proto_fix;
42
43
/* desegmentation of fix */
44
static bool fix_desegment = true;
45
46
/* Initialize the subtree pointers */
47
static int ett_fix;
48
static int ett_unknown;
49
static int ett_badfield;
50
static int ett_checksum;
51
52
static expert_field ei_fix_checksum_bad;
53
static expert_field ei_fix_missing_field;
54
static expert_field ei_fix_tag_invalid;
55
static expert_field ei_fix_field_invalid;
56
57
static int hf_fix_data; /* continuation data */
58
static int hf_fix_checksum_good;
59
static int hf_fix_checksum_bad;
60
static int hf_fix_field_value;
61
static int hf_fix_field_tag;
62
63
static dissector_handle_t fix_handle;
64
65
/* 8=FIX */
66
3.87k
#define MARKER_TAG "8=FIX"
67
3.88k
#define MARKER_LEN 5
68
69
static int fix_marker(tvbuff_t *tvb, unsigned offset)
70
3.87k
{
71
3.87k
    return tvb_strneql(tvb, offset, MARKER_TAG, MARKER_LEN);
72
3.87k
}
73
74
/*
75
 * Fields and messages generated from http://www.quickfixengine.org/ xml (slightly modified)
76
 */
77
78
#include "packet-fix.h"
79
80
16
static void dissect_fix_init(void) {
81
    /* TODO load xml def for private field */
82
    /* TODO check that fix_fields is really sorted */
83
16
}
84
85
static int
86
fix_field_tag_compar(const void *v_needle, const void *v_entry)
87
0
{
88
0
    int key = *(const int *)v_needle;
89
0
    int entry_tag = ((const fix_field *)v_entry)->tag;
90
0
    return key > entry_tag ? 1 : (key < entry_tag ? -1 : 0);
91
0
}
92
93
/* Code to actually dissect the packets */
94
static int fix_next_header(tvbuff_t *tvb, packet_info* pinfo, unsigned offset)
95
94
{
96
    /* try to resync to the next start */
97
94
    unsigned      min_len = tvb_captured_length_remaining(tvb, offset);
98
94
    const char *data    = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset, min_len, ENC_ASCII);
99
94
    const char *start   = data;
100
101
95
    while ((start = strstr(start, "\0018"))) {
102
2
        min_len = (unsigned) (start +1 -data);
103
        /*  if remaining length < 6 return and let the next desegment round
104
            test for 8=FIX
105
        */
106
2
        if (tvb_reported_length_remaining(tvb, min_len + offset) < MARKER_LEN)
107
0
           break;
108
2
        if (!fix_marker(tvb, min_len +offset) )
109
1
            break;
110
1
        start++;
111
1
    }
112
94
    return min_len;
113
94
}
114
115
/* ----------------------------------------------
116
  Format: name=value\001
117
*/
118
static fix_parameter *fix_param(tvbuff_t *tvb, unsigned offset)
119
35
{
120
35
    static   fix_parameter ret;
121
35
    unsigned equals;
122
123
35
    if (!tvb_find_uint8_remaining(tvb, offset, 0x01, &ret.ctrla_offset)) {
124
11
        return NULL;
125
11
    }
126
127
24
    ret.field_len = ret.ctrla_offset - offset + 1;
128
24
    if (!tvb_find_uint8_length(tvb, offset, ret.field_len, '=', &equals)) {
129
12
        return NULL;
130
12
    }
131
132
12
    ret.value_offset = equals + 1;
133
12
    ret.tag_len      = ret.value_offset - offset - 1;
134
12
    ret.value_len    = ret.ctrla_offset - ret.value_offset;
135
12
    return &ret;
136
24
}
137
138
/* ---------------------------------------------- */
139
static unsigned
140
fix_header_len(tvbuff_t *tvb, packet_info* pinfo, unsigned offset)
141
94
{
142
94
    unsigned       base_offset, ctrla_offset;
143
94
    int32_t        value;
144
94
    unsigned       size;
145
94
    fix_parameter *tag;
146
147
94
    base_offset = offset;
148
149
    /* get at least the fix version: 8=FIX.x.x */
150
94
    if (fix_marker(tvb, offset) != 0) {
151
62
        return fix_next_header(tvb, pinfo, offset);
152
62
    }
153
154
    /* begin string */
155
32
    if (!tvb_find_uint8_length(tvb, offset, -1, 0x01, &ctrla_offset)) {
156
        /* it should be there, (minimum size is big enough)
157
         * if not maybe it's not really
158
         * a FIX packet but it's too late to bail out.
159
        */
160
5
        return fix_next_header(tvb, pinfo, offset +MARKER_LEN) +MARKER_LEN;
161
5
    }
162
27
    offset = ctrla_offset + 1;
163
164
    /* msg length */
165
27
    if (!(tag = fix_param(tvb, offset)) || tvb_strneql(tvb, offset, "9=", 2)) {
166
        /* not a tag or not the BodyLength tag, give up */
167
27
        return fix_next_header(tvb, pinfo, offset);
168
27
    }
169
170
0
    if (!tvb_get_string_int(tvb, tag->value_offset, tag->value_len, ENC_STR_DEC, &value, NULL))
171
0
        return fix_next_header(tvb, pinfo, base_offset +MARKER_LEN)  +MARKER_LEN;
172
    /* Fix version, msg type, length and checksum aren't in body length.
173
     * If the packet is big enough find the checksum
174
    */
175
0
    size = value + tag->ctrla_offset - base_offset + 1;
176
0
    if (tvb_reported_length_remaining(tvb, base_offset) > size +4) {
177
        /* 10= should be there */
178
0
        offset = base_offset +size;
179
0
        if (tvb_strneql(tvb, offset, "10=", 3) != 0) {
180
            /* No? bogus packet, try to find the next header */
181
0
            return fix_next_header(tvb, pinfo, base_offset +MARKER_LEN)  +MARKER_LEN;
182
0
        }
183
0
        if (!tvb_find_uint8_remaining(tvb, offset, 0x01, &ctrla_offset)) {
184
            /* assume checksum is 7 bytes 10=xxx\01 */
185
0
            return size+7;
186
0
        }
187
0
        return size +ctrla_offset -offset +1;
188
0
    }
189
0
    else {
190
0
    }
191
    /* assume checksum is 7 bytes 10=xxx\01 */
192
0
    return size +7;
193
0
}
194
195
/* ---------------------------------------------- */
196
static int
197
dissect_fix_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
198
54
{
199
    /* Set up structures needed to add the protocol subtree and manage it */
200
54
    proto_item    *ti;
201
54
    proto_tree    *fix_tree;
202
54
    unsigned       pdu_len;
203
54
    unsigned       offset = 0;
204
54
    unsigned       field_offset, ctrla_offset;
205
54
    uint32_t       tag_value;
206
54
    char          *value;
207
54
    int32_t        ivalue;
208
54
    bool           ivalue_valid;
209
54
    proto_item*    pi;
210
54
    fix_parameter *tag;
211
54
    const char *msg_type;
212
213
    /* Make entries in Protocol column and Info column on summary display */
214
54
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "FIX");
215
54
    col_clear(pinfo->cinfo, COL_INFO);
216
217
    /* get at least the fix version: 8=FIX.x.x */
218
54
    if (fix_marker(tvb, 0) != 0) {
219
        /* not a fix packet start but it's a fix packet */
220
29
        col_set_str(pinfo->cinfo, COL_INFO, "[FIX continuation]");
221
29
        ti = proto_tree_add_item(tree, proto_fix, tvb, 0, -1, ENC_NA);
222
29
        fix_tree = proto_item_add_subtree(ti, ett_fix);
223
29
        proto_tree_add_item(fix_tree, hf_fix_data, tvb, 0, -1, ENC_NA);
224
29
        return tvb_captured_length(tvb);
225
29
    }
226
227
25
    pdu_len = tvb_reported_length(tvb);
228
25
    ti = proto_tree_add_item(tree, proto_fix, tvb, 0, -1, ENC_NA);
229
25
    fix_tree = proto_item_add_subtree(ti, ett_fix);
230
231
    /* begin string */
232
25
    if (!tvb_find_uint8_remaining(tvb, offset, 0x01, &ctrla_offset)) {
233
9
        expert_add_info_format(pinfo, ti, &ei_fix_missing_field, "Missing BeginString field");
234
9
        return tvb_captured_length(tvb);
235
9
    }
236
16
    offset = ctrla_offset + 1;
237
238
    /* msg length */
239
16
    if (!!tvb_find_uint8_remaining(tvb, offset, 0x01, &ctrla_offset)) {
240
8
        expert_add_info_format(pinfo, ti, &ei_fix_missing_field, "Missing BodyLength field");
241
8
        return tvb_captured_length(tvb);
242
8
    }
243
8
    offset = ctrla_offset + 1;
244
245
    /* msg type */
246
8
    if (!(tag = fix_param(tvb, offset)) || tag->value_len < 1) {
247
0
        expert_add_info_format(pinfo, ti, &ei_fix_missing_field, "Missing MsgType field");
248
0
        return tvb_captured_length(tvb);
249
0
    }
250
251
    /* In the interest of speed, if "tree" is NULL, don't do any work not
252
     * necessary to generate protocol tree items.
253
     */
254
8
    field_offset = 0;
255
256
8
    while(field_offset < pdu_len && (tag = fix_param(tvb, field_offset)) ) {
257
0
        const fix_field *field;
258
259
0
        if (tag->tag_len < 1) {
260
0
            field_offset = tag->ctrla_offset + 1;
261
0
            continue;
262
0
        }
263
264
0
        if (!tvb_get_string_uint(tvb, field_offset, tag->tag_len, ENC_STR_DEC, &tag_value, NULL)) {
265
0
            proto_tree_add_expert(fix_tree, pinfo, &ei_fix_tag_invalid, tvb, field_offset, tag->tag_len);
266
0
            break;
267
0
        }
268
0
        if (tag->value_len < 1) {
269
0
            proto_tree *field_tree;
270
            /* XXX - put an error indication here.  It's too late
271
               to return false; we've already started dissecting,
272
               and if a heuristic dissector starts dissecting
273
               (either updating the columns or creating a protocol
274
               tree) and then gives up, it leaves crud behind that
275
               messes up other dissectors that might process the
276
               packet. */
277
0
            field_tree = proto_tree_add_subtree_format(fix_tree, tvb, field_offset, tag->field_len, ett_badfield, NULL, "%i: <missing value>", tag_value);
278
0
            proto_tree_add_uint(field_tree, hf_fix_field_tag, tvb, field_offset, tag->tag_len, tag_value);
279
0
            field_offset =  tag->ctrla_offset + 1;
280
0
            continue;
281
0
        }
282
283
        /* fix_fields array is sorted by tag_value */
284
0
        field = bsearch(&tag_value, fix_fields, array_length(fix_fields), sizeof *fix_fields, fix_field_tag_compar);
285
286
0
        value = (char*)tvb_get_string_enc(pinfo->pool, tvb, tag->value_offset, tag->value_len, ENC_ASCII);
287
0
        ivalue_valid = ws_strtoi32(value, NULL, &ivalue);
288
0
        if (field) {
289
0
            int hf = fix_hf[field - fix_fields];
290
291
0
            if (field->table) {
292
0
                if (tree) {
293
0
                    switch (field->type) {
294
0
                    case 1: /* strings */
295
0
                        proto_tree_add_string_format_value(fix_tree, hf, tvb, field_offset, tag->field_len, value,
296
0
                            "%s (%s)", value, str_to_str_wmem(pinfo->pool, value, (const string_string *)field->table, "unknown %s"));
297
0
                        if (tag_value == 35) {
298
                            /* Make message type part of the Info column */
299
0
                            msg_type = str_to_str_wmem(pinfo->pool, value, messages_val, "FIX Message (%s)");
300
0
                            col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", msg_type);
301
0
                            col_set_fence(pinfo->cinfo, COL_INFO);
302
0
                        }
303
0
                        break;
304
0
                    case 2: /* char */
305
0
                        proto_tree_add_string_format_value(fix_tree, hf, tvb, field_offset, tag->field_len, value,
306
0
                            "%s (%s)", value, val_to_str(pinfo->pool, *value, (const value_string *)field->table, "unknown %d"));
307
0
                        break;
308
0
                    default:
309
0
                        if (ivalue_valid)
310
0
                            proto_tree_add_string_format_value(fix_tree, hf, tvb, field_offset, tag->field_len, value,
311
0
                                "%s (%s)", value, val_to_str(pinfo->pool, ivalue, (const value_string *)field->table, "unknown %d"));
312
0
                        else {
313
0
                            pi = proto_tree_add_string(fix_tree, hf, tvb, field_offset, tag->field_len, value);
314
0
                            expert_add_info_format(pinfo, pi, &ei_fix_field_invalid, "Invalid string %s for fix field tag %i", value, field->tag);
315
0
                        }
316
0
                        break;
317
0
                    }
318
0
                }
319
0
            }
320
0
            else {
321
0
              proto_item *item;
322
323
              /* checksum */
324
0
              switch(tag_value) {
325
0
              case 10:
326
0
                {
327
0
                    proto_tree *checksum_tree;
328
0
                    uint8_t sum = 0;
329
0
                    const uint8_t *sum_data = tvb_get_ptr(tvb, 0, field_offset);
330
0
                    bool sum_ok;
331
0
                    unsigned j;
332
333
0
                    for (j = 0; j < field_offset; j++, sum_data++) {
334
0
                         sum += *sum_data;
335
0
                    }
336
0
                    sum_ok = (ivalue == sum);
337
0
                    if (sum_ok) {
338
0
                        item = proto_tree_add_string_format_value(fix_tree, hf, tvb, field_offset, tag->field_len,
339
0
                                value, "%s [correct]", value);
340
0
                    }
341
0
                    else {
342
0
                        item = proto_tree_add_string_format_value(fix_tree, hf, tvb, field_offset, tag->field_len,
343
0
                                value, "%s [incorrect should be %d]", value, sum);
344
0
                    }
345
0
                    checksum_tree = proto_item_add_subtree(item, ett_checksum);
346
0
                    item = proto_tree_add_boolean(checksum_tree, hf_fix_checksum_good, tvb, field_offset, tag->field_len, sum_ok);
347
0
                    proto_item_set_generated(item);
348
0
                    item = proto_tree_add_boolean(checksum_tree, hf_fix_checksum_bad, tvb, field_offset, tag->field_len, !sum_ok);
349
0
                    proto_item_set_generated(item);
350
0
                    if (!sum_ok)
351
0
                        expert_add_info(pinfo, item, &ei_fix_checksum_bad);
352
0
                }
353
0
                break;
354
0
              default:
355
0
                proto_tree_add_string(fix_tree, hf, tvb, field_offset, tag->field_len, value);
356
0
                break;
357
0
              }
358
0
            }
359
0
        }
360
0
        else if (tree) {
361
0
          proto_tree *field_tree;
362
363
          /* XXX - it could be -1 if the tag isn't a number */
364
0
          field_tree = proto_tree_add_subtree_format(fix_tree, tvb, field_offset, tag->field_len, ett_unknown, NULL,
365
0
              "%i: %s", tag_value, value);
366
0
          proto_tree_add_uint(field_tree, hf_fix_field_tag, tvb, field_offset, tag->tag_len, tag_value);
367
0
          proto_tree_add_item(field_tree, hf_fix_field_value, tvb, tag->value_offset, tag->value_len, ENC_ASCII);
368
0
        }
369
370
0
        field_offset =  tag->ctrla_offset + 1;
371
0
    }
372
8
    return tvb_captured_length(tvb);
373
8
}
374
375
static unsigned
376
get_fix_pdu_len(packet_info *pinfo, tvbuff_t *tvb, int offset, void *data _U_)
377
94
{
378
94
    unsigned fix_len;
379
380
94
    fix_len = fix_header_len(tvb, pinfo, offset);
381
94
    return fix_len;
382
94
}
383
384
/* ------------------------------------
385
   fixed-length part isn't really a constant but if we assume it's at least:
386
       8=FIX.x.y\01   10
387
       9=x\01          4
388
       35=x\01         5
389
       10=y\01         5
390
                      24
391
       it should catch all 9= size
392
*/
393
394
73
#define FIX_MIN_LEN 24
395
396
static int
397
dissect_fix_pdus(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
398
73
{
399
73
    tcp_dissect_pdus(tvb, pinfo, tree, fix_desegment, FIX_MIN_LEN,
400
73
                     get_fix_pdu_len, dissect_fix_packet, data);
401
402
73
    return tvb_captured_length(tvb);
403
73
}
404
405
static int
406
dissect_fix(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
407
54
{
408
54
    return dissect_fix_pdus(tvb, pinfo, tree, data);
409
54
}
410
411
/* Code to actually dissect the packets */
412
static bool
413
dissect_fix_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
414
3.72k
{
415
3.72k
    conversation_t *conv;
416
417
    /* get at least the fix version: 8=FIX.x.x */
418
3.72k
    if (fix_marker(tvb, 0) != 0) {
419
        /* not a fix packet */
420
3.70k
        return false;
421
3.70k
    }
422
423
19
    conv = find_or_create_conversation(pinfo);
424
19
    conversation_set_dissector(conv, fix_handle);
425
426
19
    dissect_fix_pdus(tvb, pinfo, tree, data);
427
19
    return true;
428
3.72k
}
429
430
static bool
431
dissect_fix_heur_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
432
0
{
433
0
    struct tlsinfo *tlsinfo = (struct tlsinfo *)data;
434
    /* get at least the fix version: 8=FIX.x.x */
435
0
    if (fix_marker(tvb, 0) != 0) {
436
        /* not a fix packet */
437
0
        return false;
438
0
    }
439
440
0
    dissect_fix_pdus(tvb, pinfo, tree, data);
441
0
    *(tlsinfo->app_handle) = fix_handle;
442
0
    return true;
443
0
}
444
445
/* this format is require because a script is used to build the C function
446
   that calls all the protocol registration.
447
*/
448
449
void
450
proto_register_fix(void)
451
16
{
452
16
    static hf_register_info hf[] = {
453
16
        { &hf_fix_data,
454
16
          { "Continuation Data", "fix.data", FT_BYTES, BASE_NONE, NULL, 0x00,
455
16
            NULL, HFILL }
456
16
        },
457
458
16
        { &hf_fix_field_tag,
459
16
          { "Field Tag",         "fix.field.tag", FT_UINT16, BASE_DEC, NULL, 0x0,
460
16
            "Field length.", HFILL }},
461
462
16
        { &hf_fix_field_value,
463
16
          { "Field Value",       "fix.field.value", FT_STRING, BASE_NONE, NULL, 0x0,
464
16
            NULL, HFILL }},
465
466
16
        { &hf_fix_checksum_good,
467
16
          { "Good Checksum",       "fix.checksum_good", FT_BOOLEAN, BASE_NONE, NULL, 0x0,
468
16
            "True: checksum matches packet content; False: doesn't match content or not checked", HFILL }},
469
470
16
        { &hf_fix_checksum_bad,
471
16
          { "Bad Checksum",        "fix.checksum_bad", FT_BOOLEAN, BASE_NONE, NULL, 0x0,
472
16
            "True: checksum doesn't match packet content; False: matches content or not checked", HFILL }},
473
16
    };
474
475
/* Setup protocol subtree array */
476
16
    static int *ett[] = {
477
16
        &ett_fix,
478
16
        &ett_unknown,
479
16
        &ett_badfield,
480
16
        &ett_checksum,
481
16
    };
482
483
16
    static ei_register_info ei[] = {
484
16
        { &ei_fix_checksum_bad, { "fix.checksum_bad.expert", PI_CHECKSUM, PI_ERROR, "Bad checksum", EXPFILL }},
485
16
        { &ei_fix_missing_field, { "fix.missing_field", PI_MALFORMED, PI_ERROR, "Missing mandatory field", EXPFILL }},
486
16
        { &ei_fix_tag_invalid, { "fix.tag.invalid", PI_MALFORMED, PI_ERROR, "Invalid Tag", EXPFILL }},
487
16
        { &ei_fix_field_invalid, { "fix.invalid_integer_string", PI_MALFORMED, PI_ERROR, "Invalid integer string", EXPFILL }}
488
16
    };
489
490
16
    module_t *fix_module;
491
16
    expert_module_t* expert_fix;
492
493
    /* register re-init routine */
494
16
    register_init_routine(&dissect_fix_init);
495
496
    /* Register the protocol name and description */
497
16
    proto_fix = proto_register_protocol("Financial Information eXchange Protocol", "FIX", "fix");
498
499
    /* Allow dissector to find be found by name. */
500
16
    fix_handle = register_dissector("fix", dissect_fix, proto_fix);
501
502
16
    proto_register_field_array(proto_fix, hf, array_length(hf));
503
16
    proto_register_field_array(proto_fix, hf_FIX, array_length(hf_FIX));
504
16
    proto_register_subtree_array(ett, array_length(ett));
505
16
    expert_fix = expert_register_protocol(proto_fix);
506
16
    expert_register_field_array(expert_fix, ei, array_length(ei));
507
508
16
    fix_module = prefs_register_protocol(proto_fix, NULL);
509
16
    prefs_register_bool_preference(fix_module, "desegment",
510
16
                                   "Reassemble FIX messages spanning multiple TCP segments",
511
16
                                   "Whether the FIX dissector should reassemble messages spanning multiple TCP segments."
512
16
                                   " To use this option, you must also enable"
513
16
                                   " \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
514
16
                                   &fix_desegment);
515
16
}
516
517
518
void
519
proto_reg_handoff_fix(void)
520
16
{
521
    /* Let the tcp dissector know that we're interested in traffic      */
522
16
    heur_dissector_add("tcp", dissect_fix_heur, "FIX over TCP", "fix_tcp", proto_fix, HEURISTIC_ENABLE);
523
16
    heur_dissector_add("tls", dissect_fix_heur_ssl, "FIX over TLS", "fix_tls", proto_fix, HEURISTIC_ENABLE);
524
16
    dissector_add_uint_range_with_preference("tcp.port", "", fix_handle);
525
16
}
526
527
/*
528
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
529
 *
530
 * Local variables:
531
 * c-basic-offset: 4
532
 * tab-width: 8
533
 * indent-tabs-mode: nil
534
 * End:
535
 *
536
 * vi: set shiftwidth=4 tabstop=8 expandtab:
537
 * :indentSize=4:tabSize=8:noTabs=true:
538
 */