9b0cde48da0ca11e725729fd01fe46318bc22d89
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 "libsysdb_test.h"
37 #include "utils/strbuf.h"
39 #include <assert.h>
40 #include <check.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
46 #include <pthread.h>
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <sys/un.h>
52 static char username[1024];
54 /*
55 * private helper functions
56 */
58 static void
59 mock_conn_destroy(sdb_conn_t *conn)
60 {
61 if (SDB_OBJ(conn)->name)
62 free(SDB_OBJ(conn)->name);
63 sdb_strbuf_destroy(conn->buf);
64 sdb_strbuf_destroy(conn->errbuf);
65 if (conn->fd >= 0)
66 close(conn->fd);
67 if (conn->username)
68 free(conn->username);
69 free(conn);
70 } /* mock_conn_destroy */
72 static ssize_t
73 mock_conn_read(sdb_conn_t *conn, size_t len)
74 {
75 return sdb_strbuf_read(conn->buf, conn->fd, len);
76 } /* conn_read */
78 static ssize_t
79 mock_conn_write(sdb_conn_t *conn, const void *buf, size_t len)
80 {
81 return sdb_write(conn->fd, len, buf);
82 } /* conn_write */
84 static sdb_conn_t *
85 mock_conn_create(void)
86 {
87 sdb_conn_t *conn;
89 char tmp_file[] = "connection_test_socket.XXXXXX";
91 conn = calloc(1, sizeof(*conn));
92 if (! conn) {
93 fail("INTERNAL ERROR: failed to allocate connection object");
94 return NULL;
95 }
97 SDB_OBJ(conn)->name = strdup("mock_connection");
98 SDB_OBJ(conn)->ref_cnt = 1;
100 conn->buf = sdb_strbuf_create(0);
101 conn->errbuf = sdb_strbuf_create(0);
102 if ((! conn->buf) || (! conn->errbuf)) {
103 mock_conn_destroy(conn);
104 fail("INTERNAL ERROR: failed to allocate connection object");
105 return NULL;
106 }
108 conn->fd = mkstemp(tmp_file);
109 if (conn->fd < 0) {
110 mock_conn_destroy(conn);
111 fail("INTERNAL ERROR: failed to allocate connection object");
112 return NULL;
113 }
115 unlink(tmp_file);
117 conn->read = mock_conn_read;
118 conn->write = mock_conn_write;
120 conn->username = strdup(username);
121 assert(conn->username);
123 conn->cmd = SDB_CONNECTION_IDLE;
124 conn->cmd_len = 0;
125 return conn;
126 } /* mock_conn_create */
128 static void
129 mock_conn_rewind(sdb_conn_t *conn)
130 {
131 lseek(conn->fd, 0, SEEK_SET);
132 } /* mock_conn_rewind */
134 static void
135 mock_conn_truncate(sdb_conn_t *conn)
136 {
137 int status;
138 lseek(conn->fd, 0, SEEK_SET);
139 status = ftruncate(conn->fd, 0);
140 fail_unless(status == 0,
141 "INTERNAL ERROR: ftruncate(%d, 0) = %d; expected: 0",
142 conn->fd, status);
143 } /* mock_conn_truncate */
145 static int
146 mock_unixsock_listener(char *socket_path)
147 {
148 struct sockaddr_un sa;
149 int fd, status;
151 fd = socket(AF_UNIX, SOCK_STREAM, 0);
152 fail_unless(fd >= 0,
153 "INTERNAL ERROR: socket() = %d; expected: >=0", fd);
155 memset(&sa, 0, sizeof(sa));
156 sa.sun_family = AF_UNIX;
157 strncpy(sa.sun_path, socket_path, sizeof(sa.sun_path));
159 status = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
160 fail_unless(status == 0,
161 "INTERNAL ERROR: bind() = %d; expected: 0", status);
162 status = listen(fd, 32);
163 fail_unless(status == 0,
164 "INTERNAL ERROR: listen() = %d; expected: 0", status);
166 return fd;
167 } /* mock_unixsock */
169 static void *
170 mock_client(void *arg)
171 {
172 char *socket_path = arg;
174 struct sockaddr_un sa;
175 int fd, check;
177 fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
178 fail_unless(fd >= 0,
179 "INTERNAL ERROR: socket() = %d; expected: >= 0", fd);
181 memset(&sa, 0, sizeof(sa));
182 sa.sun_family = AF_UNIX;
183 strncpy(sa.sun_path, socket_path, sizeof(sa.sun_path));
185 check = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
186 fail_unless(check == 0,
187 "INTERNAL ERROR: connect() = %d; expected: 0", check);
189 close(fd);
190 return NULL;
191 } /* mock_client */
193 static void
194 connection_startup(sdb_conn_t *conn)
195 {
196 ssize_t check, expected;
198 expected = 2 * sizeof(uint32_t) + strlen(username);
199 check = sdb_connection_send(conn, SDB_CONNECTION_STARTUP,
200 (uint32_t)strlen(username), username);
201 fail_unless(check == expected,
202 "sdb_connection_send(STARTUP, %s) = %zi; expected: %zi",
203 username, check, expected);
205 mock_conn_rewind(conn);
206 check = sdb_connection_handle(conn);
207 fail_unless(check == expected,
208 "On startup: sdb_connection_handle() = %zi; expected: %zi",
209 check, expected);
211 fail_unless(sdb_strbuf_len(conn->errbuf) == 0,
212 "sdb_connection_handle() left %zu bytes in the error "
213 "buffer (%s); expected: 0", sdb_strbuf_len(conn->errbuf),
214 sdb_strbuf_string(conn->errbuf));
216 mock_conn_truncate(conn);
217 } /* connection_startup */
219 /*
220 * tests
221 */
223 START_TEST(test_conn_accept)
224 {
225 char socket_path[] = "connection_test_socket.XXXXXX";
226 int fd, check;
228 sdb_conn_t *conn;
230 pthread_t thr;
232 conn = sdb_connection_accept(-1);
233 fail_unless(conn == NULL,
234 "sdb_connection_accept(-1) = %p; expected: NULL", conn);
236 fd = mkstemp(socket_path);
237 unlink(socket_path);
238 close(fd);
240 fd = mock_unixsock_listener(socket_path);
241 check = pthread_create(&thr, /* attr = */ NULL, mock_client, socket_path);
242 fail_unless(check == 0,
243 "INTERNAL ERROR: pthread_create() = %i; expected: 0", check);
245 conn = sdb_connection_accept(fd);
246 fail_unless(conn != NULL,
247 "sdb_connection_accept(%d) = %p; expected: <conn>", fd, conn);
249 unlink(socket_path);
250 sdb_connection_close(conn);
251 sdb_object_deref(SDB_OBJ(conn));
252 pthread_join(thr, NULL);
253 }
254 END_TEST
256 /* test connection setup and very basic commands */
257 START_TEST(test_conn_setup)
258 {
259 sdb_conn_t *conn = mock_conn_create();
261 struct {
262 uint32_t code;
263 const char *msg;
264 const char *err;
265 } golden_data[] = {
266 /* code == UINT32_MAX => no data will be sent */
267 { UINT32_MAX, NULL, NULL },
268 { SDB_CONNECTION_IDLE, "fakedata", "Authentication required" },
269 { SDB_CONNECTION_PING, NULL, "Authentication required" },
270 { SDB_CONNECTION_STARTUP, username, NULL },
271 { SDB_CONNECTION_PING, NULL, NULL },
272 { SDB_CONNECTION_IDLE, NULL, "Invalid command 0" },
273 { SDB_CONNECTION_PING, "fakedata", NULL },
274 { SDB_CONNECTION_IDLE, NULL, "Invalid command 0" },
275 };
277 size_t i;
279 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
280 ssize_t check, expected = 0;
282 mock_conn_truncate(conn);
284 if (golden_data[i].code != UINT32_MAX) {
285 expected = 2 * sizeof(uint32_t)
286 + (golden_data[i].msg ? strlen(golden_data[i].msg) : 0);
288 check = sdb_connection_send(conn, golden_data[i].code,
289 (uint32_t)(golden_data[i].msg
290 ? strlen(golden_data[i].msg) : 0),
291 golden_data[i].msg);
292 fail_unless(check == expected,
293 "sdb_connection_send(%d, %s) = %zi; expected: %zi",
294 golden_data[i].code,
295 golden_data[i].msg ? golden_data[i].msg : "<null>",
296 check, expected);
297 }
299 mock_conn_rewind(conn);
300 check = sdb_connection_handle(conn);
301 fail_unless(check == expected,
302 "sdb_connection_handle() = %zi; expected: %zi",
303 check, expected);
305 fail_unless(sdb_strbuf_len(conn->buf) == 0,
306 "sdb_connection_handle() left %zu bytes in the buffer; "
307 "expected: 0", sdb_strbuf_len(conn->buf));
309 if (golden_data[i].err) {
310 const char *err = sdb_strbuf_string(conn->errbuf);
311 fail_unless(strcmp(err, golden_data[i].err) == 0,
312 "sdb_connection_handle(): got error '%s'; "
313 "expected: '%s'", err, golden_data[i].err);
314 }
315 else
316 fail_unless(sdb_strbuf_len(conn->errbuf) == 0,
317 "sdb_connection_handle() left %zu bytes in the error "
318 "buffer (%s); expected: 0", sdb_strbuf_len(conn->errbuf),
319 sdb_strbuf_string(conn->errbuf));
320 }
322 mock_conn_destroy(conn);
323 }
324 END_TEST
326 /* test simple I/O on open connections */
327 START_TEST(test_conn_io)
328 {
329 sdb_conn_t *conn = mock_conn_create();
331 struct {
332 uint32_t code;
333 uint32_t msg_len;
334 const char *msg;
335 size_t buf_len; /* number of bytes we expect in conn->buf */
336 const char *err;
337 } golden_data[] = {
338 /* code == UINT32_MAX => this is a follow-up package */
339 { SDB_CONNECTION_PING, 20, "9876543210", 0, "Authentication required" },
340 { UINT32_MAX, -1, "9876543210", 0, "Authentication required" },
341 { SDB_CONNECTION_PING, 10, "9876543210", 0, "Authentication required" },
342 { SDB_CONNECTION_IDLE, 10, "9876543210", 0, "Authentication required" },
343 { SDB_CONNECTION_IDLE, 20, "9876543210", 0, "Authentication required" },
344 { UINT32_MAX, -1, "9876543210", 0, "Authentication required" },
345 { SDB_CONNECTION_STARTUP, -1, NULL, 0, NULL },
346 { SDB_CONNECTION_PING, 20, "9876543210", 10, NULL },
347 { UINT32_MAX, -1, "9876543210", 0, NULL },
348 { SDB_CONNECTION_IDLE, 20, "9876543210", 0, "Invalid command 0" },
349 { UINT32_MAX, -1, "9876543210", 0, "Invalid command 0" },
350 { SDB_CONNECTION_IDLE, 20, "9876543210", 0, "Invalid command 0" },
351 { UINT32_MAX, -1, "9876543210", 0, "Invalid command 0" },
352 { SDB_CONNECTION_PING, 10, "9876543210", 0, NULL },
353 { SDB_CONNECTION_PING, 20, "9876543210", 10, NULL },
354 { UINT32_MAX, -1, "9876543210", 0, NULL },
355 };
357 size_t i;
359 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
360 size_t msg_len = golden_data[i].msg ? strlen(golden_data[i].msg) : 0;
361 char buffer[2 * sizeof(uint32_t) + msg_len];
362 size_t offset = 0;
364 ssize_t check;
366 mock_conn_truncate(conn);
368 if (golden_data[i].code == SDB_CONNECTION_STARTUP) {
369 connection_startup(conn);
370 continue;
371 }
373 if (golden_data[i].code != UINT32_MAX) {
374 uint32_t tmp;
376 tmp = htonl(golden_data[i].code);
377 memcpy(buffer, &tmp, sizeof(tmp));
378 tmp = htonl(golden_data[i].msg_len);
379 memcpy(buffer + sizeof(tmp), &tmp, sizeof(tmp));
381 msg_len += 2 * sizeof(uint32_t);
382 offset += 2 * sizeof(uint32_t);
383 }
385 memcpy(buffer + offset, golden_data[i].msg,
386 strlen(golden_data[i].msg));
388 check = sdb_write(conn->fd, msg_len, buffer);
389 fail_unless(check == (ssize_t)msg_len,
390 "sdb_write(%s) = %zi; expected: %zu",
391 check, msg_len);
393 mock_conn_rewind(conn);
394 check = sdb_connection_handle(conn);
395 fail_unless(check == (ssize_t)msg_len,
396 "sdb_connection_handle() = %zi; expected: %zu",
397 check, msg_len);
399 if (golden_data[i].buf_len) {
400 /* partial commands need to be stored in the object */
401 fail_unless(conn->cmd == golden_data[i].code,
402 "sdb_connection_handle() set partial command "
403 "to %u; expected: %u", conn->cmd, golden_data[i].code);
404 fail_unless(conn->cmd_len > golden_data[i].buf_len,
405 "sdb_connection_handle() set partial command length "
406 "to %u; expected: > %u", conn->cmd_len,
407 golden_data[i].buf_len);
408 }
409 else {
410 fail_unless(conn->cmd == SDB_CONNECTION_IDLE,
411 "sdb_connection_handle() did not reset command; "
412 "got %u; expected: %u", conn->cmd, SDB_CONNECTION_IDLE);
413 fail_unless(conn->cmd_len == 0,
414 "sdb_connection_handle() did not reset command length; "
415 "got %u; expected: 0", conn->cmd_len);
416 }
418 fail_unless(sdb_strbuf_len(conn->buf) == golden_data[i].buf_len,
419 "sdb_connection_handle() left %zu bytes in the buffer; "
420 "expected: %zu", sdb_strbuf_len(conn->buf),
421 golden_data[i].buf_len);
423 if (golden_data[i].err) {
424 const char *err = sdb_strbuf_string(conn->errbuf);
425 fail_unless(strcmp(err, golden_data[i].err) == 0,
426 "sdb_connection_handle(): got error '%s'; "
427 "expected: '%s'", err, golden_data[i].err);
428 }
429 else
430 fail_unless(sdb_strbuf_len(conn->errbuf) == 0,
431 "sdb_connection_handle() left %zu bytes in the error "
432 "buffer; expected: 0", sdb_strbuf_len(conn->errbuf));
433 }
435 mock_conn_destroy(conn);
436 }
437 END_TEST
439 Suite *
440 fe_conn_suite(void)
441 {
442 Suite *s = suite_create("frontend::connection");
443 TCase *tc;
445 char *tmp = sdb_get_current_user();
446 assert(tmp);
447 strcpy(username, tmp);
448 free(tmp);
450 tc = tcase_create("core");
451 tcase_add_test(tc, test_conn_accept);
452 tcase_add_test(tc, test_conn_setup);
453 tcase_add_test(tc, test_conn_io);
454 suite_add_tcase(s, tc);
456 return s;
457 } /* fe_conn_suite */
459 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */