Instrument Neutral Distributed Interface INDI  2.0.2
ConnectionMock.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2022 Ludovic Pollet. 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 <system_error>
20 #include <unistd.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 
28 #include "ConnectionMock.h"
29 #include "SharedBuffer.h"
30 #include "XmlAwaiter.h"
31 #include "utils.h"
32 
33 
35 {
36  fds[0] = -1;
37  fds[1] = -1;
38  bufferReceiveAllowed = false;
39 }
40 
42 {
43  release();
44 }
45 
46 void ConnectionMock::release()
47 {
48  for(auto i : receivedFds)
49  {
50  ::close(i);
51  }
52  receivedFds.clear();
53  bufferReceiveAllowed = false;
54  fds[0] = -1;
55  fds[1] = -1;
56 }
57 
58 
59 void ConnectionMock::setFds(int rd, int wr)
60 {
61  release();
62  fds[0] = rd;
63  fds[1] = wr;
64  bufferReceiveAllowed = false;
65 }
66 
68 {
69  if ((!state) && receivedFds.size())
70  {
71  throw std::runtime_error("More buffer were received");
72  }
73  bufferReceiveAllowed = state;
74 }
75 
76 ssize_t ConnectionMock::read(void * buffer, size_t len)
77 {
78  struct msghdr msgh;
79  struct iovec iov;
80 
81  union
82  {
83  struct cmsghdr cmsgh;
84  /* Space large enough to hold an 'int' */
85  char control[CMSG_SPACE(256 * sizeof(int))];
86  } control_un;
87 
88  iov.iov_base = buffer;
89  iov.iov_len = len;
90 
91  msgh.msg_name = NULL;
92  msgh.msg_namelen = 0;
93  msgh.msg_iov = &iov;
94  msgh.msg_iovlen = 1;
95  msgh.msg_flags = 0;
96  msgh.msg_control = control_un.control;
97  msgh.msg_controllen = sizeof(control_un.control);
98 
99  int recvflag;
100 #ifdef __linux__
101  recvflag = MSG_CMSG_CLOEXEC;
102 #else
103  recvflag = 0;
104 #endif
105 
106  auto size = recvmsg(fds[0], &msgh, recvflag);
107  if (size == -1)
108  {
109  return -1;
110  }
111 
112  for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg))
113  {
114  if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
115  {
116  int fdCount = 0;
117  while(cmsg->cmsg_len >= CMSG_LEN((fdCount + 1) * sizeof(int)))
118  {
119  fdCount++;
120  }
121  int * fds = (int*)CMSG_DATA(cmsg);
122  for(int i = 0; i < fdCount; ++i)
123  {
124  if (!bufferReceiveAllowed)
125  {
126  pendingData = std::string((char*)buffer, size);
127  throw std::runtime_error("Received unexpected buffer");
128  }
129 #ifndef __linux__
130  fcntl(fds[i], F_SETFD, FD_CLOEXEC);
131 #endif
132  receivedFds.push_back(fds[i]);
133  }
134  }
135  }
136  return size;
137 }
138 
140 {
141  if (receivedFds.empty())
142  {
143  throw std::runtime_error("Buffer not received");
144  }
145  int fd = receivedFds.front();
146  receivedFds.pop_front();
147  sb.attach(fd);
148 }
149 
150 void ConnectionMock::expect(const std::string &str)
151 {
152  ssize_t l = str.size();
153  char buff[l];
154 
155  char * in = buff;
156  ssize_t left = l;
157  while(left)
158  {
159  // FIXME: could interrupt sooner if input does not match
160  ssize_t rd = read(in, left);
161  if (rd == 0)
162  {
163  throw std::runtime_error("Input closed while expecting " + str);
164  }
165  if (rd == -1)
166  {
167  int e = errno;
168  throw std::system_error(e, std::generic_category(), "Read failed while expecting " + str);
169  }
170  left -= rd;
171  in += rd;
172  }
173  if (strncmp(str.c_str(), buff, l))
174  {
175  throw std::runtime_error("Received unexpected content while expecting " + str + ": " + std::string(buff, l));
176  }
177 }
178 
179 char ConnectionMock::readChar(const std::string &expected)
180 {
181  char buff[1];
182  ssize_t rd = read(buff, 1);
183  if (rd == 0)
184  {
185  throw std::runtime_error("Input closed while expecting " + expected);
186  }
187  if (rd == -1)
188  {
189  int e = errno;
190  throw std::system_error(e, std::generic_category(), "Read failed while expecting " + expected);
191  }
192  return buff[0];
193 }
194 
196 
197 static std::string parseXmlFragmentFromString(const std::string &str)
198 {
199  ssize_t pos = 0;
200  auto lambda = [&pos, &str]()->char
201  {
202  return str[pos++];
203  };
204  std::string result = parseXmlFragment(lambda);
205  while((unsigned)pos < str.length() && str[pos] == '\n') pos++;
206  if ((unsigned)pos != str.length())
207  {
208  throw std::runtime_error("Expected string contains garbage: " + str);
209  }
210  return result;
211 }
212 
213 
214 std::string ConnectionMock::receiveMore()
215 {
216 
217  auto currentFlag = fcntl(fds[0], F_GETFL, 0);
218  if (! (currentFlag & O_NONBLOCK))
219  {
220  fcntl(fds[0], F_SETFL, currentFlag | O_NONBLOCK);
221  }
222 
223  char buffer[256];
224  auto r = read(buffer, 256);
225  if (! (currentFlag & O_NONBLOCK))
226  {
227  auto errno_cpy = errno;
228  fcntl(fds[0], F_SETFL, currentFlag);
229  errno = errno_cpy;
230  }
231 
232  if (r != -1)
233  {
234  return pendingData + std::string(buffer, r);
235  }
236  perror("receiveMore");
237  return pendingData;
238 }
239 
240 
241 void ConnectionMock::expectXml(const std::string &expected)
242 {
243  std::string expectedCanonical = parseXmlFragmentFromString(expected);
244 
245  std::string received;
246  auto readchar = [this, expected, &received]()->char
247  {
248  char c = readChar(expected);
249  received += c;
250  return c;
251  };
252  try
253  {
254  auto fragment = parseXmlFragment(readchar);
255  if (fragment != expectedCanonical)
256  {
257  fprintf(stderr, "canonicalized as %s\n", fragment.c_str());
258  throw std::runtime_error("xml fragment does not match");
259  }
260  }
261  catch(std::runtime_error &e)
262  {
263  received += receiveMore();
264  throw std::runtime_error(std::string(e.what()) + "\nexpected: " + expected + "\nReceived: " + received);
265  }
266 }
267 
268 void ConnectionMock::send(const std::string &str)
269 {
270  ssize_t l = str.size();
271  ssize_t wr = write(fds[1], str.c_str(), l);
272  if (wr == -1)
273  {
274  int e = errno;
275  throw std::system_error(e, std::generic_category(), "Write failed while sending " + str);
276  }
277  if (wr < l)
278  {
279  throw std::runtime_error("Input closed while sending " + str);
280  }
281 }
282 
283 void ConnectionMock::shutdown(bool rd, bool wr)
284 {
285  if (fds[0] != fds[1]) {
286  if (rd && fds[0] != -1) {
287  if (::shutdown(fds[0], SHUT_RD) == -1) {
288  perror("shutdown read fd");
289  }
290  }
291  if (wr && fds[1] != -1) {
292  if (::shutdown(fds[1], SHUT_WR) == -1) {
293  perror("shutdown write fd");
294  }
295  }
296  } else {
297  if (!(rd || wr)) return;
298 
299  int how = (rd && wr) ? SHUT_RDWR : rd ? SHUT_RD : SHUT_WR;
300  if (::shutdown(fds[0], how) == -1) {
301  if (rd && wr) {
302  perror("shutdown rdwr");
303  } else if (rd) {
304  perror("shutdown rd");
305  } else {
306  perror("shutdown wr");
307  }
308  }
309  }
310 }
311 
312 void ConnectionMock::send(const std::string &str, const SharedBuffer ** buffers)
313 {
314  int fdCount = 0;
315  while(buffers[fdCount]) fdCount++;
316 
317  if (fdCount == 0)
318  {
319  send(str);
320  return;
321  }
322 
323  if (str.size() == 0)
324  {
325  throw std::runtime_error("Can't attach buffer to empty message");
326  }
327 
328  struct msghdr msgh;
329  struct iovec iov[2];
330  int cmsghdrlength;
331  struct cmsghdr * cmsgh;
332 
333 
334  cmsghdrlength = CMSG_SPACE((fdCount * sizeof(int)));
335  cmsgh = (struct cmsghdr*)malloc(cmsghdrlength);
336  memset(cmsgh, 0, cmsghdrlength);
337 
338  /* Write the fd as ancillary data */
339  cmsgh->cmsg_len = CMSG_LEN(sizeof(int));
340  cmsgh->cmsg_level = SOL_SOCKET;
341  cmsgh->cmsg_type = SCM_RIGHTS;
342  msgh.msg_control = cmsgh;
343  msgh.msg_controllen = cmsghdrlength;
344  for(int i = 0; i < fdCount; ++i)
345  {
346  int fd = buffers[i]->getFd();
347  ((int *) CMSG_DATA(CMSG_FIRSTHDR(&msgh)))[i] = fd;
348  }
349 
350  iov[0].iov_base = (void*)str.data();
351  iov[0].iov_len = str.size();
352 
353  msgh.msg_flags = 0;
354  msgh.msg_name = NULL;
355  msgh.msg_namelen = 0;
356  msgh.msg_iov = iov;
357  msgh.msg_iovlen = 1;
358 
359 
360  int ret = sendmsg(fds[1], &msgh, 0);
361  if (ret == -1)
362  {
363  int e = errno;
364  throw std::system_error(e, std::generic_category(), "Write with buffer failed for " + str);
365  }
366 
367  if ((unsigned)ret < str.size())
368  {
369  throw std::runtime_error("Input closed while buffer sending " + str);
370  }
371 }
372 
373 void ConnectionMock::send(const std::string &str, const SharedBuffer &buffer)
374 {
375  const SharedBuffer * bufferPtrs[2];
376  bufferPtrs[0] = &buffer;
377  bufferPtrs[1] = nullptr;
378  send(str, bufferPtrs);
379 }
XmlStatus
@ ATTRIB
@ WAIT_ATTRIB
@ PRE
@ WAIT_CLOSE
@ QUOTE
@ TAGNAME
std::string parseXmlFragmentFromString(const std::string &str)
std::string parseXmlFragment(std::function< char()> readchar)
Definition: XmlAwaiter.cpp:24
void allowBufferReceive(bool state)
void expectBuffer(SharedBuffer &fd)
void shutdown(bool rd, bool wr)
void expect(const std::string &content)
void send(const std::string &content)
void setFds(int rd, int wr)
void expectXml(const std::string &xml)
int getFd() const
void attach(int fd)
int errno
int fd
Definition: intelliscope.c:43
std::vector< uint8_t > buffer