/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #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; }