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