aboutsummaryrefslogtreecommitdiff
path: root/src/tests/test-qcdm-serial-port.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/test-qcdm-serial-port.c')
-rw-r--r--src/tests/test-qcdm-serial-port.c482
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;
+}
+