1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
|
// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/io_service.h>
#include <asiolink/unix_domain_socket.h>
#include <asiolink/testutils/test_server_unix_socket.h>
#include <gtest/gtest.h>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <string>
using namespace isc::asiolink;
namespace {
/// @brief Test unix socket file name.
const std::string TEST_SOCKET = "test-socket";
/// @brief Test timeout in ms.
const long TEST_TIMEOUT = 10000;
/// @brief Test fixture class for @ref UnixDomainSocket class.
class UnixDomainSocketTest : public ::testing::Test {
public:
/// @brief Constructor.
///
/// Removes unix socket descriptor before the test.
UnixDomainSocketTest() :
io_service_(),
test_socket_(new test::TestServerUnixSocket(io_service_,
unixSocketFilePath())),
response_(),
read_buf_() {
test_socket_->startTimer(TEST_TIMEOUT);
removeUnixSocketFile();
}
/// @brief Destructor.
///
/// Removes unix socket descriptor after the test.
virtual ~UnixDomainSocketTest() {
removeUnixSocketFile();
}
/// @brief Returns socket file path.
///
/// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
/// socket file is created in the location pointed to by this variable.
/// Otherwise, it is created in the build directory.
static std::string unixSocketFilePath() {
std::ostringstream s;
const char* env = getenv("KEA_SOCKET_TEST_DIR");
if (env) {
s << std::string(env);
} else {
s << TEST_DATA_BUILDDIR;
}
s << "/" << TEST_SOCKET;
return (s.str());
}
/// @brief Removes unix socket descriptor.
void removeUnixSocketFile() {
static_cast<void>(remove(unixSocketFilePath().c_str()));
}
/// @brief Performs asynchronous receive on unix domain socket.
///
/// This function performs partial read from the unix domain socket.
/// It uses @c read_buf_ or small size to ensure that the buffer fills
/// in before all that have been read. The partial responses are
/// appended to the @c response_ class member.
///
/// If the response received so far is shorter than the expected
/// response, another partial read is scheduled.
///
/// @param socket Reference to the unix domain socket.
/// @param expected_response Expected response.
void doReceive(UnixDomainSocket& socket,
const std::string& expected_response) {
socket.asyncReceive(&read_buf_[0], read_buf_.size(),
[this, &socket, expected_response]
(const boost::system::error_code& ec, size_t length) {
if (!ec) {
// Append partial response received and see if the
// size of the response received so far is still
// smaller than expected. If it is, schedule another
// partial read.
response_.append(&read_buf_[0], length);
if (expected_response.size() > response_.size()) {
doReceive(socket, expected_response);
}
} else if (ec.value() != boost::asio::error::operation_aborted) {
ADD_FAILURE() << "error occurred while asynchronously receiving"
" data via unix domain socket: " << ec.message();
}
});
}
/// @brief IO service used by the tests.
IOService io_service_;
/// @brief Server side unix socket used in these tests.
test::TestServerUnixSocketPtr test_socket_;
/// @brief String containing a response received with @c doReceive.
std::string response_;
/// @brief Read buffer used by @c doReceive.
std::array<char, 2> read_buf_;
};
// This test verifies that the client can send data over the unix
// domain socket and receive a response.
TEST_F(UnixDomainSocketTest, sendReceive) {
// Start the server.
test_socket_->bindServerSocket();
// Setup client side.
UnixDomainSocket socket(io_service_);
ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
// Send "foo".
const std::string outbound_data = "foo";
size_t sent_size = 0;
ASSERT_NO_THROW(sent_size = socket.write(outbound_data.c_str(),
outbound_data.size()));
// Make sure all data have been sent.
ASSERT_EQ(outbound_data.size(), sent_size);
// Run IO service to generate server's response.
while ((test_socket_->getResponseNum() < 1) &&
(!test_socket_->isStopped())) {
io_service_.run_one();
}
// Receive response from the socket.
std::array<char, 1024> read_buf;
size_t bytes_read = 0;
ASSERT_NO_THROW(bytes_read = socket.receive(&read_buf[0], read_buf.size()));
std::string response(&read_buf[0], bytes_read);
// The server should prepend "received" to the data we had sent.
EXPECT_EQ("received foo", response);
}
// This test verifies that the client can send the data over the unix
// domain socket and receive a response asynchronously.
TEST_F(UnixDomainSocketTest, asyncSendReceive) {
// Start the server.
test_socket_->bindServerSocket();
// Setup client side.
UnixDomainSocket socket(io_service_);
// We're going to asynchronously connect to the server. The boolean value
// below will be modified by the connect handler function (lambda) invoked
// when the connection is established or if an error occurs.
bool connect_handler_invoked = false;
ASSERT_NO_THROW(socket.asyncConnect(unixSocketFilePath(),
[this, &connect_handler_invoked](const boost::system::error_code& ec) {
// Indicate that the handler has been called so as the loop below gets
// interrupted.
connect_handler_invoked = true;
// Operation aborted indicates that IO service has been stopped. This
// shouldn't happen here.
if (ec && (ec.value() != boost::asio::error::operation_aborted)) {
ADD_FAILURE() << "error occurred while asynchronously connecting"
" via unix domain socket: " << ec.message();
}
}
));
// Run IO service until connect handler is invoked.
while (!connect_handler_invoked && (!test_socket_->isStopped())) {
io_service_.run_one();
}
// We are going to asynchronously send the 'foo' over the unix socket.
const std::string outbound_data = "foo";
size_t sent_size = 0;
ASSERT_NO_THROW(socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
[this, &sent_size](const boost::system::error_code& ec, size_t length) {
// If we have been successful sending the data, record the number of
// bytes we have sent.
if (!ec) {
sent_size = length;
} else if (ec.value() != boost::asio::error::operation_aborted) {
ADD_FAILURE() << "error occurred while asynchronously sending the"
" data over unix domain socket: " << ec.message();
}
}
));
// Run IO service to generate server's response.
while ((test_socket_->getResponseNum() < 1) &&
(!test_socket_->isStopped())) {
io_service_.run_one();
}
// There is no guarantee that all data have been sent so we only check that
// some data have been sent.
ASSERT_GT(sent_size, 0);
std::string expected_response = "received foo";
doReceive(socket, expected_response);
// Run IO service until we get the full response from the server.
while ((response_.size() < expected_response.size()) &&
!test_socket_->isStopped()) {
io_service_.run_one();
}
// Check that the entire response has been received and is correct.
EXPECT_EQ(expected_response, response_);
}
// This test verifies that UnixDomainSocketError exception is thrown
// on attempt to connect, write or receive when the server socket
// is not available.
TEST_F(UnixDomainSocketTest, clientErrors) {
UnixDomainSocket socket(io_service_);
ASSERT_THROW(socket.connect(unixSocketFilePath()), UnixDomainSocketError);
const std::string outbound_data = "foo";
ASSERT_THROW(socket.write(outbound_data.c_str(), outbound_data.size()),
UnixDomainSocketError);
std::array<char, 1024> read_buf;
ASSERT_THROW(socket.receive(&read_buf[0], read_buf.size()),
UnixDomainSocketError);
}
// This test verifies that an error is returned on attempt to asynchronously
// connect, write or receive when the server socket is not available.
TEST_F(UnixDomainSocketTest, asyncClientErrors) {
UnixDomainSocket socket(io_service_);
// Asynchronous operations signal errors through boost::system::error_code
// object passed to the handler function. This object casts to boolean.
// In case of success the object casts to false. In case of an error it
// casts to true. The actual error codes can be retrieved by comparing the
// ec objects to predefined error objects. We don't check for the actual
// errors here, because it is not certain that the same error codes would
// be returned on various operating systems.
// In the following tests we use C++11 lambdas as callbacks.
// Connect
bool connect_handler_invoked = false;
socket.asyncConnect(unixSocketFilePath(),
[this, &connect_handler_invoked](const boost::system::error_code& ec) {
connect_handler_invoked = true;
EXPECT_TRUE(ec);
});
while (!connect_handler_invoked && !test_socket_->isStopped()) {
io_service_.run_one();
}
// Send
const std::string outbound_data = "foo";
bool send_handler_invoked = false;
socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
[this, &send_handler_invoked]
(const boost::system::error_code& ec, size_t length) {
send_handler_invoked = true;
EXPECT_TRUE(ec);
});
while (!send_handler_invoked && !test_socket_->isStopped()) {
io_service_.run_one();
}
// Receive
bool receive_handler_invoked = false;
std::array<char, 1024> read_buf;
socket.asyncReceive(&read_buf[0], read_buf.size(),
[this, &receive_handler_invoked]
(const boost::system::error_code& ec, size_t length) {
receive_handler_invoked = true;
EXPECT_TRUE(ec);
});
while (!receive_handler_invoked && !test_socket_->isStopped()) {
io_service_.run_one();
}
}
// Check that native socket descriptor is returned correctly when
// the socket is connected.
TEST_F(UnixDomainSocketTest, getNative) {
// Start the server.
test_socket_->bindServerSocket();
// Setup client side.
UnixDomainSocket socket(io_service_);
ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
ASSERT_GE(socket.getNative(), 0);
}
// Check that protocol returned is 0.
TEST_F(UnixDomainSocketTest, getProtocol) {
UnixDomainSocket socket(io_service_);
EXPECT_EQ(0, socket.getProtocol());
}
}
|