nut-debian/clients/nutclient.cpp
2022-07-10 09:23:45 +02:00

2056 lines
39 KiB
C++

/* nutclient.cpp - nutclient C++ library implementation
Copyright (C) 2012 Emilien Kia <emilien.kia@gmail.com>
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 <sstream>
#include <errno.h>
#include <string.h>
#include <stdio.h>
/* Windows/Linux Socket compatibility layer: */
/* Thanks to Benjamin Roux (http://broux.developpez.com/articles/c/sockets/) */
#ifdef WIN32
# include <winsock2.h>
#else
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <unistd.h> /* close */
# include <netdb.h> /* gethostbyname */
# include <fcntl.h>
# 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 <stdlib.h>
#include <string.h>
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<uintmax_t>(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<size_t>(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<size_t>(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<std::string> 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<Device> Client::getDevices()
{
std::set<Device> res;
std::set<std::string> devs = getDeviceNames();
for(std::set<std::string>::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<std::string> names = getDeviceVariableNames(dev);
return names.find(name) != names.end();
}
std::map<std::string,std::vector<std::string> > Client::getDeviceVariableValues(const std::string& dev)
{
std::map<std::string,std::vector<std::string> > res;
std::set<std::string> names = getDeviceVariableNames(dev);
for(std::set<std::string>::iterator it=names.begin(); it!=names.end(); ++it)
{
const std::string& name = *it;
res[name] = getDeviceVariableValue(dev, name);
}
return res;
}
std::map<std::string,std::map<std::string,std::vector<std::string> > > Client::getDevicesVariableValues(const std::set<std::string>& devs)
{
std::map<std::string,std::map<std::string,std::vector<std::string> > > res;
for(std::set<std::string>::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<std::string> 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<std::string> TcpClient::getDeviceNames()
{
std::set<std::string> res;
std::vector<std::vector<std::string> > devs = list("UPS");
for(std::vector<std::vector<std::string> >::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<std::string> TcpClient::getDeviceVariableNames(const std::string& dev)
{
std::set<std::string> set;
std::vector<std::vector<std::string> > res = list("VAR", dev);
for(size_t n=0; n<res.size(); ++n)
{
set.insert(res[n][0]);
}
return set;
}
std::set<std::string> TcpClient::getDeviceRWVariableNames(const std::string& dev)
{
std::set<std::string> set;
std::vector<std::vector<std::string> > res = list("RW", dev);
for(size_t n=0; n<res.size(); ++n)
{
set.insert(res[n][0]);
}
return set;
}
std::string TcpClient::getDeviceVariableDescription(const std::string& dev, const std::string& name)
{
return get("DESC", dev + " " + name)[0];
}
std::vector<std::string> TcpClient::getDeviceVariableValue(const std::string& dev, const std::string& name)
{
return get("VAR", dev + " " + name);
}
std::map<std::string,std::vector<std::string> > TcpClient::getDeviceVariableValues(const std::string& dev)
{
std::map<std::string,std::vector<std::string> > map;
std::vector<std::vector<std::string> > res = list("VAR", dev);
for(size_t n=0; n<res.size(); ++n)
{
std::vector<std::string>& vals = res[n];
std::string var = vals[0];
vals.erase(vals.begin());
map[var] = vals;
}
return map;
}
std::map<std::string,std::map<std::string,std::vector<std::string> > > TcpClient::getDevicesVariableValues(const std::set<std::string>& devs)
{
std::map<std::string,std::map<std::string,std::vector<std::string> > > 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<std::string> queries;
for (std::set<std::string>::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it)
{
queries.push_back("LIST VAR " + *it);
}
sendAsyncQueries(queries);
for (std::set<std::string>::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it)
{
try
{
std::map<std::string,std::vector<std::string> > map2;
std::vector<std::vector<std::string> > res = parseList("VAR " + *it);
for (std::vector<std::vector<std::string> >::iterator it2=res.begin(); it2!=res.end(); ++it2)
{
std::vector<std::string>& 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<std::string>& values)
{
std::string query = "SET VAR " + dev + " " + name;
for(size_t n=0; n<values.size(); ++n)
{
query += " " + escape(values[n]);
}
return sendTrackingQuery(query);
}
std::set<std::string> TcpClient::getDeviceCommandNames(const std::string& dev)
{
std::set<std::string> cmds;
std::vector<std::vector<std::string> > res = list("CMD", dev);
for(size_t n=0; n<res.size(); ++n)
{
cmds.insert(res[n][0]);
}
return cmds;
}
std::string TcpClient::getDeviceCommandDescription(const std::string& dev, const std::string& name)
{
return get("CMDDESC", dev + " " + name)[0];
}
TrackingID TcpClient::executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param)
{
return sendTrackingQuery("INSTCMD " + dev + " " + name + " " + param);
}
void TcpClient::deviceLogin(const std::string& dev)
{
detectError(sendQuery("LOGIN " + dev));
}
/* NOTE: "master" is deprecated since NUT v2.8.0 in favor of "primary".
* For the sake of old/new server/client interoperability,
* practical implementations should try to use one and fall
* back to the other, and only fail if both return "ERR".
*/
void TcpClient::deviceMaster(const std::string& dev)
{
try {
detectError(sendQuery("MASTER " + dev));
} catch (NutException &exOrig) {
try {
detectError(sendQuery("PRIMARY " + dev));
} catch (NutException &exRetry) {
NUT_UNUSED_VARIABLE(exRetry);
throw exOrig;
}
}
}
void TcpClient::devicePrimary(const std::string& dev)
{
try {
detectError(sendQuery("PRIMARY " + dev));
} catch (NutException &exOrig) {
try {
detectError(sendQuery("MASTER " + dev));
} catch (NutException &exRetry) {
NUT_UNUSED_VARIABLE(exRetry);
throw exOrig;
}
}
}
void TcpClient::deviceForcedShutdown(const std::string& dev)
{
detectError(sendQuery("FSD " + dev));
}
int TcpClient::deviceGetNumLogins(const std::string& dev)
{
std::string num = get("NUMLOGINS", dev)[0];
return atoi(num.c_str());
}
TrackingResult TcpClient::getTrackingResult(const TrackingID& id)
{
if (id.empty())
{
return TrackingResult::SUCCESS;
}
std::string result = sendQuery("GET TRACKING " + id);
if (result == "PENDING")
{
return TrackingResult::PENDING;
}
else if (result == "SUCCESS")
{
return TrackingResult::SUCCESS;
}
else if (result == "ERR UNKNOWN")
{
return TrackingResult::UNKNOWN;
}
else if (result == "ERR INVALID-ARGUMENT")
{
return TrackingResult::INVALID_ARGUMENT;
}
else
{
return TrackingResult::FAILURE;
}
}
bool TcpClient::isFeatureEnabled(const Feature& feature)
{
std::string result = sendQuery("GET " + feature);
detectError(result);
if (result == "ON")
{
return true;
}
else if (result == "OFF")
{
return false;
}
else
{
throw NutException("Unknown feature result " + result);
}
}
void TcpClient::setFeature(const Feature& feature, bool status)
{
std::string result = sendQuery("SET " + feature + " " + (status ? "ON" : "OFF"));
detectError(result);
}
std::vector<std::string> 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<std::vector<std::string> > TcpClient::list
(const std::string& subcmd, const std::string& params)
{
std::string req = subcmd;
if(!params.empty())
{
req += " " + params;
}
std::vector<std::string> query;
query.push_back("LIST " + req);
sendAsyncQueries(query);
return parseList(req);
}
std::vector<std::vector<std::string> > TcpClient::parseList
(const std::string& req)
{
std::string res = _socket->read();
detectError(res);
if(res != ("BEGIN LIST " + req))
{
throw NutException("Invalid response");
}
std::vector<std::vector<std::string> > 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<std::string>& req)
{
for (std::vector<std::string>::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<std::string> TcpClient::explode(const std::string& str, size_t begin)
{
std::vector<std::string> res;
std::string temp;
enum STATE {
INIT,
SIMPLE_STRING,
QUOTED_STRING,
SIMPLE_ESCAPE,
QUOTED_ESCAPE
} state = INIT;
for(size_t idx=begin; idx<str.size(); ++idx)
{
char c = str[idx];
switch(state)
{
case INIT:
if(c==' ' /* || c=='\t' */)
{ /* Do nothing */ }
else if(c=='"')
{
state = QUOTED_STRING;
}
else if(c=='\\')
{
state = SIMPLE_ESCAPE;
}
/* What about bad characters ? */
else
{
temp += c;
state = SIMPLE_STRING;
}
break;
case SIMPLE_STRING:
if(c==' ' /* || c=='\t' */)
{
/* if(!temp.empty()) : Must not occur */
res.push_back(temp);
temp.clear();
state = INIT;
}
else if(c=='\\')
{
state = SIMPLE_ESCAPE;
}
else if(c=='"')
{
/* if(!temp.empty()) : Must not occur */
res.push_back(temp);
temp.clear();
state = QUOTED_STRING;
}
/* What about bad characters ? */
else
{
temp += c;
}
break;
case QUOTED_STRING:
if(c=='\\')
{
state = QUOTED_ESCAPE;
}
else if(c=='"')
{
res.push_back(temp);
temp.clear();
state = INIT;
}
/* What about bad characters ? */
else
{
temp += c;
}
break;
case SIMPLE_ESCAPE:
if(c=='\\' || c=='"' || c==' ' /* || c=='\t'*/)
{
temp += c;
}
else
{
temp += '\\' + c; // Really do this ?
}
state = SIMPLE_STRING;
break;
case QUOTED_ESCAPE:
if(c=='\\' || c=='"')
{
temp += c;
}
else
{
temp += '\\' + c; // Really do this ?
}
state = QUOTED_STRING;
break;
}
}
if(!temp.empty())
{
res.push_back(temp);
}
return res;
}
std::string TcpClient::escape(const std::string& str)
{
std::string res = "\"";
for(size_t n=0; n<str.size(); n++)
{
char c = str[n];
if(c=='"')
res += "\\\"";
else if(c=='\\')
res += "\\\\";
else
res += c;
}
res += '"';
return res;
}
TrackingID TcpClient::sendTrackingQuery(const std::string& req)
{
std::string reply = sendQuery(req);
detectError(reply);
std::vector<std::string> 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()<dev.getName();
}
std::string Device::getDescription()
{
if (!isOk()) throw NutException("Invalid device");
return getClient()->getDeviceDescription(getName());
}
std::vector<std::string> Device::getVariableValue(const std::string& name)
{
if (!isOk()) throw NutException("Invalid device");
return getClient()->getDeviceVariableValue(getName(), name);
}
std::map<std::string,std::vector<std::string> > Device::getVariableValues()
{
if (!isOk()) throw NutException("Invalid device");
return getClient()->getDeviceVariableValues(getName());
}
std::set<std::string> Device::getVariableNames()
{
if (!isOk()) throw NutException("Invalid device");
return getClient()->getDeviceVariableNames(getName());
}
std::set<std::string> 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<std::string>& 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<Variable> Device::getVariables()
{
std::set<Variable> set;
if (!isOk()) throw NutException("Invalid device");
std::set<std::string> names = getClient()->getDeviceVariableNames(getName());
for(std::set<std::string>::iterator it=names.begin(); it!=names.end(); ++it)
{
set.insert(Variable(this, *it));
}
return set;
}
std::set<Variable> Device::getRWVariables()
{
std::set<Variable> set;
if (!isOk()) throw NutException("Invalid device");
std::set<std::string> names = getClient()->getDeviceRWVariableNames(getName());
for(std::set<std::string>::iterator it=names.begin(); it!=names.end(); ++it)
{
set.insert(Variable(this, *it));
}
return set;
}
std::set<std::string> Device::getCommandNames()
{
if (!isOk()) throw NutException("Invalid device");
return getClient()->getDeviceCommandNames(getName());
}
std::set<Command> Device::getCommands()
{
std::set<Command> cmds;
std::set<std::string> res = getCommandNames();
for(std::set<std::string>::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()<var.getName();
}
std::vector<std::string> 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<std::string>& 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()<cmd.getName();
}
std::string Command::getDescription()
{
return getDevice()->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<strarr>(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<std::string>& strset)
{
strarr arr = strarr_alloc(strset.size());
strarr pstr = arr;
for(std::set<std::string>::const_iterator it=strset.begin(); it!=strset.end(); ++it)
{
*pstr = xstrdup(it->c_str());
pstr++;
}
return arr;
}
strarr stringvector_to_strarr(const std::vector<std::string>& strset)
{
strarr arr = strarr_alloc(strset.size());
strarr pstr = arr;
for(std::vector<std::string>::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<NUTCLIENT_TCP_t>(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<nut::Client*>(client);
}
}
int nutclient_tcp_is_connected(NUTCLIENT_TCP_t client)
{
if(client)
{
nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(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<nut::TcpClient*>(static_cast<nut::Client*>(client));
if(cl)
{
cl->disconnect();
}
}
}
int nutclient_tcp_reconnect(NUTCLIENT_TCP_t client)
{
if(client)
{
nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(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<nut::TcpClient*>(static_cast<nut::Client*>(client));
if(cl)
{
cl->setTimeout(timeout);
}
}
}
time_t nutclient_tcp_get_timeout(NUTCLIENT_TCP_t client)
{
if(client)
{
nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(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<nut::Client*>(client);
if(cl)
{
try
{
cl->authenticate(login, passwd);
}
catch(...){}
}
}
}
void nutclient_logout(NUTCLIENT_t client)
{
if(client)
{
nut::Client* cl = static_cast<nut::Client*>(client);
if(cl)
{
try
{
cl->logout();
}
catch(...){}
}
}
}
void nutclient_device_login(NUTCLIENT_t client, const char* dev)
{
if(client)
{
nut::Client* cl = static_cast<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(client);
if(cl)
{
try
{
cl->deviceForcedShutdown(dev);
}
catch(...){}
}
}
}
strarr nutclient_get_devices(NUTCLIENT_t client)
{
if(client)
{
nut::Client* cl = static_cast<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(client);
if(cl)
{
try
{
std::vector<std::string> vals;
strarr pstr = static_cast<strarr>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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<nut::Client*>(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" */