llfix
Low-latency FIX engine
incoming_fix_message.h
1 // DISCLAIMER_PLACEHOLDER
2 #pragma once
3 
4 #include <cstdint>
5 #include <cstddef>
6 #include <type_traits>
7 #include <string>
8 #include <string_view>
9 
10 #include "core/compiler/hints_branch_predictor.h"
11 #include "core/compiler/unused.h"
12 
13 #include "core/os/assert_msg.h"
14 
15 #include "core/utilities/converters.h"
16 #include "core/utilities/dictionary.h"
17 
18 #include "electronic_trading/common/fixed_point.h"
19 
20 #include "fix_constants.h"
21 #include "fix_string_view.h"
22 #include "incoming_fix_repeating_groups.h"
23 
24 namespace llfix
25 {
26 
27 struct IncomingValue
28 {
29  FixStringView* value = nullptr;
30  uint64_t generation_id = 0;
31 };
32 
45 {
46  public:
47 
48  IncomingFixMessage() = default;
49  ~IncomingFixMessage() = default;
50 
51  bool initialise()
52  {
53  return m_dict.initialise(64);
54  }
55 
62  bool has_tag(uint32_t tag) const
63  {
64  if (m_dict.has_key(tag) == false)
65  {
66  return false;
67  }
68 
69  return m_dict[tag].generation_id == m_generation_id;
70  }
71 
72  void set_tag(uint32_t tag, FixStringView* value)
73  {
74  IncomingValue node;
75  node.value = value;
76  node.generation_id = m_generation_id;
77 
78  if (llfix_likely(m_dict.has_key(tag)))
79  {
80  m_dict.set_existing_item(tag, node);
81  }
82  else
83  {
84  m_dict.insert(tag, node);
85  }
86  }
87 
88  void copy_non_dirty_tag_values_from(const IncomingFixMessage& other)
89  {
90  for (const auto& item : other.m_dict)
91  {
92  if(other.has_tag(item.key))
93  {
94  set_tag(item.key, item.value.value);
95  }
96  }
97 
98  m_repeating_groups.copy_non_dirty_values_from(other.m_repeating_groups);
99  }
100 
101  IncomingFixRepeatingGroups<FixStringView>& get_repeating_groups()
102  {
103  return m_repeating_groups;
104  }
105 
106  void set_repeating_group_tag(uint32_t tag, FixStringView* value)
107  {
108  m_repeating_groups.set(tag, value);
109  }
110 
118  bool has_repeating_group_tag(uint32_t tag) const
119  {
120  return m_repeating_groups.has_tag(tag);
121  }
122 
123  void reset()
124  {
125  m_generation_id++;
126 
127  if(m_generation_id == 0)
128  {
129  // Wrap-around protection to avoid stale values
130  for (auto& it : m_dict)
131  {
132  it.value.generation_id = 0;
133  }
134 
135  m_generation_id++;
136  }
137 
138  // Reset the repeating groups
139  m_repeating_groups.reset();
140  }
141 
156  std::string to_string() const
157  {
158  std::string ret;
159 
160  auto is_header_tag = [](uint32_t tag)
161  {
162  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)
163  return true;
164  return false;
165  };
166 
167  auto is_trailer_tag = [](uint32_t tag)
168  {
169  if (tag == FixConstants::TAG_CHECKSUM)
170  return true;
171  return false;
172  };
173 
174  try
175  {
176  // HEADER
177  if(has_tag(FixConstants::TAG_BEGIN_STRING)) ret += "8=" + get_tag_value_as<std::string>(FixConstants::TAG_BEGIN_STRING) + '|';
178  if (has_tag(FixConstants::TAG_BODY_LENGTH)) ret += "9=" + get_tag_value_as<std::string>(FixConstants::TAG_BODY_LENGTH) + '|';
179  if (has_tag(FixConstants::TAG_MSG_TYPE)) ret += "35=" + get_tag_value_as<std::string>(FixConstants::TAG_MSG_TYPE) + '|';
180  if (has_tag(FixConstants::TAG_MSG_SEQ_NUM)) ret += "34=" + get_tag_value_as<std::string>(FixConstants::TAG_MSG_SEQ_NUM) + '|';
181  if (has_tag(FixConstants::TAG_SENDER_COMP_ID)) ret += "49=" + get_tag_value_as<std::string>(FixConstants::TAG_SENDER_COMP_ID) + '|';
182  if (has_tag(FixConstants::TAG_SENDING_TIME)) ret += "52=" + get_tag_value_as<std::string>(FixConstants::TAG_SENDING_TIME) + '|';
183  if (has_tag(FixConstants::TAG_TARGET_COMP_ID)) ret += "56=" + get_tag_value_as<std::string>(FixConstants::TAG_TARGET_COMP_ID) + '|';
184 
185  // BODY
186  for (const auto& item : m_dict)
187  {
188  if (item.value.generation_id == m_generation_id)
189  {
190  if (is_header_tag(item.key) == false && is_trailer_tag(item.key) == false)
191  {
192  ret += std::to_string(item.key) + '=' + item.value.value->to_string() + '|';
193  }
194  }
195  }
196 
197  // BODY - REPEATING GROUPS
198  ret += m_repeating_groups.to_string();
199 
200  // TRAILER
201  if (has_tag(FixConstants::TAG_CHECKSUM)) ret += "10=" + get_tag_value_as<std::string>(FixConstants::TAG_CHECKSUM) + '|';
202  }
203  catch (...)
204  {
205  return "An error occured during IncomingFixMessage::to_string call";
206  }
207 
208  return ret;
209  }
211  // GET TAG VALUE METHODS
212  FixStringView* get_tag_value(uint32_t tag) const
213  {
214  llfix_assert_msg(m_dict.has_key(tag) == true, "You should call has_tag first");
215  llfix_assert_msg(m_dict[tag].generation_id == m_generation_id, "You should call has_tag first");
216  return m_dict[tag].value;
217  }
218 
237  template<typename T>
238  T get_tag_value_as(uint32_t tag, std::size_t decimal_points=0) const
239  {
240  llfix_assert_msg(m_dict.has_key(tag) == true, "You should call has_tag first");
241  llfix_assert_msg(m_dict[tag].generation_id == m_generation_id, "You should call has_tag first");
242 
243  if constexpr (std::is_same_v<T, std::string>)
244  {
245  LLFIX_UNUSED(decimal_points);
246  return m_dict[tag].value->to_string();
247 
248  }
249  else if constexpr (std::is_same_v<T, char>)
250  {
251  LLFIX_UNUSED(decimal_points);
252  return m_dict[tag].value->data()[0];
253  }
254  else if constexpr (std::is_same_v<T, std::string_view>)
255  {
256  LLFIX_UNUSED(decimal_points);
257  return m_dict[tag].value->to_string_view();
258  }
259  else if constexpr (std::is_same_v<T, bool>)
260  {
261  LLFIX_UNUSED(decimal_points);
262  return (m_dict[tag].value->data()[0] == FixConstants::FIX_BOOLEAN_TRUE) ? true : false;
263  }
264  else if constexpr (std::is_same_v<T, FixedPoint>)
265  {
266  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
267  FixedPoint fp;
268  fp.set_decimal_points(static_cast<uint32_t>(decimal_points));
269  fp.set_from_chars(m_dict[tag].value->c_str(), m_dict[tag].value->length());
270  return fp;
271  }
272  else if constexpr (std::is_floating_point<T>::value)
273  {
274  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
275  return static_cast<T>(Converters::chars_to_double(m_dict[tag].value->c_str(), m_dict[tag].value->length(), decimal_points));
276  }
277  else if constexpr (std::is_integral<T>::value && std::is_signed<T>::value)
278  {
279  LLFIX_UNUSED(decimal_points);
280  return Converters::chars_to_int<int>(m_dict[tag].value->c_str(), m_dict[tag].value->length());
281  }
282  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint64_t))
283  {
284  LLFIX_UNUSED(decimal_points);
285  return Converters::chars_to_unsigned_int<uint64_t>(m_dict[tag].value->c_str(), m_dict[tag].value->length());
286  }
287  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint32_t))
288  {
289  LLFIX_UNUSED(decimal_points);
290  return Converters::chars_to_unsigned_int<uint32_t>(m_dict[tag].value->c_str(), m_dict[tag].value->length());
291  }
292  else
293  {
294  static_assert(always_false_v<T>, "get_tag_value_as unsupported type");
295  }
296  }
297 
317  template<typename T>
318  T get_repeating_group_tag_value_as(uint32_t tag, std::size_t index, std::size_t decimal_points = 0) const
319  {
320  FixStringView* str_val = m_repeating_groups.get_value(tag, index);
321  llfix_assert_msg(str_val != nullptr, "You should call has_repeating_group_tag first");
322 
323  if constexpr (std::is_same_v<T, std::string>)
324  {
325  LLFIX_UNUSED(decimal_points);
326  return str_val->to_string();
327  }
328  else if constexpr (std::is_same_v<T, char>)
329  {
330  LLFIX_UNUSED(decimal_points);
331  return str_val->data()[0];
332  }
333  else if constexpr (std::is_same_v<T, std::string_view>)
334  {
335  LLFIX_UNUSED(decimal_points);
336  return str_val->to_string_view();
337  }
338  else if constexpr (std::is_same_v<T, bool>)
339  {
340  LLFIX_UNUSED(decimal_points);
341  return (str_val->data()[0] == FixConstants::FIX_BOOLEAN_TRUE) ? true : false;
342  }
343  else if constexpr (std::is_same_v<T, FixedPoint>)
344  {
345  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
346  FixedPoint fp;
347  fp.set_decimal_points(static_cast<uint32_t>(decimal_points));
348  fp.set_from_chars(str_val->c_str(), str_val->length());
349  return fp;
350  }
351  else if constexpr (std::is_floating_point<T>::value)
352  {
353  llfix_assert_msg(decimal_points > 0, "You have to specify a decimal points value that is greater than zero");
354  return Converters::chars_to_double(str_val->c_str(), str_val->length(), decimal_points);
355  }
356  else if constexpr (std::is_integral<T>::value && std::is_signed<T>::value)
357  {
358  LLFIX_UNUSED(decimal_points);
359  return Converters::chars_to_int<int>(str_val->c_str(), str_val->length());
360  }
361  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint64_t))
362  {
363  LLFIX_UNUSED(decimal_points);
364  return Converters::chars_to_unsigned_int<uint64_t>(str_val->c_str(), str_val->length());
365  }
366  else if constexpr (std::is_integral<T>::value && sizeof(T) == sizeof(uint32_t))
367  {
368  LLFIX_UNUSED(decimal_points);
369  return Converters::chars_to_unsigned_int<uint32_t>(str_val->c_str(), str_val->length());
370  }
371  else
372  {
373  static_assert(always_false_v<T>, "get_repeating_group_tag_value_as unsupported type");
374  }
375  }
377  // VALIDATION METHODS
378  bool is_tag_value_numeric(uint32_t tag) const
379  {
380  llfix_assert_msg(m_dict.has_key(tag) == true, "You should call has_tag first");
381  llfix_assert_msg(m_dict[tag].generation_id == m_generation_id, "You should call has_tag first");
382  return m_dict[tag].value->is_numeric();
383  }
384 
385  bool validate_count_tag(uint32_t tag, uint32_t& out_reject_message_code) const
386  {
387  return m_repeating_groups.validate_count_tag(tag, out_reject_message_code);
388  }
389 
390  Dictionary<uint32_t, IncomingValue>* get_dictionary()
391  {
392  return &m_dict;
393  }
394 
395  uint64_t get_generation_id() const { return m_generation_id; }
396 
397  private:
398  uint64_t m_generation_id = 1;
399  mutable Dictionary<uint32_t, IncomingValue> m_dict;
400  IncomingFixRepeatingGroups<FixStringView> m_repeating_groups;
401 
402  template <typename>
403  static constexpr bool always_false_v = false;
404 
405  IncomingFixMessage(const IncomingFixMessage& other) = delete;
406  IncomingFixMessage& operator= (const IncomingFixMessage& other) = delete;
407  IncomingFixMessage(IncomingFixMessage&& other) = delete;
408  IncomingFixMessage& operator=(IncomingFixMessage&& other) = delete;
409 };
410 
411 } // namespace
llfix::IncomingFixMessage::to_string
std::string to_string() const
Serialises the FIX message into a human-readable string.
Definition: incoming_fix_message.h:156
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:109
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:118
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:67
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:62
llfix::FixedPoint
Represents an unsigned fixed-point numeric value with a configurable number of decimal points....
Definition: fixed_point.h:59
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:318
llfix::IncomingFixMessage
Represents a parsed incoming FIX message.
Definition: incoming_fix_message.h:44
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:238