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