diff options
Diffstat (limited to 'src/tests/test-qcdm-serial-port.c')
-rw-r--r-- | src/tests/test-qcdm-serial-port.c | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/src/tests/test-qcdm-serial-port.c b/src/tests/test-qcdm-serial-port.c new file mode 100644 index 0000000..3aeed6a --- /dev/null +++ b/src/tests/test-qcdm-serial-port.c @@ -0,0 +1,482 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <config.h> +#include <glib.h> +#include <string.h> +#include <pty.h> +#include <unistd.h> +#include <stdlib.h> +#include <termios.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> + +#include "mm-errors.h" +#include "mm-qcdm-serial-port.h" +#include "libqcdm/src/commands.h" +#include "libqcdm/src/utils.h" +#include "libqcdm/src/com.h" +#include "mm-log.h" + +typedef struct { + int master; + int slave; + gboolean valid; + pid_t child; +} TestData; + +static gboolean +wait_for_child (TestData *d, guint32 timeout) +{ + GTimeVal start, now; + int status, ret; + + g_get_current_time (&start); + do { + status = 0; + ret = waitpid (d->child, &status, WNOHANG); + g_get_current_time (&now); + if (d->child && (now.tv_sec - start.tv_sec > timeout)) { + /* Kill it */ + if (g_test_verbose ()) + g_message ("Killing running child process %d", d->child); + kill (d->child, SIGKILL); + d->child = 0; + } + if (ret == 0) + sleep (1); + } while ((ret <= 0) || (!WIFEXITED (status) && !WIFSIGNALED (status))); + + d->child = 0; + return (WIFEXITED (status) && WEXITSTATUS (status) == 0) ? TRUE : FALSE; +} + +static void +print_buf (const char *detail, const char *buf, gsize len) +{ + int i = 0; + gboolean newline = FALSE; + + g_print ("%s (%zu) ", detail, len); + for (i = 0; i < len; i++) { + g_print ("0x%02x ", buf[i] & 0xFF); + if (((i + 1) % 12) == 0) { + g_print ("\n"); + newline = TRUE; + } else + newline = FALSE; + } + + if (!newline) + g_print ("\n"); +} + +static void +server_send_response (int fd, const char *buf, gsize len) +{ + int status; + gsize i = 0; + + if (g_test_verbose ()) + print_buf (">>>", buf, len); + + while (i < len) { + errno = 0; + status = write (fd, &buf[i], 1); + g_assert_cmpint (errno, ==, 0); + g_assert (status == 1); + i++; + usleep (1000); + } +} + +static gsize +server_wait_request (int fd, char *buf, gsize len) +{ + fd_set in; + int result; + struct timeval timeout = { 1, 0 }; + char readbuf[1024]; + ssize_t bytes_read; + int total = 0, retries = 0; + gsize decap_len = 0; + + FD_ZERO (&in); + FD_SET (fd, &in); + result = select (fd + 1, &in, NULL, NULL, &timeout); + g_assert (result == 1); + g_assert (FD_ISSET (fd, &in)); + + do { + errno = 0; + bytes_read = read (fd, &readbuf[total], 1); + if ((bytes_read == 0) || (errno == EAGAIN)) { + /* Haven't gotten the async control char yet */ + if (retries > 20) + return 0; /* 2 seconds, give up */ + + /* Otherwise wait a bit and try again */ + usleep (100000); + retries++; + continue; + } else if (bytes_read == 1) { + gboolean more = FALSE, success; + gsize used = 0; + + total++; + decap_len = 0; + success = dm_decapsulate_buffer (readbuf, total, buf, len, &decap_len, &used, &more); + + /* Discard used data */ + if (used > 0) { + total -= used; + memmove (readbuf, &readbuf[used], total); + } + + if (success && !more) { + /* Success; we have a packet */ + break; + } + } else { + /* Some error occurred */ + g_assert_not_reached (); + } + } while (total < sizeof (readbuf)); + + if (g_test_verbose ()) { + print_buf ("<<<", readbuf, total); + print_buf ("D<<", buf, decap_len); + } + + return decap_len; +} + +typedef void (*VerInfoCb) (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data); + +static void +qcdm_verinfo_expect_success_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_assert_no_error (error); + g_assert (response->len > 0); + g_main_loop_quit (loop); +} + +static void +qcdm_request_verinfo (MMQcdmSerialPort *port, VerInfoCb cb, GMainLoop *loop) +{ + GError *error = NULL; + GByteArray *verinfo; + gint len; + + /* Build up the probe command */ + verinfo = g_byte_array_sized_new (50); + len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50, &error); + if (len <= 0) { + g_byte_array_free (verinfo, TRUE); + g_assert_no_error (error); + } + verinfo->len = len; + + mm_qcdm_serial_port_queue_command (port, verinfo, 3, cb, loop); +} + +static void +qcdm_test_child (int fd, VerInfoCb cb) +{ + MMQcdmSerialPort *port; + GMainLoop *loop; + gboolean success; + GError *error = NULL; + + /* In the child */ + g_type_init (); + + loop = g_main_loop_new (NULL, FALSE); + + port = mm_qcdm_serial_port_new_fd (fd, MM_PORT_TYPE_PRIMARY); + g_assert (port); + + success = mm_serial_port_open (MM_SERIAL_PORT (port), &error); + g_assert_no_error (error); + g_assert (success); + + qcdm_request_verinfo (port, cb, loop); + g_main_loop_run (loop); + + mm_serial_port_close (MM_SERIAL_PORT (port)); + g_object_unref (port); +} + +/* Test that a Version Info request/response is processed correctly to + * make sure things in general are working. + */ +static void +test_verinfo (void *f) +{ + TestData *d = f; + char req[512]; + gsize req_len; + pid_t cpid; + const char rsp[] = { + 0x00, 0x41, 0x75, 0x67, 0x20, 0x31, 0x39, 0x20, 0x32, 0x30, 0x30, 0x38, + 0x32, 0x30, 0x3a, 0x34, 0x38, 0x3a, 0x34, 0x37, 0x4f, 0x63, 0x74, 0x20, + 0x32, 0x39, 0x20, 0x32, 0x30, 0x30, 0x37, 0x31, 0x39, 0x3a, 0x30, 0x30, + 0x3a, 0x30, 0x30, 0x53, 0x43, 0x4e, 0x52, 0x5a, 0x2e, 0x2e, 0x2e, 0x2a, + 0x06, 0x04, 0xb9, 0x0b, 0x02, 0x00, 0xb2, 0x19, 0xc4, 0x7e + }; + + signal (SIGCHLD, SIG_DFL); + cpid = fork (); + g_assert (cpid >= 0); + + if (cpid == 0) { + /* In the child */ + qcdm_test_child (d->slave, qcdm_verinfo_expect_success_cb); + exit (0); + } + /* Parent */ + d->child = cpid; + + req_len = server_wait_request (d->master, req, sizeof (req)); + g_assert (req_len == 1); + g_assert_cmpint (req[0], ==, 0x00); + + server_send_response (d->master, rsp, sizeof (rsp)); + g_assert (wait_for_child (d, 3)); +} + +static void +qcdm_verinfo_expect_fail_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_assert_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); + g_main_loop_quit (loop); +} + +/* Test that a Sierra CnS response to a Version Info command correctly + * raises an error in the child's response handler. + */ +static void +test_sierra_cns_rejected (void *f) +{ + TestData *d = f; + char req[512]; + gsize req_len; + pid_t cpid; + const char rsp[] = { + 0x7e, 0x00, 0x0a, 0x6b, 0x6d, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e + }; + + signal (SIGCHLD, SIG_DFL); + cpid = fork (); + g_assert (cpid >= 0); + + if (cpid == 0) { + /* In the child */ + qcdm_test_child (d->slave, qcdm_verinfo_expect_fail_cb); + exit (0); + } + /* Parent */ + d->child = cpid; + + req_len = server_wait_request (d->master, req, sizeof (req)); + g_assert (req_len == 1); + g_assert_cmpint (req[0], ==, 0x00); + + server_send_response (d->master, rsp, sizeof (rsp)); + + /* We expect the child to exit normally */ + g_assert (wait_for_child (d, 3)); +} + +/* Test that a random response to a Version Info command correctly + * raises an error in the child's response handler. + */ +static void +test_random_data_rejected (void *f) +{ + TestData *d = f; + char req[512]; + gsize req_len; + pid_t cpid; + const char rsp[] = { + 0x7e, 0x7e, 0x7e, 0x6b, 0x6d, 0x7e, 0x7e, 0x7e, 0x7e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e + }; + + signal (SIGCHLD, SIG_DFL); + cpid = fork (); + g_assert (cpid >= 0); + + if (cpid == 0) { + /* In the child */ + qcdm_test_child (d->slave, qcdm_verinfo_expect_fail_cb); + exit (0); + } + /* Parent */ + d->child = cpid; + + req_len = server_wait_request (d->master, req, sizeof (req)); + g_assert (req_len == 1); + g_assert_cmpint (req[0], ==, 0x00); + + server_send_response (d->master, rsp, sizeof (rsp)); + + /* We expect the child to exit normally */ + g_assert (wait_for_child (d, 3)); +} + +/* Test that a bunch of frame markers at the beginning of a valid response + * to a Version Info command is parsed correctly. + */ +static void +test_leading_frame_markers (void *f) +{ + TestData *d = f; + char req[512]; + gsize req_len; + pid_t cpid; + const char rsp[] = { + 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, + 0x00, 0x41, 0x75, 0x67, 0x20, 0x31, 0x39, 0x20, 0x32, 0x30, 0x30, 0x38, + 0x32, 0x30, 0x3a, 0x34, 0x38, 0x3a, 0x34, 0x37, 0x4f, 0x63, 0x74, 0x20, + 0x32, 0x39, 0x20, 0x32, 0x30, 0x30, 0x37, 0x31, 0x39, 0x3a, 0x30, 0x30, + 0x3a, 0x30, 0x30, 0x53, 0x43, 0x4e, 0x52, 0x5a, 0x2e, 0x2e, 0x2e, 0x2a, + 0x06, 0x04, 0xb9, 0x0b, 0x02, 0x00, 0xb2, 0x19, 0xc4, 0x7e + }; + + signal (SIGCHLD, SIG_DFL); + cpid = fork (); + g_assert (cpid >= 0); + + if (cpid == 0) { + /* In the child */ + qcdm_test_child (d->slave, qcdm_verinfo_expect_success_cb); + exit (0); + } + /* Parent */ + d->child = cpid; + + req_len = server_wait_request (d->master, req, sizeof (req)); + g_assert (req_len == 1); + g_assert_cmpint (req[0], ==, 0x00); + + server_send_response (d->master, rsp, sizeof (rsp)); + + /* We expect the child to exit normally */ + g_assert (wait_for_child (d, 3)); +} + +static void +test_pty_create (gpointer user_data) +{ + TestData *d = user_data; + struct termios stbuf; + int ret; + GError *error = NULL; + gboolean success; + + ret = openpty (&d->master, &d->slave, NULL, NULL, NULL); + g_assert (ret == 0); + d->valid = TRUE; + + /* set raw mode on the slave using kernel default parameters */ + memset (&stbuf, 0, sizeof (stbuf)); + tcgetattr (d->slave, &stbuf); + tcflush (d->slave, TCIOFLUSH); + cfmakeraw (&stbuf); + tcsetattr (d->slave, TCSANOW, &stbuf); + fcntl (d->slave, F_SETFL, O_NONBLOCK); + + fcntl (d->master, F_SETFL, O_NONBLOCK); + success = qcdm_port_setup (d->master, &error); + g_assert_no_error (error); + g_assert (success); +} + +static void +test_pty_cleanup (gpointer user_data) +{ + TestData *d = user_data; + + /* For some reason the cleanup function gets called more times + * than the setup function does... + */ + if (d->valid) { + if (d->child) + kill (d->child, SIGKILL); + if (d->master >= 0) + close (d->master); + if (d->slave >= 0) + close (d->slave); + memset (d, 0, sizeof (*d)); + } +} + +#if GLIB_CHECK_VERSION(2,25,12) +typedef GTestFixtureFunc TCFunc; +#else +typedef void (*TCFunc)(void); +#endif + +#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL) +#define TESTCASE_PTY(t, d) g_test_create_case (#t, sizeof (*d), d, (TCFunc) test_pty_create, (TCFunc) t, (TCFunc) test_pty_cleanup) + +void +_mm_log (const char *loc, + const char *func, + guint32 level, + const char *fmt, + ...) +{ + /* Dummy log function */ +} + +int main (int argc, char **argv) +{ + GTestSuite *suite; + gint result; + TestData *data = NULL; + + g_test_init (&argc, &argv, NULL); + + suite = g_test_get_root (); + + g_test_suite_add (suite, TESTCASE_PTY (test_verinfo, data)); + g_test_suite_add (suite, TESTCASE_PTY (test_sierra_cns_rejected, data)); + g_test_suite_add (suite, TESTCASE_PTY (test_random_data_rejected, data)); + g_test_suite_add (suite, TESTCASE_PTY (test_leading_frame_markers, data)); + + result = g_test_run (); + + return result; +} + |