Files
ModemManager/src/tests/test-qcdm-serial-port.c
T
Aleksander Morgado a2f4fbe373 port-serial-qcdm: commands are never cached
The QCDM port commands are never cached, so remove the option from the command()
method. Will also simplify command caching afterwards as it will be an AT-only
thing.
2014-02-13 13:40:44 +01:00

475 lines
12 KiB
C

/* -*- 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 <ModemManager.h>
#include <mm-errors-types.h>
#include "mm-port-serial-qcdm.h"
#include "libqcdm/src/commands.h"
#include "libqcdm/src/utils.h"
#include "libqcdm/src/com.h"
#include "libqcdm/src/errors.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_cmpint (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 success;
gsize used = 0;
qcdmbool more = FALSE;
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;
}
static void
qcdm_verinfo_expect_success_cb (MMPortSerialQcdm *port,
GAsyncResult *res,
GMainLoop *loop)
{
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
g_assert_no_error (error);
g_assert (response->len > 0);
g_byte_array_unref (response);
g_main_loop_quit (loop);
}
static void
qcdm_request_verinfo (MMPortSerialQcdm *port,
GAsyncReadyCallback cb,
GMainLoop *loop)
{
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);
if (len <= 0)
g_byte_array_free (verinfo, TRUE);
verinfo->len = len;
mm_port_serial_qcdm_command (port, verinfo, 3, NULL, cb, loop);
g_byte_array_unref (verinfo);
}
static void
qcdm_test_child (int fd, GAsyncReadyCallback cb)
{
MMPortSerialQcdm *port;
GMainLoop *loop;
gboolean success;
GError *error = NULL;
/* In the child */
g_type_init ();
loop = g_main_loop_new (NULL, FALSE);
port = mm_port_serial_qcdm_new_fd (fd);
g_assert (port);
success = mm_port_serial_open (MM_PORT_SERIAL (port), &error);
g_assert_no_error (error);
g_assert (success);
qcdm_request_verinfo (port, cb, loop);
g_main_loop_run (loop);
mm_port_serial_close (MM_PORT_SERIAL (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 (TestData *d)
{
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, (GAsyncReadyCallback)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 (MMPortSerialQcdm *port,
GAsyncResult *res,
GMainLoop *loop)
{
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
g_error_free (error);
g_assert (response == NULL);
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 (TestData *d)
{
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, (GAsyncReadyCallback)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 (TestData *d)
{
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, (GAsyncReadyCallback)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 (TestData *d)
{
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, (GAsyncReadyCallback)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 (TestData *d)
{
struct termios stbuf;
int ret, err;
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);
err = qcdm_port_setup (d->master);
g_assert_cmpint (err, ==, QCDM_SUCCESS);
}
static void
test_pty_cleanup (TestData *d)
{
/* 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));
}
}
void
_mm_log (const char *loc,
const char *func,
guint32 level,
const char *fmt,
...)
{
#if defined ENABLE_TEST_MESSAGE_TRACES
/* Dummy log function */
va_list args;
gchar *msg;
va_start (args, fmt);
msg = g_strdup_vprintf (fmt, args);
va_end (args);
g_print ("%s\n", msg);
g_free (msg);
#endif
}
typedef void (*TCFunc) (TestData *, gconstpointer);
#define TESTCASE_PTY(s, t) g_test_add (s, TestData, NULL, (TCFunc)test_pty_create, (TCFunc)t, (TCFunc)test_pty_cleanup);
int main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
TESTCASE_PTY ("/MM/QCDM/Verinfo", test_verinfo);
TESTCASE_PTY ("/MM/QCDM/Sierra-Cns-Rejected", test_sierra_cns_rejected);
TESTCASE_PTY ("/MM/QCDM/Random-Data-Rejected", test_random_data_rejected);
TESTCASE_PTY ("/MM/QCDM/Leading-Frame-Markers", test_leading_frame_markers);
return g_test_run ();
}