fix8  version 1.4.0
Open Source C++ FIX Framework
configuration.cpp
Go to the documentation of this file.
1 //-----------------------------------------------------------------------------------------
2 /*
3 
4 Fix8 is released under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
5 
6 Fix8 Open Source FIX Engine.
7 Copyright (C) 2010-16 David L. Dight <fix@fix8.org>
8 
9 Fix8 is free software: you can redistribute it and / or modify it under the terms of the
10 GNU Lesser General Public License as published by the Free Software Foundation, either
11 version 3 of the License, or (at your option) any later version.
12 
13 Fix8 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14 even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 
16 You should have received a copy of the GNU Lesser General Public License along with Fix8.
17 If not, see <http://www.gnu.org/licenses/>.
18 
19 BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO
20 THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
21 COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY
22 KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO
24 THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
25 YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
26 
27 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT
28 HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED
29 ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
30 CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
31 NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR
32 THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH
33 HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
34 
35 */
36 //-----------------------------------------------------------------------------------------
37 #include "precomp.hpp"
38 #include <fix8/f8config.h>
39 #ifdef FIX8_HAVE_OPENSSL
40 #include <Poco/Net/Context.h>
41 #endif
42 #include <fix8/f8includes.hpp>
43 
44 //-------------------------------------------------------------------------------------------------
45 using namespace FIX8;
46 using namespace std;
47 
48 //-------------------------------------------------------------------------------------------------
49 RegExp Configuration::_ipexp("^([^:]+):([0-9]+)$");
50 
51 //-------------------------------------------------------------------------------------------------
53 {
54  if (!_root)
55  throw ConfigurationError("could not create root xml entity");
56 
57  using item = tuple<f8String, group_types, bool>;
58  static const item items[]
59  {
60  { item("session", g_sessions, true) }, { item("log", g_loggers, false) },
61  { item("server_group", g_server_group, false) }, { item("client_group", g_client_group, false) },
62  { item("ssl_context", g_ssl_context, false) }, { item("schedule", g_schedules, false) },
63  { item("login", g_logins, false) }, { item("persist", g_persisters, false) }
64  };
65 
66  for (auto &pp : items)
67  if (!load_map("fix8/" + get<0>(pp), _groups[get<1>(pp)], get<2>(pp)) && get<2>(pp))
68  throw ConfigurationError("could not locate server session in configuration", get<0>(pp));
69 
70  return static_cast<int>(_groups[g_sessions].size());
71 }
72 
73 //-------------------------------------------------------------------------------------------------
75 {
76  string role;
77  return from_or_default(from, "role", role)
78  ? role % "initiator" ? Connection::cn_initiator
80 }
81 
82 //-------------------------------------------------------------------------------------------------
83 Poco::Net::SocketAddress Configuration::get_address(const XmlElement *from) const
84 {
85  Poco::Net::SocketAddress to;
86  string ip, port;
87  if (from_or_default(from, "ip", ip) && from_or_default(from, "port", port))
88  to = Poco::Net::SocketAddress(ip, port);
89 
90  return to;
91 }
92 
93 //-------------------------------------------------------------------------------------------------
94 Poco::Net::IPAddress Configuration::get_ip(const XmlElement *from) const
95 {
96  Poco::Net::IPAddress to;
97  string ip;
98  if (from_or_default(from, "ip", ip))
99  to = Poco::Net::IPAddress(ip);
100 
101  return to;
102 }
103 
104 //-------------------------------------------------------------------------------------------------
105 size_t Configuration::get_addresses(const XmlElement *from, vector<Server>& target) const
106 {
107  string name;
108  const XmlElement *which;
109  if (from_or_default(from, "server_group", name) && (which = find_group(g_server_group, name)))
110  {
111  XmlElement::XmlSet slist;
112  if (which->find("server_group/server", slist))
113  {
114  const Poco::Net::SocketAddress empty_addr;
115  for(const auto *pp : slist)
116  {
117  string name;
118  Poco::Net::SocketAddress addr(get_address(pp));
119  if (pp->GetAttr("name", name) && addr != empty_addr && pp->FindAttr("active", true))
120  target.push_back(Server(name, pp->FindAttr("max_retries", static_cast<int>(defaults::login_retries)), addr,
121  pp->FindAttr("reset_sequence_numbers", false)));
122  }
123  }
124  return target.size();
125  }
126 
127  return 0;
128 }
129 
130 //-------------------------------------------------------------------------------------------------
132 {
133  Tickval start(get_time_field(which, "start_time", true));
134  if (!start.is_errorval())
135  {
136  const int utc_offset(which->FindAttr("utc_offset_mins", 0)); // utc_offset is in minutes
137  const unsigned duration(which->FindAttr("duration", 0));
138  Tickval end(get_time_field(which, "end_time", true));
139 
140  if (end.is_errorval())
141  {
142  if (duration) // duration is in minutes
143  end = start.get_ticks() + duration * Tickval::minute;
144  }
145  else
146  {
147  if (end <= start)
148  throw ConfigurationError("Schedule end time cannot be equal to or before session start time");
149  }
150  string daytmp;
151  const int start_day(which->GetAttr("start_day", daytmp) ? decode_dow(daytmp) : -1);
152  const int end_day(which->GetAttr("end_day", daytmp) ? decode_dow(daytmp) : start_day < 0 ? -1 : start_day);
153  return {start, end, Tickval(static_cast<Tickval::ticks>(duration)), utc_offset, start_day, end_day};
154  }
155 
156  return {};
157 }
158 
159 //-------------------------------------------------------------------------------------------------
161 {
162  string name;
163  const XmlElement *which(0);
164  if (from_or_default(from, "schedule", name) && (which = find_group(g_schedules, name)))
165  {
166  Schedule sch(create_schedule(which));
167  f8String reject_text("Business messages are not accepted now.");
168  which->GetAttr("reject_text", reject_text); // can't use FindAttr since istream is delimeter sensitive
169  const int reject_code(which->FindAttr("reject_code", 0));
170  return new Session_Schedule(sch, reject_code, reject_text);
171  }
172 
173  return nullptr;
174 }
175 
176 //-------------------------------------------------------------------------------------------------
178 {
179  string name;
180  const XmlElement *which;
181  return from_or_default(from, "login", name) && (which = find_group(g_logins, name))
182  ? create_schedule(which) : Schedule();
183 }
184 
185 //-------------------------------------------------------------------------------------------------
186 Persister *Configuration::create_persister(const XmlElement *from, const SessionID *sid, bool flag) const
187 {
188  string name, type;
189  const XmlElement *which;
190  if (from_or_default(from, "persist", name) && (which = find_group(g_persisters, name)) && which->GetAttr("type", type))
191  {
192  if (type == "mem")
193  return new MemoryPersister;
194 
195  string dir("./"), db("persist_db");
196  which->GetAttr("dir", dir);
197  which->GetAttr("db", db) || which->GetAttr("session_prefix", db);
198 
199  if (sid)
200  db += ('.' + sid->get_senderCompID()() + '.' + sid->get_targetCompID()());
201  else if (which->FindAttr("use_session_id", false))
202  db += ('.' + get_sender_comp_id(from)() + '.' + get_target_comp_id(from)());
203 
204 #if defined FIX8_HAVE_LIBMEMCACHED
205  if (type == "memcached")
206  {
207  string config_str;
208  if (which->GetAttr("config_string", config_str))
209  {
210  unique_ptr<MemcachedPersister> result(new MemcachedPersister);
211  if (result->initialise(config_str, db, flag))
212  return result.release();
213  }
214  else
215  throw ConfigurationError("memcached: config_string attribute must be given when using memcached");
216  }
217  else
218 #endif
219 #if defined FIX8_HAVE_LIBHIREDIS
220  if (type == "redis")
221  {
222  string host_str;
223  if (which->GetAttr("host", host_str))
224  {
225  unique_ptr<HiredisPersister> result(new HiredisPersister);
226  if (result->initialise(host_str, which->FindAttr("port", 6379),
227  which->FindAttr("connect_timeout", static_cast<unsigned>(defaults::connect_timeout)), db, flag))
228  return result.release();
229  }
230  else
231  throw ConfigurationError("redis: host attribute must be given when using redis");
232  }
233  else
234 #endif
235 #if defined FIX8_HAVE_BDB
236  if (type == "bdb")
237  {
238  unique_ptr<BDBPersister> result(new BDBPersister);
239  if (result->initialise(dir, db, flag))
240  return result.release();
241  }
242  else
243 #endif
244  if (type == "file")
245  {
246  unique_ptr<FilePersister> result(new FilePersister(which->FindAttr("rotation", 0)));
247  if (result->initialise(dir, db, flag))
248  return result.release();
249  }
250  }
251 
252  return nullptr;
253 }
254 
255 //-------------------------------------------------------------------------------------------------
256 Logger *Configuration::create_logger(const XmlElement *from, const Logtype ltype, const SessionID *sid) const
257 {
258  string name;
259  if (from_or_default(from, ltype == session_log ? "session_log" : "protocol_log", name))
260  {
261  const XmlElement *which(find_group(g_loggers, name));
262  if (which)
263  {
264  Logger::LogPositions positions;
265  string type;
266 
267  if (which->GetAttr("type", type)
268  && ((type % "session" && ltype == session_log) || (type % "protocol" && ltype == protocol_log)))
269  {
270  string logname("logname_not_set.log"), levstr, delim(" ");
271  which->FindAttrRef("filename", logname);
272  trim(logname);
273  if (which->GetAttr("delimiter", delim) && delim.size() > 2) // "|" or "<>" or "{}" etc
274  throw ConfigurationError("invalid logging field delimiter");
275 
276  which->GetAttr("levels", levstr);
277  const Logger::Levels levels(levstr.empty() || levstr % "All"
278  ? Logger::Levels(Logger::All) : levstr % "None"
279  ? Logger::Levels(Logger::None) : get_logflags<Logger::Levels>("levels", Logger::_level_names, which));
280 
281  const Logger::LogFlags flags(which->HasAttr("flags")
282  ? get_logflags<Logger::LogFlags>("flags", Logger::_bit_names, which, &positions) : Logger::LogFlags(Logger::StdFlags));
283 
284  if (logname[0] == '|')
285  {
286 #ifndef FIX8_HAVE_POPEN
287  throw ConfigurationError("popen not supported on your platform");
288 #endif
289  return new PipeLogger(logname, flags, levels, delim, positions);
290  }
291 
292  RegMatch match;
293  if (_ipexp.SearchString(match, logname, 3) == 3)
294  {
295  f8String ip, port;
296  _ipexp.SubExpr(match, logname, ip, 0, 1);
297  _ipexp.SubExpr(match, logname, port, 0, 2);
298  return new BCLogger(ip, stoul(port), flags, levels, delim, positions);
299  }
300 
301  get_logname(which, logname, sid); // only applies to file loggers
302  if (flags & Logger::xml)
303  return new XmlFileLogger(logname, flags, levels, delim, positions, get_logfile_rotation(which));
304  return new FileLogger(logname, flags, levels, delim, positions, get_logfile_rotation(which));
305  }
306  }
307  }
308 
309  return nullptr;
310 }
311 
312 //-------------------------------------------------------------------------------------------------
314 {
315  string name;
316  const XmlElement *which;
317  Clients clients;
318  if (from_or_default(from, "target_comp_id", name) && (which = find_group(g_client_group, name)))
319  {
320  XmlElement::XmlSet slist;
321  if (which->find("client_group/client", slist))
322  {
323  // <client name="Goldsteins" target_comp_id="DLD_TEX" ip="192.168.0.17" active="true" />
324  for(const auto *pp : slist)
325  {
326  string name, tci;
327  const Poco::Net::IPAddress addr(get_ip(pp));
328  if (pp->GetAttr("name", name) && pp->GetAttr("target_comp_id", tci) && pp->FindAttr("active", true))
329  if (!clients.insert({tci, Client(name, addr)}).second)
330  throw ConfigurationError("Failed to add client from client_group", tci);
331  }
332  }
333  }
334 
335  return clients;
336 }
337 
338 //-------------------------------------------------------------------------------------------------
339 string& Configuration::get_logname(const XmlElement *from, string& to, const SessionID *sid) const
340 {
341  if (sid)
342  to += ('.' + sid->get_senderCompID()() + '.' + sid->get_targetCompID()());
343  else if (from && from->FindAttr("use_session_id", false))
344  to += ('.' + get_sender_comp_id(from)() + '.' + get_target_comp_id(from)());
345 
346  return to;
347 }
348 
349 //-------------------------------------------------------------------------------------------------
350 template<typename T>
351 T Configuration::get_logflags(const string& tag, const vector<string>& names,
352  const XmlElement *from, Logger::LogPositions *positions) const
353 {
354  T flags;
355  string flags_str;
356  if (from && from->GetAttr(tag, flags_str))
357  {
358  istringstream istr(flags_str);
359  for(char extr[32]; !istr.get(extr, sizeof(extr), '|').fail(); istr.ignore(1))
360  {
361  string result(extr);
362  const int evalue(flags.set(names, trim(result), true, true));
363  if (positions && evalue >= 0)
364  positions->push_back(evalue);
365  }
366  }
367 
368  return flags;
369 }
370 
371 //-------------------------------------------------------------------------------------------------
372 unsigned Configuration::get_all_sessions(vector<const XmlElement *>& target, const Connection::Role role) const
373 {
374  for (const auto *pp : _allsessions)
375  if (role == Connection::cn_unknown || get_role(pp) == role)
376  target.push_back(pp);
377  return static_cast<unsigned>(target.size());
378 }
379 
380 //-------------------------------------------------------------------------------------------------
382 {
383  static const vector<f8String> process_strings { "threaded", "pipelined", "coroutine" };
384  string pm;
385  return from_or_default(from, "process_model", pm)
386  ? enum_str_get(process_strings, pm, pm_thread) : pm_pipeline; // default to pipelined
387 }
388 
389 //-------------------------------------------------------------------------------------------------
390 #ifdef FIX8_HAVE_OPENSSL
391 SslContext Configuration::get_ssl_context(const XmlElement *from) const
392 {
393  SslContext target;
394  string name;
395  const XmlElement *which;
396  if (from_or_default(from, "ssl_context", name) && (which = find_group(g_ssl_context, name)))
397  {
398  static std::string empty, cipher("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"), relaxed("relaxed");
399  target._private_key_file = which->FindAttrRef("private_key_file", empty);
400  target._certificate_file = which->FindAttrRef("ceritificte_file", empty);
401  target._ca_location = which->FindAttrRef("ca_location", empty);
402  target._verification_depth = which->FindAttr("verification_depth", static_cast<int>(defaults::verification_depth));
403  target._load_default_cas = which->FindAttr("load_default_cas", false);
404  target._cipher_list = which->FindAttrRef("cipher_list", cipher);
405  name = which->FindAttrRef("verification_mode", relaxed);
406  if (name == "none")
407  target._verification_mode = Poco::Net::Context::VERIFY_NONE;
408  else if (name == "relaxed")
409  target._verification_mode = Poco::Net::Context::VERIFY_RELAXED;
410  else if (name == "strict")
411  target._verification_mode = Poco::Net::Context::VERIFY_STRICT;
412  else if (name == "once")
413  target._verification_mode = Poco::Net::Context::VERIFY_ONCE;
414  else
416  target._valid = true;
417  }
418  return target;
419 }
420 #endif
const MessageSpec * find_group(const CommonGroupMap &globmap, int &vers, unsigned tp, uint32_t key)
Definition: f8c.cpp:1554
T FindAttr(const std::string &what, const T defValue) const
Definition: xml.hpp:209
f8_thread delegated async logging class
Definition: logger.hpp:153
std::string _certificate_file
static const int StdFlags
Definition: logger.hpp:169
F8API std::string & get_logname(const XmlElement *from, std::string &to, const SessionID *sid=nullptr) const
POSIX regex wrapper class.
Definition: f8utils.hpp:370
static const int All
Definition: logger.hpp:161
int _verification_mode
are used (see loadDefaultCAs).
std::string trim(const std::string &source, const std::string &ws=" \t")
Definition: f8utils.hpp:153
T & FindAttrRef(const std::string &what, T &target) const
Definition: xml.hpp:227
std::string _cipher_list
"ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"
Base (ABC) Persister class.
Definition: persist.hpp:55
Class to hold server settings for failoverable sessions.
bool _load_default_cas
false
F8API Schedule create_schedule(const XmlElement *from) const
F8API Poco::Net::SocketAddress get_address(const XmlElement *from) const
ebitset< Level > Levels
Definition: logger.hpp:175
static const std::vector< std::string > _bit_names
string representation of logflags
Definition: logger.hpp:326
const sender_comp_id & get_senderCompID() const
Definition: session.hpp:101
Role
Roles: acceptor, initiator or unknown.
Definition: connection.hpp:512
Quickfix style sessionid.
Definition: session.hpp:46
static const int None
Definition: logger.hpp:165
std::unordered_map< f8String, Client > Clients
Definition: session.hpp:182
ProcessModel
Supported session process models.
Definition: f8types.hpp:56
A file logger.
Definition: logger.hpp:385
F8API Persister * create_persister(const XmlElement *from, const SessionID *sid=nullptr, bool flag=false) const
F8API size_t get_addresses(const XmlElement *from, std::vector< Server > &target) const
std::vector< int > LogPositions
Definition: logger.hpp:176
A simple xml parser with Xpath style lookup.
Definition: xml.hpp:48
An bad or missing configuration parameter.
F8API unsigned get_all_sessions(std::vector< const XmlElement * > &target, const Connection::Role role=Connection::cn_unknown) const
static const std::vector< std::string > _level_names
string representation of levels
Definition: logger.hpp:329
F8API int decode_dow(const std::string &from)
F8API Connection::Role get_role(const XmlElement *from) const
bool is_errorval() const
Definition: tickval.hpp:173
A broadcast logger.
Definition: logger.hpp:436
std::set< const XmlElement *, EntityOrderComp > XmlSet
Definition: xml.hpp:74
std::string _private_key_file
T enum_str_get(const std::vector< std::string > &sset, const std::string &what, const T def, bool ignorecase=false)
Definition: f8utils.hpp:1048
const target_comp_id & get_targetCompID() const
Definition: session.hpp:105
F8API bool GetAttr(const std::string &what, std::string &target) const
Definition: xml.cpp:748
A file logger.
Definition: logger.hpp:357
Memory based message persister.
Definition: persist.hpp:292
#define SSL_VERIFY_PEER
F8API Session_Schedule * create_session_schedule(const XmlElement *from) const
F8API Poco::Net::IPAddress get_ip(const XmlElement *from) const
static const ticks minute
Definition: tickval.hpp:79
T get_logflags(const std::string &tag, const std::vector< std::string > &names, const XmlElement *from, Logger::LogPositions *positions=nullptr) const
A class to contain regex matches using RegExp.
Definition: f8utils.hpp:308
A pipe logger.
Definition: logger.hpp:418
F8API Schedule create_login_schedule(const XmlElement *from) const
F8API Clients create_clients(const XmlElement *from) const
std::string _ca_location
bool HasAttr(const std::string &what) const
Definition: xml.hpp:184
Class to hold SSL context for failoverable sessions.
F8API Logger * create_logger(const XmlElement *from, const Logtype ltype, const SessionID *sid=nullptr) const
ticks get_ticks() const
Definition: tickval.hpp:129
static RegExp _ipexp
F8API ProcessModel get_process_model(const XmlElement *from) const
F8API const XmlElement * find(const std::string &what, const std::string *atag=nullptr, const std::string *aval=nullptr, const char delim='/') const
std::string f8String
Definition: f8types.hpp:47