Instrument Neutral Distributed Interface INDI 2.2.5
baseclient.cpp
Go to the documentation of this file.
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#include "abstractbaseclient.h"
21
22#include "baseclient.h"
23#include "baseclient_p.h"
24
25#define MAXINDIBUF 49152
26#define DISCONNECTION_DELAY_US 500000
27#define MAXFD_PER_MESSAGE 16 /* No more than 16 buffer attached to a message */
28
29#ifdef ENABLE_INDI_SHARED_MEMORY
30# include "sharedblob_parse.h"
31# include <sys/socket.h>
32# include <sys/un.h>
33# include <unistd.h>
34#endif
35
36#ifndef __linux__
37# include <fcntl.h>
38#endif
39
40namespace INDI
41{
42
43//ClientSharedBlobs
44#ifdef ENABLE_INDI_SHARED_MEMORY
45ClientSharedBlobs::Blobs::~Blobs()
46{
47 releaseBlobUids(*this);
48}
49
50void ClientSharedBlobs::enableDirectBlobAccess(const char * dev, const char * prop)
51{
52 if (dev == nullptr || !dev[0])
53 {
54 directBlobAccess[""].insert("");
55 return;
56 }
57 if (prop == nullptr || !prop[0])
58 {
59 directBlobAccess[dev].insert("");
60 }
61 else
62 {
63 directBlobAccess[dev].insert(prop);
64 }
65}
66
67void ClientSharedBlobs::disableDirectBlobAccess()
68{
69 directBlobAccess.clear();
70}
71
72bool ClientSharedBlobs::parseAttachedBlobs(const INDI::LilXmlElement &root, ClientSharedBlobs::Blobs &blobs)
73{
74 // parse all elements in root that are attached.
75 // Create for each a new GUID and associate it in a global map
76 // modify the xml to add an attribute with the guid
77 for (auto &blobContent : root.getElementsByTagName("oneBLOB"))
78 {
79 auto attached = blobContent.getAttribute("attached");
80
81 if (attached.toString() != "true")
82 continue;
83
84 auto device = root.getAttribute("dev");
85 auto name = root.getAttribute("name");
86
87 blobContent.removeAttribute("attached");
88 blobContent.removeAttribute("enclen");
89
90 if (incomingSharedBuffers.empty())
91 {
92 return false;
93 }
94
95 int fd = *incomingSharedBuffers.begin();
96 incomingSharedBuffers.pop_front();
97
98 auto id = allocateBlobUid(fd);
99 blobs.push_back(id);
100
101 // Put something here for later replacement
102 blobContent.removeAttribute("attached-data-id");
103 blobContent.removeAttribute("attachment-direct");
104 blobContent.addAttribute("attached-data-id", id.c_str());
105 if (isDirectBlobAccess(device.toString(), name.toString()))
106 {
107 // If client support read-only shared blob, mark it here
108 blobContent.addAttribute("attachment-direct", "true");
109 }
110 }
111 return true;
112}
113
114bool ClientSharedBlobs::hasDirectBlobAccessEntry(const std::map<std::string, std::set<std::string>> &directBlobAccess,
115 const std::string &dev, const std::string &prop)
116{
117 auto devAccess = directBlobAccess.find(dev);
118 if (devAccess == directBlobAccess.end())
119 {
120 return false;
121 }
122 return devAccess->second.find(prop) != devAccess->second.end();
123}
124
125bool ClientSharedBlobs::isDirectBlobAccess(const std::string &dev, const std::string &prop) const
126{
127 return hasDirectBlobAccessEntry(directBlobAccess, "", "")
128 || hasDirectBlobAccessEntry(directBlobAccess, dev, "")
129 || hasDirectBlobAccessEntry(directBlobAccess, dev, prop);
130}
131
132void ClientSharedBlobs::addIncomingSharedBuffer(int fd)
133{
134 incomingSharedBuffers.push_back(fd);
135}
136
137void ClientSharedBlobs::clear()
138{
139 for (int fd : incomingSharedBuffers)
140 {
141 ::close(fd);
142 }
143 incomingSharedBuffers.clear();
144}
145
146void TcpSocketSharedBlobs::readyRead()
147{
148 char buffer[MAXINDIBUF];
149
150 struct msghdr msgh;
151 struct iovec iov;
152
153 int recvflag = MSG_DONTWAIT;
154#ifdef __linux__
155 recvflag |= MSG_CMSG_CLOEXEC;
156#endif
157
158 union
159 {
160 struct cmsghdr cmsgh;
161 /* Space large enough to hold an 'int' */
162 char control[CMSG_SPACE(MAXFD_PER_MESSAGE * sizeof(int))];
163 } control_un;
164
165 iov.iov_base = buffer;
166 iov.iov_len = MAXINDIBUF;
167
168 msgh.msg_name = NULL;
169 msgh.msg_namelen = 0;
170 msgh.msg_iov = &iov;
171 msgh.msg_iovlen = 1;
172 msgh.msg_flags = 0;
173 msgh.msg_control = control_un.control;
174 msgh.msg_controllen = sizeof(control_un.control);
175
176 int n = recvmsg(reinterpret_cast<ptrdiff_t>(socketDescriptor()), &msgh, recvflag);
177
178 if (n >= 0)
179 {
180 for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msgh); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msgh, cmsg))
181 {
182 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
183 {
184 int fdCount = 0;
185 while(cmsg->cmsg_len >= CMSG_LEN((fdCount + 1) * sizeof(int)))
186 {
187 fdCount++;
188 }
189 //IDLog("Received %d fds\n", fdCount);
190 int * fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
191 for(int i = 0; i < fdCount; ++i)
192 {
193 int fd = fds[i];
194 //IDLog("Received fd %d\n", fd);
195#ifndef __linux__
196 fcntl(fds[i], F_SETFD, FD_CLOEXEC);
197#endif
198 sharedBlobs.addIncomingSharedBuffer(fd);
199 }
200 }
201 else
202 {
203 IDLog("Ignoring ancillary data level %d, type %d\n", cmsg->cmsg_level, cmsg->cmsg_type);
204 }
205 }
206 }
207
208 if (n <= 0)
209 {
210 setSocketError(TcpSocket::ConnectionRefusedError);
211 return;
212 }
213
214 emitData(buffer, n);
215}
216#endif
217// BaseClientPrivate
218
221{
222 clientSocket.onData([this](const char *data, size_t size)
223 {
224 char msg[MAXRBUF];
225 auto documents = xmlParser.parseChunk(data, size);
226
227 if (documents.size() == 0)
228 {
229 if (xmlParser.hasErrorMessage())
230 {
231 IDLog("Bad XML from %s/%d: %s\n%.*s\n", cServer.c_str(), cPort, xmlParser.errorMessage(), int(size), data);
232 }
233 return;
234 }
235
236 for (const auto &doc : documents)
237 {
238 LilXmlElement root = doc.root();
239
240 if (verbose)
241 root.print(stderr, 0);
242
243#ifdef ENABLE_INDI_SHARED_MEMORY
244 ClientSharedBlobs::Blobs blobs;
245
246 if (!clientSocket.sharedBlobs.parseAttachedBlobs(root, blobs))
247 {
248 IDLog("Missing attachment from %s/%d\n", cServer.c_str(), cPort);
249 return;
250 }
251#endif
252
253 int err_code = dispatchCommand(root, msg);
254
255 if (err_code < 0)
256 {
257 // Silently ignore property duplication errors
258 if (err_code != INDI_PROPERTY_DUPLICATED)
259 {
260 IDLog("Dispatch command error(%d): %s\n", err_code, msg);
261 root.print(stderr, 0);
262 }
263 }
264 }
265 });
266
267 clientSocket.onErrorOccurred([this] (TcpSocket::SocketError)
268 {
269 if (sConnected == false)
270 return;
271
272 this->parent->serverDisconnected(-1);
273 clear();
274 watchDevice.unwatchDevices();
275 });
276}
277
278BaseClientPrivate::~BaseClientPrivate()
279{ }
280
281ssize_t BaseClientPrivate::sendData(const void *data, size_t size)
282{
283 return clientSocket.write(static_cast<const char *>(data), size);
284}
285
286// BaseClient
287
288BaseClient::BaseClient()
290{ }
291
293{
294 D_PTR(BaseClient);
295 d->clear();
296}
297
298bool BaseClientPrivate::connectToHostAndWait(std::string hostname, unsigned short port)
299{
300 if (hostname == "localhost:")
301 {
302 hostname = "localhost:/tmp/indiserver";
303 }
304 clientSocket.connectToHost(hostname, port);
305 return clientSocket.waitForConnected(timeout_sec * 1000 + timeout_us / 1000);
306}
307
309{
310 D_PTR(BaseClient);
311
312 if (d->sConnected == true)
313 {
314 IDLog("INDI::BaseClient::connectServer: Already connected.\n");
315 return false;
316 }
317
318 IDLog("INDI::BaseClient::connectServer: creating new connection...\n");
319
320#if !defined (_WIN32)
321 // System with unix support automatically connect over unix domain
322 if (d->cServer != "localhost" || d->cServer != "127.0.0.1" || d->connectToHostAndWait("localhost:", d->cPort) == false)
323#endif
324 {
325 if (d->connectToHostAndWait(d->cServer, d->cPort) == false)
326 {
327 d->sConnected = false;
328 return false;
329 }
330 }
331
332 d->clear();
333
334 d->sConnected = true;
335
337
338 d->userIoGetProperties();
339
340 return true;
341}
342
344{
345 D_PTR(BaseClient);
346
347 if (d->sConnected.exchange(false) == false)
348 {
349 IDLog("INDI::BaseClient::disconnectServer: Already disconnected.\n");
350 return false;
351 }
352
353 d->clientSocket.disconnectFromHost();
354 bool ret = d->clientSocket.waitForDisconnected();
355 // same behavior as in `BaseClientQt::disconnectServer`
356 serverDisconnected(exit_code);
357 return ret;
358}
359
360void BaseClient::enableDirectBlobAccess(const char * dev, const char * prop)
361{
362#ifdef ENABLE_INDI_SHARED_MEMORY
363 D_PTR(BaseClient);
364 d->clientSocket.sharedBlobs.enableDirectBlobAccess(dev, prop);
365#else
366 INDI_UNUSED(dev);
367 INDI_UNUSED(prop);
368#endif
369}
370
371}
hid_device * device
#define MAXINDIBUF
int dispatchCommand(const INDI::LilXmlElement &root, char *errmsg)
Dispatch command received from INDI server to respective devices handled by the client.
bool connectToHostAndWait(std::string hostname, unsigned short port)
BaseClientPrivate(BaseClient *parent)
Class to provide basic client functionality.
Definition baseclient.h:52
virtual ~BaseClient()
void enableDirectBlobAccess(const char *dev=nullptr, const char *prop=nullptr)
activate zero-copy delivering of the blob content. When enabled, all blob copy will be avoided when p...
bool disconnectServer(int exit_code=0) override
Disconnect from INDI server. Any devices previously created will be deleted and memory cleared.
bool connectServer() override
Connect to INDI server.
virtual void serverDisconnected(int exit_code)
Emmited when the server gets disconnected.
Definition indibase.cpp:36
virtual void serverConnected()
Emmited when the server is connected.
Definition indibase.cpp:33
Elements getElementsByTagName(const char *tagName) const
Definition indililxml.h:388
LilXmlAttribute getAttribute(const char *name) const
Definition indililxml.h:405
void print(FILE *f, int level=0) const
Definition indililxml.h:430
std::list< LilXmlDocument > parseChunk(const char *data, size_t size)
Definition indililxml.h:485
void onData(const std::function< void(const char *, size_t)> &callback)
void connectToHost(const std::string &hostName, uint16_t port)
@ ConnectionRefusedError
Definition tcpsocket.h:33
bool waitForConnected(int timeout=2000) const
@ INDI_PROPERTY_DUPLICATED
void IDLog(const char *fmt,...)
Definition indicom.c:305
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define MAXRBUF
Definition indidriver.c:52
#define MAXFD_PER_MESSAGE
int fd
Namespace to encapsulate INDI client, drivers, and mediator classes.
void releaseBlobUids(const std::vector< std::string > &blobs)
std::string allocateBlobUid(int fd)