Instrument Neutral Distributed Interface INDI  2.0.2
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"
20 #include "abstractbaseclient_p.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 
40 namespace INDI
41 {
42 
43 //ClientSharedBlobs
44 #ifdef ENABLE_INDI_SHARED_MEMORY
45 ClientSharedBlobs::Blobs::~Blobs()
46 {
47  releaseBlobUids(*this);
48 }
49 
50 void 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 
67 void ClientSharedBlobs::disableDirectBlobAccess()
68 {
69  directBlobAccess.clear();
70 }
71 
72 bool 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 
114 bool 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 
125 bool 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 
132 void ClientSharedBlobs::addIncomingSharedBuffer(int fd)
133 {
134  incomingSharedBuffers.push_back(fd);
135 }
136 
137 void ClientSharedBlobs::clear()
138 {
139  for (int fd : incomingSharedBuffers)
140  {
141  ::close(fd);
142  }
143  incomingSharedBuffers.clear();
144 }
145 
146 void 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 
220  : AbstractBaseClientPrivate(parent)
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  // Silenty 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 
278 BaseClientPrivate::~BaseClientPrivate()
279 { }
280 
281 ssize_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 
288 BaseClient::BaseClient()
290 { }
291 
293 {
294  D_PTR(BaseClient);
295  d->clear();
296 }
297 
298 bool 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 #ifndef _WINDOWS
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 
336  serverConnected();
337 
338  d->userIoGetProperties();
339 
340  return true;
341 }
342 
343 bool BaseClient::disconnectServer(int exit_code)
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 
360 void 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 MAXFD_PER_MESSAGE
Definition: baseclient.cpp:27
#define MAXINDIBUF
Definition: baseclient.cpp:25
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)
Definition: baseclient.cpp:298
BaseClientPrivate(BaseClient *parent)
Definition: baseclient.cpp:219
LilXmlParser xmlParser
Definition: baseclient_p.h:69
Class to provide basic client functionality.
Definition: baseclient.h:52
virtual ~BaseClient()
Definition: baseclient.cpp:292
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...
Definition: baseclient.cpp:360
bool disconnectServer(int exit_code=0) override
Disconnect from INDI server. Any devices previously created will be deleted and memory cleared.
Definition: baseclient.cpp:343
bool connectServer() override
Connect to INDI server.
Definition: baseclient.cpp:308
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)
Definition: tcpsocket.cpp:438
void connectToHost(const std::string &hostName, uint16_t port)
Definition: tcpsocket.cpp:342
@ ConnectionRefusedError
Definition: tcpsocket.h:32
bool waitForConnected(int timeout=2000) const
Definition: tcpsocket.cpp:448
@ INDI_PROPERTY_DUPLICATED
Definition: indibasetypes.h:65
void IDLog(const char *fmt,...)
Definition: indicom.c:316
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
#define MAXRBUF
Definition: indiserver.cpp:102
int fd
Definition: intelliscope.c:43
std::vector< uint8_t > buffer
Namespace to encapsulate INDI client, drivers, and mediator classes.
void releaseBlobUids(const std::vector< std::string > &blobs)
std::string allocateBlobUid(int fd)
Definition: json.h:4973