BlinkenArea - GitList
Repositories
Blog
Wiki
libetherpix
Code
Commits
Branches
Tags
Search
Tree:
5c70d9d
Branches
Tags
master
libetherpix
simulator
config.cpp
implement receiving packets
Stefan Schuermans
commited
5c70d9d
at 2017-06-11 15:48:14
config.cpp
Blame
History
Raw
/* * EtherPix simulator * * Copyright 2017 Stefan Schuermans <stefan schuermans info> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <algorithm> #include <arpa/inet.h> #include <cctype> #include <cmath> #include <cstdlib> #include <functional> #include <fstream> #include <map> #include <netinet/in.h> #include <stdexcept> #include <sstream> #include <string> #include <sys/socket.h> #include "bbox.h" #include "config.h" #include "distri.h" #include "mapping.h" #include "pixel.h" /** * @brief constructor * @param[in] configFile name of config file */ Config::Config(std::string const &configFile): m_distriMap(), m_bb() { readFile(configFile); } /** * @brief create sockets and listen for incoming packets * @throws std::exception in case of error */ void Config::start() { try { DistriMap::iterator dist; for (dist = m_distriMap.begin(); dist != m_distriMap.end(); ++dist) { Distri &distri = dist->second; distri.start(); } } catch (...) { stop(); throw; } } /** * @brief stop listening for incoming packets and close sockets */ void Config::stop() { DistriMap::iterator dist; for (dist = m_distriMap.begin(); dist != m_distriMap.end(); ++dist) { Distri &distri = dist->second; distri.stop(); } } /// get and reset packet counter of all distributors unsigned long Config::getPacketCounterAndReset() { unsigned long sum = 0; DistriMap::iterator dist; for (dist = m_distriMap.begin(); dist != m_distriMap.end(); ++dist) { Distri &distri = dist->second; sum += distri.getPacketCounterAndReset(); } return sum; } /** * @brief draw pixels of all distributors * @param[in] cairo cairo context for drawing * @param[in] tf coordinate transformation */ void Config::draw(Cairo::RefPtr<Cairo::Context> &cairo, Transform const &tf) const { DistriMap::const_iterator dist; for (dist = m_distriMap.begin(); dist != m_distriMap.end(); ++dist) { Distri const &distri = dist->second; distri.draw(cairo, tf); } } /** * @brief remove whitespace at beging and end of string * @param[in,out] str string to process */ void Config::trim(std::string &str) { // remove leading whitespace str.erase(str.begin(), std::find_if(str.begin(), str.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); // remove trailing whitespace str.erase(std::find_if(str.rbegin(), str.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), str.end()); } /** * @brief split string at sequences of whitespace * @param[in] str string to split * @param[out] fields fields of string */ void Config::split(std::string const &str, std::vector<std::string> &fields) { std::stringstream strm(str); fields.clear(); while (! strm.eof()) { std::string field; strm >> field; if (! field.empty()) { fields.push_back(field); } } } /** * @brief split string at specific character * @param[in] str string to split * @param[in] delim delimiter character * @param[out] fields fields of string */ void Config::split_chr(std::string const &str, char delim, std::vector<std::string> &fields) { std::string::size_type pos = 0, end; fields.clear(); while ((end = str.find(delim, pos)) != std::string::npos) { fields.push_back(str.substr(pos, end - pos)); pos = end + 1; } fields.push_back(str.substr(pos)); } /** * @brief convert string to unsigned long * @param[in] str string to convert * @return unsigned long parsed from string * @throws std::exception in case of error */ unsigned long Config::str2ul(std::string const &str) { char const *sz = str.c_str(); char *end; unsigned long ul = strtoul(sz, &end, 0); if (end == sz || *end != 0) { std::stringstream msg; msg << "invalid unsigned integer \"" << str << "\""; throw std::runtime_error(msg.str()); } return ul; } /** * @brief convert string to two unsigned longs * @param[in] str string containing two comma-separated unsigned integers * @param[out] ul1 first unsigned long parsed from string * @param[out] ul2 second unsigned long parsed from string * @throws std::exception in case of error */ void Config::str2ul2(std::string const &str, unsigned long &ul1, unsigned long &ul2) { std::vector<std::string> fields; split_chr(str, ',', fields); if (fields.size() != 2) { std::stringstream msg; msg << "expected two comma-separated unsigned integers, found \"" << str << "\""; throw std::runtime_error(msg.str()); } try { ul1 = str2ul(fields[0]); ul2 = str2ul(fields[1]); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid unsigned integer pair \"" << str << "\": " << ex.what(); throw std::runtime_error(msg.str()); } } /** * @brief convert string to double (using the decimal dot for any locale) * @param[in] str string to convert * @return double parsed from string * @throws std::exception in case of error */ double Config::str2d(std::string const &str) { char const *sz = str.c_str(); // skip leading whitespace while (isspace(*sz)) { ++sz; } // start processing after leading whitespace char const *ptr = sz; // read optional sign double sign = 1.0; if (*ptr == '+') { sign = 1.0; ++ptr; } else if (*ptr == '-') { sign = -1.0; ++ptr; } // read digits before dot double value = 0.0; while (*ptr >= '0' && *ptr <= '9') { value = value * 10.0 + (double)(*ptr - '0'); ++ptr; } // read dot and digits after it double weight = 0.1; if (*ptr == '.') { ++ptr; /* read digits after dot */ while (*ptr >= '0' && *ptr <= '9') { value += weight * (double)(*ptr - '0'); weight *= 0.1; ++ptr; } } // if (*ptr == '.') // merge sign into value value *= sign; // read exponent if (*ptr == 'E' || *ptr == 'e') { ++ptr; /* read optional sign */ int exp_sign = 1; if (*ptr == '+') { exp_sign = 1; ++ptr; } else if (*ptr == '-') { exp_sign = -1; ++ptr; } /* read digits */ int exp_value = 0; while (*ptr >= '0' && *ptr <= '9') { exp_value = exp_value * 10 + (int)(*ptr - '0'); ++ptr; } /* merge exponent into value */ value *= pow(10.0, exp_sign * exp_value); } // if (*nptr == 'E' || *nptr == 'e') // skip trailing whitespace while (isspace(*ptr)) { ++ptr; } // leftover characters -> error */ if (ptr == sz || *ptr != 0) { std::stringstream msg; msg << "invalid floating point value \"" << str << "\""; throw std::runtime_error(msg.str()); } // return value return value; } /** * @brief convert string to three doubles (decimal dot for any locale) * @param[in] str string containing three comma-separated doubles * @param[out] d1 first double parsed from string * @param[out] d2 second double parsed from string * @param[out] d3 third double parsed from string * @throws std::exception in case of error */ void Config::str2d3(std::string const &str, double &d1, double &d2, double &d3) { std::vector<std::string> fields; split_chr(str, ',', fields); if (fields.size() != 3) { std::stringstream msg; msg << "expected three comma-separated floating point values, found \"" << str << "\""; throw std::runtime_error(msg.str()); } try { d1 = str2d(fields[0]); d2 = str2d(fields[1]); d3 = str2d(fields[2]); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid floating point triplet \"" << str << "\": " << ex.what(); throw std::runtime_error(msg.str()); } } /** * @brief convert string to IPv4/port address * @param[in] str string containing IPv4/port address * @throws std::exception in case of error */ void Config::str2addr(std::string const &str, struct sockaddr_in &addr) { // split address into IPv4 and port std::vector<std::string> fields; split_chr(str, ':', fields); if (fields.size() != 2) { std::stringstream msg; msg << "expected \"<IPv4>:<port>\", found \"" << str << "\""; throw std::runtime_error(msg.str()); } std::string &strIPv4 = fields[0], &strPort = fields[1]; // parse IPv4 address if (! inet_aton(strIPv4.c_str(), &addr.sin_addr)) { std::stringstream msg; msg << "invalid IPv4 \"" << strIPv4 << "\""; throw std::runtime_error(msg.str()); } // parse port unsigned long port; try { port = str2ul(strPort); if (port == 0 || port > 65535) { throw std::runtime_error("port number out of range"); } } catch (std::exception &ex) { std::stringstream msg; msg << "invalid port number \"" << strPort << "\": " << ex.what(); throw std::runtime_error(msg.str()); } addr.sin_port = htons(port); // fin in remaining fields of address structure addr.sin_family = AF_INET; } /** * @brief read config file * @param[in] configFile name of config file * @throws std::exception in case of error */ void Config::readFile(std::string const &configFile) { // open file std::ifstream cf(configFile.c_str(), std::ios::in); if (! cf.is_open()) { std::stringstream msg; msg << "cannot open config file \"" << configFile << "\" for reading"; throw std::runtime_error(msg.str()); } // read lines and process them std::string line; size_t no; for (no = 1; std::getline(cf, line); ++no) { // process line try { procLine(line); } catch (std::exception &ex) { std::stringstream msg; msg << "error in line " << no << " of config file \"" << configFile << "\": " << ex.what(); throw std::runtime_error(msg.str()); } } // close file cf.close(); // update internal data update(); } /** * @brief process line from config file * @param[in] line line to process * @throws std::exception in case of error */ void Config::procLine(std::string line) { // remove comment ("# ...") size_t commentStart = line.find("#"); if (commentStart != std::string::npos) { line.erase(commentStart); } trim(line); // ignore empty lines if (line.empty()) { return; } // split line at equal sign size_t equalSign = line.find("="); if (equalSign == std::string::npos) { std::stringstream msg; msg << "no equal sign found in \"" << line << "\""; throw std::runtime_error(msg.str()); } std::string setting = line.substr(0, equalSign); std::string value = line.substr(equalSign + 1); trim(setting); trim(value); procSetting(setting, value); } /** * @brief process setting from config file * @param[in] setting name of the setting * @param[in] value value for setting * @throws std::exception in case of error */ void Config::procSetting(std::string const &setting, std::string const &value) { std::vector<std::string> setFields, valFields; split(setting, setFields); split(value, valFields); // empty setting if (setFields.size() < 1) { throw std::runtime_error("empty setting name"); } // distributor if (setFields[0] == "distributor") { proc_distributor(setFields, valFields); } // distributor address else if (setFields[0] == "distributorAddr") { proc_distributorAddr(setFields, valFields); } // mapping else if (setFields[0] == "mapping") { proc_mapping(setFields, valFields); } // output else if (setFields[0] == "output") { proc_output(setFields, valFields); } // unknown setting else { std::stringstream msg; msg << "unknown setting \"" << setting << "\""; throw std::runtime_error(msg.str()); } } /** * @brief process distributor line from config file * @param[in] setFields fields of the setting parts * @param[in] valFields fields of the value part * @throws std::exception in case of error */ void Config::proc_distributor(std::vector<std::string> const &setFields, std::vector<std::string> const &valFields) { // check number of setting fields if (setFields.size() != 2) { std::stringstream msg; msg << "invalid \"distributor\" setting with " << setFields.size() << " fields, expected \"distributor <distno>\""; throw std::runtime_error(msg.str()); } // get and check distributor number unsigned long distno; try { distno = str2ul(setFields[1]); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid distributor number \"" << setFields[1] << "\": " << ex.what(); throw std::runtime_error(msg.str()); } if (m_distriMap.find(distno) != m_distriMap.end()) { std::stringstream msg; msg << "duplicate \"distributor " << distno << "\" setting"; throw std::runtime_error(msg.str()); } // check number of value fields if (valFields.size() != 1) { std::stringstream msg; msg << "invalid value for \"distributor " << distno << "\": " << setFields.size() << " fields, expected \"<outputs>,<pixels>\""; throw std::runtime_error(msg.str()); } // get and check outputs and pixels per output unsigned long outputs, pixels; try { str2ul2(valFields[0], outputs, pixels); if (outputs < 1) { std::stringstream msg; msg << "invalid number of outputs \"" << outputs << "\""; throw std::runtime_error(msg.str()); } if (pixels < 1) { std::stringstream msg; msg << "invalid number of pixels per output \"" << pixels << "\""; throw std::runtime_error(msg.str()); } } catch (std::exception &ex) { std::stringstream msg; msg << "invalid value for \"distributor " << distno << "\": " << ex.what(); throw std::runtime_error(msg.str()); } // create distributor m_distriMap[distno].init(distno, outputs, pixels); } /** * @brief process distributorAddr line from config file * @param[in] setFields fields of the setting parts * @param[in] valFields fields of the value part * @throws std::exception in case of error */ void Config::proc_distributorAddr(std::vector<std::string> const &setFields, std::vector<std::string> const &valFields) { // check number of setting fields if (setFields.size() != 2) { std::stringstream msg; msg << "invalid \"distributorAddr\" setting with " << setFields.size() << " fields, expected \"distributorAddr <distno>\""; throw std::runtime_error(msg.str()); } // get distributor Distri &distri = getDistri(setFields[1]); // check number of value fields if (valFields.size() != 1) { std::stringstream msg; msg << "invalid value for \"distributorAddr " << distri.getNo() << "\": " << setFields.size() << " fields, expected \"<IPv4>:<port>\""; throw std::runtime_error(msg.str()); } // parse address struct sockaddr_in addr; try { str2addr(valFields[0], addr); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid value for \"mdistributorAddr " << distri.getNo() << "\": " << ex.what(); throw std::runtime_error(msg.str()); } // set address distri.setAddr(addr); } /** * @brief process mapping line from config file * @param[in] setFields fields of the setting parts * @param[in] valFields fields of the value part * @throws std::exception in case of error */ void Config::proc_mapping(std::vector<std::string> const &setFields, std::vector<std::string> const &valFields) { // check number of setting fields if (setFields.size() != 3) { std::stringstream msg; msg << "invalid \"mapping\" setting with " << setFields.size() << " fields, expected \"mapping <distno> [red|green|blue]\""; throw std::runtime_error(msg.str()); } // get distributor Distri &distri = getDistri(setFields[1]); // get channel std::string strColor = setFields[2]; Mapping::Channel channel; if (strColor == "red" ) { channel = Mapping::Red; } else if (strColor == "green" ) { channel = Mapping::Green; } else if (strColor == "blue" ) { channel = Mapping::Blue; } else { std::stringstream msg; msg << "invalid \"mapping\" setting: unknown color \"" << strColor << "\" fields, expected \"red\", \"green\" or \"blue\""; throw std::runtime_error(msg.str()); } // check number of value fields if (valFields.size() != 3) { std::stringstream msg; msg << "invalid value for \"mapping " << distri.getNo() << " " << strColor << "\": " << setFields.size() << " fields, expected \"<base> <factor> <gamma>\""; throw std::runtime_error(msg.str()); } // parse value fields double base, factor, gamma; try { base = str2d(valFields[0]); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid value for \"mapping " << distri.getNo() << " " << strColor << "\": invalid value \"" << valFields[0] << "\"for \"base\": " << ex.what(); throw std::runtime_error(msg.str()); } try { factor = str2d(valFields[1]); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid value for \"mapping " << distri.getNo() << " " << strColor << "\": invalid value \"" << valFields[1] << "\"for \"factor\": " << ex.what(); throw std::runtime_error(msg.str()); } try { gamma = str2d(valFields[2]); if (gamma <= 0.0) { throw std::runtime_error("gamma value must be positive"); } } catch (std::exception &ex) { std::stringstream msg; msg << "invalid value for \"mapping " << distri.getNo() << " " << strColor << "\": invalid value \"" << valFields[2] << "\"for \"base\": " << ex.what(); throw std::runtime_error(msg.str()); } // set mapping of distributor distri.setMapping(channel, Mapping(base, factor, gamma)); } /** * @brief process output line from config file * @param[in] setFields fields of the setting parts * @param[in] valFields fields of the value part * @throws std::exception in case of error */ void Config::proc_output(std::vector<std::string> const &setFields, std::vector<std::string> const &valFields) { // check number of setting fields if (setFields.size() != 2) { std::stringstream msg; msg << "invalid \"output\" setting with " << setFields.size() << " fields, expected \"output <distno>,<outno>\""; throw std::runtime_error(msg.str()); } // parse distributor and output numbers unsigned long distno, outno; try { str2ul2(setFields[1], distno, outno); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid output specifier \"" << setFields[1] << "\": expected \"<distno>,<outno>\": " << ex.what(); throw std::runtime_error(msg.str()); } // get distributor Distri &distri = getDistri(distno); // check output number unsigned long outputs = distri.getOutputs(); if (outno > outputs) { std::stringstream msg; msg << "invalid output number " << outno << " for distributor " << distno << ": only " << outputs << " outputs available"; throw std::runtime_error(msg.str()); } // add output to distributor try { distri.addOutput(outno); } catch (std::exception &ex) { std::stringstream msg; msg << "cannot add output " << outno << " to distributor " << distno << ": " << ex.what(); throw std::runtime_error(msg.str()); } // parse all pixels std::vector<std::string>::const_iterator pix; unsigned long p; for (pix = valFields.begin(), p = 0; pix != valFields.end(); ++pix, ++p) { // parse pixel description double x, y, r; try { str2d3(*pix, x, y, r); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid pixel description \"" << *pix << "\" for pixel " << p << " at output " << outno << " of distributor " << distno << ": expected \"<x>,<y>,<r>\": " << ex.what(); throw std::runtime_error(msg.str()); } // add pixel try { distri.addPixel(outno, Pixel(x, y, r)); } catch (std::exception &ex) { std::stringstream msg; msg << "cannot add pixel \"" << *pix << "\" (pixel " << p << ") to output " << outno << " of distributor " << distno << ": " << ex.what(); throw std::runtime_error(msg.str()); } } // for pixel } /** * @brief get distributor * @param[in] strDistno distributor number as string * @return reference to distributor object in map * @throws std::exception in case of error */ Distri & Config::getDistri(std::string const &strDistno) { unsigned long distno; try { distno = str2ul(strDistno); } catch (std::exception &ex) { std::stringstream msg; msg << "invalid distributor number \"" << strDistno << "\": " << ex.what(); throw std::runtime_error(msg.str()); } return getDistri(distno); } /** * @brief get distributor * @param[in] distno distributor number * @return reference to distributor object in map * @throws std::exception in case of error */ Distri & Config::getDistri(unsigned long distno) { DistriMap::iterator distri = m_distriMap.find(distno); if (distri == m_distriMap.end()) { std::stringstream msg; msg << "no distributor with number \"" << distno << "\" found"; throw std::runtime_error(msg.str()); } return distri->second; } /// update internal data void Config::update() { // re-compute bounding box of all pixels m_bb.reset(); DistriMap::const_iterator dist; for (dist = m_distriMap.begin(); dist != m_distriMap.end(); ++dist) { Distri const &distri = dist->second; distri.updateBBox(m_bb); } }