merging refactor (replacing)

This commit is contained in:
2019-04-12 10:53:09 +02:00
parent 0bb800cc8a
commit 89a06f099c
1176 changed files with 82698 additions and 159058 deletions

View File

@ -0,0 +1,42 @@
#include "ClientInterface.h"
#include "ClientSocket.h"
ClientInterface::ClientInterface(sls::ClientSocket *socket, int n) : socket_(socket){}
void ClientInterface::Client_Receive(int &ret, char *mess, void *retval, int sizeOfRetval) {
// get result of operation
socket_->receiveData(reinterpret_cast<char *>(&ret), sizeof(ret));
bool unrecognizedFunction = false;
if (ret == FAIL) {
bool created = false;
// allocate mess if null
if (!mess) {
created = true;
mess = new char[MAX_STR_LENGTH];
memset(mess, 0, MAX_STR_LENGTH);
}
// get error message
socket_->receiveData(mess, MAX_STR_LENGTH);
// cprintf(RED, "%s %d returned error: %s", type.c_str(), index, mess);
// unrecognized function, do not ask for retval
if (strstr(mess, "Unrecognized Function") != nullptr)
unrecognizedFunction = true;
// delete allocated mess
if (created)
delete[] mess;
}
// get retval
if (!unrecognizedFunction)
socket_->receiveData(reinterpret_cast<char *>(retval), sizeOfRetval);
}
int ClientInterface::Client_Send(int fnum, void *args, int sizeOfArgs, void *retval,
int sizeOfRetval, char *mess) {
int ret = FAIL;
socket_->sendData(reinterpret_cast<char *>(&fnum), sizeof(fnum));
socket_->sendData(reinterpret_cast<char *>(args), sizeOfArgs);
Client_Receive(ret, mess, retval, sizeOfRetval);
return ret;
}

View File

@ -0,0 +1,89 @@
#include "ClientSocket.h"
#include "logger.h"
#include "sls_detector_defs.h"
#include "sls_detector_exceptions.h"
#include <arpa/inet.h>
#include <cassert>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <unistd.h>
namespace sls {
ClientSocket::ClientSocket(std::string stype, const std::string &host, uint16_t port)
: DataSocket(socket(AF_INET, SOCK_STREAM, 0)), socketType(stype) {
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags |= AI_CANONNAME;
if (getaddrinfo(host.c_str(), NULL, &hints, &result) != 0) {
std::string msg =
"ClientSocket cannot decode host:" + host + " on port " + std::to_string(port) + "\n";
throw SocketError(msg);
}
// TODO! Erik, results could have multiple entries do we need to loop through them?
// struct sockaddr_in serverAddr {};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
memcpy((char *)&serverAddr.sin_addr.s_addr, &((struct sockaddr_in *)result->ai_addr)->sin_addr,
sizeof(in_addr_t));
if (::connect(getSocketId(), (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) {
freeaddrinfo(result);
std::string msg = "ClientSocket: Cannot connect to " + socketType + ":" +
host + " on port " + std::to_string(port) + "\n";
throw SocketError(msg);
}
freeaddrinfo(result);
}
ClientSocket::ClientSocket(std::string sType, struct sockaddr_in addr)
: DataSocket(socket(AF_INET, SOCK_STREAM, 0)), socketType(sType) {
if (::connect(getSocketId(), (struct sockaddr *)&addr, sizeof(addr)) != 0) {
char address[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, address, INET_ADDRSTRLEN);
std::string msg = "ClientSocket: Cannot connect to " + socketType + ":" + address + " on port " +
std::to_string(addr.sin_port) + "\n";
throw SocketError(msg);
}
}
int ClientSocket::sendCommandThenRead(int fnum, void *args, size_t args_size, void *retval,
size_t retval_size) {
int ret = slsDetectorDefs::FAIL;
sendData(&fnum, sizeof(fnum));
sendData(args, args_size);
readReply(ret, retval, retval_size);
return ret;
}
void ClientSocket::readReply(int &ret, void *retval, size_t retval_size) {
receiveData(&ret, sizeof(ret));
if (ret == slsDetectorDefs::FAIL) {
char mess[MAX_STR_LENGTH]{};
// get error message
receiveData(mess, sizeof(mess));
FILE_LOG(logERROR) << socketType << " returned error: " << mess;
std::cout << "\n"; // needed to reset the color.
// Do we need to know hostname here?
// In that case save it???
if (socketType == "Receiver") {
throw ReceiverError(mess);
} else if (socketType == "Detector") {
throw DetectorError(mess);
} else {
throw GuiError(mess);
}
}
// get retval
receiveData(retval, retval_size);
}
}; // namespace sls

View File

@ -0,0 +1,84 @@
#include "CmdLineParser.h"
#include <cstdio>
#include <cstring>
#include <iostream>
#include <iterator>
#include <sstream>
//printing function for debugging
void CmdLineParser::Print() {
std::cout << "\nCmdLineParser::Print()\n";
std::cout << "\tmulti_id: " << multi_id_ << ", detector_id: " << detector_id_ << std::endl;
std::cout << "\texecutable: " << executable_ << '\n';
std::cout << "\tcommand: " << command_ << '\n';
std::cout << "\tn_arguments: " << n_arguments() << '\n';
std::cout << "\targuments: ";
for (const auto &argument : arguments_) {
std::cout << argument << " ";
}
std::cout << "\n\n";
};
void CmdLineParser::Parse(int argc, char *argv[]) {
//first element of argv is the command used to call the executable ->skipping
//and if this is the only command skip all
executable_ = argv[0];
if (argc > 1) {
//second element is cmd string that needs to be decoded
DecodeIdAndPosition(argv[1]);
//The rest of the arguments goes into a vector for later processing
for (int i = 2; i < argc; ++i) {
arguments_.emplace_back(std::string(argv[i]));
}
}
};
void CmdLineParser::Parse(const std::string &s) {
std::istringstream iss(s);
auto it = std::istream_iterator<std::string>(iss);
//read the first element and increment
command_ = *it++;
arguments_ = std::vector<std::string>(it, std::istream_iterator<std::string>());
;
DecodeIdAndPosition(command_.c_str());
}
void CmdLineParser::DecodeIdAndPosition(const char *c) {
bool contains_id = std::strchr(c, '-') != nullptr;
bool contains_pos = std::strchr(c, ':') != nullptr;
char tmp[100];
if (contains_id && contains_pos) {
int r = sscanf(c, "%d-%d:%s", &multi_id_, &detector_id_, tmp);
if (r != 3) {
throw(std::invalid_argument("Cannot decode client or detector id from: \"" + std::string(c) + "\"\n"));
}
command_ = tmp;
} else if (contains_id && !contains_pos) {
int r = sscanf(c, "%d-%s", &multi_id_, tmp);
if (r != 2) {
throw(std::invalid_argument("Cannot decode client id from: \"" + std::string(c) + "\"\n"));
}
command_ = tmp;
} else if (!contains_id && contains_pos) {
int r = sscanf(c, "%d:%s", &detector_id_, tmp);
if (r != 2) {
throw(std::invalid_argument("Cannot decode detector id from: \"" + std::string(c) + "\"\n"));
}
command_ = tmp;
} else {
command_ = c;
}
}
std::vector<char *> CmdLineParser::argv() {
std::vector<char *> vec;
if (command_.empty()!=true){
vec.push_back(&command_.front());
}
for (auto &arg : arguments_) {
vec.push_back(&arg.front());
}
return vec;
}

159
slsSupportLib/src/DataSocket.cpp Executable file
View File

@ -0,0 +1,159 @@
#include "DataSocket.h"
#include "logger.h"
#include "sls_detector_exceptions.h"
#include <algorithm>
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
namespace sls {
DataSocket::DataSocket(int socketId) : socketId_(socketId) {}
DataSocket::~DataSocket() {
if (socketId_ <= 0) {
return;
} else {
try {
close();
} catch (...) {
// pass
}
}
}
void DataSocket::swap(DataSocket &other) noexcept { std::swap(socketId_, other.socketId_); }
DataSocket::DataSocket(DataSocket &&move) noexcept { move.swap(*this); }
DataSocket &DataSocket::operator=(DataSocket &&move) noexcept {
move.swap(*this);
return *this;
}
size_t DataSocket::receiveData(void *buffer, size_t size) {
// std::cout << "Sending\n";
size_t dataRead = 0;
while (dataRead < size) {
dataRead +=
read(getSocketId(), reinterpret_cast<char *>(buffer) + dataRead, size - dataRead);
}
return dataRead;
}
size_t DataSocket::sendData(void *buffer, size_t size) {
// std::cout << "Receiving\n";
size_t dataSent = 0;
while (dataSent < size) {
dataSent +=
write(getSocketId(), reinterpret_cast<char *>(buffer) + dataSent, size - dataSent);
}
return dataSent;
}
int DataSocket::setTimeOut(int t_seconds) {
if (t_seconds <= 0)
return -1;
struct timeval t;
t.tv_sec = 0;
t.tv_usec = 0;
// Receive timeout indefinet
if (::setsockopt(getSocketId(), SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)) < 0) {
FILE_LOG(logERROR) << "setsockopt SO_RCVTIMEO " << 0;
}
t.tv_sec = t_seconds;
t.tv_usec = 0;
// Sending timeout in seconds
if (::setsockopt(getSocketId(), SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(struct timeval)) < 0) {
FILE_LOG(logERROR) << "setsockopt SO_SNDTIMEO " << t_seconds;
}
return 0;
}
void DataSocket::close() {
if (socketId_ > 0) {
if(::close(socketId_)){
throw SocketError("could not close socket");
}
socketId_ = -1;
} else {
throw std::runtime_error("Socket ERROR: close called on bad socket\n");
}
}
void DataSocket::shutDownSocket() {
shutdown(getSocketId(), SHUT_RDWR);
close();
}
struct sockaddr_in ConvertHostnameToInternetAddress(const std::string &hostname) {
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags |= AI_CANONNAME;
struct sockaddr_in serverAddr {};
if (getaddrinfo(hostname.c_str(), NULL, &hints, &result) != 0) {
freeaddrinfo(result);
std::string msg = "ClientSocket cannot decode host:" + hostname + "\n";
throw SocketError(msg);
}
serverAddr.sin_family = AF_INET;
memcpy((char *)&serverAddr.sin_addr.s_addr, &((struct sockaddr_in *)result->ai_addr)->sin_addr,
sizeof(in_addr_t));
freeaddrinfo(result);
return serverAddr;
}
int ConvertHostnameToInternetAddress(const char *const hostname, struct ::addrinfo **res) {
// criteria in selecting socket address structures returned by res
struct ::addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
// get host info into res
int errcode = getaddrinfo(hostname, NULL, &hints, res);
if (errcode != 0) {
FILE_LOG(logERROR) << "Could not convert hostname (" << hostname
<< ") to internet address (zmq):" << gai_strerror(errcode);
} else {
if (*res == NULL) {
FILE_LOG(logERROR) << "Could not converthostname (" << hostname
<< ") to internet address (zmq):"
"gettaddrinfo returned null";
} else {
return 0;
}
}
FILE_LOG(logERROR) << "Could not convert hostname to internet address";
return 1;
};
/**
* Convert Internet Address structure pointer to ip string (char*)
* Clears the internet address structure as well
* @param res pointer to internet address structure
* @param ip pointer to char array to store result in
* @param ipsize size available in ip buffer
* @return 1 for fail, 0 for success
*/
// Do not make this static (for multi threading environment)
int ConvertInternetAddresstoIpString(struct ::addrinfo *res, char *ip, const int ipsize) {
if (inet_ntop(res->ai_family, &((struct sockaddr_in *)res->ai_addr)->sin_addr, ip, ipsize) !=
NULL) {
::freeaddrinfo(res);
return 0;
}
FILE_LOG(logERROR) << "Could not convert internet address to ip string";
return 1;
}
} // namespace sls

View File

@ -0,0 +1,143 @@
#include "ServerInterface.h"
ServerInterface::ServerInterface(MySocketTCP *socket, int n, std::string t): mySocket(socket),
index(n),
type(t){}
void ServerInterface::SetSocket(MySocketTCP *socket) {
mySocket = socket;
}
void ServerInterface::Client_Receive(int& ret, char* mess, void* retval, int sizeOfRetval) {
// get result of operation
mySocket->ReceiveDataOnly(&ret,sizeof(ret));
bool unrecognizedFunction = false;
if (ret == FAIL) {
bool created = false;
// allocate mess if null
if (!mess){
created = true;
mess = new char[MAX_STR_LENGTH];
memset(mess, 0, MAX_STR_LENGTH);
}
// get error message
mySocket->ReceiveDataOnly(mess,MAX_STR_LENGTH);
cprintf(RED, "%s %d returned error: %s", type.c_str(), index, mess);
// unrecognized function, do not ask for retval
if(strstr(mess,"Unrecognized Function") != nullptr)
unrecognizedFunction = true;
// delete allocated mess
if (created)
delete [] mess;
}
// get retval
if (!unrecognizedFunction)
mySocket->ReceiveDataOnly(retval, sizeOfRetval);
}
int ServerInterface::Client_Send(int fnum,
void* args, int sizeOfArgs,
void* retval, int sizeOfRetval,
char* mess) {
int ret = FAIL;
mySocket->SendDataOnly(&fnum,sizeof(fnum));
mySocket->SendDataOnly(args, sizeOfArgs);
Client_Receive(ret, mess, retval, sizeOfRetval);
return ret;
}
int ServerInterface::Server_SendResult(bool update, int ret,
void* retval, int retvalSize, char* mess) {
// update if different clients
if (update && ret == OK && mySocket->differentClients)
ret = FORCE_UPDATE;
// send success of operation
mySocket->SendDataOnly(&ret,sizeof(ret));
if(ret == FAIL) {
// send error message
if (mess)
mySocket->SendDataOnly(mess, MAX_STR_LENGTH);
// debugging feature. should not happen.
else
FILE_LOG(logERROR) << "No error message provided for this failure. Will mess up TCP\n";
}
// send return value
mySocket->SendDataOnly(retval, retvalSize);
return ret;
}
int ServerInterface::Server_ReceiveArg(int& ret, char* mess, void* arg, int sizeofArg, bool checkbase, void* base) {
// client socket crash, cannot receive arguments
if (sizeofArg && mySocket->ReceiveDataOnly(arg, sizeofArg) < 0)
return Server_SocketCrash();
// check if server object created
if (checkbase && base == nullptr)
Server_NullObjectError(ret, mess);
// no crash
return OK;
}
int ServerInterface::Server_VerifyLock(int& ret, char* mess, int lockstatus) {
// server locked
if (mySocket->differentClients && lockstatus)
return Server_LockedError(ret, mess);
return ret;
}
int ServerInterface::Server_VerifyLockAndIdle(int& ret, char* mess, int lockstatus, slsDetectorDefs::runStatus status, int fnum) {
// server locked
if (mySocket->differentClients && lockstatus)
return Server_LockedError(ret, mess);
// server not idle for this command
if (status != slsDetectorDefs::IDLE)
return Server_NotIdleError(ret, mess, fnum);
return ret;
}
void ServerInterface::Server_NullObjectError(int& ret, char* mess) {
ret=FAIL;
strcpy(mess,"Receiver not set up. Please use rx_hostname first.\n");
FILE_LOG(logERROR) << mess;
}
int ServerInterface::Server_SocketCrash() {
FILE_LOG(logERROR) << "Reading from socket failed. Possible socket crash";
return FAIL;
}
int ServerInterface::Server_LockedError(int& ret, char* mess) {
ret = FAIL;
sprintf(mess,"Receiver locked by %s\n", mySocket->lastClientIP);
FILE_LOG(logERROR) << mess;
return ret;
}
int ServerInterface::Server_NotIdleError(int& ret, char* mess, int fnum) {
ret = FAIL;
sprintf(mess,"Can not execute %s when receiver is not idle\n",
getFunctionNameFromEnum((enum detFuncs)fnum));
FILE_LOG(logERROR) << mess;
return ret;
}

View File

@ -0,0 +1,79 @@
#include "ServerSocket.h"
#include "DataSocket.h"
#include "logger.h"
#include "sls_detector_defs.h"
#include "sls_detector_exceptions.h"
#include "string_utils.h"
#include <arpa/inet.h>
#include <iostream>
#include <stdexcept>
#include <unistd.h>
#include <cstring>
#define DEFAULT_PACKET_SIZE 1286
#define SOCKET_BUFFER_SIZE (100 * 1024 * 1024) // 100 MB
#define DEFAULT_BACKLOG 5
namespace sls {
ServerSocket::ServerSocket(int port)
: DataSocket(socket(AF_INET, SOCK_STREAM, 0)), serverPort(port) {
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(getSocketId(), (struct sockaddr *)&serverAddr,
sizeof(serverAddr)) != 0) {
close();
throw std::runtime_error("Server ERROR: cannot bind socket");
}
if (listen(getSocketId(), DEFAULT_BACKLOG) != 0) {
close();
throw std::runtime_error("Server ERROR: cannot listen to socket");
}
}
DataSocket ServerSocket::accept() {
struct sockaddr_in clientAddr;
socklen_t addr_size = sizeof clientAddr;
int newSocket =
::accept(getSocketId(), (struct sockaddr *)&clientAddr, &addr_size);
if (newSocket == -1) {
throw std::runtime_error("Server ERROR: socket accept failed\n");
}
inet_ntop(AF_INET, &(clientAddr.sin_addr), &thisClient_.front(),
INET_ADDRSTRLEN);
std::cout << "lastClient: " << lastClient_ << " thisClient: " << thisClient_
<< '\n';
// Here goes any check for locks etc
lastClient_ = thisClient_;
return DataSocket(newSocket);
}
const std::string &ServerSocket::getLastClient() { return lastClient_; }
int ServerSocket::getPort() const { return serverPort; }
void ServerSocket::SendResult(int &ret, void* retval, int retvalSize, char* mess) {
// send success of operation
sendData(&ret, sizeof(ret));
if (ret == slsDetectorDefs::FAIL) {
// create error message if empty
if (!strlen(mess)) {
strcpy(mess, "No error message provided for this failure in server. Will mess up TCP.");
}
sendData(mess, MAX_STR_LENGTH);
throw sls::RuntimeError(mess);
}
// send return value
sendData(retval, retvalSize);
}
}; // namespace sls

View File

@ -0,0 +1,81 @@
#include "file_utils.h"
#include "logger.h"
#include <iostream>
#include <sstream>
int readDataFile(std::ifstream &infile, short int *data, int nch, int offset) {
int ichan, iline=0;
short int idata;
int interrupt=0;
std::string str;
while (infile.good() and interrupt==0) {
getline(infile,str);
std::istringstream ssstr(str);
ssstr >> ichan >> idata;
if (ssstr.fail() || ssstr.bad()) {
interrupt=1;
break;
}
if (iline<nch) {
if (ichan>=offset) {
data[iline]=idata;
iline++;
}
} else {
interrupt=1;
break;
}
return iline;
};
return iline;
}
int readDataFile(std::string fname, short int *data, int nch) {
std::ifstream infile;
int iline=0;
std::string str;
infile.open(fname.c_str(), std::ios_base::in);
if (infile.is_open()) {
iline=readDataFile(infile, data, nch, 0);
infile.close();
} else {
FILE_LOG(logERROR) << "Could not read file " << fname;
return -1;
}
return iline;
}
int writeDataFile(std::ofstream &outfile,int nch, short int *data, int offset) {
if (data==nullptr)
return slsDetectorDefs::FAIL;
for (int ichan=0; ichan<nch; ichan++)
outfile << ichan+offset << " " << *(data+ichan) << std::endl;
return slsDetectorDefs::OK;
}
int writeDataFile(std::string fname,int nch, short int *data) {
std::ofstream outfile;
if (data==nullptr)
return slsDetectorDefs::FAIL;
outfile.open (fname.c_str(),std::ios_base::out);
if (outfile.is_open()) {
writeDataFile(outfile, nch, data, 0);
outfile.close();
return slsDetectorDefs::OK;
} else {
FILE_LOG(logERROR) << "Could not open file " << fname << "for writing";
return slsDetectorDefs::FAIL;
}
}

View File

@ -0,0 +1,95 @@
#include "sls_detector_exceptions.h"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "network_utils.h"
namespace sls {
IpAddr::IpAddr(const std::string &address) {
inet_pton(AF_INET, address.c_str(), &addr_);
}
IpAddr::IpAddr(const char *address) {
inet_pton(AF_INET, address, &addr_);
}
std::string IpAddr::str() const {
char ipstring[INET_ADDRSTRLEN]{};
inet_ntop(AF_INET, &addr_, ipstring, INET_ADDRSTRLEN);
return ipstring;
}
std::string IpAddr::hex() const {
std::ostringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2);
for (int i = 0; i != 4; ++i) {
ss << ((addr_ >> i * 8) & 0xFF);
}
return ss.str();
}
MacAddr::MacAddr(std::string mac) {
if ((mac.length() != 17) || (mac[2] != ':') || (mac[5] != ':') ||
(mac[8] != ':') || (mac[11] != ':') || (mac[14] != ':')) {
addr_ = 0;
} else {
mac.erase(std::remove(mac.begin(), mac.end(), ':'), mac.end());
addr_ = std::strtoul(mac.c_str(), nullptr, 16);
}
}
MacAddr::MacAddr(const char *address) : MacAddr(std::string(address)) {}
std::string MacAddr::to_hex(const char delimiter) const {
std::ostringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2);
ss << ((addr_ >> 40) & 0xFF);
for (int i = 32; i >= 0; i -= 8) {
if (delimiter)
ss << delimiter;
ss << ((addr_ >> i) & 0xFF);
}
return ss.str();
}
std::string MacAddr::str() const {
return to_hex(':');
}
std::string MacAddr::hex() const {
return to_hex();
}
std::ostream &operator<<(std::ostream &out, const IpAddr &addr) {
return out << addr.str();
}
std::ostream &operator<<(std::ostream &out, const MacAddr &addr) {
return out << addr.str();
}
uint32_t HostnameToIp(const char *hostname) {
addrinfo hints;
addrinfo *result = nullptr;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(hostname, NULL, &hints, &result)) {
freeaddrinfo(result);
throw RuntimeError("Could not convert hostname to ip");
}
uint32_t ip = ((sockaddr_in *)result->ai_addr)->sin_addr.s_addr;
freeaddrinfo(result);
return ip;
}
} // namespace sls

View File

@ -0,0 +1,54 @@
#include "string_utils.h"
#include "container_utils.h"
#include "network_utils.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
namespace sls {
std::vector<std::string> split(const std::string &strToSplit, char delimeter) {
std::stringstream ss(strToSplit);
std::string item;
std::vector<std::string> splittedStrings;
while (std::getline(ss, item, delimeter)) {
splittedStrings.push_back(item);
}
return splittedStrings;
}
std::string concatenateNonEmptyStrings(const std::vector<std::string> &vec) {
std::string ret;
for (const auto &s : vec)
if (!s.empty())
ret += s + '+';
return ret;
}
std::string concatenateIfDifferent(const std::vector<std::string> &container) {
if (allEqual(container)) {
return container.front();
} else {
std::string result;
for (const auto &s : container)
result += s + '+';
return result;
}
}
template <typename T>
std::string concatenateIfDifferent(const std::vector<T> &container) {
if (allEqual(container)) {
return container.front().str();
} else {
std::string result;
for (const auto &s : container)
result += s.str() + '+';
return result;
}
}
template std::string concatenateIfDifferent(const std::vector<IpAddr> &);
template std::string concatenateIfDifferent(const std::vector<MacAddr> &);
}; // namespace sls