diff --git a/src/api_config.cpp b/src/api_config.cpp index a43ef9a68..517ee2c65 100644 --- a/src/api_config.cpp +++ b/src/api_config.cpp @@ -3,9 +3,12 @@ #include "util/inireader.hpp" #include #include +#include #include #include +#include #include +#include #include #include @@ -46,7 +49,9 @@ bool file_is_readable(const std::string &filename) { return f.good(); } -api_config::api_config() { +api_config::api_config(bool skip_load_settings) { + if (skip_load_settings) return; + // for each config file location under consideration... std::vector filenames; if (getenv("LSLAPICFG")) { @@ -75,157 +80,17 @@ api_config::api_config() { load_from_file(); } - void api_config::load_from_file(const std::string &filename) { + if(filename.empty()) return; try { - INI pt; - if (!filename.empty()) { - std::ifstream infile(filename); - if (infile.good()) { - pt.load(infile); - } - } - - // read out the [ports] parameters - multicast_port_ = pt.get("ports.MulticastPort", 16571); - base_port_ = pt.get("ports.BasePort", 16572); - port_range_ = pt.get("ports.PortRange", 32); - allow_random_ports_ = pt.get("ports.AllowRandomPorts", true); - std::string ipv6_str = pt.get("ports.IPv6", -#ifdef __APPLE__ - "disable"); // on Mac OS (10.7) there's a bug in the IPv6 implementation that breaks LSL - // when it tries to use both v4 and v6 -#else - "allow"); -#endif - allow_ipv4_ = true; - allow_ipv6_ = true; - // fix some common mis-spellings - if (ipv6_str == "disabled" || ipv6_str == "disable") - allow_ipv6_ = false; - else if (ipv6_str == "allowed" || ipv6_str == "allow") - allow_ipv6_ = true; - else if (ipv6_str == "forced" || ipv6_str == "force") - allow_ipv4_ = false; - else - throw std::runtime_error("Unsupported setting for the IPv6 parameter."); - - // read the [multicast] parameters - resolve_scope_ = pt.get("multicast.ResolveScope", "site"); - listen_address_ = pt.get("multicast.ListenAddress", ""); - // Note about multicast addresses: IPv6 multicast addresses should be - // FF0x::1 (see RFC2373, RFC1884) or a predefined multicast group - std::string ipv6_multicast_group = - pt.get("multicast.IPv6MulticastGroup", "113D:6FDD:2C17:A643:FFE2:1BD1:3CD2"); - std::vector machine_group = - parse_set(pt.get("multicast.MachineAddresses", "{127.0.0.1}")); - // 224.0.0.1 is the group for all directly connected hosts (RFC1112) - std::vector link_group = parse_set( - pt.get("multicast.LinkAddresses", "{255.255.255.255, 224.0.0.1, 224.0.0.183}")); - // Multicast groups defined by the organization (and therefore subject - // to filtering / forwarding are in the 239.192.0.0/14 subnet (RFC2365) - std::vector site_group = - parse_set(pt.get("multicast.SiteAddresses", "{239.255.172.215}")); - // Organization groups use the same broadcast addresses (IPv4), but - // have a larger TTL. On the network site, it requires the routers - // to forward the broadcast packets (both IGMP and UDP) - std::vector organization_group = - parse_set(pt.get("multicast.OrganizationAddresses", "{}")); - std::vector global_group = - parse_set(pt.get("multicast.GlobalAddresses", "{}")); - enum { machine = 0, link, site, organization, global } scope; - // construct list of addresses & TTL according to the ResolveScope. - if (resolve_scope_ == "machine") - scope = machine; - else if (resolve_scope_ == "link") - scope = link; - else if (resolve_scope_ == "site") - scope = site; - else if (resolve_scope_ == "organization") - scope = organization; - else if (resolve_scope_ == "global") - scope = global; - else - throw std::runtime_error("This ResolveScope setting is unsupported."); - - multicast_addresses_.insert( - multicast_addresses_.end(), machine_group.begin(), machine_group.end()); - multicast_ttl_ = 0; - - if (scope >= link) { - multicast_addresses_.insert( - multicast_addresses_.end(), link_group.begin(), link_group.end()); - multicast_addresses_.push_back("FF02:" + ipv6_multicast_group); - multicast_ttl_ = 1; - } - if (scope >= site) { - multicast_addresses_.insert( - multicast_addresses_.end(), site_group.begin(), site_group.end()); - multicast_addresses_.push_back("FF05:" + ipv6_multicast_group); - multicast_ttl_ = 24; - } - if (scope >= organization) { - multicast_addresses_.insert( - multicast_addresses_.end(), organization_group.begin(), organization_group.end()); - multicast_addresses_.push_back("FF08:" + ipv6_multicast_group); - multicast_ttl_ = 32; - } - if (scope >= global) { - multicast_addresses_.insert( - multicast_addresses_.end(), global_group.begin(), global_group.end()); - multicast_addresses_.push_back("FF0E:" + ipv6_multicast_group); - multicast_ttl_ = 255; - } + std::ifstream infile(filename); + if (!infile.good()) + return; - // apply overrides, if any - int ttl_override = pt.get("multicast.TTLOverride", -1); - std::vector address_override = - parse_set(pt.get("multicast.AddressesOverride", "{}")); - if (ttl_override >= 0) multicast_ttl_ = ttl_override; - if (!address_override.empty()) multicast_addresses_ = address_override; + load_ini_file(infile, [this](const std::string §ion, const std::string &key, + const std::string &value) { set_option(section, key, value); }); - // read the [lab] settings - known_peers_ = parse_set(pt.get("lab.KnownPeers", "{}")); - session_id_ = pt.get("lab.SessionID", "default"); - // read the [tuning] settings - use_protocol_version_ = std::min( - LSL_PROTOCOL_VERSION, pt.get("tuning.UseProtocolVersion", LSL_PROTOCOL_VERSION)); - watchdog_check_interval_ = pt.get("tuning.WatchdogCheckInterval", 15.0); - watchdog_time_threshold_ = pt.get("tuning.WatchdogTimeThreshold", 15.0); - multicast_min_rtt_ = pt.get("tuning.MulticastMinRTT", 0.5); - multicast_max_rtt_ = pt.get("tuning.MulticastMaxRTT", 3.0); - unicast_min_rtt_ = pt.get("tuning.UnicastMinRTT", 0.75); - unicast_max_rtt_ = pt.get("tuning.UnicastMaxRTT", 5.0); - continuous_resolve_interval_ = pt.get("tuning.ContinuousResolveInterval", 0.5); - timer_resolution_ = pt.get("tuning.TimerResolution", 1); - max_cached_queries_ = pt.get("tuning.MaxCachedQueries", 100); - time_update_interval_ = pt.get("tuning.TimeUpdateInterval", 2.0); - time_update_minprobes_ = pt.get("tuning.TimeUpdateMinProbes", 6); - time_probe_count_ = pt.get("tuning.TimeProbeCount", 8); - time_probe_interval_ = pt.get("tuning.TimeProbeInterval", 0.064); - time_probe_max_rtt_ = pt.get("tuning.TimeProbeMaxRTT", 0.128); - outlet_buffer_reserve_ms_ = pt.get("tuning.OutletBufferReserveMs", 5000); - outlet_buffer_reserve_samples_ = pt.get("tuning.OutletBufferReserveSamples", 128); - socket_send_buffer_size_ = pt.get("tuning.SendSocketBufferSize", 0); - inlet_buffer_reserve_ms_ = pt.get("tuning.InletBufferReserveMs", 5000); - inlet_buffer_reserve_samples_ = pt.get("tuning.InletBufferReserveSamples", 128); - socket_receive_buffer_size_ = pt.get("tuning.ReceiveSocketBufferSize", 0); - smoothing_halftime_ = pt.get("tuning.SmoothingHalftime", 90.0F); - force_default_timestamps_ = pt.get("tuning.ForceDefaultTimestamps", false); - - // read the [log] settings - int log_level = pt.get("log.level", (int) loguru::Verbosity_INFO); - if (log_level < -3 || log_level > 9) - throw std::runtime_error("Invalid log.level (valid range: -3 to 9"); - - std::string log_file = pt.get("log.file", ""); - if (!log_file.empty()) { - loguru::add_file(log_file.c_str(), loguru::Append, log_level); - // don't duplicate log to stderr - loguru::g_stderr_verbosity = -9; - } else - loguru::g_stderr_verbosity = log_level; // log config filename only after setting the verbosity level if (!filename.empty()) @@ -243,6 +108,183 @@ void api_config::load_from_file(const std::string &filename) { } } +void lsl::api_config::update_multicast_groups() { + int ttls[] = {0, 1, 24, 32, 255}; + multicast_ttl_ = ttls[resolve_scope_]; + + const char IPv6_multicast_scopes[] = {'\0', '2', '5', '8', 'E'}; + + multicast_addresses_.clear(); + std::string v6_multicast_group = "FF0?" + ipv6_multicast_group_; + for (int scope = machine; scope <= resolve_scope_; ++scope) { + const auto &grpaddrs = multicast_group_addresses_[scope]; + multicast_addresses_.insert(multicast_addresses_.end(), grpaddrs.begin(), grpaddrs.end()); + if (IPv6_multicast_scopes[scope]) { + v6_multicast_group[4] = IPv6_multicast_scopes[scope]; + multicast_addresses_.push_back(v6_multicast_group); + } + } +} + +template +inline void set_from_string( + T &var, const std::string &val, T (*converter)(const std::string &) = from_string) { + var = converter(val); +} + +bool lsl::api_config::set_option( + const std::string §ion, const std::string &key, const std::string &value) { + + // [ports] + if (section == "ports") { + if (key == "MulticastPort") + set_from_string(multicast_port_, value); + else if (key == "BasePort") + set_from_string(base_port_, value); + else if (key == "PortRange") + set_from_string(port_range_, value); + else if (key == "AllowRandomPorts") + set_from_string(allow_random_ports_, value); + else if (key == "IPv6") { + allow_ipv4_ = true; + allow_ipv6_ = true; + if (value == "disabled" || value == "disable") + allow_ipv6_ = false; + else if (value == "allowed" || value == "allow") + allow_ipv6_ = true; + else if (value == "forced" || value == "force") + allow_ipv4_ = false; + else + throw std::runtime_error("Unsupported setting for the IPv6 parameter."); + } else if (key == "MachineAddresses") + multicast_group_addresses_[machine] = parse_set(value); + else if (key == "LinkAddresses") + multicast_group_addresses_[link] = parse_set(value); + else if (key == "SiteAddresses") + multicast_group_addresses_[site] = parse_set(value); + else if (key == "OrganizationAddresses") + multicast_group_addresses_[organization] = parse_set(value); + else if (key == "GlobalAddresses") + multicast_group_addresses_[global] = parse_set(value); + } + // [multicast] + else if (section == "multicast") { + if (key == "ResolveScope") { + if (value == "machine") + resolve_scope_ = machine; + else if (value == "link") + resolve_scope_ = link; + else if (value == "site") + resolve_scope_ = site; + else if (value == "organization") + resolve_scope_ = organization; + else if (value == "global") + resolve_scope_ = global; + else + throw std::runtime_error("This ResolveScope setting is unsupported."); + update_multicast_groups(); + } else if (key == "ListenAddress") + listen_address_ = value; + else if (value == "TTLOverride") { + // apply overrides, if any + int ttl_override = from_string(value); + if (ttl_override < 0 || ttl_override > 255) + throw std::runtime_error("Invalid TTLOverride value"); + multicast_ttl_ = ttl_override; + } + /*std::vector address_override = + parse_set(pt.get("multicast.AddressesOverride", "{}")); + if (!address_override.empty()) multicast_addresses_ = address_override;*/ + + } + // [lab] + else if (section == "lab") { + if (key == "KnownPeers") + known_peers_ = parse_set(value); + else if (key == "SessionID") + session_id_ = value; + } + // [log] + else if(section=="log") { + // read the [log] settings + if(key=="level") { + int8_t log_level = from_string(value); + if (log_level < -3 || log_level > 9) + throw std::runtime_error("Invalid log.level (valid range: -3 to 9"); + log_level_ = log_level; + } + else if(key == "file") { + if (!value.empty()) { + loguru::add_file(value.c_str(), loguru::Append, log_level_); + // don't duplicate log to stderr + loguru::g_stderr_verbosity = -9; + } else + loguru::g_stderr_verbosity = log_level_; + } + + } + // [tuning] + else if (section == "tuning") { + if (key == "UseProtocolVersion") { + int tmpversion = from_string(value); + if (tmpversion > LSL_PROTOCOL_VERSION) + throw std::runtime_error("Requested protocol version " + value + + " too new; this library supports up to " + + std::to_string(LSL_PROTOCOL_VERSION)); + use_protocol_version_ = tmpversion; + } else if (key == "ContinuousResolveInterval") + set_from_string(continuous_resolve_interval_, value); + else if (key == "ForceDefaultTimestamps") + set_from_string(force_default_timestamps_, value); + else if (key == "InletBufferReserveMs") + set_from_string(inlet_buffer_reserve_ms_, value); + else if (key == "InletBufferReserveSamples") + set_from_string(inlet_buffer_reserve_samples_, value); + else if (key == "MaxCachedQueries") + set_from_string(max_cached_queries_, value); + else if (key == "MulticastMaxRTT") + set_from_string(multicast_max_rtt_, value); + else if (key == "MulticastMinRTT") + set_from_string(multicast_min_rtt_, value); + else if (key == "OutletBufferReserveMs") + set_from_string(outlet_buffer_reserve_ms_, value); + else if (key == "OutletBufferReserveSamples") + set_from_string(outlet_buffer_reserve_samples_, value); + else if (key == "ReceiveSocketBufferSize") + set_from_string(socket_receive_buffer_size_, value); + else if (key == "SendSocketBufferSize") + set_from_string(socket_send_buffer_size_, value); + else if (key == "SmoothingHalftime") + set_from_string(smoothing_halftime_, value); + else if (key == "TimeProbeCount") + set_from_string(time_probe_count_, value); + else if (key == "TimeProbeInterval") + set_from_string(time_probe_interval_, value); + else if (key == "TimeProbeMaxRTT") + set_from_string(time_probe_max_rtt_, value); + else if (key == "TimeUpdateInterval") + set_from_string(time_update_interval_, value); + else if (key == "TimeUpdateMinProbes") + set_from_string(time_update_minprobes_, value); + else if (key == "TimerResolution") + set_from_string(timer_resolution_, value); + else if (key == "UnicastMaxRTT") + set_from_string(unicast_max_rtt_, value); + else if (key == "UnicastMinRTT") + set_from_string(unicast_min_rtt_, value); + else if (key == "WatchdogCheckInterval") + set_from_string(watchdog_check_interval_, value); + else if (key == "WatchdogTimeThreshold") + set_from_string(watchdog_time_threshold_, value); + } + else { + LOG_F(ERROR, "Unknown configuration option %s.%s = %s", section.c_str(), key.c_str(), value.c_str()); + return false; + } + + return true; +} + static std::once_flag api_config_once_flag; const api_config *api_config::get_instance() { diff --git a/src/api_config.h b/src/api_config.h index 3368dc8f6..3c029e23a 100644 --- a/src/api_config.h +++ b/src/api_config.h @@ -1,10 +1,12 @@ #ifndef API_CONFIG_H #define API_CONFIG_H +#include "common.h" #include #include #include + namespace lsl { /** * A configuration object: holds all the configurable settings of liblsl. @@ -72,14 +74,6 @@ class api_config { bool allow_ipv6() const { return allow_ipv6_; } bool allow_ipv4() const { return allow_ipv4_; } - /** - * @brief The range or scope of stream lookup when using multicast-based discovery - * - * determines the output of the member functions multicast_addresses() and multicast_ttl(). - * Can take the values "machine", "link", "site", "organization", or "global". - */ - const std::string &resolve_scope() const { return resolve_scope_; } - /** * @brief List of multicast addresses on which inlets / outlets advertise/discover streams. * @@ -194,6 +188,9 @@ class api_config { api_config &operator=(const api_config &rhs) = delete; private: + /// unit test function that may access api config internals + friend void test_api_config(); + /// Get the api_config singleton after thread-safe initialization if needed static api_config *get_instance_internal(); @@ -201,7 +198,7 @@ class api_config { * Constructor. * Applies default settings and overrides them based on a config file (if present). */ - api_config(); + api_config(bool skip_load_settings = false); /** * @brief Load a configuration file (or use defaults if a filename is empty). @@ -209,42 +206,69 @@ class api_config { */ void load_from_file(const std::string &filename = std::string()); + bool set_option(const std::string §ion, const std::string &key, const std::string &value); + void update_multicast_groups(); + // core parameters - bool allow_ipv6_, allow_ipv4_; - uint16_t base_port_; - uint16_t port_range_; - bool allow_random_ports_; - uint16_t multicast_port_; - std::string resolve_scope_; + bool allow_ipv6_ = true, allow_ipv4_ = true; + uint16_t base_port_ = 16572; + uint16_t port_range_ = 32; + bool allow_random_ports_ = true; + uint16_t multicast_port_ = 16571; + + /** The range or scope of stream lookup when using multicast-based discovery + * + * It determines the output of the member functions + * `multicast_addresses()` and `multicast_ttl()`. + * Can take the values `machine`, `link`, `site`, `organization`, or `global`. + */ + enum { machine = 0, link, site, organization, global } resolve_scope_ = site; + /** Configured multicast group addresses. Default addresses are: + * + * - link: 224.0.0.1 / all directory connected hosts (RFC1112) + * - site: a multicast group from the 239.192.0.0 / 14 subnet(RFC2365) */ + std::vector multicast_group_addresses_[5] = { + {"127.0.0.1"}, + {"255.255.255.255", "224.0.0.1", "224.0.0.183"}, + {"239.255.172.215"}, + {}, + {}}; + /** suffix for IPv6 multicast. + * Note about multicast addresses: IPv6 multicast addresses should be + * FF0x::1 (see RFC2373, RFC1884) or a predefined multicast group*/ + std::string ipv6_multicast_group_ = "113D:6FDD:2C17:A643:FFE2:1BD1:3CD2"; std::vector multicast_addresses_; - int multicast_ttl_; + int multicast_ttl_ = 2; std::string listen_address_; std::vector known_peers_; - std::string session_id_; + std::string session_id_ = "default"; + // log + int8_t log_level_ = 0; + bool log_file_enabled_ = false; // tuning parameters - int use_protocol_version_; - double watchdog_time_threshold_; - double watchdog_check_interval_; - double multicast_min_rtt_; - double multicast_max_rtt_; - double unicast_min_rtt_; - double unicast_max_rtt_; - double continuous_resolve_interval_; - int timer_resolution_; - int max_cached_queries_; - double time_update_interval_; - int time_update_minprobes_; - int time_probe_count_; - double time_probe_interval_; - double time_probe_max_rtt_; - int outlet_buffer_reserve_ms_; - int outlet_buffer_reserve_samples_; + int use_protocol_version_ = LSL_PROTOCOL_VERSION; + double watchdog_time_threshold_ = 15.0; + double watchdog_check_interval_ = 15.0; + double multicast_min_rtt_ = .5; + double multicast_max_rtt_ = 3.; + double unicast_min_rtt_ = .75; + double unicast_max_rtt_ = 5.; + double continuous_resolve_interval_ = .5; + int timer_resolution_ = 1; + int max_cached_queries_ = 100; + double time_update_interval_ = 2.; + int time_update_minprobes_ = 6; + int time_probe_count_ = 8; + double time_probe_interval_ = .064; + double time_probe_max_rtt_ = .128; + int outlet_buffer_reserve_ms_ = 5000; + int outlet_buffer_reserve_samples_ = 128; int socket_send_buffer_size_; - int inlet_buffer_reserve_ms_; - int inlet_buffer_reserve_samples_; + int inlet_buffer_reserve_ms_ = 5000; + int inlet_buffer_reserve_samples_ = 128; int socket_receive_buffer_size_; - float smoothing_halftime_; - bool force_default_timestamps_; + float smoothing_halftime_ = 90.F; + bool force_default_timestamps_ = false; }; } // namespace lsl diff --git a/src/util/cast.cpp b/src/util/cast.cpp index 9b1b1ee77..43818351b 100644 --- a/src/util/cast.cpp +++ b/src/util/cast.cpp @@ -1,6 +1,7 @@ #include "cast.hpp" #include #include +#include #include namespace lsl { @@ -20,6 +21,9 @@ template <> std::string to_string(float val) { } template T from_string(const std::string &str) { + if(std::numeric_limits::is_integer) + return static_cast(std::stoi(str)); + std::istringstream is(str); is.imbue(std::locale::classic()); T res; @@ -27,6 +31,10 @@ template T from_string(const std::string &str) { return res; } +template<> int64_t from_string(const std::string &str) { + return std::stoll(str); +} + // Explicit template instantiations so from_string doesn't have to be compiled // in every compilation unit template float from_string(const std::string &); @@ -35,6 +43,7 @@ template double from_string(const std::string &); template signed char from_string(const std::string &); template char from_string(const std::string &); template int16_t from_string(const std::string &); +template uint16_t from_string(const std::string &); template int32_t from_string(const std::string &); -template int64_t from_string(const std::string &); + } // namespace lsl diff --git a/src/util/inireader.cpp b/src/util/inireader.cpp deleted file mode 100644 index 709d74c04..000000000 --- a/src/util/inireader.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "inireader.hpp" -#include -#include - -void INI::load(std::istream &infile) { - std::string line; - std::string section; - int linenr = 0; - static const char ws[] = " \t\r\n"; - - while (std::getline(infile, line)) { - linenr++; - // Comment / empty line - if (line[0] == ';' || line.find_first_not_of(ws) == std::string::npos) continue; - // Section - if (line[0] == '[') { - std::size_t closingbracket = line.find(']'); - if (closingbracket == std::string::npos) - throw std::runtime_error( - "No closing bracket ] found in line " + std::to_string(linenr)); - line[closingbracket] = '.'; - section = line.substr(1, closingbracket); - continue; - } - // Key / Value - Pair - std::size_t eqpos = line.find('='); - if (eqpos == std::string::npos) - throw std::runtime_error("No Key-Value pair in line " + std::to_string(linenr)); - auto keybegin = line.find_first_not_of(ws), keyend = line.find_last_not_of(ws, eqpos - 1), - valbegin = line.find_first_not_of(ws, eqpos + 1), valend = line.find_last_not_of(ws); - if (keyend == std::string::npos) - throw std::runtime_error("Empty key in line " + std::to_string(linenr)); - if (valbegin == std::string::npos || valend == eqpos) - throw std::runtime_error("Empty value in line " + std::to_string(linenr)); - - std::string key = section; - key += line.substr(keybegin, keyend - keybegin + 1); - if (values.find(key) != values.end()) throw std::runtime_error("Duplicate key " + key); - values.insert(std::make_pair(key, line.substr(valbegin, valend - valbegin + 1))); - } -} diff --git a/src/util/inireader.hpp b/src/util/inireader.hpp index 5d437f5f2..6ff92da8a 100644 --- a/src/util/inireader.hpp +++ b/src/util/inireader.hpp @@ -1,32 +1,41 @@ #include "cast.hpp" #include +#include #include #include -// Reads an INI file from a stream into a map -class INI { - std::unordered_map values; +template void load_ini_file(std::istream &infile, Fn &&callback) { + std::string line; + std::string section; + int linenr = 0; + static const char ws[] = " \t\r\n"; - template inline T convert(const std::string &val) { - return lsl::from_string(val); - } - -public: - void load(std::istream &infile); - - template inline T get(const char *key, T defaultval = T()) { - auto it = values.find(key); - if (it == values.end()) - return defaultval; - else { - return convert(it->second); + while (std::getline(infile, line)) { + linenr++; + // Comment / empty line + if (line[0] == ';' || line.find_first_not_of(ws) == std::string::npos) continue; + // Section + if (line[0] == '[') { + std::size_t closingbracket = line.find(']'); + if (closingbracket == std::string::npos) + throw std::runtime_error( + "No closing bracket ] found in line " + std::to_string(linenr)); + section = line.substr(1, closingbracket - 1); + continue; } - } -}; + // Key / Value - Pair + std::size_t eqpos = line.find('='); + if (eqpos == std::string::npos) + throw std::runtime_error("No Key-Value pair in line " + std::to_string(linenr)); + auto keybegin = line.find_first_not_of(ws), keyend = line.find_last_not_of(ws, eqpos - 1), + valbegin = line.find_first_not_of(ws, eqpos + 1), valend = line.find_last_not_of(ws); + if (keyend == std::string::npos) + throw std::runtime_error("Empty key in line " + std::to_string(linenr)); + if (valbegin == std::string::npos || valend == eqpos) + throw std::runtime_error("Empty value in line " + std::to_string(linenr)); -// specialization for const char*, returns an empty string ("") by default instead of nullptr -template <> inline const char *INI::get(const char *key, const char *defaultval) { - static const char empty[] = ""; - auto it = values.find(key); - return it == values.end() ? (defaultval ? defaultval : empty) : it->second.c_str(); + std::string key = line.substr(keybegin, keyend - keybegin + 1); + std::string value = line.substr(valbegin, valend - valbegin + 1); + callback(section, key, value); + } } diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index cdc9f3ec0..70ad96e7b 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -32,12 +32,12 @@ target_link_libraries(lsl_test_exported PRIVATE lsl catch_main Threads::Threads) find_package(Threads REQUIRED) add_executable(lsl_test_internal - test_int_inireader.cpp test_int_loguruthreadnames.cpp test_int_network.cpp test_int_stringfuncs.cpp test_int_streaminfo.cpp test_int_samples.cpp + internal/api_config.cpp internal/postproc.cpp internal/serialization_v100.cpp ) diff --git a/testing/internal/api_config.cpp b/testing/internal/api_config.cpp new file mode 100644 index 000000000..4ea7be54a --- /dev/null +++ b/testing/internal/api_config.cpp @@ -0,0 +1,56 @@ +#include "api_config.h" +#include "util/inireader.hpp" +#include +#include + +namespace lsl { +void test_api_config() { + lsl::api_config cfg(true); + + // default settings have to be applied in any case + REQUIRE(cfg.base_port_ == 16572); + + cfg.set_option("tuning", "ReceiveSocketBufferSize", "100"); + REQUIRE(cfg.socket_receive_buffer_size_ == 100); + + CHECK_FALSE(cfg.set_option("invalid", "option", "")); + CHECK_THROWS(cfg.set_option("log", "level", "-100")); + CHECK_THROWS(cfg.set_option("log", "level", "not a number")); +} +} + +TEST_CASE("api_config", "[basic]") { + lsl::test_api_config(); +} + +void try_load(const char *contents) { + auto callback = [](const std::string&, const std::string&, const std::string&){}; + std::istringstream stream{std::string(contents)}; + load_ini_file(stream, callback); +} + +TEST_CASE("ini files are parsed correctly", "[ini][basic]") { + std::map cfg; + std::string ini{"x=5\n" + "y=2\n" + "[foo]\n" + "foo=bar\n" + "; foo=commented out\n" + "double=equals=sign\n" + "[white space]\n" + "\tfoo =\t bar\r\n"}; + std::istringstream stream{std::string(ini)}; + load_ini_file( + stream, [&cfg](const std::string &key, const std::string §ion, + const std::string &value) { cfg[key + '.' + section] = value; }); + CHECK(cfg["foo.foo"] == std::string("bar")); + CHECK(cfg["white space.foo"] == std::string("bar")); +} + +TEST_CASE("bad ini files are rejected", "[ini][basic]") { + CHECK_THROWS(try_load("[badsection")); + CHECK_THROWS(try_load("duplicate=1\nduplicate=2")); + CHECK_THROWS(try_load("missingval")); + CHECK_THROWS(try_load("missingval= ")); + CHECK_THROWS(try_load(" = missingkey")); +} diff --git a/testing/test_int_inireader.cpp b/testing/test_int_inireader.cpp deleted file mode 100644 index eb83a04d2..000000000 --- a/testing/test_int_inireader.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "util/inireader.hpp" -#include -#include - -void try_load(INI &pt, const char *contents) { - std::istringstream stream{std::string(contents)}; - pt.load(stream); -} - -TEST_CASE("ini files are parsed correctly", "[ini][basic]") { - INI pt; - try_load(pt, "x=5\n" - "y=2\n" - "[foo]\n" - "foo=bar\n" - "; foo=commented out\n" - "double=equals=sign\n" - "[white space]\n" - "\tfoo =\t bar\r\n"); - CHECK(pt.get("doesntexist", 0) == 0); - CHECK(pt.get("defaultval") == 0); - CHECK(pt.get("x") == 5); - CHECK(pt.get("foo.foo", "") == std::string("bar")); - CHECK(pt.get("white space.foo") == std::string("bar")); - CHECK(pt.get("emptydefault") == std::string("")); -} - -TEST_CASE("bad ini files are rejected", "[ini][basic]") { - INI pt; - CHECK_THROWS(try_load(pt, "[badsection")); - CHECK_THROWS(try_load(pt, "duplicate=1\nduplicate=2")); - CHECK_THROWS(try_load(pt, "missingval")); - CHECK_THROWS(try_load(pt, "missingval= ")); - CHECK_THROWS(try_load(pt, " = missingkey")); -}