blob: e0d164312d3fdb44779494fc1ceabb9e612a656e [file] [log] [blame] [edit]
/*
* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#define _POSIX_C_SOURCE 201108L
#define _XOPEN_SOURCE 600
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <glib.h>
#include <dbus/dbus-glib.h>
GIOChannel* ioc;
int masterfd;
typedef struct {
GRegex *command;
char *reply; // generic text
char *responsetext; // ERROR, +CMS ERROR, etc.
} Pattern;
typedef struct _FakeModem {
GObject parent;
gboolean echo;
gboolean verbose;
GPtrArray *patterns;
} FakeModem;
typedef struct _FakeModemClass
{
GObjectClass parent_class;
} FakeModemClass;
GType fakemodem_get_type (void) G_GNUC_CONST;
#define FAKEMODEM_TYPE (fake_modem_get_type ())
#define FAKEMODEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FAKEMODEM_TYPE, FakeModem))
G_DEFINE_TYPE (FakeModem, fake_modem, G_TYPE_OBJECT)
static void
fake_modem_init (FakeModem* self)
{
self->echo = TRUE;
self->verbose = TRUE;
self->patterns = NULL;
}
static void
fake_modem_class_init (FakeModemClass* self)
{
}
static gboolean master_read (GIOChannel *source, GIOCondition condition,
gpointer data);
static const gchar *handle_cmd (FakeModem *fakemodem, const gchar *cmd);
static gboolean send_unsolicited (FakeModem* fakemodem, const gchar* text);
static gboolean set_response (FakeModem* fakemodem, const gchar* command,
const gchar* reply, const gchar* response);
static gboolean remove_response (FakeModem* fakemodem, const gchar* command);
#include "fakemodem-dbus.h"
GPtrArray *
parse_pattern_files(char **pattern_files, GError **error)
{
gint linenum;
GRegex *skip, *parts;
GPtrArray *patterns;
int i;
patterns = g_ptr_array_new();
skip = g_regex_new ("^\\s*(#.*)?$", 0, 0, error);
if (skip == NULL)
return NULL;
parts = g_regex_new ("^(\\S+)\\s*(\"([^\"]*)\")?\\s*(.*)$", 0, 0, error);
if (parts == NULL)
return NULL;
for (i = 0 ; pattern_files[i] != NULL; i++) {
GIOChannel *pf;
gchar *pattern_file;
gchar *line;
gsize len, term;
pattern_file = pattern_files[i];
pf = g_io_channel_new_file (pattern_file, "r", error);
if (pf == NULL)
return NULL;
linenum = 0;
while (g_io_channel_read_line (pf, &line, &len, &term, error) ==
G_IO_STATUS_NORMAL) {
/* Don't need the terminator */
line[term] = '\0';
linenum++;
if (!g_regex_match (skip, line, 0, NULL)) {
GMatchInfo *info;
gboolean ret;
gchar *command, *responsetext;
ret = g_regex_match (parts, line, 0, &info);
if (ret) {
Pattern *pat;
pat = g_malloc (sizeof (*pat));
command = g_match_info_fetch (info, 1);
pat->command = g_regex_new (command,
G_REGEX_ANCHORED |
G_REGEX_CASELESS |
G_REGEX_RAW |
G_REGEX_OPTIMIZE,
0,
error);
g_free (command);
if (pat->command == NULL) {
printf ("error: %s\n", (*error)->message);
g_error_free (*error);
*error = NULL;
}
responsetext = g_match_info_fetch (info, 3);
if (strlen (responsetext) == 0) {
g_free (responsetext);
responsetext = NULL;
}
pat->responsetext = responsetext;
pat->reply = g_match_info_fetch (info, 4);
while (pat->reply[strlen (pat->reply) - 1] == '\\') {
gchar *origstr;
pat->reply[strlen (pat->reply) - 1] = '\0';
g_free (line); /* probably invalidates fields in 'info' */
g_io_channel_read_line (pf, &line, &len, &term, error);
line[term] = '\0';
linenum++;
origstr = pat->reply;
pat->reply = g_strjoin ("\r\n", origstr, line, NULL);
g_free (origstr);
}
g_ptr_array_add (patterns, pat);
} else {
printf (" Line %d '%s' was not parsed"
" as a command-response pattern\n",
linenum, line);
}
g_match_info_free (info);
}
g_free (line);
}
g_io_channel_shutdown (pf, TRUE, NULL);
}
g_regex_unref (skip);
g_regex_unref (parts);
return patterns;
}
#define FM_DBUS_SERVICE "org.chromium.FakeModem"
static DBusGProxy *
create_dbus_proxy (DBusGConnection *bus)
{
DBusGProxy *proxy;
GError *err = NULL;
int request_name_result;
proxy = dbus_g_proxy_new_for_name (bus,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus");
if (!dbus_g_proxy_call (proxy, "RequestName", &err,
G_TYPE_STRING, FM_DBUS_SERVICE,
G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
G_TYPE_INVALID,
G_TYPE_UINT, &request_name_result,
G_TYPE_INVALID)) {
g_print ("Could not acquire the %s service.\n"
" Message: '%s'\n", FM_DBUS_SERVICE, err->message);
g_error_free (err);
g_object_unref (proxy);
proxy = NULL;
} else if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
g_print ("Could not acquire the " FM_DBUS_SERVICE
" service as it is already taken. Return: %d\n",
request_name_result);
g_object_unref (proxy);
proxy = NULL;
} else {
dbus_g_proxy_add_signal (proxy, "NameOwnerChanged",
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_INVALID);
}
return proxy;
}
int
main (int argc, char *argv[])
{
DBusGConnection *bus;
DBusGProxy *proxy;
GMainLoop* loop;
const char *slavedevice;
struct termios t;
FakeModem *fakemodem;
GOptionContext *opt_ctx;
char **pattern_files = NULL;
gboolean session = FALSE;
GError *err = NULL;
GOptionEntry entries[] = {
{ "patternfile", 0, 0, G_OPTION_ARG_STRING_ARRAY, &pattern_files,
"Path to pattern file", NULL},
{ "session", 0, 0, G_OPTION_ARG_NONE, &session,
"Bind to session bus", NULL},
{ "system", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &session,
"Bind to system bus (default)", NULL},
{ NULL }
};
#if !GLIB_CHECK_VERSION(2,35,0)
g_type_init ();
#endif
opt_ctx = g_option_context_new (NULL);
g_option_context_set_summary (opt_ctx,
"Emulate a modem with a set of "
"regexp-programmed responses.");
g_option_context_add_main_entries (opt_ctx, entries, NULL);
if (!g_option_context_parse (opt_ctx, &argc, &argv, &err)) {
g_warning ("%s\n", err->message);
g_error_free (err);
exit (1);
}
g_option_context_free (opt_ctx);
fakemodem = g_object_new (FAKEMODEM_TYPE, NULL);
if (pattern_files) {
fakemodem->patterns = parse_pattern_files (pattern_files, &err);
if (fakemodem->patterns == NULL) {
g_warning ("%s\n", err->message);
g_error_free (err);
exit (1);
}
} else
fakemodem->patterns = g_ptr_array_sized_new (0);
loop = g_main_loop_new (NULL, FALSE);
dbus_g_object_type_install_info (FAKEMODEM_TYPE,
&dbus_glib_fakemodem_object_info);
err = NULL;
if (session)
bus = dbus_g_bus_get (DBUS_BUS_SESSION, &err);
else
bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err);
if (bus == NULL) {
g_warning ("%s\n", err->message);
g_error_free (err);
exit (1);
}
proxy = create_dbus_proxy (bus);
if (!proxy)
exit (1);
dbus_g_connection_register_g_object (bus,
"/",
G_OBJECT (fakemodem));
masterfd = posix_openpt (O_RDWR | O_NOCTTY);
if (masterfd == -1
|| grantpt (masterfd) == -1
|| unlockpt (masterfd) == -1
|| (slavedevice = ptsname (masterfd)) == NULL)
exit (1);
printf ("%s\n", slavedevice);
fflush (stdout);
/* Echo is actively harmful here */
tcgetattr (masterfd, &t);
t.c_lflag &= ~ECHO;
tcsetattr (masterfd, TCSANOW, &t);
ioc = g_io_channel_unix_new (masterfd);
g_io_channel_set_encoding (ioc, NULL, NULL);
g_io_channel_set_line_term (ioc, "\r", 1);
g_io_add_watch (ioc, G_IO_IN, master_read, fakemodem);
g_main_loop_run (loop);
g_main_loop_unref (loop);
g_object_unref (fakemodem);
return 0;
}
/*
* &?[A-CE-RT-Z][0-9]*
* S[0-9]+?
* S[0-9]+=(([0-9A-F]+|"[^"]*")?,)+
*/
/*
* action +[A-Z][A-Z0-9%-./:_]{0,15}
* test +[A-Z][A-Z0-9%-./:_]{0,15}=?
* get +[A-Z][A-Z0-9%-./:_]{0,15}?
* set +[A-Z][A-Z0-9%-./:_]{0,15}=(([0-9A-F]+|"[^"]*")?,)+
*/
#define VALUE "([0-9A-F]+|\"[^\"]*\")"
#define CVALUE VALUE "?(," VALUE "?)*"
static char *command_patterns[] =
{"\\s*(&?[A-CE-RT-Z][0-9]*)",
"\\s*(S[0-9]+\\?)",
"\\s*(S[0-9]+=" CVALUE ")",
/* ATD... (dial string) handling is missing */
"\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}=\\?)",
"\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}=" CVALUE ")",
"\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}(\\?)?)",
};
#undef VALUE
#undef CVALUE
static gboolean master_read (GIOChannel *source, GIOCondition condition,
gpointer data)
{
FakeModem *fakemodem = data;
gchar *line, *next;
const gchar *response;
gsize term;
GError *error = NULL;
GIOStatus status;
int i, rval;
static GPtrArray *commands;
if (commands == NULL) {
int n;
n = sizeof (command_patterns) / sizeof (command_patterns[0]);
commands = g_ptr_array_sized_new (n);
for (i = 0 ; i < n ; i++) {
GRegex *re = g_regex_new (command_patterns[i],
G_REGEX_CASELESS |
G_REGEX_ANCHORED |
G_REGEX_RAW |
G_REGEX_OPTIMIZE,
0,
&error);
if (re == NULL) {
g_warning ("Couldn't generate command regex: %s\n", error->message);
g_error_free (error);
exit (1);
}
g_ptr_array_add (commands, re);
}
}
status = g_io_channel_read_line (source, &line, NULL, &term, &error);
if (status == G_IO_STATUS_ERROR)
return FALSE;
line[term] = '\0';
printf ("Line: '%s'\n", line);
if (fakemodem->echo) {
rval = write (masterfd, line, term);
assert(term == rval);
rval = write (masterfd, "\r\n", 2);
assert(2 == rval);
}
if (g_ascii_strncasecmp (line, "AT", 2) != 0) {
if (line[0] == '\0')
goto out;
response = "ERROR";
goto done;
}
response = NULL;
next = line + 2;
while (!response && *next) {
for (i = 0 ; i < commands->len; i++) {
GMatchInfo *info;
if (g_regex_match (g_ptr_array_index (commands, i), next, 0, &info)) {
gint start, end;
gchar *cmd;
g_match_info_fetch_pos (info, 1, &start, &end);
cmd = g_strndup (next + start, end - start);
response = handle_cmd (fakemodem, cmd);
g_free (cmd);
g_match_info_free (info);
next += end;
break;
}
g_match_info_free (info);
}
if (i == commands->len) {
response = "ERROR";
break;
}
}
done:
if (fakemodem->verbose) {
gchar *rstr;
if (response == NULL)
response = "OK";
rstr = g_strdup_printf("\r\n%s\r\n", response);
rval = write (masterfd, rstr, strlen (rstr));
assert(strlen(rstr) == rval);
g_free (rstr);
} else {
gchar *rstr;
rstr = g_strdup_printf("%s\n", response);
rval = write (masterfd, rstr, strlen (rstr));
assert(strlen(rstr) == rval);
g_free (rstr);
}
out:
g_free (line);
return TRUE;
}
static const gchar *
handle_cmd(FakeModem *fakemodem, const gchar *cmd)
{
guint i;
Pattern *pat = NULL;
printf (" Cmd: '%s'\n", cmd);
if (toupper (cmd[0]) >= 'A' && toupper (cmd[0]) <= 'Z') {
switch (toupper (cmd[0])) {
case 'E':
if (cmd[1] == '0')
fakemodem->echo = FALSE;
else if (cmd[1] == '1')
fakemodem->echo = TRUE;
else
return "ERROR";
return "OK";
case 'V':
if (cmd[1] == '0')
fakemodem->verbose = FALSE;
else if (cmd[1] == '1')
fakemodem->verbose = TRUE;
else
return "ERROR";
return "OK";
case 'Z':
fakemodem->echo = TRUE;
fakemodem->verbose = TRUE;
return "OK";
}
}
for (i = 0 ; i < fakemodem->patterns->len; i++) {
pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
if (g_regex_match (pat->command, cmd, 0, NULL)) {
break;
}
}
if (i == fakemodem->patterns->len)
return "ERROR";
if (pat->reply && pat->reply[0]) {
int rval;
printf (" Reply: '%s'\n", pat->reply);
rval = write (masterfd, pat->reply, strlen (pat->reply));
assert(strlen(pat->reply) == rval);
rval = write (masterfd, "\r\n", 2);
assert(2 == rval);
}
return pat->responsetext; /* NULL implies "OK" and keep processing */
}
static gboolean
send_unsolicited (FakeModem *fakemodem, const gchar* text)
{
int rval;
rval = write (masterfd, "\r\n", 2);
rval = write (masterfd, text, strlen (text));
assert(strlen(text) == rval);
rval = write (masterfd, "\r\n", 2);
assert(2 == rval);
return TRUE;
}
static gboolean
set_response (FakeModem *fakemodem,
const gchar* command,
const gchar* reply,
const gchar* response)
{
int i;
Pattern *pat;
if (strlen (response) == 0)
response = "OK";
for (i = 0 ; i < fakemodem->patterns->len; i++) {
pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
if (strcmp (g_regex_get_pattern (pat->command), command) == 0) {
g_free (pat->reply);
pat->reply = g_strdup (reply);
g_free (pat->responsetext);
pat->responsetext = g_strdup (response);
break;
}
}
if (i == fakemodem->patterns->len) {
GError *error = NULL;
pat = g_malloc (sizeof (*pat));
pat->command = g_regex_new (command,
G_REGEX_ANCHORED |
G_REGEX_CASELESS |
G_REGEX_RAW |
G_REGEX_OPTIMIZE,
0,
&error);
if (pat->command == NULL) {
printf ("error: %s\n", error->message);
g_free (pat);
return FALSE;
}
pat->responsetext = g_strdup (response);
pat->reply = g_strdup (reply);
g_ptr_array_add (fakemodem->patterns, pat);
}
return TRUE;
}
static gboolean
remove_response (FakeModem* fakemodem, const gchar* command)
{
int i;
gboolean found;
Pattern *pat;
found = FALSE;
for (i = 0 ; i < fakemodem->patterns->len; i++) {
pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
if (strcmp (g_regex_get_pattern (pat->command), command) == 0) {
g_ptr_array_remove_index (fakemodem->patterns, i);
found = TRUE;
break;
}
}
return found;
}