1 /* nutclient.cpp - nutclient C++ library implementation
2 
3    Copyright (C) 2012  Emilien Kia <emilien.kia@gmail.com>
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 
20 #include "nutclient.h"
21 
22 #include <sstream>
23 
24 #include <errno.h>
25 #include <string.h>
26 #include <stdio.h>
27 
28 /* Windows/Linux Socket compatibility layer: */
29 /* Thanks to Benjamin Roux (http://broux.developpez.com/articles/c/sockets/) */
30 #ifdef WIN32
31 #  include <winsock2.h>
32 #else
33 #  include <sys/types.h>
34 #  include <sys/socket.h>
35 #  include <netinet/in.h>
36 #  include <arpa/inet.h>
37 #  include <unistd.h> /* close */
38 #  include <netdb.h> /* gethostbyname */
39 #  include <fcntl.h>
40 #  define INVALID_SOCKET -1
41 #  define SOCKET_ERROR -1
42 #  define closesocket(s) close(s)
43    typedef int SOCKET;
44    typedef struct sockaddr_in SOCKADDR_IN;
45    typedef struct sockaddr SOCKADDR;
46    typedef struct in_addr IN_ADDR;
47 #endif /* WIN32 */
48 /* End of Windows/Linux Socket compatibility layer: */
49 
50 
51 /* Include nut common utility functions or define simple ones if not */
52 #ifdef HAVE_NUTCOMMON
53 #include "common.h"
54 #else /* HAVE_NUTCOMMON */
55 #include <stdlib.h>
56 #include <string.h>
xmalloc(size_t size)57 static inline void *xmalloc(size_t size){return malloc(size);}
xcalloc(size_t number,size_t size)58 static inline void *xcalloc(size_t number, size_t size){return calloc(number, size);}
xrealloc(void * ptr,size_t size)59 static inline void *xrealloc(void *ptr, size_t size){return realloc(ptr, size);}
xstrdup(const char * string)60 static inline char *xstrdup(const char *string){return strdup(string);}
61 #endif /* HAVE_NUTCOMMON */
62 
63 /* To stay in line with modern C++, we use nullptr (not numeric NULL
64  * or shim __null on some systems) which was defined after C++98.
65  * The NUT C++ interface is intended for C++11 and newer, so we
66  * quiesce these warnigns if possible.
67  * An idea might be to detect if we do build with old C++ standard versions
68  * and define a nullptr like https://stackoverflow.com/a/44517878/4715872
69  * but again - currently we do not intend to support that officially.
70  */
71 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT
72 #pragma GCC diagnostic push
73 #endif
74 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT_PEDANTIC
75 #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic"
76 #endif
77 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT
78 #pragma GCC diagnostic ignored "-Wc++98-compat"
79 #endif
80 
81 namespace nut
82 {
83 
SystemException()84 SystemException::SystemException():
85 NutException(err())
86 {
87 }
88 
err()89 std::string SystemException::err()
90 {
91 	if(errno==0)
92 		return "Undefined system error";
93 	else
94 	{
95 		std::stringstream str;
96 		str << "System error " << errno << ": " << strerror(errno);
97 		return str.str();
98 	}
99 }
100 
101 /* Implemented out-of-line to avoid "Weak vtables" warnings and related overheads
102  * But now with clang-9 C++11 linter (though not C++17) they complain with
103  *   error: definition of implicit copy constructor for 'NutException'
104  *          is deprecated because it has a user-declared destructor
105  * This is fixed in header with declarations like:
106  *   NutException(const NutException&) = default;
107  * and assignment operator to accompany the copy constructor, per
108  * https://lgtm.com/rules/2165180572/ like:
109  *   NutException& operator=(NutException& rhs) = default;
110  */
~NutException()111 NutException::~NutException() {}
~SystemException()112 SystemException::~SystemException() {}
~IOException()113 IOException::~IOException() {}
~UnknownHostException()114 UnknownHostException::~UnknownHostException() {}
~NotConnectedException()115 NotConnectedException::~NotConnectedException() {}
~TimeoutException()116 TimeoutException::~TimeoutException() {}
117 
118 
119 namespace internal
120 {
121 
122 /**
123  * Internal socket wrapper.
124  * Provides only client socket functions.
125  *
126  * Implemented as separate internal class to easily hide plateform specificities.
127  */
128 class Socket
129 {
130 public:
131 	Socket();
132 	~Socket();
133 
134 	void connect(const std::string& host, int port);
135 	void disconnect();
136 	bool isConnected()const;
137 
138 	void setTimeout(long timeout);
hasTimeout() const139 	bool hasTimeout()const{return _tv.tv_sec>=0;}
140 
141 	size_t read(void* buf, size_t sz);
142 	size_t write(const void* buf, size_t sz);
143 
144 	std::string read();
145 	void write(const std::string& str);
146 
147 
148 private:
149 	SOCKET _sock;
150 	struct timeval	_tv;
151 	std::string _buffer; /* Received buffer, string because data should be text only. */
152 };
153 
Socket()154 Socket::Socket():
155 _sock(INVALID_SOCKET),
156 _tv()
157 {
158 	_tv.tv_sec = -1;
159 	_tv.tv_usec = 0;
160 }
161 
~Socket()162 Socket::~Socket()
163 {
164 	disconnect();
165 }
166 
setTimeout(long timeout)167 void Socket::setTimeout(long timeout)
168 {
169 	_tv.tv_sec = timeout;
170 }
171 
connect(const std::string & host,int port)172 void Socket::connect(const std::string& host, int port)
173 {
174 	int	sock_fd;
175 	struct addrinfo	hints, *res, *ai;
176 	char			sport[NI_MAXSERV];
177 	int			v;
178 	fd_set 			wfds;
179 	int			error;
180 	socklen_t		error_size;
181 	long			fd_flags;
182 
183 	_sock = -1;
184 
185 	if (host.empty()) {
186 		throw nut::UnknownHostException();
187 	}
188 
189 	snprintf(sport, sizeof(sport), "%hu", static_cast<unsigned short int>(port));
190 
191 	memset(&hints, 0, sizeof(hints));
192 	hints.ai_family = AF_UNSPEC;
193 	hints.ai_socktype = SOCK_STREAM;
194 	hints.ai_protocol = IPPROTO_TCP;
195 
196 	while ((v = getaddrinfo(host.c_str(), sport, &hints, &res)) != 0) {
197 		switch (v)
198 		{
199 		case EAI_AGAIN:
200 			continue;
201 		case EAI_NONAME:
202 			throw nut::UnknownHostException();
203 		case EAI_SYSTEM:
204 			throw nut::SystemException();
205 		case EAI_MEMORY:
206 			throw nut::NutException("Out of memory");
207 		default:
208 			throw nut::NutException("Unknown error");
209 		}
210 	}
211 
212 	for (ai = res; ai != nullptr; ai = ai->ai_next) {
213 
214 		sock_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
215 
216 		if (sock_fd < 0) {
217 			switch (errno)
218 			{
219 			case EAFNOSUPPORT:
220 			case EINVAL:
221 				break;
222 			default:
223 				throw nut::SystemException();
224 			}
225 			continue;
226 		}
227 
228 		/* non blocking connect */
229 		if(hasTimeout()) {
230 			fd_flags = fcntl(sock_fd, F_GETFL);
231 			fd_flags |= O_NONBLOCK;
232 			fcntl(sock_fd, F_SETFL, fd_flags);
233 		}
234 
235 		while ((v = ::connect(sock_fd, ai->ai_addr, ai->ai_addrlen)) < 0) {
236 			if(errno == EINPROGRESS) {
237 				FD_ZERO(&wfds);
238 				FD_SET(sock_fd, &wfds);
239 				select(sock_fd+1, nullptr, &wfds, nullptr, hasTimeout() ? &_tv : nullptr);
240 				if (FD_ISSET(sock_fd, &wfds)) {
241 					error_size = sizeof(error);
242 					getsockopt(sock_fd, SOL_SOCKET, SO_ERROR,
243 							&error, &error_size);
244 					if( error == 0) {
245 						/* connect successful */
246 						v = 0;
247 						break;
248 					}
249 					errno = error;
250 				}
251 				else {
252 					/* Timeout */
253 					v = -1;
254 					break;
255 				}
256 			}
257 
258 			switch (errno)
259 			{
260 			case EAFNOSUPPORT:
261 				break;
262 			case EINTR:
263 			case EAGAIN:
264 				continue;
265 			default:
266 //				ups->upserror = UPSCLI_ERR_CONNFAILURE;
267 //				ups->syserrno = errno;
268 				break;
269 			}
270 			break;
271 		}
272 
273 		if (v < 0) {
274 			close(sock_fd);
275 			continue;
276 		}
277 
278 		/* switch back to blocking operation */
279 		if(hasTimeout()) {
280 			fd_flags = fcntl(sock_fd, F_GETFL);
281 			fd_flags &= ~O_NONBLOCK;
282 			fcntl(sock_fd, F_SETFL, fd_flags);
283 		}
284 
285 		_sock = sock_fd;
286 //		ups->upserror = 0;
287 //		ups->syserrno = 0;
288 		break;
289 	}
290 
291 	freeaddrinfo(res);
292 
293 	if (_sock < 0) {
294 		throw nut::IOException("Cannot connect to host");
295 	}
296 
297 
298 #ifdef OLD
299 	struct hostent *hostinfo = nullptr;
300 	SOCKADDR_IN sin = { 0 };
301 	hostinfo = ::gethostbyname(host.c_str());
302 	if(hostinfo == nullptr) /* Host doesnt exist */
303 	{
304 		throw nut::UnknownHostException();
305 	}
306 
307 	// Create socket
308 	_sock = ::socket(PF_INET, SOCK_STREAM, 0);
309 	if(_sock == INVALID_SOCKET)
310 	{
311 		throw nut::IOException("Cannot create socket");
312 	}
313 
314 	// Connect
315 	sin.sin_addr = *(IN_ADDR *) hostinfo->h_addr;
316 	sin.sin_port = htons(port);
317 	sin.sin_family = AF_INET;
318 	if(::connect(_sock,(SOCKADDR *) &sin, sizeof(SOCKADDR)) == SOCKET_ERROR)
319 	{
320 		_sock = INVALID_SOCKET;
321 		throw nut::IOException("Cannot connect to host");
322 	}
323 #endif // OLD
324 }
325 
disconnect()326 void Socket::disconnect()
327 {
328 	if(_sock != INVALID_SOCKET)
329 	{
330 		::closesocket(_sock);
331 		_sock = INVALID_SOCKET;
332 	}
333 	_buffer.clear();
334 }
335 
isConnected() const336 bool Socket::isConnected()const
337 {
338 	return _sock!=INVALID_SOCKET;
339 }
340 
read(void * buf,size_t sz)341 size_t Socket::read(void* buf, size_t sz)
342 {
343 	if(!isConnected())
344 	{
345 		throw nut::NotConnectedException();
346 	}
347 
348 	if(_tv.tv_sec>=0)
349 	{
350 		fd_set fds;
351 		FD_ZERO(&fds);
352 		FD_SET(_sock, &fds);
353 		int ret = select(_sock+1, &fds, nullptr, nullptr, &_tv);
354 		if (ret < 1) {
355 			throw nut::TimeoutException();
356 		}
357 	}
358 
359 	ssize_t res = ::read(_sock, buf, sz);
360 	if(res==-1)
361 	{
362 		disconnect();
363 		throw nut::IOException("Error while reading on socket");
364 	}
365 	return static_cast<size_t>(res);
366 }
367 
write(const void * buf,size_t sz)368 size_t Socket::write(const void* buf, size_t sz)
369 {
370 	if(!isConnected())
371 	{
372 		throw nut::NotConnectedException();
373 	}
374 
375 	if(_tv.tv_sec>=0)
376 	{
377 		fd_set fds;
378 		FD_ZERO(&fds);
379 		FD_SET(_sock, &fds);
380 		int ret = select(_sock+1, nullptr, &fds, nullptr, &_tv);
381 		if (ret < 1) {
382 			throw nut::TimeoutException();
383 		}
384 	}
385 
386 	ssize_t res = ::write(_sock, buf, sz);
387 	if(res==-1)
388 	{
389 		disconnect();
390 		throw nut::IOException("Error while writing on socket");
391 	}
392 	return static_cast<size_t>(res);
393 }
394 
read()395 std::string Socket::read()
396 {
397 	std::string res;
398 	char buff[256];
399 
400 	while(true)
401 	{
402 		// Look at already read data in _buffer
403 		if(!_buffer.empty())
404 		{
405 			size_t idx = _buffer.find('\n');
406 			if(idx!=std::string::npos)
407 			{
408 				res += _buffer.substr(0, idx);
409 				_buffer.erase(0, idx+1);
410 				return res;
411 			}
412 			res += _buffer;
413 		}
414 
415 		// Read new buffer
416 		size_t sz = read(&buff, 256);
417 		if(sz==0)
418 		{
419 			disconnect();
420 			throw nut::IOException("Server closed connection unexpectedly");
421 		}
422 		_buffer.assign(buff, sz);
423 	}
424 }
425 
write(const std::string & str)426 void Socket::write(const std::string& str)
427 {
428 //	write(str.c_str(), str.size());
429 //	write("\n", 1);
430 	std::string buff = str + "\n";
431 	write(buff.c_str(), buff.size());
432 }
433 
434 }/* namespace internal */
435 
436 
437 /*
438  *
439  * Client implementation
440  *
441  */
442 
443 /* Pedantic builds complain about the static variable below...
444  * It is assumed safe to ignore since it is a std::string with
445  * no complex teardown at program exit.
446  */
447 #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)
448 #pragma GCC diagnostic push
449 # ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS
450 #  pragma GCC diagnostic ignored "-Wglobal-constructors"
451 # endif
452 # ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS
453 #  pragma GCC diagnostic ignored "-Wexit-time-destructors"
454 # endif
455 #endif
456 const Feature Client::TRACKING = "TRACKING";
457 #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)
458 #pragma GCC diagnostic pop
459 #endif
460 
Client()461 Client::Client()
462 {
463 }
464 
~Client()465 Client::~Client()
466 {
467 }
468 
hasDevice(const std::string & dev)469 bool Client::hasDevice(const std::string& dev)
470 {
471 	std::set<std::string> devs = getDeviceNames();
472 	return devs.find(dev) != devs.end();
473 }
474 
getDevice(const std::string & name)475 Device Client::getDevice(const std::string& name)
476 {
477 	if(hasDevice(name))
478 		return Device(this, name);
479 	else
480 		return Device(nullptr, "");
481 }
482 
getDevices()483 std::set<Device> Client::getDevices()
484 {
485 	std::set<Device> res;
486 
487 	std::set<std::string> devs = getDeviceNames();
488 	for(std::set<std::string>::iterator it=devs.begin(); it!=devs.end(); ++it)
489 	{
490 		res.insert(Device(this, *it));
491 	}
492 
493 	return res;
494 }
495 
hasDeviceVariable(const std::string & dev,const std::string & name)496 bool Client::hasDeviceVariable(const std::string& dev, const std::string& name)
497 {
498 	std::set<std::string> names = getDeviceVariableNames(dev);
499 	return names.find(name) != names.end();
500 }
501 
getDeviceVariableValues(const std::string & dev)502 std::map<std::string,std::vector<std::string> > Client::getDeviceVariableValues(const std::string& dev)
503 {
504 	std::map<std::string,std::vector<std::string> > res;
505 
506 	std::set<std::string> names = getDeviceVariableNames(dev);
507 	for(std::set<std::string>::iterator it=names.begin(); it!=names.end(); ++it)
508 	{
509 		const std::string& name = *it;
510 		res[name] = getDeviceVariableValue(dev, name);
511 	}
512 
513 	return res;
514 }
515 
getDevicesVariableValues(const std::set<std::string> & devs)516 std::map<std::string,std::map<std::string,std::vector<std::string> > > Client::getDevicesVariableValues(const std::set<std::string>& devs)
517 {
518 	std::map<std::string,std::map<std::string,std::vector<std::string> > > res;
519 
520 	for(std::set<std::string>::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it)
521 	{
522 		res[*it] = getDeviceVariableValues(*it);
523 	}
524 
525 	return res;
526 }
527 
hasDeviceCommand(const std::string & dev,const std::string & name)528 bool Client::hasDeviceCommand(const std::string& dev, const std::string& name)
529 {
530 	std::set<std::string> names = getDeviceCommandNames(dev);
531 	return names.find(name) != names.end();
532 }
533 
hasFeature(const Feature & feature)534 bool Client::hasFeature(const Feature& feature)
535 {
536 	try
537 	{
538 		// If feature is known, querying it won't throw an exception.
539 		isFeatureEnabled(feature);
540 		return true;
541 	}
542 	catch(...)
543 	{
544 		return false;
545 	}
546 }
547 
548 /*
549  *
550  * TCP Client implementation
551  *
552  */
553 
TcpClient()554 TcpClient::TcpClient():
555 Client(),
556 _host("localhost"),
557 _port(3493),
558 _timeout(0),
559 _socket(new internal::Socket)
560 {
561 	// Do not connect now
562 }
563 
TcpClient(const std::string & host,int port)564 TcpClient::TcpClient(const std::string& host, int port):
565 Client(),
566 _timeout(0),
567 _socket(new internal::Socket)
568 {
569 	connect(host, port);
570 }
571 
~TcpClient()572 TcpClient::~TcpClient()
573 {
574 	delete _socket;
575 }
576 
connect(const std::string & host,int port)577 void TcpClient::connect(const std::string& host, int port)
578 {
579 	_host = host;
580 	_port = port;
581 	connect();
582 }
583 
connect()584 void TcpClient::connect()
585 {
586 	_socket->connect(_host, _port);
587 }
588 
getHost() const589 std::string TcpClient::getHost()const
590 {
591 	return _host;
592 }
593 
getPort() const594 int TcpClient::getPort()const
595 {
596 	return _port;
597 }
598 
isConnected() const599 bool TcpClient::isConnected()const
600 {
601 	return _socket->isConnected();
602 }
603 
disconnect()604 void TcpClient::disconnect()
605 {
606 	_socket->disconnect();
607 }
608 
setTimeout(long timeout)609 void TcpClient::setTimeout(long timeout)
610 {
611 	_timeout = timeout;
612 }
613 
getTimeout() const614 long TcpClient::getTimeout()const
615 {
616 	return _timeout;
617 }
618 
authenticate(const std::string & user,const std::string & passwd)619 void TcpClient::authenticate(const std::string& user, const std::string& passwd)
620 {
621 	detectError(sendQuery("USERNAME " + user));
622 	detectError(sendQuery("PASSWORD " + passwd));
623 }
624 
logout()625 void TcpClient::logout()
626 {
627 	detectError(sendQuery("LOGOUT"));
628 	_socket->disconnect();
629 }
630 
getDevice(const std::string & name)631 Device TcpClient::getDevice(const std::string& name)
632 {
633 	try
634 	{
635 		get("UPSDESC", name);
636 	}
637 	catch(NutException& ex)
638 	{
639 		if(ex.str()=="UNKNOWN-UPS")
640 			return Device(nullptr, "");
641 		else
642 			throw;
643 	}
644 	return Device(this, name);
645 }
646 
getDeviceNames()647 std::set<std::string> TcpClient::getDeviceNames()
648 {
649 	std::set<std::string> res;
650 
651 	std::vector<std::vector<std::string> > devs = list("UPS");
652 	for(std::vector<std::vector<std::string> >::iterator it=devs.begin();
653 		it!=devs.end(); ++it)
654 	{
655 		std::string id = (*it)[0];
656 		if(!id.empty())
657 			res.insert(id);
658 	}
659 
660 	return res;
661 }
662 
getDeviceDescription(const std::string & name)663 std::string TcpClient::getDeviceDescription(const std::string& name)
664 {
665 	return get("UPSDESC", name)[0];
666 }
667 
getDeviceVariableNames(const std::string & dev)668 std::set<std::string> TcpClient::getDeviceVariableNames(const std::string& dev)
669 {
670 	std::set<std::string> set;
671 
672 	std::vector<std::vector<std::string> > res = list("VAR", dev);
673 	for(size_t n=0; n<res.size(); ++n)
674 	{
675 		set.insert(res[n][0]);
676 	}
677 
678 	return set;
679 }
680 
getDeviceRWVariableNames(const std::string & dev)681 std::set<std::string> TcpClient::getDeviceRWVariableNames(const std::string& dev)
682 {
683 	std::set<std::string> set;
684 
685 	std::vector<std::vector<std::string> > res = list("RW", dev);
686 	for(size_t n=0; n<res.size(); ++n)
687 	{
688 		set.insert(res[n][0]);
689 	}
690 
691 	return set;
692 }
693 
getDeviceVariableDescription(const std::string & dev,const std::string & name)694 std::string TcpClient::getDeviceVariableDescription(const std::string& dev, const std::string& name)
695 {
696 	return get("DESC", dev + " " + name)[0];
697 }
698 
getDeviceVariableValue(const std::string & dev,const std::string & name)699 std::vector<std::string> TcpClient::getDeviceVariableValue(const std::string& dev, const std::string& name)
700 {
701 	return get("VAR", dev + " " + name);
702 }
703 
getDeviceVariableValues(const std::string & dev)704 std::map<std::string,std::vector<std::string> > TcpClient::getDeviceVariableValues(const std::string& dev)
705 {
706 
707 	std::map<std::string,std::vector<std::string> >  map;
708 
709 	std::vector<std::vector<std::string> > res = list("VAR", dev);
710 	for(size_t n=0; n<res.size(); ++n)
711 	{
712 		std::vector<std::string>& vals = res[n];
713 		std::string var = vals[0];
714 		vals.erase(vals.begin());
715 		map[var] = vals;
716 	}
717 
718 	return map;
719 }
720 
getDevicesVariableValues(const std::set<std::string> & devs)721 std::map<std::string,std::map<std::string,std::vector<std::string> > > TcpClient::getDevicesVariableValues(const std::set<std::string>& devs)
722 {
723 	std::map<std::string,std::map<std::string,std::vector<std::string> > > map;
724 
725 	if (devs.empty())
726 	{
727 		// This request might come from processing the empty valid
728 		// response of an upsd server which was allowed to start
729 		// with no device sections in its ups.conf
730 		return map;
731 	}
732 
733 	std::vector<std::string> queries;
734 	for (std::set<std::string>::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it)
735 	{
736 		queries.push_back("LIST VAR " + *it);
737 	}
738 	sendAsyncQueries(queries);
739 
740 	for (std::set<std::string>::const_iterator it=devs.cbegin(); it!=devs.cend(); ++it)
741 	{
742 		try
743 		{
744 			std::map<std::string,std::vector<std::string> > map2;
745 			std::vector<std::vector<std::string> > res = parseList("VAR " + *it);
746 			for (std::vector<std::vector<std::string> >::iterator it2=res.begin(); it2!=res.end(); ++it2)
747 			{
748 				std::vector<std::string>& vals = *it2;
749 				std::string var = vals[0];
750 				vals.erase(vals.begin());
751 				map2[var] = vals;
752 			}
753 			map[*it] = map2;
754 		}
755 		catch (NutException&)
756 		{
757 			// We sent a bunch of queries, we need to process them all to clear up the backlog.
758 		}
759 	}
760 
761 	if (map.empty())
762 	{
763 		// We may fail on some devices, but not on ALL devices.
764 		throw NutException("Invalid device");
765 	}
766 
767 	return map;
768 }
769 
setDeviceVariable(const std::string & dev,const std::string & name,const std::string & value)770 TrackingID TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)
771 {
772 	std::string query = "SET VAR " + dev + " " + name + " " + escape(value);
773 	return sendTrackingQuery(query);
774 }
775 
setDeviceVariable(const std::string & dev,const std::string & name,const std::vector<std::string> & values)776 TrackingID TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::vector<std::string>& values)
777 {
778 	std::string query = "SET VAR " + dev + " " + name;
779 	for(size_t n=0; n<values.size(); ++n)
780 	{
781 		query += " " + escape(values[n]);
782 	}
783 	return sendTrackingQuery(query);
784 }
785 
getDeviceCommandNames(const std::string & dev)786 std::set<std::string> TcpClient::getDeviceCommandNames(const std::string& dev)
787 {
788 	std::set<std::string> cmds;
789 
790 	std::vector<std::vector<std::string> > res = list("CMD", dev);
791 	for(size_t n=0; n<res.size(); ++n)
792 	{
793 		cmds.insert(res[n][0]);
794 	}
795 
796 	return cmds;
797 }
798 
getDeviceCommandDescription(const std::string & dev,const std::string & name)799 std::string TcpClient::getDeviceCommandDescription(const std::string& dev, const std::string& name)
800 {
801 	return get("CMDDESC", dev + " " + name)[0];
802 }
803 
executeDeviceCommand(const std::string & dev,const std::string & name,const std::string & param)804 TrackingID TcpClient::executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param)
805 {
806 	return sendTrackingQuery("INSTCMD " + dev + " " + name + " " + param);
807 }
808 
deviceLogin(const std::string & dev)809 void TcpClient::deviceLogin(const std::string& dev)
810 {
811 	detectError(sendQuery("LOGIN " + dev));
812 }
813 
814 /* FIXME: Protocol update needed to handle master/primary alias
815  * and probably an API bump also, to rename/alias the routine.
816  */
deviceMaster(const std::string & dev)817 void TcpClient::deviceMaster(const std::string& dev)
818 {
819 	detectError(sendQuery("MASTER " + dev));
820 }
821 
deviceForcedShutdown(const std::string & dev)822 void TcpClient::deviceForcedShutdown(const std::string& dev)
823 {
824 	detectError(sendQuery("FSD " + dev));
825 }
826 
deviceGetNumLogins(const std::string & dev)827 int TcpClient::deviceGetNumLogins(const std::string& dev)
828 {
829 	std::string num = get("NUMLOGINS", dev)[0];
830 	return atoi(num.c_str());
831 }
832 
getTrackingResult(const TrackingID & id)833 TrackingResult TcpClient::getTrackingResult(const TrackingID& id)
834 {
835 	if (id.empty())
836 	{
837 		return TrackingResult::SUCCESS;
838 	}
839 
840 	std::string result = sendQuery("GET TRACKING " + id);
841 
842 	if (result == "PENDING")
843 	{
844 		return TrackingResult::PENDING;
845 	}
846 	else if (result == "SUCCESS")
847 	{
848 		return TrackingResult::SUCCESS;
849 	}
850 	else if (result == "ERR UNKNOWN")
851 	{
852 		return TrackingResult::UNKNOWN;
853 	}
854 	else if (result == "ERR INVALID-ARGUMENT")
855 	{
856 		return TrackingResult::INVALID_ARGUMENT;
857 	}
858 	else
859 	{
860 		return TrackingResult::FAILURE;
861 	}
862 }
863 
isFeatureEnabled(const Feature & feature)864 bool TcpClient::isFeatureEnabled(const Feature& feature)
865 {
866 	std::string result = sendQuery("GET " + feature);
867 	detectError(result);
868 
869 	if (result == "ON")
870 	{
871 		return true;
872 	}
873 	else if (result == "OFF")
874 	{
875 		return false;
876 	}
877 	else
878 	{
879 		throw NutException("Unknown feature result " + result);
880 	}
881 }
setFeature(const Feature & feature,bool status)882 void TcpClient::setFeature(const Feature& feature, bool status)
883 {
884 	std::string result = sendQuery("SET " + feature + " " + (status ? "ON" : "OFF"));
885 	detectError(result);
886 }
887 
get(const std::string & subcmd,const std::string & params)888 std::vector<std::string> TcpClient::get
889 	(const std::string& subcmd, const std::string& params)
890 {
891 	std::string req = subcmd;
892 	if(!params.empty())
893 	{
894 		req += " " + params;
895 	}
896 	std::string res = sendQuery("GET " + req);
897 	detectError(res);
898 	if(res.substr(0, req.size()) != req)
899 	{
900 		throw NutException("Invalid response");
901 	}
902 
903 	return explode(res, req.size());
904 }
905 
list(const std::string & subcmd,const std::string & params)906 std::vector<std::vector<std::string> > TcpClient::list
907 	(const std::string& subcmd, const std::string& params)
908 {
909 	std::string req = subcmd;
910 	if(!params.empty())
911 	{
912 		req += " " + params;
913 	}
914 	std::vector<std::string> query;
915 	query.push_back("LIST " + req);
916 	sendAsyncQueries(query);
917 	return parseList(req);
918 }
919 
parseList(const std::string & req)920 std::vector<std::vector<std::string> > TcpClient::parseList
921 	(const std::string& req)
922 {
923 	std::string res = _socket->read();
924 	detectError(res);
925 	if(res != ("BEGIN LIST " + req))
926 	{
927 		throw NutException("Invalid response");
928 	}
929 
930 	std::vector<std::vector<std::string> > arr;
931 	while(true)
932 	{
933 		res = _socket->read();
934 		detectError(res);
935 		if(res == ("END LIST " + req))
936 		{
937 			return arr;
938 		}
939 		if(res.substr(0, req.size()) == req)
940 		{
941 			arr.push_back(explode(res, req.size()));
942 		}
943 		else
944 		{
945 			throw NutException("Invalid response");
946 		}
947 	}
948 }
949 
sendQuery(const std::string & req)950 std::string TcpClient::sendQuery(const std::string& req)
951 {
952 	_socket->write(req);
953 	return _socket->read();
954 }
955 
sendAsyncQueries(const std::vector<std::string> & req)956 void TcpClient::sendAsyncQueries(const std::vector<std::string>& req)
957 {
958 	for (std::vector<std::string>::const_iterator it = req.cbegin(); it != req.cend(); ++it)
959 	{
960 		_socket->write(*it);
961 	}
962 }
963 
detectError(const std::string & req)964 void TcpClient::detectError(const std::string& req)
965 {
966 	if(req.substr(0,3)=="ERR")
967 	{
968 		throw NutException(req.substr(4));
969 	}
970 }
971 
explode(const std::string & str,size_t begin)972 std::vector<std::string> TcpClient::explode(const std::string& str, size_t begin)
973 {
974 	std::vector<std::string> res;
975 	std::string temp;
976 
977 	enum STATE {
978 		INIT,
979 		SIMPLE_STRING,
980 		QUOTED_STRING,
981 		SIMPLE_ESCAPE,
982 		QUOTED_ESCAPE
983 	} state = INIT;
984 
985 	for(size_t idx=begin; idx<str.size(); ++idx)
986 	{
987 		char c = str[idx];
988 		switch(state)
989 		{
990 		case INIT:
991 			if(c==' ' /* || c=='\t' */)
992 			{ /* Do nothing */ }
993 			else if(c=='"')
994 			{
995 				state = QUOTED_STRING;
996 			}
997 			else if(c=='\\')
998 			{
999 				state = SIMPLE_ESCAPE;
1000 			}
1001 			/* What about bad characters ? */
1002 			else
1003 			{
1004 				temp += c;
1005 				state = SIMPLE_STRING;
1006 			}
1007 			break;
1008 		case SIMPLE_STRING:
1009 			if(c==' ' /* || c=='\t' */)
1010 			{
1011 				/* if(!temp.empty()) : Must not occur */
1012 					res.push_back(temp);
1013 				temp.clear();
1014 				state = INIT;
1015 			}
1016 			else if(c=='\\')
1017 			{
1018 				state = SIMPLE_ESCAPE;
1019 			}
1020 			else if(c=='"')
1021 			{
1022 				/* if(!temp.empty()) : Must not occur */
1023 					res.push_back(temp);
1024 				temp.clear();
1025 				state = QUOTED_STRING;
1026 			}
1027 			/* What about bad characters ? */
1028 			else
1029 			{
1030 				temp += c;
1031 			}
1032 			break;
1033 		case QUOTED_STRING:
1034 			if(c=='\\')
1035 			{
1036 				state = QUOTED_ESCAPE;
1037 			}
1038 			else if(c=='"')
1039 			{
1040 				res.push_back(temp);
1041 				temp.clear();
1042 				state = INIT;
1043 			}
1044 			/* What about bad characters ? */
1045 			else
1046 			{
1047 				temp += c;
1048 			}
1049 			break;
1050 		case SIMPLE_ESCAPE:
1051 			if(c=='\\' || c=='"' || c==' ' /* || c=='\t'*/)
1052 			{
1053 				temp += c;
1054 			}
1055 			else
1056 			{
1057 				temp += '\\' + c; // Really do this ?
1058 			}
1059 			state = SIMPLE_STRING;
1060 			break;
1061 		case QUOTED_ESCAPE:
1062 			if(c=='\\' || c=='"')
1063 			{
1064 				temp += c;
1065 			}
1066 			else
1067 			{
1068 				temp += '\\' + c; // Really do this ?
1069 			}
1070 			state = QUOTED_STRING;
1071 			break;
1072 		}
1073 	}
1074 
1075 	if(!temp.empty())
1076 	{
1077 		res.push_back(temp);
1078 	}
1079 
1080 	return res;
1081 }
1082 
escape(const std::string & str)1083 std::string TcpClient::escape(const std::string& str)
1084 {
1085 	std::string res = "\"";
1086 
1087 	for(size_t n=0; n<str.size(); n++)
1088 	{
1089 		char c = str[n];
1090 		if(c=='"')
1091 			res += "\\\"";
1092 		else if(c=='\\')
1093 			res += "\\\\";
1094 		else
1095 			res += c;
1096 	}
1097 
1098 	res += '"';
1099 	return res;
1100 }
1101 
sendTrackingQuery(const std::string & req)1102 TrackingID TcpClient::sendTrackingQuery(const std::string& req)
1103 {
1104 	std::string reply = sendQuery(req);
1105 	detectError(reply);
1106 	std::vector<std::string> res = explode(reply);
1107 
1108 	if (res.size() == 1 && res[0] == "OK")
1109 	{
1110 		return TrackingID("");
1111 	}
1112 	else if (res.size() == 3 && res[0] == "OK" && res[1] == "TRACKING")
1113 	{
1114 		return TrackingID(res[2]);
1115 	}
1116 	else
1117 	{
1118 		throw NutException("Unknown query result");
1119 	}
1120 }
1121 
1122 /*
1123  *
1124  * Device implementation
1125  *
1126  */
1127 
Device(Client * client,const std::string & name)1128 Device::Device(Client* client, const std::string& name):
1129 _client(client),
1130 _name(name)
1131 {
1132 }
1133 
Device(const Device & dev)1134 Device::Device(const Device& dev):
1135 _client(dev._client),
1136 _name(dev._name)
1137 {
1138 }
1139 
operator =(const Device & dev)1140 Device& Device::operator=(const Device& dev)
1141 {
1142 	// Self assignment?
1143 	if (this==&dev)
1144 		return *this;
1145 
1146 	_client = dev._client;
1147 	_name = dev._name;
1148 	return *this;
1149 }
1150 
~Device()1151 Device::~Device()
1152 {
1153 }
1154 
getName() const1155 std::string Device::getName()const
1156 {
1157 	return _name;
1158 }
1159 
getClient() const1160 const Client* Device::getClient()const
1161 {
1162 	return _client;
1163 }
1164 
getClient()1165 Client* Device::getClient()
1166 {
1167 	return _client;
1168 }
1169 
isOk() const1170 bool Device::isOk()const
1171 {
1172 	return _client!=nullptr && !_name.empty();
1173 }
1174 
operator bool() const1175 Device::operator bool()const
1176 {
1177 	return isOk();
1178 }
1179 
operator !() const1180 bool Device::operator!()const
1181 {
1182 	return !isOk();
1183 }
1184 
operator ==(const Device & dev) const1185 bool Device::operator==(const Device& dev)const
1186 {
1187 	return dev._client==_client && dev._name==_name;
1188 }
1189 
operator <(const Device & dev) const1190 bool Device::operator<(const Device& dev)const
1191 {
1192 	return getName()<dev.getName();
1193 }
1194 
getDescription()1195 std::string Device::getDescription()
1196 {
1197 	if (!isOk()) throw NutException("Invalid device");
1198 	return getClient()->getDeviceDescription(getName());
1199 }
1200 
getVariableValue(const std::string & name)1201 std::vector<std::string> Device::getVariableValue(const std::string& name)
1202 {
1203 	if (!isOk()) throw NutException("Invalid device");
1204 	return getClient()->getDeviceVariableValue(getName(), name);
1205 }
1206 
getVariableValues()1207 std::map<std::string,std::vector<std::string> > Device::getVariableValues()
1208 {
1209 	if (!isOk()) throw NutException("Invalid device");
1210 	return getClient()->getDeviceVariableValues(getName());
1211 }
1212 
getVariableNames()1213 std::set<std::string> Device::getVariableNames()
1214 {
1215 	if (!isOk()) throw NutException("Invalid device");
1216 	return getClient()->getDeviceVariableNames(getName());
1217 }
1218 
getRWVariableNames()1219 std::set<std::string> Device::getRWVariableNames()
1220 {
1221 	if (!isOk()) throw NutException("Invalid device");
1222 	return getClient()->getDeviceRWVariableNames(getName());
1223 }
1224 
setVariable(const std::string & name,const std::string & value)1225 void Device::setVariable(const std::string& name, const std::string& value)
1226 {
1227 	if (!isOk()) throw NutException("Invalid device");
1228 	getClient()->setDeviceVariable(getName(), name, value);
1229 }
1230 
setVariable(const std::string & name,const std::vector<std::string> & values)1231 void Device::setVariable(const std::string& name, const std::vector<std::string>& values)
1232 {
1233 	if (!isOk()) throw NutException("Invalid device");
1234 	getClient()->setDeviceVariable(getName(), name, values);
1235 }
1236 
1237 
1238 
getVariable(const std::string & name)1239 Variable Device::getVariable(const std::string& name)
1240 {
1241 	if (!isOk()) throw NutException("Invalid device");
1242 	if(getClient()->hasDeviceVariable(getName(), name))
1243 		return Variable(this, name);
1244 	else
1245 		return Variable(nullptr, "");
1246 }
1247 
getVariables()1248 std::set<Variable> Device::getVariables()
1249 {
1250 	std::set<Variable> set;
1251 	if (!isOk()) throw NutException("Invalid device");
1252 
1253 	std::set<std::string> names = getClient()->getDeviceVariableNames(getName());
1254 	for(std::set<std::string>::iterator it=names.begin(); it!=names.end(); ++it)
1255 	{
1256 		set.insert(Variable(this, *it));
1257 	}
1258 
1259 	return set;
1260 }
1261 
getRWVariables()1262 std::set<Variable> Device::getRWVariables()
1263 {
1264 	std::set<Variable> set;
1265 	if (!isOk()) throw NutException("Invalid device");
1266 
1267 	std::set<std::string> names = getClient()->getDeviceRWVariableNames(getName());
1268 	for(std::set<std::string>::iterator it=names.begin(); it!=names.end(); ++it)
1269 	{
1270 		set.insert(Variable(this, *it));
1271 	}
1272 
1273 	return set;
1274 }
1275 
getCommandNames()1276 std::set<std::string> Device::getCommandNames()
1277 {
1278 	if (!isOk()) throw NutException("Invalid device");
1279 	return getClient()->getDeviceCommandNames(getName());
1280 }
1281 
getCommands()1282 std::set<Command> Device::getCommands()
1283 {
1284 	std::set<Command> cmds;
1285 
1286 	std::set<std::string> res = getCommandNames();
1287 	for(std::set<std::string>::iterator it=res.begin(); it!=res.end(); ++it)
1288 	{
1289 		cmds.insert(Command(this, *it));
1290 	}
1291 
1292 	return cmds;
1293 }
1294 
getCommand(const std::string & name)1295 Command Device::getCommand(const std::string& name)
1296 {
1297 	if (!isOk()) throw NutException("Invalid device");
1298 	if(getClient()->hasDeviceCommand(getName(), name))
1299 		return Command(this, name);
1300 	else
1301 		return Command(nullptr, "");
1302 }
1303 
executeCommand(const std::string & name,const std::string & param)1304 TrackingID Device::executeCommand(const std::string& name, const std::string& param)
1305 {
1306 	if (!isOk()) throw NutException("Invalid device");
1307 	return getClient()->executeDeviceCommand(getName(), name, param);
1308 }
1309 
login()1310 void Device::login()
1311 {
1312 	if (!isOk()) throw NutException("Invalid device");
1313 	getClient()->deviceLogin(getName());
1314 }
1315 
1316 /* FIXME: Protocol update needed to handle master/primary alias
1317  * and probably an API bump also, to rename/alias the routine.
1318  */
master()1319 void Device::master()
1320 {
1321 	if (!isOk()) throw NutException("Invalid device");
1322 	getClient()->deviceMaster(getName());
1323 }
1324 
forcedShutdown()1325 void Device::forcedShutdown()
1326 {
1327 }
1328 
getNumLogins()1329 int Device::getNumLogins()
1330 {
1331 	if (!isOk()) throw NutException("Invalid device");
1332 	return getClient()->deviceGetNumLogins(getName());
1333 }
1334 
1335 /*
1336  *
1337  * Variable implementation
1338  *
1339  */
1340 
Variable(Device * dev,const std::string & name)1341 Variable::Variable(Device* dev, const std::string& name):
1342 _device(dev),
1343 _name(name)
1344 {
1345 }
1346 
Variable(const Variable & var)1347 Variable::Variable(const Variable& var):
1348 _device(var._device),
1349 _name(var._name)
1350 {
1351 }
1352 
operator =(const Variable & var)1353 Variable& Variable::operator=(const Variable& var)
1354 {
1355 	// Self assignment?
1356 	if (this==&var)
1357 		return *this;
1358 
1359 	_device = var._device;
1360 	_name = var._name;
1361 	return *this;
1362 }
1363 
~Variable()1364 Variable::~Variable()
1365 {
1366 }
1367 
getName() const1368 std::string Variable::getName()const
1369 {
1370 	return _name;
1371 }
1372 
getDevice() const1373 const Device* Variable::getDevice()const
1374 {
1375 	return _device;
1376 }
1377 
getDevice()1378 Device* Variable::getDevice()
1379 {
1380 	return _device;
1381 }
1382 
isOk() const1383 bool Variable::isOk()const
1384 {
1385 	return _device!=nullptr && !_name.empty();
1386 
1387 }
1388 
operator bool() const1389 Variable::operator bool()const
1390 {
1391 	return isOk();
1392 }
1393 
operator !() const1394 bool Variable::operator!()const
1395 {
1396 	return !isOk();
1397 }
1398 
operator ==(const Variable & var) const1399 bool Variable::operator==(const Variable& var)const
1400 {
1401 	return var._device==_device && var._name==_name;
1402 }
1403 
operator <(const Variable & var) const1404 bool Variable::operator<(const Variable& var)const
1405 {
1406 	return getName()<var.getName();
1407 }
1408 
getValue()1409 std::vector<std::string> Variable::getValue()
1410 {
1411 	return getDevice()->getClient()->getDeviceVariableValue(getDevice()->getName(), getName());
1412 }
1413 
getDescription()1414 std::string Variable::getDescription()
1415 {
1416 	return getDevice()->getClient()->getDeviceVariableDescription(getDevice()->getName(), getName());
1417 }
1418 
setValue(const std::string & value)1419 void Variable::setValue(const std::string& value)
1420 {
1421 	getDevice()->setVariable(getName(), value);
1422 }
1423 
setValues(const std::vector<std::string> & values)1424 void Variable::setValues(const std::vector<std::string>& values)
1425 {
1426 	getDevice()->setVariable(getName(), values);
1427 }
1428 
1429 
1430 /*
1431  *
1432  * Command implementation
1433  *
1434  */
1435 
Command(Device * dev,const std::string & name)1436 Command::Command(Device* dev, const std::string& name):
1437 _device(dev),
1438 _name(name)
1439 {
1440 }
1441 
Command(const Command & cmd)1442 Command::Command(const Command& cmd):
1443 _device(cmd._device),
1444 _name(cmd._name)
1445 {
1446 }
1447 
operator =(const Command & cmd)1448 Command& Command::operator=(const Command& cmd)
1449 {
1450 	// Self assignment?
1451 	if (this==&cmd)
1452 		return *this;
1453 
1454 	_device = cmd._device;
1455 	_name = cmd._name;
1456 	return *this;
1457 }
1458 
~Command()1459 Command::~Command()
1460 {
1461 }
1462 
getName() const1463 std::string Command::getName()const
1464 {
1465 	return _name;
1466 }
1467 
getDevice() const1468 const Device* Command::getDevice()const
1469 {
1470 	return _device;
1471 }
1472 
getDevice()1473 Device* Command::getDevice()
1474 {
1475 	return _device;
1476 }
1477 
isOk() const1478 bool Command::isOk()const
1479 {
1480 	return _device!=nullptr && !_name.empty();
1481 }
1482 
operator bool() const1483 Command::operator bool()const
1484 {
1485 	return isOk();
1486 }
1487 
operator !() const1488 bool Command::operator!()const
1489 {
1490 	return !isOk();
1491 }
1492 
operator ==(const Command & cmd) const1493 bool Command::operator==(const Command& cmd)const
1494 {
1495 	return cmd._device==_device && cmd._name==_name;
1496 }
1497 
operator <(const Command & cmd) const1498 bool Command::operator<(const Command& cmd)const
1499 {
1500 	return getName()<cmd.getName();
1501 }
1502 
getDescription()1503 std::string Command::getDescription()
1504 {
1505 	return getDevice()->getClient()->getDeviceCommandDescription(getDevice()->getName(), getName());
1506 }
1507 
execute(const std::string & param)1508 void Command::execute(const std::string& param)
1509 {
1510 	getDevice()->executeCommand(getName(), param);
1511 }
1512 
1513 } /* namespace nut */
1514 
1515 
1516 /**
1517  * C nutclient API.
1518  */
1519 extern "C" {
1520 
1521 
strarr_alloc(size_t count)1522 strarr strarr_alloc(size_t count)
1523 {
1524 	strarr arr = static_cast<strarr>(xcalloc(count+1, sizeof(char*)));
1525 
1526 	if (arr == nullptr) {
1527 		throw nut::NutException("Out of memory");
1528 	}
1529 
1530 	arr[count] = nullptr;
1531 	return arr;
1532 }
1533 
strarr_free(strarr arr)1534 void strarr_free(strarr arr)
1535 {
1536 	char** pstr = arr;
1537 	while(*pstr!=nullptr)
1538 	{
1539 		free(*pstr);
1540 		++pstr;
1541 	}
1542 	free(arr);
1543 }
1544 
stringset_to_strarr(const std::set<std::string> & strset)1545 strarr stringset_to_strarr(const std::set<std::string>& strset)
1546 {
1547 	strarr arr = strarr_alloc(strset.size());
1548 	strarr pstr = arr;
1549 	for(std::set<std::string>::const_iterator it=strset.begin(); it!=strset.end(); ++it)
1550 	{
1551 		*pstr = xstrdup(it->c_str());
1552 		pstr++;
1553 	}
1554 	return arr;
1555 }
1556 
stringvector_to_strarr(const std::vector<std::string> & strset)1557 strarr stringvector_to_strarr(const std::vector<std::string>& strset)
1558 {
1559 	strarr arr = strarr_alloc(strset.size());
1560 	strarr pstr = arr;
1561 	for(std::vector<std::string>::const_iterator it=strset.begin(); it!=strset.end(); ++it)
1562 	{
1563 		*pstr = xstrdup(it->c_str());
1564 		pstr++;
1565 	}
1566 	return arr;
1567 }
1568 
nutclient_tcp_create_client(const char * host,unsigned short port)1569 NUTCLIENT_TCP_t nutclient_tcp_create_client(const char* host, unsigned short port)
1570 {
1571 	nut::TcpClient* client = new nut::TcpClient;
1572 	try
1573 	{
1574 		client->connect(host, port);
1575 		return static_cast<NUTCLIENT_TCP_t>(client);
1576 	}
1577 	catch(nut::NutException& ex)
1578 	{
1579 		// TODO really catch it
1580 		NUT_UNUSED_VARIABLE(ex);
1581 		delete client;
1582 		return nullptr;
1583 	}
1584 
1585 }
1586 
nutclient_destroy(NUTCLIENT_t client)1587 void nutclient_destroy(NUTCLIENT_t client)
1588 {
1589 	if(client)
1590 	{
1591 		delete static_cast<nut::Client*>(client);
1592 	}
1593 }
1594 
nutclient_tcp_is_connected(NUTCLIENT_TCP_t client)1595 int nutclient_tcp_is_connected(NUTCLIENT_TCP_t client)
1596 {
1597 	if(client)
1598 	{
1599 		nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(client));
1600 		if(cl)
1601 		{
1602 			return cl->isConnected() ? 1 : 0;
1603 		}
1604 	}
1605 	return 0;
1606 }
1607 
nutclient_tcp_disconnect(NUTCLIENT_TCP_t client)1608 void nutclient_tcp_disconnect(NUTCLIENT_TCP_t client)
1609 {
1610 	if(client)
1611 	{
1612 		nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(client));
1613 		if(cl)
1614 		{
1615 			cl->disconnect();
1616 		}
1617 	}
1618 }
1619 
nutclient_tcp_reconnect(NUTCLIENT_TCP_t client)1620 int nutclient_tcp_reconnect(NUTCLIENT_TCP_t client)
1621 {
1622 	if(client)
1623 	{
1624 		nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(client));
1625 		if(cl)
1626 		{
1627 			try
1628 			{
1629 				cl->connect();
1630 				return 0;
1631 			}
1632 			catch(...){}
1633 		}
1634 	}
1635 	return -1;
1636 }
1637 
nutclient_tcp_set_timeout(NUTCLIENT_TCP_t client,long timeout)1638 void nutclient_tcp_set_timeout(NUTCLIENT_TCP_t client, long timeout)
1639 {
1640 	if(client)
1641 	{
1642 		nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(client));
1643 		if(cl)
1644 		{
1645 			cl->setTimeout(timeout);
1646 		}
1647 	}
1648 }
1649 
nutclient_tcp_get_timeout(NUTCLIENT_TCP_t client)1650 long nutclient_tcp_get_timeout(NUTCLIENT_TCP_t client)
1651 {
1652 	if(client)
1653 	{
1654 		nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(client));
1655 		if(cl)
1656 		{
1657 			return cl->getTimeout();
1658 		}
1659 	}
1660 	return -1;
1661 }
1662 
nutclient_authenticate(NUTCLIENT_t client,const char * login,const char * passwd)1663 void nutclient_authenticate(NUTCLIENT_t client, const char* login, const char* passwd)
1664 {
1665 	if(client)
1666 	{
1667 		nut::Client* cl = static_cast<nut::Client*>(client);
1668 		if(cl)
1669 		{
1670 			try
1671 			{
1672 				cl->authenticate(login, passwd);
1673 			}
1674 			catch(...){}
1675 		}
1676 	}
1677 }
1678 
nutclient_logout(NUTCLIENT_t client)1679 void nutclient_logout(NUTCLIENT_t client)
1680 {
1681 	if(client)
1682 	{
1683 		nut::Client* cl = static_cast<nut::Client*>(client);
1684 		if(cl)
1685 		{
1686 			try
1687 			{
1688 				cl->logout();
1689 			}
1690 			catch(...){}
1691 		}
1692 	}
1693 }
1694 
nutclient_device_login(NUTCLIENT_t client,const char * dev)1695 void nutclient_device_login(NUTCLIENT_t client, const char* dev)
1696 {
1697 	if(client)
1698 	{
1699 		nut::Client* cl = static_cast<nut::Client*>(client);
1700 		if(cl)
1701 		{
1702 			try
1703 			{
1704 				cl->deviceLogin(dev);
1705 			}
1706 			catch(...){}
1707 		}
1708 	}
1709 }
1710 
nutclient_get_device_num_logins(NUTCLIENT_t client,const char * dev)1711 int nutclient_get_device_num_logins(NUTCLIENT_t client, const char* dev)
1712 {
1713 	if(client)
1714 	{
1715 		nut::Client* cl = static_cast<nut::Client*>(client);
1716 		if(cl)
1717 		{
1718 			try
1719 			{
1720 				return cl->deviceGetNumLogins(dev);
1721 			}
1722 			catch(...){}
1723 		}
1724 	}
1725 	return -1;
1726 }
1727 
1728 /* FIXME: Protocol update needed to handle master/primary alias
1729  * and probably an API bump also, to rename/alias the routine.
1730  */
nutclient_device_master(NUTCLIENT_t client,const char * dev)1731 void nutclient_device_master(NUTCLIENT_t client, const char* dev)
1732 {
1733 	if(client)
1734 	{
1735 		nut::Client* cl = static_cast<nut::Client*>(client);
1736 		if(cl)
1737 		{
1738 			try
1739 			{
1740 				cl->deviceMaster(dev);
1741 			}
1742 			catch(...){}
1743 		}
1744 	}
1745 }
1746 
nutclient_device_forced_shutdown(NUTCLIENT_t client,const char * dev)1747 void nutclient_device_forced_shutdown(NUTCLIENT_t client, const char* dev)
1748 {
1749 	if(client)
1750 	{
1751 		nut::Client* cl = static_cast<nut::Client*>(client);
1752 		if(cl)
1753 		{
1754 			try
1755 			{
1756 				cl->deviceForcedShutdown(dev);
1757 			}
1758 			catch(...){}
1759 		}
1760 	}
1761 }
1762 
nutclient_get_devices(NUTCLIENT_t client)1763 strarr nutclient_get_devices(NUTCLIENT_t client)
1764 {
1765 	if(client)
1766 	{
1767 		nut::Client* cl = static_cast<nut::Client*>(client);
1768 		if(cl)
1769 		{
1770 			try
1771 			{
1772 				return stringset_to_strarr(cl->getDeviceNames());
1773 			}
1774 			catch(...){}
1775 		}
1776 	}
1777 	return nullptr;
1778 }
1779 
nutclient_has_device(NUTCLIENT_t client,const char * dev)1780 int nutclient_has_device(NUTCLIENT_t client, const char* dev)
1781 {
1782 	if(client)
1783 	{
1784 		nut::Client* cl = static_cast<nut::Client*>(client);
1785 		if(cl)
1786 		{
1787 			try
1788 			{
1789 				return cl->hasDevice(dev)?1:0;
1790 			}
1791 			catch(...){}
1792 		}
1793 	}
1794 	return 0;
1795 }
1796 
nutclient_get_device_description(NUTCLIENT_t client,const char * dev)1797 char* nutclient_get_device_description(NUTCLIENT_t client, const char* dev)
1798 {
1799 	if(client)
1800 	{
1801 		nut::Client* cl = static_cast<nut::Client*>(client);
1802 		if(cl)
1803 		{
1804 			try
1805 			{
1806 				return xstrdup(cl->getDeviceDescription(dev).c_str());
1807 			}
1808 			catch(...){}
1809 		}
1810 	}
1811 	return nullptr;
1812 }
1813 
nutclient_get_device_variables(NUTCLIENT_t client,const char * dev)1814 strarr nutclient_get_device_variables(NUTCLIENT_t client, const char* dev)
1815 {
1816 	if(client)
1817 	{
1818 		nut::Client* cl = static_cast<nut::Client*>(client);
1819 		if(cl)
1820 		{
1821 			try
1822 			{
1823 				return stringset_to_strarr(cl->getDeviceVariableNames(dev));
1824 			}
1825 			catch(...){}
1826 		}
1827 	}
1828 	return nullptr;
1829 }
1830 
nutclient_get_device_rw_variables(NUTCLIENT_t client,const char * dev)1831 strarr nutclient_get_device_rw_variables(NUTCLIENT_t client, const char* dev)
1832 {
1833 	if(client)
1834 	{
1835 		nut::Client* cl = static_cast<nut::Client*>(client);
1836 		if(cl)
1837 		{
1838 			try
1839 			{
1840 				return stringset_to_strarr(cl->getDeviceRWVariableNames(dev));
1841 			}
1842 			catch(...){}
1843 		}
1844 	}
1845 	return nullptr;
1846 }
1847 
nutclient_has_device_variable(NUTCLIENT_t client,const char * dev,const char * var)1848 int nutclient_has_device_variable(NUTCLIENT_t client, const char* dev, const char* var)
1849 {
1850 	if(client)
1851 	{
1852 		nut::Client* cl = static_cast<nut::Client*>(client);
1853 		if(cl)
1854 		{
1855 			try
1856 			{
1857 				return cl->hasDeviceVariable(dev, var)?1:0;
1858 			}
1859 			catch(...){}
1860 		}
1861 	}
1862 	return 0;
1863 }
1864 
nutclient_get_device_variable_description(NUTCLIENT_t client,const char * dev,const char * var)1865 char* nutclient_get_device_variable_description(NUTCLIENT_t client, const char* dev, const char* var)
1866 {
1867 	if(client)
1868 	{
1869 		nut::Client* cl = static_cast<nut::Client*>(client);
1870 		if(cl)
1871 		{
1872 			try
1873 			{
1874 				return xstrdup(cl->getDeviceVariableDescription(dev, var).c_str());
1875 			}
1876 			catch(...){}
1877 		}
1878 	}
1879 	return nullptr;
1880 }
1881 
nutclient_get_device_variable_values(NUTCLIENT_t client,const char * dev,const char * var)1882 strarr nutclient_get_device_variable_values(NUTCLIENT_t client, const char* dev, const char* var)
1883 {
1884 	if(client)
1885 	{
1886 		nut::Client* cl = static_cast<nut::Client*>(client);
1887 		if(cl)
1888 		{
1889 			try
1890 			{
1891 				return stringvector_to_strarr(cl->getDeviceVariableValue(dev, var));
1892 			}
1893 			catch(...){}
1894 		}
1895 	}
1896 	return nullptr;
1897 }
1898 
nutclient_set_device_variable_value(NUTCLIENT_t client,const char * dev,const char * var,const char * value)1899 void nutclient_set_device_variable_value(NUTCLIENT_t client, const char* dev, const char* var, const char* value)
1900 {
1901 	if(client)
1902 	{
1903 		nut::Client* cl = static_cast<nut::Client*>(client);
1904 		if(cl)
1905 		{
1906 			try
1907 			{
1908 				cl->setDeviceVariable(dev, var, value);
1909 			}
1910 			catch(...){}
1911 		}
1912 	}
1913 }
1914 
nutclient_set_device_variable_values(NUTCLIENT_t client,const char * dev,const char * var,const strarr values)1915 void nutclient_set_device_variable_values(NUTCLIENT_t client, const char* dev, const char* var, const strarr values)
1916 {
1917 	if(client)
1918 	{
1919 		nut::Client* cl = static_cast<nut::Client*>(client);
1920 		if(cl)
1921 		{
1922 			try
1923 			{
1924 				std::vector<std::string> vals;
1925 				strarr pstr = static_cast<strarr>(values);
1926 				while(*pstr)
1927 				{
1928 					vals.push_back(std::string(*pstr));
1929 					++pstr;
1930 				}
1931 
1932 				cl->setDeviceVariable(dev, var, vals);
1933 			}
1934 			catch(...){}
1935 		}
1936 	}
1937 }
1938 
nutclient_get_device_commands(NUTCLIENT_t client,const char * dev)1939 strarr nutclient_get_device_commands(NUTCLIENT_t client, const char* dev)
1940 {
1941 	if(client)
1942 	{
1943 		nut::Client* cl = static_cast<nut::Client*>(client);
1944 		if(cl)
1945 		{
1946 			try
1947 			{
1948 				return stringset_to_strarr(cl->getDeviceCommandNames(dev));
1949 			}
1950 			catch(...){}
1951 		}
1952 	}
1953 	return nullptr;
1954 }
1955 
nutclient_has_device_command(NUTCLIENT_t client,const char * dev,const char * cmd)1956 int nutclient_has_device_command(NUTCLIENT_t client, const char* dev, const char* cmd)
1957 {
1958 	if(client)
1959 	{
1960 		nut::Client* cl = static_cast<nut::Client*>(client);
1961 		if(cl)
1962 		{
1963 			try
1964 			{
1965 				return cl->hasDeviceCommand(dev, cmd)?1:0;
1966 			}
1967 			catch(...){}
1968 		}
1969 	}
1970 	return 0;
1971 }
1972 
nutclient_get_device_command_description(NUTCLIENT_t client,const char * dev,const char * cmd)1973 char* nutclient_get_device_command_description(NUTCLIENT_t client, const char* dev, const char* cmd)
1974 {
1975 	if(client)
1976 	{
1977 		nut::Client* cl = static_cast<nut::Client*>(client);
1978 		if(cl)
1979 		{
1980 			try
1981 			{
1982 				return xstrdup(cl->getDeviceCommandDescription(dev, cmd).c_str());
1983 			}
1984 			catch(...){}
1985 		}
1986 	}
1987 	return nullptr;
1988 }
1989 
nutclient_execute_device_command(NUTCLIENT_t client,const char * dev,const char * cmd,const char * param)1990 void nutclient_execute_device_command(NUTCLIENT_t client, const char* dev, const char* cmd, const char* param)
1991 {
1992 	if(client)
1993 	{
1994 		nut::Client* cl = static_cast<nut::Client*>(client);
1995 		if(cl)
1996 		{
1997 			try
1998 			{
1999 				cl->executeDeviceCommand(dev, cmd, param);
2000 			}
2001 			catch(...){}
2002 		}
2003 	}
2004 }
2005 
2006 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_CXX98_COMPAT
2007 #pragma GCC diagnostic pop
2008 #endif
2009 
2010 } /* extern "C" */
2011