GCC Code Coverage Report


Directory: ./
File: src/PosixProcess.cpp
Date: 2024-11-05 15:23:15
Exec Total Coverage
Lines: 118 126 93.7%
Branches: 59 103 57.3%

Line Branch Exec Source
1 /*****************************************************************************
2 * vim:tw=78
3 *
4 * Copyright (C) 2021 Bjarne von Horn (vh at igh dot de).
5 *
6 * This file is part of the PdCom library.
7 *
8 * The PdCom library is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * The PdCom library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
16 * License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with the PdCom library. If not, see <http://www.gnu.org/licenses/>.
20 *
21 *****************************************************************************/
22
23 #include <algorithm>
24 #include <arpa/inet.h>
25 #include <array>
26 #include <cstring>
27 #include <errno.h>
28 #include <memory>
29 #include <netdb.h>
30 #include <netinet/in.h>
31 #include <pdcom5/Exception.h>
32 #include <pdcom5/PosixProcess.h>
33 #include <sys/select.h>
34 #include <sys/socket.h>
35 #include <sys/types.h>
36 #include <unistd.h>
37
38 using PdCom::PosixProcess;
39
40 namespace {
41 template <typename T>
42 7 bool do_connect(int &fd, const T &addr, int af, socklen_t len = sizeof(T))
43 {
44 static_assert(!std::is_pointer<T>::value, "addr must be by reference");
45 7 fd = socket(af, SOCK_STREAM, 0);
46
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
7 if (fd == -1)
47 return false;
48
4/6
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1 times.
7 if (::connect(fd, reinterpret_cast<const sockaddr *>(&addr), len) >= 0)
49 2 return true;
50 5 ::close(fd);
51 5 fd = -1;
52 5 return false;
53 }
54
55 1882 void writeToSocket(
56 const char *begin,
57 const char *const end,
58 const int fd,
59 const timeval &ts)
60 {
61 using PdCom::WriteFailure;
62
2/2
✓ Branch 0 taken 1877 times.
✓ Branch 1 taken 5 times.
3757 while (begin != end) {
63 1877 const auto res = ::write(fd, begin, end - begin);
64
2/2
✓ Branch 0 taken 940 times.
✓ Branch 1 taken 937 times.
1877 if (res > 0) {
65 940 begin += res;
66 }
67
1/2
✓ Branch 0 taken 937 times.
✗ Branch 1 not taken.
937 else if (res <= 0) {
68
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 936 times.
937 if (errno != EAGAIN)
69
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 throw WriteFailure(errno);
70
71
2/4
✓ Branch 0 taken 936 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 936 times.
936 if (fd < 0 || fd >= FD_SETSIZE)
72 throw WriteFailure("fd >= FD_SETSIZE");
73 936 timeval timeout = ts;
74 936 fd_set fds;
75 936 FD_ZERO(&fds);
76
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 936 times.
936 FD_SET(fd, &fds);
77
3/5
✓ Branch 1 taken 936 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 935 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
936 switch (::select(fd + 1, nullptr, &fds, nullptr, &timeout)) {
78 935 case 1:
79 935 break;
80 1 case 0:
81
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 throw WriteFailure(ETIMEDOUT);
82 default:
83 throw WriteFailure(errno);
84 }
85 }
86 }
87 5 }
88 } // namespace
89
90 9 class PDCOM5_LOCAL PdCom::PosixProcess::Impl
91 {
92 public:
93 9 class PDCOM5_LOCAL Buffer
94 {
95 std::array<char, 1024> buffer_ = {};
96
97 size_t size_ = 0;
98
99 public:
100 ::timeval timeout = {1, 0};
101
102 2 size_t size() const { return size_; }
103 2 size_t capacity() const { return buffer_.size(); }
104
105 2 bool append(const char *buf, size_t count)
106 {
107
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
2 if (count > (capacity() - size()))
108 1 return false;
109 1 std::copy(buf, buf + count, buffer_.data() + size_);
110 1 size_ += count;
111 1 return true;
112 }
113
114 2 void flush(const int fd)
115 {
116 2 writeToSocket(buffer_.data(), buffer_.data() + size_, fd, timeout);
117 2 size_ = 0;
118 2 }
119
120 4 void clear() { size_ = 0; }
121 } buffer_;
122 };
123
124 4 PosixProcess::PosixProcess(const char *host, unsigned short port) :
125 6 fd_(-1), impl_(std::make_shared<Impl>())
126 {
127
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
4 reconnect(host, port);
128 2 }
129
130 4 void PosixProcess::reconnect(const char *host, unsigned short port)
131 {
132 8 const auto connect_v4 = [&]() {
133 4 sockaddr_in server_addr;
134 4 ::memset(&server_addr, 0, sizeof(server_addr));
135 4 server_addr.sin_family = AF_INET;
136 4 server_addr.sin_port = htons(port);
137
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
4 if (inet_pton(AF_INET, host, &server_addr.sin_addr) <= 0)
138 2 return false;
139
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 return do_connect(fd_, server_addr, AF_INET);
140 4 };
141
142 7 const auto connect_v6 = [&]() {
143 3 sockaddr_in6 server_addr;
144 3 ::memset(&server_addr, 0, sizeof(server_addr));
145 3 server_addr.sin6_family = AF_INET6;
146 3 server_addr.sin6_port = htons(port);
147
1/2
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 if (inet_pton(AF_INET6, host, &server_addr.sin6_addr) <= 0)
148 3 return false;
149 return do_connect(fd_, server_addr, AF_INET6);
150 4 };
151
152 7 const auto connect_dns = [&]() {
153
1/2
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 const auto port_s = std::to_string(port);
154 3 const ::addrinfo hints {
155 AI_NUMERICSERV | AI_V4MAPPED | AI_ADDRCONFIG,
156 AF_UNSPEC,
157 SOCK_STREAM,
158 0,
159 0,
160 nullptr,
161 nullptr,
162 nullptr};
163 struct MyAddrInfo
164 {
165 ::addrinfo *res = nullptr;
166 3 ~MyAddrInfo()
167 3 {
168
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if (res)
169 3 freeaddrinfo(res);
170 3 }
171 6 } res;
172
2/4
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
3 if (::getaddrinfo(host, port_s.c_str(), &hints, &res.res) != 0)
173 return false;
174 3 ::addrinfo *i = res.res;
175
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
11 while (i) {
176
3/4
✓ Branch 7 taken 5 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 4 times.
5 if (do_connect(fd_, *(i->ai_addr), i->ai_family, i->ai_addrlen))
177 1 return true;
178 4 i = i->ai_next;
179 }
180 2 return false;
181 4 };
182
183
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if (fd_ != -1)
184 close(fd_);
185 4 impl_->buffer_.clear();
186
10/14
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 times.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 3 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 2 times.
✓ Branch 14 taken 1 times.
✓ Branch 15 taken 2 times.
✓ Branch 16 taken 2 times.
4 if (!connect_v4() and !connect_v6() and !connect_dns()) {
187
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 throw ConnectionFailed();
188 }
189 2 }
190
191 5 PosixProcess::PosixProcess(int fd) : fd_(fd), impl_(std::make_shared<Impl>())
192 5 {}
193
194 14 PosixProcess::~PosixProcess()
195 {
196
1/2
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
7 if (fd_ >= 0)
197 7 close(fd_);
198 7 }
199
200
201 1 void PosixProcess::posixFlush()
202 {
203 1 impl_->buffer_.flush(fd_);
204 1 }
205
206 2 void PosixProcess::posixWriteBuffered(const char *buf, size_t count)
207 {
208
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
2 if (!impl_->buffer_.append(buf, count)) {
209 1 impl_->buffer_.flush(fd_);
210 1 posixWriteDirect(buf, count);
211 }
212 2 }
213
214 5 void PosixProcess::posixWriteDirect(const char *buf, size_t count)
215 {
216 5 writeToSocket(buf, buf + count, fd_, impl_->buffer_.timeout);
217 3 }
218
219 5 int PosixProcess::posixRead(char *buf, int count)
220 {
221 5 const auto res = ::read(fd_, buf, count);
222
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (res < 0) {
223
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (errno != EAGAIN && errno != EINTR)
224 throw ReadFailure(errno);
225 1 int e = errno;
226 1 errno = 0;
227 1 return -e;
228 }
229 4 return res;
230 }
231
232 2 void PosixProcess::setWriteTimeout(std::chrono::milliseconds ms)
233 {
234
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ms);
235 2 const auto us =
236
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 std::chrono::duration_cast<std::chrono::microseconds>(ms - seconds);
237
238 2 impl_->buffer_.timeout.tv_sec = seconds.count();
239 2 impl_->buffer_.timeout.tv_usec = us.count();
240 2 }
241