1 /*
2 * SysDB - t/unit/frontend/connection_test.c
3 * Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "frontend/connection.h"
33 #include "frontend/connection-private.h"
34 #include "utils/os.h"
35 #include "testutils.h"
37 #include "utils/strbuf.h"
39 #include <check.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
45 #include <pthread.h>
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <sys/un.h>
51 static char username[1024];
53 /*
54 * private helper functions
55 */
57 static void
58 mock_conn_destroy(sdb_conn_t *conn)
59 {
60 if (SDB_OBJ(conn)->name)
61 free(SDB_OBJ(conn)->name);
62 sdb_strbuf_destroy(conn->buf);
63 sdb_strbuf_destroy(conn->errbuf);
64 if (conn->fd >= 0)
65 close(conn->fd);
66 if (conn->username)
67 free(conn->username);
68 free(conn);
69 } /* mock_conn_destroy */
71 static ssize_t
72 mock_conn_read(sdb_conn_t *conn, size_t len)
73 {
74 return sdb_strbuf_read(conn->buf, conn->fd, len);
75 } /* conn_read */
77 static ssize_t
78 mock_conn_write(sdb_conn_t *conn, const void *buf, size_t len)
79 {
80 return sdb_write(conn->fd, len, buf);
81 } /* conn_write */
83 static sdb_conn_t *
84 mock_conn_create(void)
85 {
86 sdb_conn_t *conn;
88 char tmp_file[] = "connection_test_socket.XXXXXX";
90 conn = calloc(1, sizeof(*conn));
91 if (! conn) {
92 fail("INTERNAL ERROR: failed to allocate connection object");
93 return NULL;
94 }
96 SDB_OBJ(conn)->name = strdup("mock_connection");
97 SDB_OBJ(conn)->ref_cnt = 1;
99 conn->buf = sdb_strbuf_create(0);
100 conn->errbuf = sdb_strbuf_create(0);
101 if ((! conn->buf) || (! conn->errbuf)) {
102 mock_conn_destroy(conn);
103 fail("INTERNAL ERROR: failed to allocate connection object");
104 return NULL;
105 }
107 conn->fd = mkstemp(tmp_file);
108 if (conn->fd < 0) {
109 mock_conn_destroy(conn);
110 fail("INTERNAL ERROR: failed to allocate connection object");
111 return NULL;
112 }
114 unlink(tmp_file);
116 conn->read = mock_conn_read;
117 conn->write = mock_conn_write;
119 conn->username = strdup(username);
120 ck_assert(conn->username != NULL);
122 conn->cmd = SDB_CONNECTION_IDLE;
123 conn->cmd_len = 0;
124 return conn;
125 } /* mock_conn_create */
127 static void
128 mock_conn_rewind(sdb_conn_t *conn)
129 {
130 lseek(conn->fd, 0, SEEK_SET);
131 } /* mock_conn_rewind */
133 static void
134 mock_conn_truncate(sdb_conn_t *conn)
135 {
136 int status;
137 lseek(conn->fd, 0, SEEK_SET);
138 status = ftruncate(conn->fd, 0);
139 fail_unless(status == 0,
140 "INTERNAL ERROR: ftruncate(%d, 0) = %d; expected: 0",
141 conn->fd, status);
142 } /* mock_conn_truncate */
144 static int
145 mock_unixsock_listener(char *socket_path)
146 {
147 struct sockaddr_un sa;
148 int fd, status;
150 fd = socket(AF_UNIX, SOCK_STREAM, 0);
151 fail_unless(fd >= 0,
152 "INTERNAL ERROR: socket() = %d; expected: >=0", fd);
154 memset(&sa, 0, sizeof(sa));
155 sa.sun_family = AF_UNIX;
156 strncpy(sa.sun_path, socket_path, sizeof(sa.sun_path));
158 status = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
159 fail_unless(status == 0,
160 "INTERNAL ERROR: bind() = %d; expected: 0", status);
161 status = listen(fd, 32);
162 fail_unless(status == 0,
163 "INTERNAL ERROR: listen() = %d; expected: 0", status);
165 return fd;
166 } /* mock_unixsock */
168 static void *
169 mock_client(void *arg)
170 {
171 char *socket_path = arg;
173 struct sockaddr_un sa;
174 int fd, check;
176 fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
177 fail_unless(fd >= 0,
178 "INTERNAL ERROR: socket() = %d; expected: >= 0", fd);
180 memset(&sa, 0, sizeof(sa));
181 sa.sun_family = AF_UNIX;
182 strncpy(sa.sun_path, socket_path, sizeof(sa.sun_path));
184 check = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
185 fail_unless(check == 0,
186 "INTERNAL ERROR: connect() = %d; expected: 0", check);
188 close(fd);
189 return NULL;
190 } /* mock_client */
192 static void
193 connection_startup(sdb_conn_t *conn)
194 {
195 ssize_t check, expected;
197 expected = 2 * sizeof(uint32_t) + strlen(username);
198 check = sdb_connection_send(conn, SDB_CONNECTION_STARTUP,
199 (uint32_t)strlen(username), username);
200 fail_unless(check == expected,
201 "sdb_connection_send(STARTUP, %s) = %zi; expected: %zi",
202 username, check, expected);
204 mock_conn_rewind(conn);
205 check = sdb_connection_handle(conn);
206 fail_unless(check == expected,
207 "On startup: sdb_connection_handle() = %zi; expected: %zi",
208 check, expected);
210 fail_unless(sdb_strbuf_len(conn->errbuf) == 0,
211 "sdb_connection_handle() left %zu bytes in the error "
212 "buffer (%s); expected: 0", sdb_strbuf_len(conn->errbuf),
213 sdb_strbuf_string(conn->errbuf));
215 mock_conn_truncate(conn);
216 } /* connection_startup */
218 /*
219 * tests
220 */
222 START_TEST(test_conn_accept)
223 {
224 char socket_path[] = "connection_test_socket.XXXXXX";
225 int fd, check;
227 sdb_conn_t *conn;
229 pthread_t thr;
231 conn = sdb_connection_accept(-1, NULL, NULL);
232 fail_unless(conn == NULL,
233 "sdb_connection_accept(-1) = %p; expected: NULL", conn);
235 fd = mkstemp(socket_path);
236 unlink(socket_path);
237 close(fd);
239 fd = mock_unixsock_listener(socket_path);
240 check = pthread_create(&thr, /* attr = */ NULL, mock_client, socket_path);
241 fail_unless(check == 0,
242 "INTERNAL ERROR: pthread_create() = %i; expected: 0", check);
244 conn = sdb_connection_accept(fd, NULL, NULL);
245 fail_unless(conn != NULL,
246 "sdb_connection_accept(%d) = %p; expected: <conn>", fd, conn);
248 unlink(socket_path);
249 sdb_connection_close(conn);
250 sdb_object_deref(SDB_OBJ(conn));
251 pthread_join(thr, NULL);
252 }
253 END_TEST
255 /* test connection setup and very basic commands */
256 START_TEST(test_conn_setup)
257 {
258 sdb_conn_t *conn = mock_conn_create();
260 struct {
261 uint32_t code;
262 const char *msg;
263 const char *err;
264 } golden_data[] = {
265 /* code == UINT32_MAX => no data will be sent */
266 { UINT32_MAX, NULL, NULL },
267 { SDB_CONNECTION_IDLE, "fakedata", "Authentication required" },
268 { SDB_CONNECTION_PING, NULL, "Authentication required" },
269 { SDB_CONNECTION_STARTUP, username, NULL },
270 { SDB_CONNECTION_PING, NULL, NULL },
271 { SDB_CONNECTION_IDLE, NULL, "Invalid command 0" },
272 { SDB_CONNECTION_PING, "fakedata", NULL },
273 { SDB_CONNECTION_IDLE, NULL, "Invalid command 0" },
274 };
276 size_t i;
278 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
279 ssize_t check, expected = 0;
281 mock_conn_truncate(conn);
283 if (golden_data[i].code != UINT32_MAX) {
284 expected = 2 * sizeof(uint32_t)
285 + (golden_data[i].msg ? strlen(golden_data[i].msg) : 0);
287 check = sdb_connection_send(conn, golden_data[i].code,
288 (uint32_t)(golden_data[i].msg
289 ? strlen(golden_data[i].msg) : 0),
290 golden_data[i].msg);
291 fail_unless(check == expected,
292 "sdb_connection_send(%d, %s) = %zi; expected: %zi",
293 golden_data[i].code,
294 golden_data[i].msg ? golden_data[i].msg : "<null>",
295 check, expected);
296 }
298 mock_conn_rewind(conn);
299 check = sdb_connection_handle(conn);
300 fail_unless(check == expected,
301 "sdb_connection_handle() = %zi; expected: %zi",
302 check, expected);
304 fail_unless(sdb_strbuf_len(conn->buf) == 0,
305 "sdb_connection_handle() left %zu bytes in the buffer; "
306 "expected: 0", sdb_strbuf_len(conn->buf));
308 if (golden_data[i].err) {
309 const char *err = sdb_strbuf_string(conn->errbuf);
310 fail_unless(strcmp(err, golden_data[i].err) == 0,
311 "sdb_connection_handle(): got error '%s'; "
312 "expected: '%s'", err, golden_data[i].err);
313 }
314 else
315 fail_unless(sdb_strbuf_len(conn->errbuf) == 0,
316 "sdb_connection_handle() left %zu bytes in the error "
317 "buffer (%s); expected: 0", sdb_strbuf_len(conn->errbuf),
318 sdb_strbuf_string(conn->errbuf));
319 }
321 mock_conn_destroy(conn);
322 }
323 END_TEST
325 /* test simple I/O on open connections */
326 START_TEST(test_conn_io)
327 {
328 sdb_conn_t *conn = mock_conn_create();
330 struct {
331 uint32_t code;
332 uint32_t msg_len;
333 const char *msg;
334 size_t buf_len; /* number of bytes we expect in conn->buf */
335 const char *err;
336 } golden_data[] = {
337 /* code == UINT32_MAX => this is a follow-up package */
338 { SDB_CONNECTION_PING, 20, "9876543210", 0, "Authentication required" },
339 { UINT32_MAX, -1, "9876543210", 0, "Authentication required" },
340 { SDB_CONNECTION_PING, 10, "9876543210", 0, "Authentication required" },
341 { SDB_CONNECTION_IDLE, 10, "9876543210", 0, "Authentication required" },
342 { SDB_CONNECTION_IDLE, 20, "9876543210", 0, "Authentication required" },
343 { UINT32_MAX, -1, "9876543210", 0, "Authentication required" },
344 { SDB_CONNECTION_STARTUP, -1, NULL, 0, NULL },
345 { SDB_CONNECTION_PING, 20, "9876543210", 10, NULL },
346 { UINT32_MAX, -1, "9876543210", 0, NULL },
347 { SDB_CONNECTION_IDLE, 20, "9876543210", 0, "Invalid command 0" },
348 { UINT32_MAX, -1, "9876543210", 0, "Invalid command 0" },
349 { SDB_CONNECTION_IDLE, 20, "9876543210", 0, "Invalid command 0" },
350 { UINT32_MAX, -1, "9876543210", 0, "Invalid command 0" },
351 { SDB_CONNECTION_PING, 10, "9876543210", 0, NULL },
352 { SDB_CONNECTION_PING, 20, "9876543210", 10, NULL },
353 { UINT32_MAX, -1, "9876543210", 0, NULL },
354 };
356 size_t i;
358 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
359 size_t msg_len = golden_data[i].msg ? strlen(golden_data[i].msg) : 0;
360 char buffer[2 * sizeof(uint32_t) + msg_len];
361 size_t offset = 0;
363 ssize_t check;
365 mock_conn_truncate(conn);
367 if (golden_data[i].code == SDB_CONNECTION_STARTUP) {
368 connection_startup(conn);
369 continue;
370 }
372 if (golden_data[i].code != UINT32_MAX) {
373 uint32_t tmp;
375 tmp = htonl(golden_data[i].code);
376 memcpy(buffer, &tmp, sizeof(tmp));
377 tmp = htonl(golden_data[i].msg_len);
378 memcpy(buffer + sizeof(tmp), &tmp, sizeof(tmp));
380 msg_len += 2 * sizeof(uint32_t);
381 offset += 2 * sizeof(uint32_t);
382 }
384 memcpy(buffer + offset, golden_data[i].msg,
385 strlen(golden_data[i].msg));
387 check = sdb_write(conn->fd, msg_len, buffer);
388 fail_unless(check == (ssize_t)msg_len,
389 "sdb_write(%s) = %zi; expected: %zu",
390 check, msg_len);
392 mock_conn_rewind(conn);
393 check = sdb_connection_handle(conn);
394 fail_unless(check == (ssize_t)msg_len,
395 "sdb_connection_handle() = %zi; expected: %zu",
396 check, msg_len);
398 if (golden_data[i].buf_len) {
399 /* partial commands need to be stored in the object */
400 fail_unless(conn->cmd == golden_data[i].code,
401 "sdb_connection_handle() set partial command "
402 "to %u; expected: %u", conn->cmd, golden_data[i].code);
403 fail_unless(conn->cmd_len > golden_data[i].buf_len,
404 "sdb_connection_handle() set partial command length "
405 "to %u; expected: > %u", conn->cmd_len,
406 golden_data[i].buf_len);
407 }
408 else {
409 fail_unless(conn->cmd == SDB_CONNECTION_IDLE,
410 "sdb_connection_handle() did not reset command; "
411 "got %u; expected: %u", conn->cmd, SDB_CONNECTION_IDLE);
412 fail_unless(conn->cmd_len == 0,
413 "sdb_connection_handle() did not reset command length; "
414 "got %u; expected: 0", conn->cmd_len);
415 }
417 fail_unless(sdb_strbuf_len(conn->buf) == golden_data[i].buf_len,
418 "sdb_connection_handle() left %zu bytes in the buffer; "
419 "expected: %zu", sdb_strbuf_len(conn->buf),
420 golden_data[i].buf_len);
422 if (golden_data[i].err) {
423 const char *err = sdb_strbuf_string(conn->errbuf);
424 fail_unless(strcmp(err, golden_data[i].err) == 0,
425 "sdb_connection_handle(): got error '%s'; "
426 "expected: '%s'", err, golden_data[i].err);
427 }
428 else
429 fail_unless(sdb_strbuf_len(conn->errbuf) == 0,
430 "sdb_connection_handle() left %zu bytes in the error "
431 "buffer; expected: 0", sdb_strbuf_len(conn->errbuf));
432 }
434 mock_conn_destroy(conn);
435 }
436 END_TEST
438 TEST_MAIN("frontend::connection")
439 {
440 TCase *tc;
442 char *tmp = sdb_get_current_user();
443 strcpy(username, tmp);
444 free(tmp);
446 tc = tcase_create("core");
447 tcase_add_test(tc, test_conn_accept);
448 tcase_add_test(tc, test_conn_setup);
449 tcase_add_test(tc, test_conn_io);
450 ADD_TCASE(tc);
451 }
452 TEST_MAIN_END
454 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */