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  LLFIX_ALIGN_CODE_32;
226  while (true)
227  {
228  if (buffer[index] == FixConstants::FIX_DELIMITER)
229  {
230  break;
231  }
232 
233  if (index == 0)
234  {
235  return false;
236  }
237 
238  index--;
239  }
240  #endif
241 
242  return true;
243  }
244 
245  LLFIX_FORCE_INLINE static void find_tag10_start_from_end(char* buffer, std::size_t buffer_size, int& index, int& final_tag10_delimiter_index)
246  {
247  if (buffer_size < 3)
248  return;
249 
250  const int max_index = static_cast<int>(buffer_size - 3);
251  if (index > max_index)
252  index = max_index;
253 
254  LLFIX_ALIGN_CODE_32;
255  while (true)
256  {
257  if (buffer[index] == '1' && buffer[index + 1] == '0' && buffer[index + 2] == FixConstants::FIX_EQUALS)
258  {
259  // FIND OUT TAG10
260  int temp_index = index + 2;
261 
262  LLFIX_ALIGN_CODE_32;
263  while (true)
264  {
265  if (temp_index == static_cast<int>(buffer_size))
266  {
267  break;
268  }
269 
270  if (buffer[temp_index] == FixConstants::FIX_DELIMITER)
271  {
272  final_tag10_delimiter_index = temp_index;
273  break;
274  }
275 
276  temp_index++;
277  }
278  break;
279  }
280 
281  if (index == 0)
282  {
283  break;
284  }
285 
286  index--;
287  }
288  }
289 
290  LLFIX_FORCE_INLINE static void find_begin_string_position(char* buffer, std::size_t buffer_size, int& begin_string_position)
291  {
292  std::size_t current_index = 0;
293 
294  LLFIX_ALIGN_CODE_32;
295  while (current_index<buffer_size-1)
296  {
297  if(buffer[current_index] == '8' && buffer[current_index+1] == FixConstants::FIX_EQUALS)
298  {
299  begin_string_position = static_cast<int>(current_index);
300  break;
301  }
302 
303  current_index++;
304  }
305  }
306 
307  LLFIX_FORCE_INLINE static void encode_checksum_no_simd(const char* buffer, std::size_t buffer_length, char* out)
308  {
309  assert(out);
310  uint32_t sum{ 0 };
311 
312  for (std::size_t i = 0; i < buffer_length; ++i)
313  {
314  sum += static_cast<unsigned char>(buffer[i]);
315  }
316 
317  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
318 
319  out[0] = '0' + (checksum / 100);
320  out[1] = '0' + ((checksum / 10) % 10);
321  out[2] = '0' + (checksum % 10);
322  }
323 
324  // No aligned address requirement
325  LLFIX_SIMD_TARGET_AVX2
326  static void encode_checksum_simd_avx2(const char* buffer, std::size_t buffer_length, char* out)
327  {
328  assert(out);
329 
330  uint32_t sum = 0;
331 
332  const std::size_t simd_width = 32;
333  const __m256i zero = _mm256_setzero_si256();
334  __m256i acc = zero;
335 
336  std::size_t i = 0;
337  for (; i + simd_width <= buffer_length; i += simd_width)
338  {
339  __m256i bytes = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(buffer + i));
340 
341  __m256i lo = _mm256_unpacklo_epi8(bytes, zero);
342  __m256i hi = _mm256_unpackhi_epi8(bytes, zero);
343 
344  acc = _mm256_add_epi16(acc, lo);
345  acc = _mm256_add_epi16(acc, hi);
346  }
347 
348  __m128i acc_lo = _mm256_extracti128_si256(acc, 0);
349  __m128i acc_hi = _mm256_extracti128_si256(acc, 1);
350  __m128i total = _mm_add_epi16(acc_lo, acc_hi);
351 
352  uint16_t temp[8];
353  _mm_storeu_si128(reinterpret_cast<__m128i*>(temp), total);
354 
355  for (int j = 0; j < 8; ++j)
356  sum += temp[j];
357 
358  for (; i < buffer_length; ++i)
359  sum += static_cast<unsigned char>(buffer[i]);
360 
361  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
362 
363  out[0] = '0' + (checksum / 100);
364  out[1] = '0' + ((checksum / 10) % 10);
365  out[2] = '0' + (checksum % 10);
366  }
367 
368  LLFIX_FORCE_INLINE static bool validate_checksum_no_simd(const char* buffer, std::size_t buffer_length, uint32_t actual_checksum)
369  {
370  uint32_t sum{ 0 };
371 
372  for (std::size_t i = 0; i < buffer_length; ++i)
373  {
374  sum += static_cast<unsigned char>(buffer[i]);
375  }
376 
377  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
378 
379  return checksum == actual_checksum;
380  }
381 
382  // No aligned address requirement
383  LLFIX_SIMD_TARGET_AVX2
384  static bool validate_checksum_simd_avx2(const char* buffer, std::size_t buffer_length, uint32_t actual_checksum)
385  {
386  uint32_t sum = 0;
387 
388  const std::size_t simd_width = 32;
389  const __m256i zero = _mm256_setzero_si256();
390  __m256i acc = zero;
391 
392  std::size_t i = 0;
393  for (; i + simd_width <= buffer_length; i += simd_width)
394  {
395  __m256i bytes = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(buffer + i));
396 
397  __m256i lo = _mm256_unpacklo_epi8(bytes, zero);
398  __m256i hi = _mm256_unpackhi_epi8(bytes, zero);
399 
400  acc = _mm256_add_epi16(acc, lo);
401  acc = _mm256_add_epi16(acc, hi);
402  }
403 
404  __m128i acc_lo = _mm256_extracti128_si256(acc, 0);
405  __m128i acc_hi = _mm256_extracti128_si256(acc, 1);
406  __m128i total = _mm_add_epi16(acc_lo, acc_hi);
407 
408  uint16_t temp[8];
409  _mm_storeu_si128(reinterpret_cast<__m128i*>(temp), total);
410 
411  for (int j = 0; j < 8; ++j)
412  sum += temp[j];
413 
414  for (; i < buffer_length; ++i)
415  sum += static_cast<unsigned char>(buffer[i]);
416 
417  const uint32_t checksum = sum % FixConstants::FIX_CHECKSUM_MODULO;
418 
419  return checksum == actual_checksum;
420  }
421 
422  static uint32_t pack_message_type(const std::string_view& mt)
423  {
424  assert(mt.size()>0 && mt.size() <= FixConstants::MAX_SUPPORTED_MESSAGE_TYPE_LENGTH);
425  uint32_t ret = 0;
426 
427  for (std::size_t i = 0; i < mt.size(); ++i)
428  ret |= uint32_t(uint8_t(mt[i])) << (i * 8);
429 
430  return ret;
431  }
432 
433  static std::string unpack_message_type(uint32_t encoded_msg_type)
434  {
435  std::string ret;
436  ret.reserve(FixConstants::MAX_SUPPORTED_MESSAGE_TYPE_LENGTH);
437 
438  for (std::size_t i = 0; i < FixConstants::MAX_SUPPORTED_MESSAGE_TYPE_LENGTH; ++i)
439  {
440  char c = char((encoded_msg_type >> (i * 8)) & 0xFF);
441 
442  if (c == '\0')
443  break;
444 
445  ret.push_back(c);
446  }
447 
448  return ret;
449  }
450 
451  // Slow and allocates memory but used only loading existing serialised files
452  static uint32_t get_sequence_number_value_from_fix_message(const std::string& buffer)
453  {
454  auto tag_value_pairs = StringUtilities::split(buffer, FixConstants::FIX_DELIMITER);
455 
456  for (const auto& tag_value_pair : tag_value_pairs)
457  {
458  if (tag_value_pair.length() > 3)
459  {
460  if (tag_value_pair[0] == '3' && tag_value_pair[1] == '4' && tag_value_pair[2] == FixConstants::FIX_EQUALS)
461  {
462  return Converters::chars_to_unsigned_int<uint32_t>(&tag_value_pair[3], tag_value_pair.length() - 3);
463  }
464  }
465  }
466 
467  return 0;
468  }
469 };
470 
471 // Wrapper for get_sequence_number_value_from_fix_message
472 struct FixMessageSequenceNumberExtractor
473 {
474  static uint32_t get_sequence_number_from_message(const std::string& message)
475  {
476  return FixUtilities::get_sequence_number_value_from_fix_message(message);
477  }
478 };
479 
480 } // 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