/* nutclient.cpp - nutclient C++ library implementation Copyright (C) 2012 Emilien Kia 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; either version 2 of the License, or (at your option) any later version. 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 General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "nutclient.h" #include #include #include #include /* Windows/Linux Socket compatibility layer: */ /* Thanks to Benjamin Roux (http://broux.developpez.com/articles/c/sockets/) */ #ifdef WIN32 # include #else # include # include # include # include # include /* close */ # include /* gethostbyname */ # include # define INVALID_SOCKET -1 # define SOCKET_ERROR -1 # define closesocket(s) close(s) typedef int SOCKET; typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr SOCKADDR; typedef struct in_addr IN_ADDR; #endif /* WIN32 */ /* End of Windows/Linux Socket compatibility layer: */ /* Include nut common utility functions or define simple ones if not */ #ifdef HAVE_NUTCOMMON #include "common.h" #else /* HAVE_NUTCOMMON */ #include #include static inline void *xmalloc(size_t size){return malloc(size);} static inline void *xcalloc(size_t number, size_t size){return calloc(number, size);} static inline void *xrealloc(void *ptr, size_t size){return realloc(ptr, size);} static inline char *xstrdup(const char *string){return strdup(string);} #endif /* HAVE_NUTCOMMON */ /* To stay in line with modern C++, we use nullptr (not numeric NULL * or shim __null on some systems) which was defined after C++98. * The NUT C++ interface is intended for C++11 and newer, so we * quiesce these warnigns if possible. * An idea might be to detect if we do build with old C++ standard versions * and define a nullptr like https://stackoverflow.com/a/44517878/4715872 * but again - currently we do not intend to support that officially. */ #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT_PEDANTIC #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT #pragma GCC diagnostic ignored "-Wc++98-compat" #endif namespace nut { SystemException::SystemException(): NutException(err()) { } std::string SystemException::err() { if(errno==0) return "Undefined system error"; else { std::stringstream str; str << "System error " << errno << ": " << strerror(errno); return str.str(); } } /* Implemented out-of-line to avoid "Weak vtables" warnings and related overheads * But now with clang-9 C++11 linter (though not C++17) they complain with * error: definition of implicit copy constructor for 'NutException' * is deprecated because it has a user-declared destructor * This is fixed in header with declarations like: * NutException(const NutException&) = default; * and assignment operator to accompany the copy constructor, per * https://lgtm.com/rules/2165180572/ like: * NutException& operator=(NutException& rhs) = default; */ NutException::~NutException() {} SystemException::~SystemException() {} IOException::~IOException() {} UnknownHostException::~UnknownHostException() {} NotConnectedException::~NotConnectedException() {} TimeoutException::~TimeoutException() {} namespace internal { /** * Internal socket wrapper. * Provides only client socket functions. * * Implemented as separate internal class to easily hide plateform specificities. */ class Socket { public: Socket(); ~Socket(); void connect(const std::string& host, uint16_t port); void disconnect(); bool isConnected()const; void setTimeout(time_t timeout); bool hasTimeout()const{return _tv.tv_sec>=0;} size_t read(void* buf, size_t sz); size_t write(const void* buf, size_t sz); std::string read(); void write(const std::string& str); private: SOCKET _sock; struct timeval _tv; std::string _buffer; /* Received buffer, string because data should be text only. */ }; Socket::Socket(): _sock(INVALID_SOCKET), _tv() { _tv.tv_sec = -1; _tv.tv_usec = 0; } Socket::~Socket() { disconnect(); } void Socket::setTimeout(time_t timeout) { _tv.tv_sec = timeout; } void Socket::connect(const std::string& host, uint16_t port) { int sock_fd; struct addrinfo hints, *res, *ai; char sport[NI_MAXSERV]; int v; fd_set wfds; int error; socklen_t error_size; long fd_flags; _sock = -1; if (host.empty()) { throw nut::UnknownHostException(); } snprintf(sport, sizeof(sport), "%ju", static_cast(port)); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; while ((v = getaddrinfo(host.c_str(), sport, &hints, &res)) != 0) { switch (v) { case EAI_AGAIN: continue; case EAI_NONAME: throw nut::UnknownHostException(); case EAI_SYSTEM: throw nut::SystemException(); case EAI_MEMORY: throw nut::NutException("Out of memory"); default: throw nut::NutException("Unknown error"); } } for (ai = res; ai != nullptr; ai = ai->ai_next) { sock_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock_fd < 0) { switch (errno) { case EAFNOSUPPORT: case EINVAL: break; default: throw nut::SystemException(); } continue; } /* non blocking connect */ if(hasTimeout()) { fd_flags = fcntl(sock_fd, F_GETFL); fd_flags |= O_NONBLOCK; fcntl(sock_fd, F_SETFL, fd_flags); } while ((v = ::connect(sock_fd, ai->ai_addr, ai->ai_addrlen)) < 0) { if(errno == EINPROGRESS) { FD_ZERO(&wfds); FD_SET(sock_fd, &wfds); select(sock_fd+1, nullptr, &wfds, nullptr, hasTimeout() ? &_tv : nullptr); if (FD_ISSET(sock_fd, &wfds)) { error_size = sizeof(error); getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &error, &error_size); if( error == 0) { /* connect successful */ v = 0; break; } errno = error; } else { /* Timeout */ v = -1; break; } } switch (errno) { case EAFNOSUPPORT: break; case EINTR: case EAGAIN: continue; default: // ups->upserror = UPSCLI_ERR_CONNFAILURE; // ups->syserrno = errno; break; } break; } if (v < 0) { close(sock_fd); continue; } /* switch back to blocking operation */ if(hasTimeout()) { fd_flags = fcntl(sock_fd, F_GETFL); fd_flags &= ~O_NONBLOCK; fcntl(sock_fd, F_SETFL, fd_flags); } _sock = sock_fd; // ups->upserror = 0; // ups->syserrno = 0; break; } freeaddrinfo(res); if (_sock < 0) { throw nut::IOException("Cannot connect to host"); } #ifdef OLD struct hostent *hostinfo = nullptr; SOCKADDR_IN sin = { 0 }; hostinfo = ::gethostbyname(host.c_str()); if(hostinfo == nullptr) /* Host doesnt exist */ { throw nut::UnknownHostException(); } // Create socket _sock = ::socket(PF_INET, SOCK_STREAM, 0); if(_sock == INVALID_SOCKET) { throw nut::IOException("Cannot create socket"); } // Connect sin.sin_addr = *(IN_ADDR *) hostinfo->h_addr; sin.sin_port = htons(port); sin.sin_family = AF_INET; if(::connect(_sock,(SOCKADDR *) &sin, sizeof(SOCKADDR)) == SOCKET_ERROR) { _sock = INVALID_SOCKET; throw nut::IOException("Cannot connect to host"); } #endif // OLD } void Socket::disconnect() { if(_sock != INVALID_SOCKET) { ::closesocket(_sock); _sock = INVALID_SOCKET; } _buffer.clear(); } bool Socket::isConnected()const { return _sock!=INVALID_SOCKET; } size_t Socket::read(void* buf, size_t sz) { if(!isConnected()) { throw nut::NotConnectedException(); } if(_tv.tv_sec>=0) { fd_set fds; FD_ZERO(&fds); FD_SET(_sock, &fds); int ret = select(_sock+1, &fds, nullptr, nullptr, &_tv); if (ret < 1) { throw nut::TimeoutException(); } } ssize_t res = ::read(_sock, buf, sz); if(res==-1) { disconnect(); throw nut::IOException("Error while reading on socket"); } return static_cast(res); } size_t Socket::write(const void* buf, size_t sz) { if(!isConnected()) { throw nut::NotConnectedException(); } if(_tv.tv_sec>=0) { fd_set fds; FD_ZERO(&fds); FD_SET(_sock, &fds); int ret = select(_sock+1, nullptr, &fds, nullptr, &_tv); if (ret < 1) { throw nut::TimeoutException(); } } ssize_t res = ::write(_sock, buf, sz); if(res==-1) { disconnect(); throw nut::IOException("Error while writing on socket"); } return static_cast(res); } std::string Socket::read() { std::string res; char buff[256]; while(true) { // Look at already read data in _buffer if(!_buffer.empty()) { size_t idx = _buffer.find('\n'); if(idx!=std::string::npos) { res += _buffer.substr(0, idx); _buffer.erase(0, idx+1); return res; } res += _buffer; } // Read new buffer size_t sz = read(&buff, 256); if(sz==0) { disconnect(); throw nut::IOException("Server closed connection unexpectedly"); } _buffer.assign(buff, sz); } } void Socket::write(const std::string& str) { // write(str.c_str(), str.size()); // write("\n", 1); std::string buff = str + "\n"; write(buff.c_str(), buff.size()); } }/* namespace internal */ /* * * Client implementation * */ /* Pedantic builds complain about the static variable below... * It is assumed safe to ignore since it is a std::string with * no complex teardown at program exit. */ #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) #pragma GCC diagnostic push # ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS # pragma GCC diagnostic ignored "-Wglobal-constructors" # endif # ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS # pragma GCC diagnostic ignored "-Wexit-time-destructors" # endif #endif const Feature Client::TRACKING = "TRACKING"; #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) #pragma GCC diagnostic pop #endif Client::Client() { } Client::~Client() { } bool Client::hasDevice(const std::string& dev) { std::set devs = getDeviceNames(); return devs.find(dev) != devs.end(); } Device Client::getDevice(const std::string& name) { if(hasDevice(name)) return Device(this, name); else return Device(nullptr, ""); } std::set Client::getDevices() { std::set res; std::set devs = getDeviceNames(); for(std::set::iterator it=devs.begin(); it!=devs.end(); ++it) { res.insert(Device(this, *it)); } return res; } bool Client::hasDeviceVariable(const std::string& dev, const std::string& name) { std::set names = getDeviceVariableNames(dev); return names.find(name) != names.end(); } std::map > Client::getDeviceVariableValues(const std::string& dev) { std::map > res; std::set names = getDeviceVariableNames(dev); for(std::set::iterator it=names.begin(); it!=names.end(); ++it) { const std::string& name = *it; res[name] = getDeviceVariableValue(dev, name); } return res; } std::map > > Client::getDevicesVariableValues(const std::set& devs) { std::map > > res; for(std::set::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it) { res[*it] = getDeviceVariableValues(*it); } return res; } bool Client::hasDeviceCommand(const std::string& dev, const std::string& name) { std::set names = getDeviceCommandNames(dev); return names.find(name) != names.end(); } bool Client::hasFeature(const Feature& feature) { try { // If feature is known, querying it won't throw an exception. isFeatureEnabled(feature); return true; } catch(...) { return false; } } /* * * TCP Client implementation * */ TcpClient::TcpClient(): Client(), _host("localhost"), _port(3493), _timeout(0), _socket(new internal::Socket) { // Do not connect now } TcpClient::TcpClient(const std::string& host, uint16_t port): Client(), _timeout(0), _socket(new internal::Socket) { connect(host, port); } TcpClient::~TcpClient() { delete _socket; } void TcpClient::connect(const std::string& host, uint16_t port) { _host = host; _port = port; connect(); } void TcpClient::connect() { _socket->connect(_host, _port); } std::string TcpClient::getHost()const { return _host; } uint16_t TcpClient::getPort()const { return _port; } bool TcpClient::isConnected()const { return _socket->isConnected(); } void TcpClient::disconnect() { _socket->disconnect(); } void TcpClient::setTimeout(time_t timeout) { _timeout = timeout; } time_t TcpClient::getTimeout()const { return _timeout; } void TcpClient::authenticate(const std::string& user, const std::string& passwd) { detectError(sendQuery("USERNAME " + user)); detectError(sendQuery("PASSWORD " + passwd)); } void TcpClient::logout() { detectError(sendQuery("LOGOUT")); _socket->disconnect(); } Device TcpClient::getDevice(const std::string& name) { try { get("UPSDESC", name); } catch(NutException& ex) { if(ex.str()=="UNKNOWN-UPS") return Device(nullptr, ""); else throw; } return Device(this, name); } std::set TcpClient::getDeviceNames() { std::set res; std::vector > devs = list("UPS"); for(std::vector >::iterator it=devs.begin(); it!=devs.end(); ++it) { std::string id = (*it)[0]; if(!id.empty()) res.insert(id); } return res; } std::string TcpClient::getDeviceDescription(const std::string& name) { return get("UPSDESC", name)[0]; } std::set TcpClient::getDeviceVariableNames(const std::string& dev) { std::set set; std::vector > res = list("VAR", dev); for(size_t n=0; n TcpClient::getDeviceRWVariableNames(const std::string& dev) { std::set set; std::vector > res = list("RW", dev); for(size_t n=0; n TcpClient::getDeviceVariableValue(const std::string& dev, const std::string& name) { return get("VAR", dev + " " + name); } std::map > TcpClient::getDeviceVariableValues(const std::string& dev) { std::map > map; std::vector > res = list("VAR", dev); for(size_t n=0; n& vals = res[n]; std::string var = vals[0]; vals.erase(vals.begin()); map[var] = vals; } return map; } std::map > > TcpClient::getDevicesVariableValues(const std::set& devs) { std::map > > map; if (devs.empty()) { // This request might come from processing the empty valid // response of an upsd server which was allowed to start // with no device sections in its ups.conf return map; } std::vector queries; for (std::set::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it) { queries.push_back("LIST VAR " + *it); } sendAsyncQueries(queries); for (std::set::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it) { try { std::map > map2; std::vector > res = parseList("VAR " + *it); for (std::vector >::iterator it2=res.begin(); it2!=res.end(); ++it2) { std::vector& vals = *it2; std::string var = vals[0]; vals.erase(vals.begin()); map2[var] = vals; } map[*it] = map2; } catch (NutException&) { // We sent a bunch of queries, we need to process them all to clear up the backlog. } } if (map.empty()) { // We may fail on some devices, but not on ALL devices. throw NutException("Invalid device"); } return map; } TrackingID TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value) { std::string query = "SET VAR " + dev + " " + name + " " + escape(value); return sendTrackingQuery(query); } TrackingID TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values) { std::string query = "SET VAR " + dev + " " + name; for(size_t n=0; n TcpClient::getDeviceCommandNames(const std::string& dev) { std::set cmds; std::vector > res = list("CMD", dev); for(size_t n=0; n TcpClient::get (const std::string& subcmd, const std::string& params) { std::string req = subcmd; if(!params.empty()) { req += " " + params; } std::string res = sendQuery("GET " + req); detectError(res); if(res.substr(0, req.size()) != req) { throw NutException("Invalid response"); } return explode(res, req.size()); } std::vector > TcpClient::list (const std::string& subcmd, const std::string& params) { std::string req = subcmd; if(!params.empty()) { req += " " + params; } std::vector query; query.push_back("LIST " + req); sendAsyncQueries(query); return parseList(req); } std::vector > TcpClient::parseList (const std::string& req) { std::string res = _socket->read(); detectError(res); if(res != ("BEGIN LIST " + req)) { throw NutException("Invalid response"); } std::vector > arr; while(true) { res = _socket->read(); detectError(res); if(res == ("END LIST " + req)) { return arr; } if(res.substr(0, req.size()) == req) { arr.push_back(explode(res, req.size())); } else { throw NutException("Invalid response"); } } } std::string TcpClient::sendQuery(const std::string& req) { _socket->write(req); return _socket->read(); } void TcpClient::sendAsyncQueries(const std::vector& req) { for (std::vector::const_iterator it = req.cbegin(); it != req.cend(); ++it) { _socket->write(*it); } } void TcpClient::detectError(const std::string& req) { if(req.substr(0,3)=="ERR") { throw NutException(req.substr(4)); } } std::vector TcpClient::explode(const std::string& str, size_t begin) { std::vector res; std::string temp; enum STATE { INIT, SIMPLE_STRING, QUOTED_STRING, SIMPLE_ESCAPE, QUOTED_ESCAPE } state = INIT; for(size_t idx=begin; idx res = explode(reply); if (res.size() == 1 && res[0] == "OK") { return TrackingID(""); } else if (res.size() == 3 && res[0] == "OK" && res[1] == "TRACKING") { return TrackingID(res[2]); } else { throw NutException("Unknown query result"); } } /* * * Device implementation * */ Device::Device(Client* client, const std::string& name): _client(client), _name(name) { } Device::Device(const Device& dev): _client(dev._client), _name(dev._name) { } Device& Device::operator=(const Device& dev) { // Self assignment? if (this==&dev) return *this; _client = dev._client; _name = dev._name; return *this; } Device::~Device() { } std::string Device::getName()const { return _name; } const Client* Device::getClient()const { return _client; } Client* Device::getClient() { return _client; } bool Device::isOk()const { return _client!=nullptr && !_name.empty(); } Device::operator bool()const { return isOk(); } bool Device::operator!()const { return !isOk(); } bool Device::operator==(const Device& dev)const { return dev._client==_client && dev._name==_name; } bool Device::operator<(const Device& dev)const { return getName()getDeviceDescription(getName()); } std::vector Device::getVariableValue(const std::string& name) { if (!isOk()) throw NutException("Invalid device"); return getClient()->getDeviceVariableValue(getName(), name); } std::map > Device::getVariableValues() { if (!isOk()) throw NutException("Invalid device"); return getClient()->getDeviceVariableValues(getName()); } std::set Device::getVariableNames() { if (!isOk()) throw NutException("Invalid device"); return getClient()->getDeviceVariableNames(getName()); } std::set Device::getRWVariableNames() { if (!isOk()) throw NutException("Invalid device"); return getClient()->getDeviceRWVariableNames(getName()); } void Device::setVariable(const std::string& name, const std::string& value) { if (!isOk()) throw NutException("Invalid device"); getClient()->setDeviceVariable(getName(), name, value); } void Device::setVariable(const std::string& name, const std::vector& values) { if (!isOk()) throw NutException("Invalid device"); getClient()->setDeviceVariable(getName(), name, values); } Variable Device::getVariable(const std::string& name) { if (!isOk()) throw NutException("Invalid device"); if(getClient()->hasDeviceVariable(getName(), name)) return Variable(this, name); else return Variable(nullptr, ""); } std::set Device::getVariables() { std::set set; if (!isOk()) throw NutException("Invalid device"); std::set names = getClient()->getDeviceVariableNames(getName()); for(std::set::iterator it=names.begin(); it!=names.end(); ++it) { set.insert(Variable(this, *it)); } return set; } std::set Device::getRWVariables() { std::set set; if (!isOk()) throw NutException("Invalid device"); std::set names = getClient()->getDeviceRWVariableNames(getName()); for(std::set::iterator it=names.begin(); it!=names.end(); ++it) { set.insert(Variable(this, *it)); } return set; } std::set Device::getCommandNames() { if (!isOk()) throw NutException("Invalid device"); return getClient()->getDeviceCommandNames(getName()); } std::set Device::getCommands() { std::set cmds; std::set res = getCommandNames(); for(std::set::iterator it=res.begin(); it!=res.end(); ++it) { cmds.insert(Command(this, *it)); } return cmds; } Command Device::getCommand(const std::string& name) { if (!isOk()) throw NutException("Invalid device"); if(getClient()->hasDeviceCommand(getName(), name)) return Command(this, name); else return Command(nullptr, ""); } TrackingID Device::executeCommand(const std::string& name, const std::string& param) { if (!isOk()) throw NutException("Invalid device"); return getClient()->executeDeviceCommand(getName(), name, param); } void Device::login() { if (!isOk()) throw NutException("Invalid device"); getClient()->deviceLogin(getName()); } /* Note: "master" is deprecated, but supported * for mixing old/new client/server combos: */ void Device::master() { if (!isOk()) throw NutException("Invalid device"); getClient()->deviceMaster(getName()); } void Device::primary() { if (!isOk()) throw NutException("Invalid device"); getClient()->devicePrimary(getName()); } void Device::forcedShutdown() { } int Device::getNumLogins() { if (!isOk()) throw NutException("Invalid device"); return getClient()->deviceGetNumLogins(getName()); } /* * * Variable implementation * */ Variable::Variable(Device* dev, const std::string& name): _device(dev), _name(name) { } Variable::Variable(const Variable& var): _device(var._device), _name(var._name) { } Variable& Variable::operator=(const Variable& var) { // Self assignment? if (this==&var) return *this; _device = var._device; _name = var._name; return *this; } Variable::~Variable() { } std::string Variable::getName()const { return _name; } const Device* Variable::getDevice()const { return _device; } Device* Variable::getDevice() { return _device; } bool Variable::isOk()const { return _device!=nullptr && !_name.empty(); } Variable::operator bool()const { return isOk(); } bool Variable::operator!()const { return !isOk(); } bool Variable::operator==(const Variable& var)const { return var._device==_device && var._name==_name; } bool Variable::operator<(const Variable& var)const { return getName() Variable::getValue() { return getDevice()->getClient()->getDeviceVariableValue(getDevice()->getName(), getName()); } std::string Variable::getDescription() { return getDevice()->getClient()->getDeviceVariableDescription(getDevice()->getName(), getName()); } void Variable::setValue(const std::string& value) { getDevice()->setVariable(getName(), value); } void Variable::setValues(const std::vector& values) { getDevice()->setVariable(getName(), values); } /* * * Command implementation * */ Command::Command(Device* dev, const std::string& name): _device(dev), _name(name) { } Command::Command(const Command& cmd): _device(cmd._device), _name(cmd._name) { } Command& Command::operator=(const Command& cmd) { // Self assignment? if (this==&cmd) return *this; _device = cmd._device; _name = cmd._name; return *this; } Command::~Command() { } std::string Command::getName()const { return _name; } const Device* Command::getDevice()const { return _device; } Device* Command::getDevice() { return _device; } bool Command::isOk()const { return _device!=nullptr && !_name.empty(); } Command::operator bool()const { return isOk(); } bool Command::operator!()const { return !isOk(); } bool Command::operator==(const Command& cmd)const { return cmd._device==_device && cmd._name==_name; } bool Command::operator<(const Command& cmd)const { return getName()getClient()->getDeviceCommandDescription(getDevice()->getName(), getName()); } void Command::execute(const std::string& param) { getDevice()->executeCommand(getName(), param); } } /* namespace nut */ /** * C nutclient API. */ extern "C" { strarr strarr_alloc(size_t count) { strarr arr = static_cast(xcalloc(count+1, sizeof(char*))); if (arr == nullptr) { throw nut::NutException("Out of memory"); } arr[count] = nullptr; return arr; } void strarr_free(strarr arr) { char** pstr = arr; while(*pstr!=nullptr) { free(*pstr); ++pstr; } free(arr); } strarr stringset_to_strarr(const std::set& strset) { strarr arr = strarr_alloc(strset.size()); strarr pstr = arr; for(std::set::const_iterator it=strset.begin(); it!=strset.end(); ++it) { *pstr = xstrdup(it->c_str()); pstr++; } return arr; } strarr stringvector_to_strarr(const std::vector& strset) { strarr arr = strarr_alloc(strset.size()); strarr pstr = arr; for(std::vector::const_iterator it=strset.begin(); it!=strset.end(); ++it) { *pstr = xstrdup(it->c_str()); pstr++; } return arr; } NUTCLIENT_TCP_t nutclient_tcp_create_client(const char* host, uint16_t port) { nut::TcpClient* client = new nut::TcpClient; try { client->connect(host, port); return static_cast(client); } catch(nut::NutException& ex) { // TODO really catch it NUT_UNUSED_VARIABLE(ex); delete client; return nullptr; } } void nutclient_destroy(NUTCLIENT_t client) { if(client) { delete static_cast(client); } } int nutclient_tcp_is_connected(NUTCLIENT_TCP_t client) { if(client) { nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { return cl->isConnected() ? 1 : 0; } } return 0; } void nutclient_tcp_disconnect(NUTCLIENT_TCP_t client) { if(client) { nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { cl->disconnect(); } } } int nutclient_tcp_reconnect(NUTCLIENT_TCP_t client) { if(client) { nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { try { cl->connect(); return 0; } catch(...){} } } return -1; } void nutclient_tcp_set_timeout(NUTCLIENT_TCP_t client, time_t timeout) { if(client) { nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { cl->setTimeout(timeout); } } } time_t nutclient_tcp_get_timeout(NUTCLIENT_TCP_t client) { if(client) { nut::TcpClient* cl = dynamic_cast(static_cast(client)); if(cl) { return cl->getTimeout(); } } return -1; } void nutclient_authenticate(NUTCLIENT_t client, const char* login, const char* passwd) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->authenticate(login, passwd); } catch(...){} } } } void nutclient_logout(NUTCLIENT_t client) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->logout(); } catch(...){} } } } void nutclient_device_login(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->deviceLogin(dev); } catch(...){} } } } int nutclient_get_device_num_logins(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return cl->deviceGetNumLogins(dev); } catch(...){} } } return -1; } /* Note: "master" is deprecated, but supported * for mixing old/new client/server combos: */ void nutclient_device_master(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->deviceMaster(dev); } catch(...){} } } } void nutclient_device_primary(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->devicePrimary(dev); } catch(...){} } } } void nutclient_device_forced_shutdown(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->deviceForcedShutdown(dev); } catch(...){} } } } strarr nutclient_get_devices(NUTCLIENT_t client) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return stringset_to_strarr(cl->getDeviceNames()); } catch(...){} } } return nullptr; } int nutclient_has_device(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return cl->hasDevice(dev)?1:0; } catch(...){} } } return 0; } char* nutclient_get_device_description(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return xstrdup(cl->getDeviceDescription(dev).c_str()); } catch(...){} } } return nullptr; } strarr nutclient_get_device_variables(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return stringset_to_strarr(cl->getDeviceVariableNames(dev)); } catch(...){} } } return nullptr; } strarr nutclient_get_device_rw_variables(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return stringset_to_strarr(cl->getDeviceRWVariableNames(dev)); } catch(...){} } } return nullptr; } int nutclient_has_device_variable(NUTCLIENT_t client, const char* dev, const char* var) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return cl->hasDeviceVariable(dev, var)?1:0; } catch(...){} } } return 0; } char* nutclient_get_device_variable_description(NUTCLIENT_t client, const char* dev, const char* var) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return xstrdup(cl->getDeviceVariableDescription(dev, var).c_str()); } catch(...){} } } return nullptr; } strarr nutclient_get_device_variable_values(NUTCLIENT_t client, const char* dev, const char* var) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return stringvector_to_strarr(cl->getDeviceVariableValue(dev, var)); } catch(...){} } } return nullptr; } void nutclient_set_device_variable_value(NUTCLIENT_t client, const char* dev, const char* var, const char* value) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->setDeviceVariable(dev, var, value); } catch(...){} } } } void nutclient_set_device_variable_values(NUTCLIENT_t client, const char* dev, const char* var, const strarr values) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { std::vector vals; strarr pstr = static_cast(values); while(*pstr) { vals.push_back(std::string(*pstr)); ++pstr; } cl->setDeviceVariable(dev, var, vals); } catch(...){} } } } strarr nutclient_get_device_commands(NUTCLIENT_t client, const char* dev) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return stringset_to_strarr(cl->getDeviceCommandNames(dev)); } catch(...){} } } return nullptr; } int nutclient_has_device_command(NUTCLIENT_t client, const char* dev, const char* cmd) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return cl->hasDeviceCommand(dev, cmd)?1:0; } catch(...){} } } return 0; } char* nutclient_get_device_command_description(NUTCLIENT_t client, const char* dev, const char* cmd) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { return xstrdup(cl->getDeviceCommandDescription(dev, cmd).c_str()); } catch(...){} } } return nullptr; } void nutclient_execute_device_command(NUTCLIENT_t client, const char* dev, const char* cmd, const char* param) { if(client) { nut::Client* cl = static_cast(client); if(cl) { try { cl->executeDeviceCommand(dev, cmd, param); } catch(...){} } } } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT #pragma GCC diagnostic pop #endif } /* extern "C" */