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