1 /*******************************************************************************
2   Copyright(c) 2011 Jasem Mutlaq. All rights reserved.
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7 
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  Library General Public License for more details.
12 
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB.  If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 *******************************************************************************/
18 
19 #define NOMINMAX
20 
21 #include "baseclient.h"
22 
23 #include "indistandardproperty.h"
24 #include "base64.h"
25 #include "basedevice.h"
26 #include "locale_compat.h"
27 
28 #include <cerrno>
29 #include <fcntl.h>
30 #include <cstdlib>
31 #include <stdarg.h>
32 #include <cstring>
33 #include <algorithm>
34 #include <chrono>
35 #include <functional>
36 #include <assert.h>
37 
38 #include "indiuserio.h"
39 
40 #ifdef _WINDOWS
41 #include <windows.h>
42 
43 #define net_read(x,y,z) recv(x,y,z,0)
44 #define net_write(x,y,z) send(x,(const char *)(y),z,0)
45 #define net_close closesocket
46 
47 #pragma comment(lib, "Ws2_32.lib")
48 #else
49 #include <netdb.h>
50 #include <unistd.h>
51 #include <sys/types.h>
52 #include <sys/socket.h>
53 #include <netinet/in.h>
54 #define net_read read
55 #define net_write write
56 #define net_close close
57 #endif
58 
59 #ifdef _MSC_VER
60 # define snprintf _snprintf
61 #endif
62 
63 #define MAXINDIBUF 49152
64 #define DISCONNECTION_DELAY_US 500000
65 
66 static userio io;
67 
68 #include "baseclient_p.h"
69 
70 namespace INDI
71 {
72 
BaseClientPrivate(BaseClient * parent)73 BaseClientPrivate::BaseClientPrivate(BaseClient *parent)
74     : parent(parent)
75     , cServer("localhost")
76     , cPort(7624)
77     , sConnected(false)
78     , verbose(false)
79     , timeout_sec(3)
80     , timeout_us(0)
81 {
82     io.write = [](void *user, const void * ptr, size_t count) -> size_t
83     {
84         auto self = static_cast<BaseClientPrivate *>(user);
85         return self->sendData(ptr, count);
86     };
87 
88     io.vprintf = [](void *user, const char * format, va_list ap) -> int
89     {
90         auto self = static_cast<BaseClientPrivate *>(user);
91         char message[MAXRBUF];
92         vsnprintf(message, MAXRBUF, format, ap);
93         return self->sendData(message, strlen(message));
94     };
95 }
96 
~BaseClientPrivate()97 BaseClientPrivate::~BaseClientPrivate()
98 {
99     if (sConnected)
100         disconnect(0);
101 
102     std::unique_lock<std::mutex> locker(sSocketBusy);
103     if (!sSocketChanged.wait_for(locker, std::chrono::milliseconds(500), [this] { return sConnected == false; }))
104     {
105         IDLog("BaseClient::~BaseClient: Probability of detecting a deadlock.\n");
106         /* #PS:
107          * KStars bug - suspicion
108          *   The function thread 'BaseClient::listenINDI' could not be terminated
109          *   because the 'dispatchCommand' function is in progress.
110          *
111          *   The function 'dispatchCommand' cannot be completed
112          *   because it is related to the function call 'ClientManager::newProperty'.
113          *
114          *   There is a call that uses BlockingQueuedConnection to the thread that is currently busy
115          *   destroying the BaseClient object.
116          *
117          */
118     }
119 }
120 
clear()121 void BaseClientPrivate::clear()
122 {
123     while (!cDevices.empty())
124     {
125         delete cDevices.back();
126         cDevices.pop_back();
127     }
128     cDevices.clear();
129     blobModes.clear();
130     // cDeviceNames.clear(); // #PS: missing?
131 }
132 
connect()133 bool BaseClientPrivate::connect()
134 {
135     std::unique_lock<std::mutex> locker(sSocketBusy);
136     if (sConnected == true)
137     {
138         IDLog("INDI::BaseClient::connectServer: Already connected.\n");
139         return false;
140     }
141 
142     IDLog("INDI::BaseClient::connectServer: creating new connection...\n");
143 
144 #ifdef _WINDOWS
145     WSADATA wsaData;
146     int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
147     if (iResult != NO_ERROR)
148     {
149         IDLog("Error at WSAStartup()\n");
150         return false;
151     }
152 #endif
153 
154     struct timeval ts;
155     ts.tv_sec  = timeout_sec;
156     ts.tv_usec = timeout_us;
157 
158     struct sockaddr_in serv_addr;
159     struct hostent *hp;
160     int ret = 0;
161 
162     /* lookup host address */
163     hp = gethostbyname(cServer.c_str());
164     if (!hp)
165     {
166         perror("gethostbyname");
167         return false;
168     }
169 
170     /* create a socket to the INDI server */
171     (void)memset((char *)&serv_addr, 0, sizeof(serv_addr));
172     serv_addr.sin_family      = AF_INET;
173     serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
174     serv_addr.sin_port        = htons(cPort);
175 #ifdef _WINDOWS
176     if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
177     {
178         IDLog("Socket error: %d\n", WSAGetLastError());
179         WSACleanup();
180         return false;
181     }
182 #else
183     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
184     {
185         perror("socket");
186         return false;
187     }
188 #endif
189 
190     /* set the socket in non-blocking */
191     //set socket nonblocking flag
192 #ifdef _WINDOWS
193     u_long iMode = 0;
194     iResult = ioctlsocket(sockfd, FIONBIO, &iMode);
195     if (iResult != NO_ERROR)
196     {
197         IDLog("ioctlsocket failed with error: %ld\n", iResult);
198         return false;
199     }
200 #else
201     int flags = 0;
202     if ((flags = fcntl(sockfd, F_GETFL, 0)) < 0)
203         return false;
204 
205     if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0)
206         return false;
207 #endif
208 
209     //clear out descriptor sets for select
210     //add socket to the descriptor sets
211     fd_set rset, wset;
212     FD_ZERO(&rset);
213     FD_SET(sockfd, &rset);
214     wset = rset; //structure assignment okok
215 
216     /* connect */
217     if ((ret = ::connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0)
218     {
219         if (errno != EINPROGRESS)
220         {
221             perror("connect");
222             net_close(sockfd);
223             return false;
224         }
225     }
226 
227     /* If it is connected, continue, otherwise wait */
228     if (ret != 0)
229     {
230         //we are waiting for connect to complete now
231         if ((ret = select(sockfd + 1, &rset, &wset, nullptr, &ts)) < 0)
232             return false;
233         //we had a timeout
234         if (ret == 0)
235         {
236 #ifdef _WINDOWS
237             IDLog("select timeout\n");
238 #else
239             errno = ETIMEDOUT;
240             perror("select timeout");
241 #endif
242             return false;
243         }
244     }
245 
246     /* we had a positivite return so a descriptor is ready */
247 #ifndef _WINDOWS
248     int error     = 0;
249     socklen_t len = sizeof(error);
250     if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset))
251     {
252         if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
253         {
254             perror("getsockopt");
255             return false;
256         }
257     }
258     else
259         return false;
260 
261     /* check if we had a socket error */
262     if (error)
263     {
264         errno = error;
265         perror("socket");
266         return false;
267     }
268 #endif
269 
270 #ifndef _WINDOWS
271     int pipefd[2];
272     ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
273 
274     if (ret < 0)
275     {
276         IDLog("notify pipe: %s\n", strerror(errno));
277         return false;
278     }
279 
280     receiveFd = pipefd[0];
281     sendFd    = pipefd[1];
282 #endif
283 
284     sConnected = true;
285     sAboutToClose = false;
286     sSocketChanged.notify_all();
287     std::thread(std::bind(&BaseClientPrivate::listenINDI, this)).detach();
288 
289     return true;
290 }
291 
disconnect(int exit_code)292 bool BaseClientPrivate::disconnect(int exit_code)
293 {
294     //IDLog("Server disconnected called\n");
295     std::lock_guard<std::mutex> locker(sSocketBusy);
296     if (sConnected == false)
297     {
298         IDLog("INDI::BaseClient::disconnectServer: Already disconnected.\n");
299         return false;
300     }
301     sAboutToClose = true;
302     sSocketChanged.notify_all();
303 #ifdef _WINDOWS
304     net_close(sockfd); // close and wakeup 'select' function
305     WSACleanup();
306     sockfd = INVALID_SOCKET;
307 #else
308     shutdown(sockfd, SHUT_RDWR); // no needed
309     size_t c = 1;
310     // wakeup 'select' function
311     ssize_t ret = write(sendFd, &c, sizeof(c));
312     if (ret != sizeof(c))
313     {
314         IDLog("INDI::BaseClient::disconnectServer: Error. The socket cannot be woken up.\n");
315     }
316 #endif
317     sExitCode = exit_code;
318     return true;
319 }
320 
listenINDI()321 void BaseClientPrivate::listenINDI()
322 {
323     char buffer[MAXINDIBUF];
324     char msg[MAXRBUF];
325 #ifdef _WINDOWS
326     SOCKET maxfd = 0;
327 #else
328     int maxfd = 0;
329 #endif
330     fd_set rs;
331     XMLEle **nodes = nullptr;
332     XMLEle *root = nullptr;
333     int inode = 0;
334 
335     connect();
336 
337     if (cDeviceNames.empty())
338     {
339         IUUserIOGetProperties(&io, this, nullptr, nullptr);
340         if (verbose)
341             IUUserIOGetProperties(userio_file(), stderr, nullptr, nullptr);
342     }
343     else
344     {
345         for (const auto &oneDevice : cDeviceNames)
346         {
347             // If there are no specific properties to watch, we watch the complete device
348             if (cWatchProperties.find(oneDevice) == cWatchProperties.end())
349             {
350                 IUUserIOGetProperties(&io, this, oneDevice.c_str(), nullptr);
351                 if (verbose)
352                     IUUserIOGetProperties(userio_file(), stderr, oneDevice.c_str(), nullptr);
353             }
354             else
355             {
356                 for (const auto &oneProperty : cWatchProperties[oneDevice])
357                 {
358                     IUUserIOGetProperties(&io, this, oneDevice.c_str(), oneProperty.c_str());
359                     if (verbose)
360                         IUUserIOGetProperties(userio_file(), stderr, oneDevice.c_str(), oneProperty.c_str());
361                 }
362             }
363         }
364     }
365 
366     FD_ZERO(&rs);
367 
368     FD_SET(sockfd, &rs);
369     maxfd = std::max(maxfd, sockfd);
370 
371 #ifndef _WINDOWS
372     FD_SET(receiveFd, &rs);
373     maxfd = std::max(maxfd, receiveFd);
374 #endif
375 
376     clear();
377     LilXML *lillp = newLilXML();
378 
379     /* read from server, exit if find all requested properties */
380     while (!sAboutToClose)
381     {
382         int n = select(maxfd + 1, &rs, nullptr, nullptr, nullptr);
383 
384         // Woken up by disconnectServer function.
385         if (sAboutToClose == true)
386         {
387             break;
388         }
389 
390         if (n < 0)
391         {
392             IDLog("INDI server %s/%d disconnected.\n", cServer.c_str(), cPort);
393             break;
394         }
395 
396         if (n == 0)
397         {
398             continue;
399         }
400 
401         if (FD_ISSET(sockfd, &rs))
402         {
403 #ifdef _WINDOWS
404             n = recv(sockfd, buffer, MAXINDIBUF, 0);
405 #else
406             n = recv(sockfd, buffer, MAXINDIBUF, MSG_DONTWAIT);
407 #endif
408             if (n < 0)
409             {
410                 continue;
411             }
412 
413             if (n == 0)
414             {
415                 IDLog("INDI server %s/%d disconnected.\n", cServer.c_str(), cPort);
416                 break;
417             }
418 
419             nodes = parseXMLChunk(lillp, buffer, n, msg);
420 
421             if (!nodes)
422             {
423                 if (msg[0])
424                 {
425                     IDLog("Bad XML from %s/%d: %s\n%s\n", cServer.c_str(), cPort, msg, buffer);
426                 }
427                 break;
428             }
429             root = nodes[inode];
430             while (root)
431             {
432                 if (verbose)
433                     prXMLEle(stderr, root, 0);
434 
435                 int err_code = dispatchCommand(root, msg);
436 
437                 if (err_code < 0)
438                 {
439                     // Silenty ignore property duplication errors
440                     if (err_code != INDI_PROPERTY_DUPLICATED)
441                     {
442                         IDLog("Dispatch command error(%d): %s\n", err_code, msg);
443                         prXMLEle(stderr, root, 0);
444                     }
445                 }
446 
447                 delXMLEle(root); // not yet, delete and continue
448                 inode++;
449                 root = nodes[inode];
450             }
451             free(nodes);
452             inode = 0;
453         }
454     }
455 
456     delLilXML(lillp);
457 
458     int exit_code;
459 
460     {
461         std::lock_guard<std::mutex> locker(sSocketBusy);
462 #ifdef _WINDOWS
463         if (sockfd != INVALID_SOCKET)
464         {
465             net_close(sockfd);
466             WSACleanup();
467             sockfd = INVALID_SOCKET;
468         }
469 #else
470         close(sockfd);
471         close(receiveFd);
472         close(sendFd);
473 #endif
474 
475         exit_code = sAboutToClose ? sExitCode : -1;
476         sConnected = false;
477         parent->serverDisconnected(exit_code);
478 
479         clear();
480         cDeviceNames.clear();
481         sSocketChanged.notify_all();
482     }
483 }
484 
sendData(const void * data,size_t size)485 size_t BaseClientPrivate::sendData(const void *data, size_t size)
486 {
487     int ret;
488 
489     do
490     {
491         std::lock_guard<std::mutex> locker(sSocketBusy);
492         if (sConnected == false)
493             return 0;
494         ret = net_write(sockfd, data, size);
495     }
496     while(ret == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
497 
498     if (ret < 0)
499     {
500         disconnect(-1);
501     }
502 
503     return std::max(ret, 0);
504 }
505 
sendString(const char * fmt,...)506 void BaseClientPrivate::sendString(const char *fmt, ...)
507 {
508     char message[MAXRBUF];
509     va_list ap;
510 
511     va_start(ap, fmt);
512     vsnprintf(message, MAXRBUF, fmt, ap);
513     va_end(ap);
514     sendData(message, strlen(message));
515 }
516 
dispatchCommand(XMLEle * root,char * errmsg)517 int BaseClientPrivate::dispatchCommand(XMLEle *root, char *errmsg)
518 {
519     const char *tag = tagXMLEle(root);
520 
521     if (!strcmp(tag, "message"))
522         return messageCmd(root, errmsg);
523     else if (!strcmp(tag, "delProperty"))
524         return delPropertyCmd(root, errmsg);
525     // Just ignore any getProperties we might get
526     else if (!strcmp(tag, "getProperties"))
527         return INDI_PROPERTY_DUPLICATED;
528 
529     /* Get the device, if not available, create it */
530     INDI::BaseDevice *dp = findDev(root, 1, errmsg);
531     if (dp == nullptr)
532     {
533         strcpy(errmsg, "No device available and none was created");
534         return INDI_DEVICE_NOT_FOUND;
535     }
536 
537     // Ignore echoed newXXX
538     if (strstr(tag, "new"))
539         return 0;
540 
541     // If device is set to BLOB_ONLY, we ignore everything else
542     // not related to blobs
543     if (parent->getBLOBMode(dp->getDeviceName()) == B_ONLY)
544     {
545         if (!strcmp(tag, "defBLOBVector"))
546             return dp->buildProp(root, errmsg);
547         else if (!strcmp(tag, "setBLOBVector"))
548             return dp->setValue(root, errmsg);
549 
550         // Ignore everything else
551         return 0;
552     }
553 
554     // If we are asked to watch for specific properties only, we ignore everything else
555     if (cWatchProperties.size() > 0)
556     {
557         const char *device = findXMLAttValu(root, "device");
558         const char *name = findXMLAttValu(root, "name");
559         if (device && name)
560         {
561             if (cWatchProperties.find(device) == cWatchProperties.end() ||
562                     cWatchProperties[device].find(name) == cWatchProperties[device].end())
563                 return 0;
564         }
565     }
566 
567     if ((!strcmp(tag, "defTextVector")) || (!strcmp(tag, "defNumberVector")) ||
568             (!strcmp(tag, "defSwitchVector")) || (!strcmp(tag, "defLightVector")) ||
569             (!strcmp(tag, "defBLOBVector")))
570         return dp->buildProp(root, errmsg);
571     else if (!strcmp(tag, "setTextVector") || !strcmp(tag, "setNumberVector") ||
572              !strcmp(tag, "setSwitchVector") || !strcmp(tag, "setLightVector") ||
573              !strcmp(tag, "setBLOBVector"))
574         return dp->setValue(root, errmsg);
575 
576     return INDI_DISPATCH_ERROR;
577 }
578 
579 
deleteDevice(const char * devName,char * errmsg)580 int BaseClientPrivate::deleteDevice(const char *devName, char *errmsg)
581 {
582     for (auto devicei = cDevices.begin(); devicei != cDevices.end();)
583     {
584         if ((*devicei)->isDeviceNameMatch(devName))
585         {
586             parent->removeDevice(*devicei);
587             delete *devicei;
588             devicei = cDevices.erase(devicei);
589             return 0;
590         }
591         else
592             ++devicei;
593     }
594 
595     snprintf(errmsg, MAXRBUF, "Device %s not found", devName);
596     return INDI_DEVICE_NOT_FOUND;
597 }
598 
599 
600 /* delete the property in the given device, including widgets and data structs.
601  * when last property is deleted, delete the device too.
602  * if no property name attribute at all, delete the whole device regardless.
603  * return 0 if ok, else -1 with reason in errmsg[].
604  */
delPropertyCmd(XMLEle * root,char * errmsg)605 int BaseClientPrivate::delPropertyCmd(XMLEle *root, char *errmsg)
606 {
607     XMLAtt *ap;
608     INDI::BaseDevice *dp;
609 
610     /* dig out device and optional property name */
611     dp = findDev(root, 0, errmsg);
612     if (!dp)
613         return INDI_DEVICE_NOT_FOUND;
614 
615     dp->checkMessage(root);
616 
617     ap = findXMLAtt(root, "name");
618 
619     /* Delete property if it exists, otherwise, delete the whole device */
620     if (ap)
621     {
622         INDI::Property *rProp = dp->getProperty(valuXMLAtt(ap));
623         if (rProp == nullptr)
624         {
625             // Silently ignore B_ONLY clients.
626             if (blobModes.empty() || blobModes.front().blobMode == B_ONLY)
627                 return 0;
628 
629             snprintf(errmsg, MAXRBUF, "Cannot delete property %s as it is not defined yet. Check driver.", valuXMLAtt(ap));
630             return -1;
631         }
632         if (sConnected)
633             parent->removeProperty(rProp);
634         int errCode = dp->removeProperty(valuXMLAtt(ap), errmsg);
635 
636         return errCode;
637     }
638     // delete the whole device
639     else
640         return deleteDevice(dp->getDeviceName(), errmsg);
641 }
642 
643 
findDev(const char * devName,char * errmsg)644 INDI::BaseDevice *BaseClientPrivate::findDev(const char *devName, char *errmsg)
645 {
646     auto pos = std::find_if(cDevices.begin(), cDevices.end(), [devName](INDI::BaseDevice * oneDevice)
647     {
648         return oneDevice->isDeviceNameMatch(devName);
649     });
650 
651     if (pos != cDevices.end())
652         return *pos;
653 
654     snprintf(errmsg, MAXRBUF, "Device %s not found", devName);
655     return nullptr;
656 }
657 
658 /* add new device */
addDevice(XMLEle * dep,char * errmsg)659 INDI::BaseDevice *BaseClientPrivate::addDevice(XMLEle *dep, char *errmsg)
660 {
661     char *device_name;
662 
663     /* allocate new INDI::BaseDriver */
664     XMLAtt *ap = findXMLAtt(dep, "device");
665     if (!ap)
666     {
667         strncpy(errmsg, "Unable to find device attribute in XML element. Cannot add device.", MAXRBUF);
668         return nullptr;
669     }
670 
671     INDI::BaseDevice *dp = new INDI::BaseDevice();
672 
673     device_name = valuXMLAtt(ap);
674 
675     dp->setMediator(parent);
676     dp->setDeviceName(device_name);
677 
678     cDevices.push_back(dp);
679 
680     parent->newDevice(dp);
681 
682     /* ok */
683     return dp;
684 }
685 
findDev(XMLEle * root,int create,char * errmsg)686 INDI::BaseDevice *BaseClientPrivate::findDev(XMLEle *root, int create, char *errmsg)
687 {
688     XMLAtt *ap;
689     INDI::BaseDevice *dp;
690     char *dn;
691 
692     /* get device name */
693     ap = findXMLAtt(root, "device");
694     if (!ap)
695     {
696         snprintf(errmsg, MAXRBUF, "No device attribute found in element %s", tagXMLEle(root));
697         return (nullptr);
698     }
699 
700     dn = valuXMLAtt(ap);
701 
702     if (*dn == '\0')
703     {
704         snprintf(errmsg, MAXRBUF, "Device name is empty! %s", tagXMLEle(root));
705         return (nullptr);
706     }
707 
708     dp = findDev(dn, errmsg);
709 
710     if (dp)
711         return dp;
712 
713     /* not found, create if ok */
714     if (create)
715         return (addDevice(root, errmsg));
716 
717     snprintf(errmsg, MAXRBUF, "INDI: <%s> no such device %s", tagXMLEle(root), dn);
718     return nullptr;
719 }
720 
721 /* a general message command received from the device.
722  * return 0 if ok, else -1 with reason in errmsg[].
723  */
messageCmd(XMLEle * root,char * errmsg)724 int BaseClientPrivate::messageCmd(XMLEle *root, char *errmsg)
725 {
726     INDI::BaseDevice *dp = findDev(root, 0, errmsg);
727 
728     if (dp)
729         dp->checkMessage(root);
730     else
731     {
732         XMLAtt *message;
733         XMLAtt *time_stamp;
734 
735         char msgBuffer[MAXRBUF];
736 
737         /* prefix our timestamp if not with msg */
738         time_stamp = findXMLAtt(root, "timestamp");
739 
740         /* finally! the msg */
741         message = findXMLAtt(root, "message");
742         if (!message)
743         {
744             strncpy(errmsg, "No message content found.", MAXRBUF);
745             return -1;
746         }
747 
748         if (time_stamp)
749             snprintf(msgBuffer, MAXRBUF, "%s: %s", valuXMLAtt(time_stamp), valuXMLAtt(message));
750         else
751         {
752             char ts[32];
753             struct tm *tp;
754             time_t t;
755             time(&t);
756             tp = gmtime(&t);
757             strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%S", tp);
758             snprintf(msgBuffer, MAXRBUF, "%s: %s", ts, valuXMLAtt(message));
759         }
760 
761         parent->newUniversalMessage(msgBuffer);
762     }
763 
764     return (0);
765 }
766 
767 
findBLOBMode(const std::string & device,const std::string & property)768 BLOBMode *INDI::BaseClientPrivate::findBLOBMode(const std::string &device, const std::string &property)
769 {
770     for (auto &blob : blobModes)
771     {
772         if (blob.device == device && (property.empty() || blob.property == property))
773             return &blob;
774     }
775 
776     return nullptr;
777 }
778 
setDriverConnection(bool status,const char * deviceName)779 void BaseClientPrivate::setDriverConnection(bool status, const char *deviceName)
780 {
781     INDI::BaseDevice *drv = parent->getDevice(deviceName);
782 
783     if (!drv)
784     {
785         IDLog("INDI::BaseClient: Error. Unable to find driver %s\n", deviceName);
786         return;
787     }
788 
789     auto drv_connection = drv->getSwitch(INDI::SP::CONNECTION);
790 
791     if (!drv_connection)
792         return;
793 
794     // If we need to connect
795     if (status)
796     {
797         // If there is no need to do anything, i.e. already connected.
798         if (drv_connection->at(0)->getState() == ISS_ON)
799             return;
800 
801         drv_connection->reset();
802         drv_connection->setState(IPS_BUSY);
803         drv_connection->at(0)->setState(ISS_ON);
804         drv_connection->at(1)->setState(ISS_OFF);
805 
806         parent->sendNewSwitch(drv_connection);
807     }
808     else
809     {
810         // If there is no need to do anything, i.e. already disconnected.
811         if (drv_connection->at(1)->getState() == ISS_ON)
812             return;
813 
814         drv_connection->reset();
815         drv_connection->setState(IPS_BUSY);
816         drv_connection->at(0)->setState(ISS_OFF);
817         drv_connection->at(1)->setState(ISS_ON);
818 
819         parent->sendNewSwitch(drv_connection);
820     }
821 }
822 
823 }
824 
BaseClient()825 INDI::BaseClient::BaseClient()
826     : d_ptr(new BaseClientPrivate(this))
827 { }
828 
~BaseClient()829 INDI::BaseClient::~BaseClient()
830 {
831 
832 }
833 
setVerbose(bool enable)834 void INDI::BaseClient::setVerbose(bool enable)
835 {
836     D_PTR(BaseClient);
837     d->verbose = enable;
838 }
839 
isVerbose() const840 bool INDI::BaseClient::isVerbose() const
841 {
842     D_PTR(const BaseClient);
843     return d->verbose;
844 }
845 
setConnectionTimeout(uint32_t seconds,uint32_t microseconds)846 void INDI::BaseClient::setConnectionTimeout(uint32_t seconds, uint32_t microseconds)
847 {
848     D_PTR(BaseClient);
849     d->timeout_sec = seconds;
850     d->timeout_us  = microseconds;
851 }
852 
setServer(const char * hostname,unsigned int port)853 void INDI::BaseClient::setServer(const char *hostname, unsigned int port)
854 {
855     D_PTR(BaseClient);
856     d->cServer = hostname;
857     d->cPort   = port;
858 }
859 
watchDevice(const char * deviceName)860 void INDI::BaseClient::watchDevice(const char *deviceName)
861 {
862     D_PTR(BaseClient);
863     d->cDeviceNames.insert(deviceName);
864 }
865 
watchProperty(const char * deviceName,const char * propertyName)866 void INDI::BaseClient::watchProperty(const char *deviceName, const char *propertyName)
867 {
868     D_PTR(BaseClient);
869     watchDevice(deviceName);
870     d->cWatchProperties[deviceName].insert(propertyName);
871 }
872 
connectServer()873 bool INDI::BaseClient::connectServer()
874 {
875     D_PTR(BaseClient);
876     return d->connect();
877 }
878 
disconnectServer(int exit_code)879 bool INDI::BaseClient::disconnectServer(int exit_code)
880 {
881     D_PTR(BaseClient);
882     return d->disconnect(exit_code);
883 }
884 
885 // #PS: avoid calling pure virtual method
serverDisconnected(int exit_code)886 void INDI::BaseClient::serverDisconnected(int exit_code)
887 {
888     INDI_UNUSED(exit_code);
889 }
890 
isServerConnected() const891 bool INDI::BaseClient::isServerConnected() const
892 {
893     D_PTR(const BaseClient);
894     return d->sConnected;
895 }
896 
connectDevice(const char * deviceName)897 void INDI::BaseClient::connectDevice(const char *deviceName)
898 {
899     D_PTR(BaseClient);
900     d->setDriverConnection(true, deviceName);
901 }
902 
disconnectDevice(const char * deviceName)903 void INDI::BaseClient::disconnectDevice(const char *deviceName)
904 {
905     D_PTR(BaseClient);
906     d->setDriverConnection(false, deviceName);
907 }
908 
getDevice(const char * deviceName)909 INDI::BaseDevice *INDI::BaseClient::getDevice(const char *deviceName)
910 {
911     D_PTR(BaseClient);
912     for (auto &device : d->cDevices)
913     {
914         if (device->isDeviceNameMatch(deviceName))
915             return device;
916     }
917     return nullptr;
918 }
919 
getDevices() const920 const std::vector<INDI::BaseDevice *> &INDI::BaseClient::getDevices() const
921 {
922     D_PTR(const BaseClient);
923     return d->cDevices;
924 }
925 
getHost() const926 const char *INDI::BaseClient::getHost() const
927 {
928     D_PTR(const BaseClient);
929     return d->cServer.c_str();
930 }
931 
getPort() const932 int INDI::BaseClient::getPort() const
933 {
934     D_PTR(const BaseClient);
935     return d->cPort;
936 }
937 
newUniversalMessage(std::string message)938 void INDI::BaseClient::newUniversalMessage(std::string message)
939 {
940     IDLog("%s\n", message.c_str());
941 }
942 
sendNewText(ITextVectorProperty * tvp)943 void INDI::BaseClient::sendNewText(ITextVectorProperty *tvp)
944 {
945     D_PTR(BaseClient);
946     tvp->s = IPS_BUSY;
947     IUUserIONewText(&io, d, tvp);
948 }
949 
sendNewText(const char * deviceName,const char * propertyName,const char * elementName,const char * text)950 void INDI::BaseClient::sendNewText(const char *deviceName, const char *propertyName, const char *elementName,
951                                    const char *text)
952 {
953     INDI::BaseDevice *drv = getDevice(deviceName);
954 
955     if (!drv)
956         return;
957 
958     auto tvp = drv->getText(propertyName);
959 
960     if (!tvp)
961         return;
962 
963     auto tp = tvp->findWidgetByName(elementName);
964 
965     if (!tp)
966         return;
967 
968     tp->setText(text);
969 
970     sendNewText(tvp);
971 }
972 
sendNewNumber(INumberVectorProperty * nvp)973 void INDI::BaseClient::sendNewNumber(INumberVectorProperty *nvp)
974 {
975     D_PTR(BaseClient);
976     nvp->s = IPS_BUSY;
977     IUUserIONewNumber(&io, d, nvp);
978 }
979 
sendNewNumber(const char * deviceName,const char * propertyName,const char * elementName,double value)980 void INDI::BaseClient::sendNewNumber(const char *deviceName, const char *propertyName, const char *elementName,
981                                      double value)
982 {
983     INDI::BaseDevice *drv = getDevice(deviceName);
984 
985     if (!drv)
986         return;
987 
988     auto nvp = drv->getNumber(propertyName);
989 
990     if (!nvp)
991         return;
992 
993     auto np = nvp->findWidgetByName(elementName);
994 
995     if (!np)
996         return;
997 
998     np->setValue(value);
999 
1000     sendNewNumber(nvp);
1001 }
1002 
sendNewSwitch(ISwitchVectorProperty * svp)1003 void INDI::BaseClient::sendNewSwitch(ISwitchVectorProperty *svp)
1004 {
1005     D_PTR(BaseClient);
1006     svp->s = IPS_BUSY;
1007     IUUserIONewSwitch(&io, d, svp);
1008 }
1009 
sendNewSwitch(const char * deviceName,const char * propertyName,const char * elementName)1010 void INDI::BaseClient::sendNewSwitch(const char *deviceName, const char *propertyName, const char *elementName)
1011 {
1012     INDI::BaseDevice *drv = getDevice(deviceName);
1013 
1014     if (!drv)
1015         return;
1016 
1017     auto svp = drv->getSwitch(propertyName);
1018 
1019     if (!svp)
1020         return;
1021 
1022     auto sp = svp->findWidgetByName(elementName);
1023 
1024     if (!sp)
1025         return;
1026 
1027     sp->setState(ISS_ON);
1028 
1029     sendNewSwitch(svp);
1030 }
1031 
startBlob(const char * devName,const char * propName,const char * timestamp)1032 void INDI::BaseClient::startBlob(const char *devName, const char *propName, const char *timestamp)
1033 {
1034     D_PTR(BaseClient);
1035     IUUserIONewBLOBStart(&io, d, devName, propName, timestamp);
1036 }
1037 
sendOneBlob(IBLOB * bp)1038 void INDI::BaseClient::sendOneBlob(IBLOB *bp)
1039 {
1040     D_PTR(BaseClient);
1041     IUUserIOBLOBContextOne(
1042         &io, d,
1043         bp->name, bp->size, bp->bloblen, bp->blob, bp->format
1044     );
1045 }
1046 
sendOneBlob(const char * blobName,unsigned int blobSize,const char * blobFormat,void * blobBuffer)1047 void INDI::BaseClient::sendOneBlob(const char *blobName, unsigned int blobSize, const char *blobFormat,
1048                                    void *blobBuffer)
1049 {
1050     D_PTR(BaseClient);
1051     IUUserIOBLOBContextOne(
1052         &io, d,
1053         blobName, blobSize, blobSize, blobBuffer, blobFormat
1054     );
1055 }
1056 
finishBlob()1057 void INDI::BaseClient::finishBlob()
1058 {
1059     D_PTR(BaseClient);
1060     IUUserIONewBLOBFinish(&io, d);
1061 }
1062 
setBLOBMode(BLOBHandling blobH,const char * dev,const char * prop)1063 void INDI::BaseClient::setBLOBMode(BLOBHandling blobH, const char *dev, const char *prop)
1064 {
1065     D_PTR(BaseClient);
1066     if (!dev[0])
1067         return;
1068 
1069     BLOBMode *bMode = d->findBLOBMode(std::string(dev), (prop ? std::string(prop) : std::string()));
1070 
1071     if (bMode == nullptr)
1072     {
1073         BLOBMode newMode;
1074         newMode.device   = std::string(dev);
1075         newMode.property = (prop ? std::string(prop) : std::string());
1076         newMode.blobMode = blobH;
1077         d->blobModes.push_back(std::move(newMode));
1078     }
1079     else
1080     {
1081         // If nothing changed, nothing to to do
1082         if (bMode->blobMode == blobH)
1083             return;
1084 
1085         bMode->blobMode = blobH;
1086     }
1087 
1088     IUUserIOEnableBLOB(&io, d, dev, prop, blobH);
1089 }
1090 
getBLOBMode(const char * dev,const char * prop)1091 BLOBHandling INDI::BaseClient::getBLOBMode(const char *dev, const char *prop)
1092 {
1093     D_PTR(BaseClient);
1094     BLOBHandling bHandle = B_ALSO;
1095 
1096     BLOBMode *bMode = d->findBLOBMode(dev, (prop ? std::string(prop) : std::string()));
1097 
1098     if (bMode)
1099         bHandle = bMode->blobMode;
1100 
1101     return bHandle;
1102 }
1103 
getDevices(std::vector<INDI::BaseDevice * > & deviceList,uint16_t driverInterface)1104 bool INDI::BaseClient::getDevices(std::vector<INDI::BaseDevice *> &deviceList, uint16_t driverInterface )
1105 {
1106     D_PTR(BaseClient);
1107     for (INDI::BaseDevice *device : d->cDevices)
1108     {
1109         if (device->getDriverInterface() & driverInterface)
1110             deviceList.push_back(device);
1111     }
1112 
1113     return (deviceList.size() > 0);
1114 }
1115