llfix
Low-latency FIX engine
incoming_fix_message.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 <cstdint>
27 #include <cstddef>
28 #include <iterator>
29 #include <type_traits>
30 #include <string>
31 #include <string_view>
32 
33 #include "core/compiler/hints_branch_predictor.h"
34 #include "core/compiler/unused.h"
35 
36 #include "core/os/assert_msg.h"
37 
38 #include "core/utilities/converters.h"
39 #include "core/utilities/dictionary.h"
40 
41 #include "electronic_trading/common/fixed_point.h"
42 
43 #include "fix_constants.h"
44 #include "fix_string_view.h"
45 #include "incoming_fix_repeating_groups.h"
46 
47 namespace llfix
48 {
49 
50 struct IncomingValue
51 {
52  FixStringView* value = nullptr;
53  uint64_t generation_id = 0;
54 };
55 
68 {
69  public:
70 
71  IncomingFixMessage() = default;
72  ~IncomingFixMessage() = default;
73 
74  bool initialise()
75  {
76  return m_dict.initialise(64);
77  }
78 
85  bool has_tag(uint32_t tag) const
86  {
87  if (m_dict.has_key(tag) == false)
88  {
89  return false;
90  }
91 
92  return m_dict[tag].generation_id == m_generation_id;
93  }
94 
95  void set_tag(uint32_t tag, FixStringView* value)
96  {
97  IncomingValue node;
98  node.value = value;
99  node.generation_id = m_generation_id;
100 
101  if (llfix_likely(m_dict.has_key(tag)))
102  {
103  m_dict.set_existing_item(tag, node);
104  }
105  else
106  {
107  m_dict.insert(tag, node);
108  }
109  }
110 
111  void copy_non_dirty_tag_values_from(const IncomingFixMessage& other)
112  {
113  for (const auto& item : other.m_dict)
114  {
115  if(other.has_tag(item.key))
116  {
117  set_tag(item.key, item.value.value);
118  }
119  }
120 
121  m_repeating_groups.copy_non_dirty_values_from(other.m_repeating_groups);
122  }
123 
124  IncomingFixRepeatingGroups<FixStringView>& get_repeating_groups()
125  {
126  return m_repeating_groups;
127  }
128 
129  void set_repeating_group_tag(uint32_t tag, FixStringView* value)
130  {
131  m_repeating_groups.set(tag, value);
132  }
133 
141  bool has_repeating_group_tag(uint32_t tag) const
142  {
143  return m_repeating_groups.has_tag(tag);
144  }
145 
146  void reset()
147  {
148  m_generation_id++;
149 
150  if(m_generation_id == 0)
151  {
152  // Wrap-around protection to avoid stale values
153  for (auto& it : m_dict)
154  {
155  it.value.generation_id = 0;
156  }
157 
158  m_generation_id++;
159  }
160 
161  // Reset the repeating groups
162  m_repeating_groups.reset();
163  }
164 
179  std::string to_string() const
180  {
181  std::string ret;
182 
183  auto is_header_tag = [](uint32_t tag)
184  {
185  if (tag == FixConstants::TAG_BEGIN_STRING || tag == FixConstants::TAG_BODY_LENGTH || tag == FixConstants::TAG_MSG_TYPE || tag == FixConstants::TAG_MSG_SEQ_NUM || tag == FixConstants::TAG_SENDING_TIME || tag == FixConstants::TAG_SENDER_COMP_ID || tag == FixConstants::TAG_TARGET_COMP_ID)
186  return true;
187  return false;
188  };
189 
190  auto is_trailer_tag = [](uint32_t tag)
191  {
192  if (tag == FixConstants::TAG_CHECKSUM)
193  return true;
194  return false;
195  };
196 
197  try
198  {
199  // HEADER
200  if(has_tag(FixConstants::TAG_BEGIN_STRING)) ret += "8=" + get_tag_value_as<std::string>(FixConstants::TAG_BEGIN_STRING) + '|';
201  if (has_tag(FixConstants::TAG_BODY_LENGTH)) ret += "9=" + get_tag_value_as<std::string>(FixConstants::TAG_BODY_LENGTH) + '|';
202  if (has_tag(FixConstants::TAG_MSG_TYPE)) ret += "35=" + get_tag_value_as<std::string>(FixConstants::TAG_MSG_TYPE) + '|';
203  if (has_tag(FixConstants::TAG_MSG_SEQ_NUM)) ret += "34=" + get_tag_value_as<std::string>(FixConstants::TAG_MSG_SEQ_NUM) + '|';
204  if (has_tag(FixConstants::TAG_SENDER_COMP_ID)) ret += "49=" + get_tag_value_as<std::string>(FixConstants::TAG_SENDER_COMP_ID) + '|';
205  if (has_tag(FixConstants::TAG_SENDING_TIME)) ret += "52=" + get_tag_value_as<std::string>(FixConstants::TAG_SENDING_TIME) + '|';
206  if (has_tag(FixConstants::TAG_TARGET_COMP_ID)) ret += "56=" + get_tag_value_as<std::string>(FixConstants::TAG_TARGET_COMP_ID) + '|';
207 
208  // BODY
209  for (const auto& item : m_dict)
210  {
211  if (item.value.generation_id == m_generation_id)
212  {
213  if (is_header_tag(item.key) == false && is_trailer_tag(item.key) == false)
214  {
215  ret += std::to_string(item.key) + '=' + item.value.value->to_string() + '|';
216  }
217  }
218  }
219 
220  // BODY - REPEATING GROUPS
221  ret += m_repeating_groups.to_string();
222 
223  // TRAILER
224  if (has_tag(FixConstants::TAG_CHECKSUM)) ret += "10=" + get_tag_value_as<std::string>(FixConstants::TAG_CHECKSUM) + '|';
225  }
226  catch (...)
227  {
228  return "An error occured during IncomingFixMessage::to_string call";
229  }
230 
231  return ret;
232  }
234  // GET TAG VALUE METHODS
235  FixStringView* get_tag_value(uint32_t tag) const
236  {
237  llfix_assert_msg(m_dict.has_key(tag) == true, "You should call has_tag first");
238  llfix_assert_msg(m_dict[tag].generation_id == m_generation_id, "You should call has_tag first");
239  return m_dict[tag].value;
240  }
241 
260  template<typename T>
261  T get_tag_value_as(uint32_t tag, std::size_t decimal_points=0) const
262  {
263  llfix_assert_msg(m_dict.has_key(tag) == true, "You should call has_tag first");
264  llfix_assert_msg(m_dict[tag].generation_id == m_generation_id, "You should call has_tag first");
265 
266  if constexpr (std::is_same_v<T, std::string>)
267  {
268  LLFIX_UNUSED(decimal_points);
269  return m_dict[tag].value->to_string();
270 
271  }
272  else if constexpr (std::is_same_v<T, char>)
273  {
274  LLFIX_UNUSED(decimal_points);
275  return m_dict[tag].value->data()[0];
276  }
277  else if constexpr (std::is_same_v<T, std::string_view>)
278  {
279  LLFIX_UNUSED(decimal_points);
280  return m_dict[tag].value->to_string_view();
281  }
282  else if constexpr (std::is_same_v<T, bool>)
283  {
284  LLFIX_UNUSED(decimal_points);
285  return (m_dict[tag].value->data()[0] == FixConstants::FIX_BOOLEAN_TRUE) ? true : false;
286  }
287  else if constexpr (std::is_same_v<T, FixedPoint>)
288  {
289  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
290  FixedPoint fp;
291  fp.set_decimal_points(static_cast<uint32_t>(decimal_points));
292  fp.set_from_chars(m_dict[tag].value->c_str(), m_dict[tag].value->length());
293  return fp;
294  }
295  else if constexpr (std::is_floating_point<T>::value)
296  {
297  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
298  return static_cast<T>(Converters::chars_to_double(m_dict[tag].value->c_str(), m_dict[tag].value->length(), decimal_points));
299  }
300  else if constexpr (std::is_integral<T>::value && std::is_signed<T>::value)
301  {
302  LLFIX_UNUSED(decimal_points);
303  return Converters::chars_to_int<int>(m_dict[tag].value->c_str(), m_dict[tag].value->length());
304  }
305  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint64_t))
306  {
307  LLFIX_UNUSED(decimal_points);
308  return Converters::chars_to_unsigned_int<uint64_t>(m_dict[tag].value->c_str(), m_dict[tag].value->length());
309  }
310  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint32_t))
311  {
312  LLFIX_UNUSED(decimal_points);
313  return Converters::chars_to_unsigned_int<uint32_t>(m_dict[tag].value->c_str(), m_dict[tag].value->length());
314  }
315  else
316  {
317  static_assert(always_false_v<T>, "get_tag_value_as unsupported type");
318  }
319  }
320 
340  template<typename T>
341  T get_repeating_group_tag_value_as(uint32_t tag, std::size_t index, std::size_t decimal_points = 0) const
342  {
343  FixStringView* str_val = m_repeating_groups.get_value(tag, index);
344  llfix_assert_msg(str_val != nullptr, "You should call has_repeating_group_tag first");
345 
346  if constexpr (std::is_same_v<T, std::string>)
347  {
348  LLFIX_UNUSED(decimal_points);
349  return str_val->to_string();
350  }
351  else if constexpr (std::is_same_v<T, char>)
352  {
353  LLFIX_UNUSED(decimal_points);
354  return str_val->data()[0];
355  }
356  else if constexpr (std::is_same_v<T, std::string_view>)
357  {
358  LLFIX_UNUSED(decimal_points);
359  return str_val->to_string_view();
360  }
361  else if constexpr (std::is_same_v<T, bool>)
362  {
363  LLFIX_UNUSED(decimal_points);
364  return (str_val->data()[0] == FixConstants::FIX_BOOLEAN_TRUE) ? true : false;
365  }
366  else if constexpr (std::is_same_v<T, FixedPoint>)
367  {
368  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
369  FixedPoint fp;
370  fp.set_decimal_points(static_cast<uint32_t>(decimal_points));
371  fp.set_from_chars(str_val->c_str(), str_val->length());
372  return fp;
373  }
374  else if constexpr (std::is_floating_point<T>::value)
375  {
376  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
377  return Converters::chars_to_double(str_val->c_str(), str_val->length(), decimal_points);
378  }
379  else if constexpr (std::is_integral<T>::value && std::is_signed<T>::value)
380  {
381  LLFIX_UNUSED(decimal_points);
382  return Converters::chars_to_int<int>(str_val->c_str(), str_val->length());
383  }
384  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint64_t))
385  {
386  LLFIX_UNUSED(decimal_points);
387  return Converters::chars_to_unsigned_int<uint64_t>(str_val->c_str(), str_val->length());
388  }
389  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint32_t))
390  {
391  LLFIX_UNUSED(decimal_points);
392  return Converters::chars_to_unsigned_int<uint32_t>(str_val->c_str(), str_val->length());
393  }
394  else
395  {
396  static_assert(always_false_v<T>, "get_repeating_group_tag_value_as unsupported type");
397  }
398  }
400  // VALIDATION METHODS
401  bool is_tag_value_numeric(uint32_t tag) const
402  {
403  llfix_assert_msg(m_dict.has_key(tag) == true, "You should call has_tag first");
404  llfix_assert_msg(m_dict[tag].generation_id == m_generation_id, "You should call has_tag first");
405  return m_dict[tag].value->is_numeric();
406  }
407 
408  bool validate_count_tag(uint32_t tag, uint32_t& out_reject_message_code) const
409  {
410  return m_repeating_groups.validate_count_tag(tag, out_reject_message_code);
411  }
412 
413  Dictionary<uint32_t, IncomingValue>* get_dictionary()
414  {
415  return &m_dict;
416  }
417 
419  // OTHERS
420  uint64_t get_generation_id() const { return m_generation_id; }
421 
422  class const_iterator
423  {
424  public:
425 
426  const_iterator(Dictionary<uint32_t, IncomingValue>::iterator current, Dictionary<uint32_t, IncomingValue>::iterator end, uint64_t generation_id)
427  : m_current(current), m_end(end), m_generation_id(generation_id)
428  {
429  skip_stale_entries();
430  }
431 
432  const Dictionary<uint32_t, IncomingValue>::DictionaryNode& operator*() const
433  {
434  return *m_current;
435  }
436 
437  const_iterator& operator++()
438  {
439  ++m_current;
440  skip_stale_entries();
441  return *this;
442  }
443 
444  friend bool operator==(const const_iterator& a, const const_iterator& b)
445  {
446  return a.m_current == b.m_current;
447  }
448 
449  friend bool operator!=(const const_iterator& a, const const_iterator& b)
450  {
451  return a.m_current != b.m_current;
452  }
453 
454  private:
455  void skip_stale_entries()
456  {
457  while (m_current != m_end && m_current->value.generation_id != m_generation_id)
458  {
459  ++m_current;
460  }
461  }
462 
463  Dictionary<uint32_t, IncomingValue>::iterator m_current;
464  Dictionary<uint32_t, IncomingValue>::iterator m_end;
465  uint64_t m_generation_id = 0;
466  };
467 
468  const_iterator begin() const
469  {
470  return const_iterator(m_dict.begin(), m_dict.end(), m_generation_id);
471  }
472 
473  const_iterator end() const
474  {
475  return const_iterator(m_dict.end(), m_dict.end(), m_generation_id);
476  }
477 
478  private:
479  uint64_t m_generation_id = 1;
480  mutable Dictionary<uint32_t, IncomingValue> m_dict;
481  IncomingFixRepeatingGroups<FixStringView> m_repeating_groups;
482 
483  template <typename>
484  static constexpr bool always_false_v = false;
485 
486  IncomingFixMessage(const IncomingFixMessage& other) = delete;
487  IncomingFixMessage& operator= (const IncomingFixMessage& other) = delete;
488  IncomingFixMessage(IncomingFixMessage&& other) = delete;
489  IncomingFixMessage& operator=(IncomingFixMessage&& other) = delete;
490 };
491 
492 } // namespace
llfix::IncomingFixMessage::to_string
std::string to_string() const
Serialises the FIX message into a human-readable string.
Definition: incoming_fix_message.h:179
llfix::FixedPoint::set_from_chars
void set_from_chars(const char *buffer, std::size_t length)
Set value from a character buffer representing a decimal number.
Definition: fixed_point.h:131
llfix::IncomingFixMessage::has_repeating_group_tag
bool has_repeating_group_tag(uint32_t tag) const
Checks whether a repeating group tag exists.
Definition: incoming_fix_message.h:141
llfix::FixedPoint::set_decimal_points
void set_decimal_points(uint32_t n)
Set the number of decimal points for this value.
Definition: fixed_point.h:89
llfix::IncomingFixMessage::has_tag
bool has_tag(uint32_t tag) const
Checks whether a FIX tag exists and is valid.
Definition: incoming_fix_message.h:85
llfix::FixedPoint
Represents an unsigned fixed-point numeric value with a configurable number of decimal points....
Definition: fixed_point.h:81
llfix::IncomingFixMessage::get_repeating_group_tag_value_as
T get_repeating_group_tag_value_as(uint32_t tag, std::size_t index, std::size_t decimal_points=0) const
Retrieves a repeating group tag value converted to the requested type.
Definition: incoming_fix_message.h:341
llfix::IncomingFixMessage
Represents a parsed incoming FIX message.
Definition: incoming_fix_message.h:67
llfix::IncomingFixMessage::get_tag_value_as
T get_tag_value_as(uint32_t tag, std::size_t decimal_points=0) const
Retrieves a FIX tag value converted to the requested type.
Definition: incoming_fix_message.h:261