llfix
Low-latency FIX engine
fix_utilities.h
1 /*
2 MIT License
3 
4 Copyright (c) 2026 Coreware Limited
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 #pragma once
25 
26 #include <cassert>
27 #include <cstddef>
28 #include <cstdint>
29 #include <cstring>
30 #include <ctime>
31 
32 #include <string>
33 #include <string_view>
34 
35 #include <immintrin.h>
36 
37 #ifdef __linux__ // VOLTRON_EXCLUDE
38 #include <string.h> // memrchr
39 #endif // VOLTRON_EXCLUDE
40 
41 #include "core/compiler/hints_branch_predictor.h"
42 #include "core/compiler/hints_hot_code.h"
43 #include "core/cpu/simd_attributes.h"
44 #include "core/os/vdso.h"
45 
46 #include "core/utilities/converters.h"
47 #include "core/utilities/std_string_utilities.h"
48 
49 #include "fix_constants.h"
50 #include "fix_string.h"
51 
52 namespace llfix
53 {
54 
62 {
63  public:
64 
65  static void get_reject_reason_text(char* target, std::size_t& copied_length, uint32_t reject_reason_code)
66  {
67  assert(target);
68  const char* text = nullptr;
69 
70  switch (reject_reason_code)
71  {
72  case FixConstants::FIX_ERROR_CODE_INVALID_TAG_NUMBER : text = "Invalid tag number"; break;
73  case FixConstants::FIX_ERROR_CODE_REQUIRED_TAG_MISSING : text = "Required tag missing"; break;
74  case FixConstants::FIX_ERROR_CODE_TAG_UNDEFINED_FOR_MSG_TYPE : text = "Tag not defined for this message type"; break;
75  case FixConstants::FIX_ERROR_CODE_UNDEFINED_TAG : text = "Undefined Tag"; break;
76  case FixConstants::FIX_ERROR_CODE_TAG_WITHOUT_VALUE : text = "Tag specified without a value"; break;
77  case FixConstants::FIX_ERROR_CODE_VALUE_INCORRECT_FOR_TAG : text = "Value is incorrect (out of range) for this tag"; break;
78  case FixConstants::FIX_ERROR_CODE_FORMAT_INCORRECT_FOR_TAG : text = "Incorrect data format for value"; break;
79  case FixConstants::FIX_ERROR_CODE_FORMAT_DECRYPTION_PROBLEM : text = "Decryption problem"; break;
80  case FixConstants::FIX_ERROR_CODE_FORMAT_SIGNATURE_PROBLEM : text = "Signature <89> problem"; break;
81  case FixConstants::FIX_ERROR_CODE_COMPID_PROBLEM : text = "CompID problem"; break;
82  case FixConstants::FIX_ERROR_CODE_SENDING_TIME_ACCURACY_PROBLEM : text = "SendingTime <52> accuracy problem"; break;
83  case FixConstants::FIX_ERROR_CODE_INVALID_MSG_TYPE : text = "Invalid MsgType <35>"; break;
84  case FixConstants::FIX_ERROR_CODE_XML_VALIDATION_ERROR : text = "XML Validation error"; break;
85  case FixConstants::FIX_ERROR_CODE_TAG_APPEARS_MORE_THAN_ONCE : text = "Tag appears more than once"; break;
86  case FixConstants::FIX_ERROR_CODE_TAG_OUT_OF_ORDER : text = "Tag specified out of required order"; break;
87  case FixConstants::FIX_ERROR_CODE_RG_OUT_OF_ORDER : text = "Repeating group fields out of order"; break;
88  case FixConstants::FIX_ERROR_CODE_INCORRECT_NUMINGROUP : text = "Incorrect NumInGroup count for repeating group"; break;
89  case FixConstants::FIX_ERROR_CODE_NON_BINARY_VALUE_WITH_SOH : text = "Non \"Data\" value includes field delimiter (<SOH> character)"; break;
90  case FixConstants::FIX_ERROR_CODE_OTHER: text = "Other"; break;
91  default: text = "Other"; break;
92  }
93 
94  copied_length = strlen(text);
95  llfix_builtin_memcpy(target, text, copied_length);
96  target[copied_length] = '\0';
97  }
98 
108  static std::string fix_to_human_readible(const char* buffer, std::size_t buffer_length)
109  {
110  assert(buffer != nullptr && buffer_length > 0 );
111  std::string ret;
112  ret.reserve(buffer_length);
113 
114  for (std::size_t i = 0; i < buffer_length; ++i)
115  {
116  ret.push_back(buffer[i] == FixConstants::FIX_DELIMITER ? '|' : buffer[i]);
117  }
118 
119  return ret;
120  }
121 
122  static bool is_a_non_retransmittable_admin_message_type(FixString* str)
123  {
124  assert(str);
125  assert(str->length());
126 
127  if(str->length() != 1)
128  {
129  return false;
130  }
131 
132  char single_char_msg_type = str->data()[0];
133 
134  switch(single_char_msg_type)
135  {
136  // All session level messages except rejects based on FIX session layer specs 4.8.5
137  case FixConstants::MSG_TYPE_HEARTBEAT: return true;
138  case FixConstants::MSG_TYPE_TEST_REQUEST: return true;
139  case FixConstants::MSG_TYPE_RESEND_REQUEST: return true;
140  case FixConstants::MSG_TYPE_SEQUENCE_RESET: return true;
141  case FixConstants::MSG_TYPE_LOGON: return true;
142  case FixConstants::MSG_TYPE_LOGOUT: return true;
143  default: return false;
144  }
145  }
146 
147  LLFIX_FORCE_INLINE static void encode_current_time(FixString* target, VDSO::SubsecondPrecision subsecond_precision)
148  {
149  assert(target);
150 
151  static constexpr uint32_t TIME_LENGTHS[] = {
152  27, // NANOSECONDS
153  24, // MICROSECONDS
154  21, // MILLISECONDS
155  17 // NONE
156  };
157 
158  using FuncPtr = void (*)(char*);
159 
160  static constexpr FuncPtr FUNC_TABLE[] =
161  {
162  &VDSO::get_datetime_as_string<true, VDSO::SubsecondPrecision::NANOSECONDS>,
163  &VDSO::get_datetime_as_string<true, VDSO::SubsecondPrecision::MICROSECONDS>,
164  &VDSO::get_datetime_as_string<true, VDSO::SubsecondPrecision::MILLISECONDS>,
165  &VDSO::get_datetime_as_string<true, VDSO::SubsecondPrecision::NONE>
166  };
167 
168  const auto index = static_cast<std::size_t>(subsecond_precision);
169 
170  FUNC_TABLE[index](target->data());
171  target->set_length(TIME_LENGTHS[index]);
172  }
173 
174  LLFIX_FORCE_INLINE static bool is_utc_timestamp_stale(const std::string_view& value, int max_allowed_age_seconds)
175  {
176  const char* value_buffer = value.data();
177 
178  auto to_int_2 = [](const char* s)
179  {
180  return (s[0] - '0') * 10 + (s[1] - '0');
181  };
182 
183  auto to_int_4 = [](const char* s)
184  {
185  return (s[0] - '0') * 1000 +
186  (s[1] - '0') * 100 +
187  (s[2] - '0') * 10 +
188  (s[3] - '0');
189  };
190 
191  std::tm tm{};
192  tm.tm_year = to_int_4(value_buffer) - 1900;
193  tm.tm_mon = to_int_2(value_buffer + 4) - 1;
194  tm.tm_mday = to_int_2(value_buffer + 6);
195  tm.tm_hour = to_int_2(value_buffer + 9);
196  tm.tm_min = to_int_2(value_buffer + 12);
197  tm.tm_sec = to_int_2(value_buffer + 15);
198 
199  time_t msg_time;
200  #ifdef __linux__
201  msg_time = timegm(&tm);
202  #elif _WIN32
203  msg_time = _mkgmtime(&tm);
204  #endif
205 
206  if (msg_time == -1)
207  return true;
208 
209  time_t now = time(nullptr);
210  return (now - msg_time) > max_allowed_age_seconds;
211  }
212 
213  LLFIX_FORCE_INLINE static bool find_delimiter_from_end(char* buffer, std::size_t buffer_size, int& index)
214  {
215  #ifdef __linux__
216  void* p = memrchr(buffer, FixConstants::FIX_DELIMITER, buffer_size);
217 
218  if (llfix_unlikely(!p) )
219  {
220  return false;
221  }
222 
223  index = static_cast<int>(static_cast<char*>(p) - buffer);
224  #elif _WIN32
225  while (true)
226  {
227  if (buffer[index] == FixConstants::FIX_DELIMITER)
228  {
229  break;
230  }
231 
232  if (index == 0)
233  {
234  return false;
235  }
236 
237  index--;
238  }
239  #endif
240 
241  return true;
242  }
243 
244  LLFIX_FORCE_INLINE static void find_tag10_start_from_end(char* buffer, std::size_t buffer_size, int& index, int& final_tag10_delimiter_index)
245  {
246  if (buffer_size < 3)
247  return;
248 
249  const int max_index = static_cast<int>(buffer_size - 3);
250  if (index > max_index)
251  index = max_index;
252 
253  while (true)
254  {
255  if (buffer[index] == '1' && buffer[index + 1] == '0' && buffer[index + 2] == FixConstants::FIX_EQUALS)
256  {
257  // FIND OUT TAG10
258  int temp_index = index + 2;
259 
260  while (true)
261  {
262  if (temp_index == static_cast<int>(buffer_size))
263  {
264  break;
265  }
266 
267  if (buffer[temp_index] == FixConstants::FIX_DELIMITER)
268  {
269  final_tag10_delimiter_index = temp_index;
270  break;
271  }
272 
273  temp_index++;
274  }
275  break;
276  }
277 
278  if (index == 0)
279  {
280  break;
281  }
282 
283  index--;
284  }
285  }
286 
287  LLFIX_FORCE_INLINE static void find_begin_string_position(char* buffer, std::size_t buffer_size, int& begin_string_position)
288  {
289  std::size_t current_index = 0;
290 
291  while (current_index<buffer_size-1)
292  {
293  if(buffer[current_index] == '8' && buffer[current_index+1] == FixConstants::FIX_EQUALS)
294  {
295  begin_string_position = static_cast<int>(current_index);
296  break;
297  }
298 
299  current_index++;
300  }
301  }
302 
303  LLFIX_FORCE_INLINE static void encode_checksum_no_simd(const char* buffer, std::size_t buffer_length, char* out)
304  {
305  assert(out);
306  uint32_t sum{ 0 };
307 
308  for (std::size_t i = 0; i < buffer_length; ++i)
309  {
310  sum += static_cast<unsigned char>(buffer[i]);
311  }
312 
313  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
314 
315  out[0] = '0' + (checksum / 100);
316  out[1] = '0' + ((checksum / 10) % 10);
317  out[2] = '0' + (checksum % 10);
318  }
319 
320  // No aligned address requirement
321  LLFIX_SIMD_TARGET_AVX2
322  static void encode_checksum_simd_avx2(const char* buffer, std::size_t buffer_length, char* out)
323  {
324  assert(out);
325 
326  uint32_t sum = 0;
327 
328  const std::size_t simd_width = 32;
329  const __m256i zero = _mm256_setzero_si256();
330  __m256i acc = zero;
331 
332  std::size_t i = 0;
333  for (; i + simd_width <= buffer_length; i += simd_width)
334  {
335  __m256i bytes = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(buffer + i));
336 
337  __m256i lo = _mm256_unpacklo_epi8(bytes, zero);
338  __m256i hi = _mm256_unpackhi_epi8(bytes, zero);
339 
340  acc = _mm256_add_epi16(acc, lo);
341  acc = _mm256_add_epi16(acc, hi);
342  }
343 
344  __m128i acc_lo = _mm256_extracti128_si256(acc, 0);
345  __m128i acc_hi = _mm256_extracti128_si256(acc, 1);
346  __m128i total = _mm_add_epi16(acc_lo, acc_hi);
347 
348  uint16_t temp[8];
349  _mm_storeu_si128(reinterpret_cast<__m128i*>(temp), total);
350 
351  for (int j = 0; j < 8; ++j)
352  sum += temp[j];
353 
354  for (; i < buffer_length; ++i)
355  sum += static_cast<unsigned char>(buffer[i]);
356 
357  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
358 
359  out[0] = '0' + (checksum / 100);
360  out[1] = '0' + ((checksum / 10) % 10);
361  out[2] = '0' + (checksum % 10);
362  }
363 
364  LLFIX_FORCE_INLINE static bool validate_checksum_no_simd(const char* buffer, std::size_t buffer_length, uint32_t actual_checksum)
365  {
366  uint32_t sum{ 0 };
367 
368  for (std::size_t i = 0; i < buffer_length; ++i)
369  {
370  sum += static_cast<unsigned char>(buffer[i]);
371  }
372 
373  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
374 
375  return checksum == actual_checksum;
376  }
377 
378  // No aligned address requirement
379  LLFIX_SIMD_TARGET_AVX2
380  static bool validate_checksum_simd_avx2(const char* buffer, std::size_t buffer_length, uint32_t actual_checksum)
381  {
382  uint32_t sum = 0;
383 
384  const std::size_t simd_width = 32;
385  const __m256i zero = _mm256_setzero_si256();
386  __m256i acc = zero;
387 
388  std::size_t i = 0;
389  for (; i + simd_width <= buffer_length; i += simd_width)
390  {
391  __m256i bytes = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(buffer + i));
392 
393  __m256i lo = _mm256_unpacklo_epi8(bytes, zero);
394  __m256i hi = _mm256_unpackhi_epi8(bytes, zero);
395 
396  acc = _mm256_add_epi16(acc, lo);
397  acc = _mm256_add_epi16(acc, hi);
398  }
399 
400  __m128i acc_lo = _mm256_extracti128_si256(acc, 0);
401  __m128i acc_hi = _mm256_extracti128_si256(acc, 1);
402  __m128i total = _mm_add_epi16(acc_lo, acc_hi);
403 
404  uint16_t temp[8];
405  _mm_storeu_si128(reinterpret_cast<__m128i*>(temp), total);
406 
407  for (int j = 0; j < 8; ++j)
408  sum += temp[j];
409 
410  for (; i < buffer_length; ++i)
411  sum += static_cast<unsigned char>(buffer[i]);
412 
413  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
414 
415  return checksum == actual_checksum;
416  }
417 
418  static uint32_t pack_message_type(const std::string_view& mt)
419  {
420  assert(mt.size()>0 && mt.size() <= FixConstants::MAX_SUPPORTED_MESSAGE_TYPE_LENGTH);
421  uint32_t ret = 0;
422 
423  for (std::size_t i = 0; i < mt.size(); ++i)
424  ret |= uint32_t(uint8_t(mt[i])) << (i * 8);
425 
426  return ret;
427  }
428 
429  static std::string unpack_message_type(uint32_t encoded_msg_type)
430  {
431  std::string ret;
432  ret.reserve(FixConstants::MAX_SUPPORTED_MESSAGE_TYPE_LENGTH);
433 
434  for (std::size_t i = 0; i < FixConstants::MAX_SUPPORTED_MESSAGE_TYPE_LENGTH; ++i)
435  {
436  char c = char((encoded_msg_type >> (i * 8)) & 0xFF);
437 
438  if (c == '\0')
439  break;
440 
441  ret.push_back(c);
442  }
443 
444  return ret;
445  }
446 
447  // Slow and allocates memory but used only loading existing serialised files
448  static uint32_t get_sequence_number_value_from_fix_message(const std::string& buffer)
449  {
450  auto tag_value_pairs = StringUtilities::split(buffer, FixConstants::FIX_DELIMITER);
451 
452  for (const auto& tag_value_pair : tag_value_pairs)
453  {
454  if (tag_value_pair.length() > 3)
455  {
456  if (tag_value_pair[0] == '3' && tag_value_pair[1] == '4' && tag_value_pair[2] == FixConstants::FIX_EQUALS)
457  {
458  return Converters::chars_to_unsigned_int<uint32_t>(&tag_value_pair[3], tag_value_pair.length() - 3);
459  }
460  }
461  }
462 
463  return 0;
464  }
465 };
466 
467 // Wrapper for get_sequence_number_value_from_fix_message
468 struct FixMessageSequenceNumberExtractor
469 {
470  static uint32_t get_sequence_number_from_message(const std::string& message)
471  {
472  return FixUtilities::get_sequence_number_value_from_fix_message(message);
473  }
474 };
475 
476 } // namespace
llfix::FixUtilities
Utility functions for working with FIX messages.
Definition: fix_utilities.h:61
llfix::FixUtilities::fix_to_human_readible
static std::string fix_to_human_readible(const char *buffer, std::size_t buffer_length)
Converts a FIX message buffer to a human-readable string.
Definition: fix_utilities.h:108