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