/* * Financial Information eXchange Protocol * * Copyright 2020 Baptiste Assmann * * 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 #include /* * Return the corresponding numerical tag id if looks like a valid FIX * protocol tag ID. Otherwise, 0 is returned (0 is an invalid id). * * If 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 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 , 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 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 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 */ 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 and return the value of . * * Returns the corresponding value if is found. If is not found * because more data are required, the message with a length set to 0 is * returned. If 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 * =FIX_DELIMITER with and 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) { /* found, return the corresponding value */ return v; } /* CheckSum tag is the last one, no found */ if (id == FIX_TAG_CheckSum) goto not_found_or_invalid; parser = istadv(parser, istlen(t) + istlen(v) + 2); } /* not enough data to find */ return ist2(istptr(msg), 0); not_found_or_invalid: return IST_NULL; }