Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      3  * Use of this source code is governed by a BSD-style license that can be
      4  * found in the LICENSE file.
      5  */
      6 
      7 #define _POSIX_C_SOURCE 201108L
      8 #define _XOPEN_SOURCE 600
      9 
     10 #include <assert.h>
     11 #include <ctype.h>
     12 #include <fcntl.h>
     13 #include <stdio.h>
     14 #include <stdlib.h>
     15 #include <string.h>
     16 #include <termios.h>
     17 #include <unistd.h>
     18 
     19 #include <glib.h>
     20 #include <dbus/dbus-glib.h>
     21 
     22 GIOChannel* ioc;
     23 int masterfd;
     24 
     25 typedef struct {
     26   GRegex *command;
     27   char *reply; // generic text
     28   char *responsetext; // ERROR, +CMS ERROR, etc.
     29 } Pattern;
     30 
     31 typedef struct _FakeModem {
     32   GObject parent;
     33   gboolean echo;
     34   gboolean verbose;
     35   GPtrArray *patterns;
     36 } FakeModem;
     37 
     38 typedef struct _FakeModemClass
     39 {
     40   GObjectClass parent_class;
     41 } FakeModemClass;
     42 
     43 GType fakemodem_get_type (void) G_GNUC_CONST;
     44 
     45 #define FAKEMODEM_TYPE         (fake_modem_get_type ())
     46 #define FAKEMODEM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), FAKEMODEM_TYPE, FakeModem))
     47 
     48 G_DEFINE_TYPE (FakeModem, fake_modem, G_TYPE_OBJECT)
     49 
     50 static void
     51 fake_modem_init (FakeModem* self)
     52 {
     53 
     54   self->echo = TRUE;
     55   self->verbose = TRUE;
     56   self->patterns = NULL;
     57 }
     58 
     59 static void
     60 fake_modem_class_init (FakeModemClass* self)
     61 {
     62 }
     63 
     64 static gboolean master_read (GIOChannel *source, GIOCondition condition,
     65                              gpointer data);
     66 
     67 static const gchar *handle_cmd (FakeModem *fakemodem, const gchar *cmd);
     68 
     69 static gboolean send_unsolicited (FakeModem* fakemodem, const gchar* text);
     70 static gboolean set_response (FakeModem* fakemodem, const gchar* command,
     71                               const gchar* reply, const gchar* response);
     72 static gboolean remove_response (FakeModem* fakemodem, const gchar* command);
     73 
     74 #include "fakemodem-dbus.h"
     75 
     76 GPtrArray *
     77 parse_pattern_files(char **pattern_files, GError **error)
     78 {
     79   gint linenum;
     80   GRegex *skip, *parts;
     81   GPtrArray *patterns;
     82   int i;
     83 
     84   patterns = g_ptr_array_new();
     85 
     86   skip = g_regex_new ("^\\s*(#.*)?$", 0, 0, error);
     87   if (skip == NULL)
     88     return NULL;
     89   parts = g_regex_new ("^(\\S+)\\s*(\"([^\"]*)\")?\\s*(.*)$", 0, 0, error);
     90   if (parts == NULL)
     91     return NULL;
     92 
     93   for (i = 0 ; pattern_files[i] != NULL; i++) {
     94     GIOChannel *pf;
     95     gchar *pattern_file;
     96     gchar *line;
     97     gsize len, term;
     98 
     99     pattern_file = pattern_files[i];
    100 
    101     pf = g_io_channel_new_file (pattern_file, "r", error);
    102     if (pf == NULL)
    103       return NULL;
    104 
    105     linenum = 0;
    106     while (g_io_channel_read_line (pf, &line, &len, &term, error) ==
    107            G_IO_STATUS_NORMAL) {
    108       /* Don't need the terminator */
    109       line[term] = '\0';
    110       linenum++;
    111 
    112       if (!g_regex_match (skip, line, 0, NULL)) {
    113         GMatchInfo *info;
    114         gboolean ret;
    115         gchar *command, *responsetext;
    116         ret = g_regex_match (parts, line, 0, &info);
    117         if (ret) {
    118           Pattern *pat;
    119           pat = g_malloc (sizeof (*pat));
    120           command = g_match_info_fetch (info, 1);
    121           pat->command = g_regex_new (command,
    122                                       G_REGEX_ANCHORED |
    123                                       G_REGEX_CASELESS |
    124                                       G_REGEX_RAW |
    125                                       G_REGEX_OPTIMIZE,
    126                                       0,
    127                                       error);
    128           g_free (command);
    129           if (pat->command == NULL) {
    130             printf ("error: %s\n", (*error)->message);
    131             g_error_free (*error);
    132             *error = NULL;
    133           }
    134           responsetext = g_match_info_fetch (info, 3);
    135           if (strlen (responsetext) == 0) {
    136             g_free (responsetext);
    137             responsetext = NULL;
    138           }
    139           pat->responsetext = responsetext;
    140           pat->reply = g_match_info_fetch (info, 4);
    141           while (pat->reply[strlen (pat->reply) - 1] == '\\') {
    142             gchar *origstr;
    143             pat->reply[strlen (pat->reply) - 1] = '\0';
    144             g_free (line); /* probably invalidates fields in 'info' */
    145             g_io_channel_read_line (pf, &line, &len, &term, error);
    146             line[term] = '\0';
    147             linenum++;
    148             origstr = pat->reply;
    149             pat->reply = g_strjoin ("\r\n", origstr, line, NULL);
    150             g_free (origstr);
    151           }
    152           g_ptr_array_add (patterns, pat);
    153         } else {
    154           printf (" Line %d '%s' was not parsed"
    155                   " as a command-response pattern\n",
    156                   linenum, line);
    157         }
    158         g_match_info_free (info);
    159       }
    160       g_free (line);
    161     }
    162     g_io_channel_shutdown (pf, TRUE, NULL);
    163   }
    164 
    165   g_regex_unref (skip);
    166   g_regex_unref (parts);
    167 
    168   return patterns;
    169 }
    170 
    171 #define FM_DBUS_SERVICE "org.chromium.FakeModem"
    172 
    173 static DBusGProxy *
    174 create_dbus_proxy (DBusGConnection *bus)
    175 {
    176     DBusGProxy *proxy;
    177     GError *err = NULL;
    178     int request_name_result;
    179 
    180     proxy = dbus_g_proxy_new_for_name (bus,
    181                                        "org.freedesktop.DBus",
    182                                        "/org/freedesktop/DBus",
    183                                        "org.freedesktop.DBus");
    184 
    185     if (!dbus_g_proxy_call (proxy, "RequestName", &err,
    186                             G_TYPE_STRING, FM_DBUS_SERVICE,
    187                             G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
    188                             G_TYPE_INVALID,
    189                             G_TYPE_UINT, &request_name_result,
    190                             G_TYPE_INVALID)) {
    191         g_print ("Could not acquire the %s service.\n"
    192                  "  Message: '%s'\n", FM_DBUS_SERVICE, err->message);
    193 
    194         g_error_free (err);
    195         g_object_unref (proxy);
    196         proxy = NULL;
    197     } else if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
    198         g_print ("Could not acquire the " FM_DBUS_SERVICE
    199                  " service as it is already taken. Return: %d\n",
    200                  request_name_result);
    201 
    202         g_object_unref (proxy);
    203         proxy = NULL;
    204     } else {
    205         dbus_g_proxy_add_signal (proxy, "NameOwnerChanged",
    206                                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
    207                                  G_TYPE_INVALID);
    208     }
    209 
    210     return proxy;
    211 }
    212 
    213 int
    214 main (int argc, char *argv[])
    215 {
    216   DBusGConnection *bus;
    217   DBusGProxy *proxy;
    218   GMainLoop* loop;
    219   const char *slavedevice;
    220   struct termios t;
    221   FakeModem *fakemodem;
    222   GOptionContext *opt_ctx;
    223   char **pattern_files = NULL;
    224   gboolean session = FALSE;
    225   GError *err = NULL;
    226 
    227   GOptionEntry entries[] = {
    228     { "patternfile", 0, 0, G_OPTION_ARG_STRING_ARRAY, &pattern_files,
    229       "Path to pattern file", NULL},
    230     { "session", 0, 0, G_OPTION_ARG_NONE, &session,
    231       "Bind to session bus", NULL},
    232     { "system", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &session,
    233       "Bind to system bus (default)", NULL},
    234     { NULL }
    235   };
    236 
    237   #if !GLIB_CHECK_VERSION(2,35,0)
    238   g_type_init ();
    239   #endif
    240 
    241   opt_ctx = g_option_context_new (NULL);
    242   g_option_context_set_summary (opt_ctx,
    243                                 "Emulate a modem with a set of "
    244                                 "regexp-programmed responses.");
    245   g_option_context_add_main_entries (opt_ctx, entries, NULL);
    246   if (!g_option_context_parse (opt_ctx, &argc, &argv, &err)) {
    247     g_warning ("%s\n", err->message);
    248     g_error_free (err);
    249     exit (1);
    250   }
    251 
    252   g_option_context_free (opt_ctx);
    253 
    254   fakemodem = g_object_new (FAKEMODEM_TYPE, NULL);
    255   if (pattern_files) {
    256     fakemodem->patterns = parse_pattern_files (pattern_files, &err);
    257     if (fakemodem->patterns == NULL) {
    258       g_warning ("%s\n", err->message);
    259       g_error_free (err);
    260       exit (1);
    261     }
    262   } else
    263     fakemodem->patterns = g_ptr_array_sized_new (0);
    264 
    265   loop = g_main_loop_new (NULL, FALSE);
    266 
    267   dbus_g_object_type_install_info (FAKEMODEM_TYPE,
    268                                    &dbus_glib_fakemodem_object_info);
    269 
    270   err = NULL;
    271   if (session)
    272     bus = dbus_g_bus_get (DBUS_BUS_SESSION, &err);
    273   else
    274     bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err);
    275 
    276   if (bus == NULL) {
    277       g_warning ("%s\n", err->message);
    278       g_error_free (err);
    279       exit (1);
    280   }
    281 
    282   proxy = create_dbus_proxy (bus);
    283   if (!proxy)
    284     exit (1);
    285 
    286   dbus_g_connection_register_g_object (bus,
    287                                        "/",
    288                                        G_OBJECT (fakemodem));
    289 
    290   masterfd = posix_openpt (O_RDWR | O_NOCTTY);
    291 
    292   if (masterfd == -1
    293       || grantpt (masterfd) == -1
    294       || unlockpt (masterfd) == -1
    295       || (slavedevice = ptsname (masterfd)) == NULL)
    296     exit (1);
    297 
    298   printf ("%s\n", slavedevice);
    299   fflush (stdout);
    300 
    301   /* Echo is actively harmful here */
    302   tcgetattr (masterfd, &t);
    303   t.c_lflag &= ~ECHO;
    304   tcsetattr (masterfd, TCSANOW, &t);
    305 
    306   ioc = g_io_channel_unix_new (masterfd);
    307   g_io_channel_set_encoding (ioc, NULL, NULL);
    308   g_io_channel_set_line_term (ioc, "\r", 1);
    309   g_io_add_watch (ioc, G_IO_IN, master_read, fakemodem);
    310 
    311   g_main_loop_run (loop);
    312 
    313   g_main_loop_unref (loop);
    314 
    315   g_object_unref (fakemodem);
    316   return 0;
    317 }
    318 
    319 
    320 /*
    321  * &?[A-CE-RT-Z][0-9]*
    322  * S[0-9]+?
    323  * S[0-9]+=(([0-9A-F]+|"[^"]*")?,)+
    324  */
    325 
    326 /*
    327  * action +[A-Z][A-Z0-9%-./:_]{0,15}
    328  * test   +[A-Z][A-Z0-9%-./:_]{0,15}=?
    329  * get    +[A-Z][A-Z0-9%-./:_]{0,15}?
    330  * set    +[A-Z][A-Z0-9%-./:_]{0,15}=(([0-9A-F]+|"[^"]*")?,)+
    331  */
    332 
    333 
    334 #define VALUE "([0-9A-F]+|\"[^\"]*\")"
    335 #define CVALUE VALUE "?(," VALUE "?)*"
    336 static char *command_patterns[] =
    337 {"\\s*(&?[A-CE-RT-Z][0-9]*)",
    338  "\\s*(S[0-9]+\\?)",
    339  "\\s*(S[0-9]+=" CVALUE ")",
    340  /* ATD... (dial string) handling is missing */
    341  "\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}=\\?)",
    342  "\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}=" CVALUE ")",
    343  "\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}(\\?)?)",
    344 };
    345 
    346 #undef VALUE
    347 #undef CVALUE
    348 
    349 static gboolean master_read (GIOChannel *source, GIOCondition condition,
    350                              gpointer data)
    351 {
    352   FakeModem *fakemodem = data;
    353   gchar *line, *next;
    354   const gchar *response;
    355   gsize term;
    356   GError *error = NULL;
    357   GIOStatus status;
    358   int i, rval;
    359 
    360   static GPtrArray *commands;
    361 
    362   if (commands == NULL) {
    363     int n;
    364     n = sizeof (command_patterns) / sizeof (command_patterns[0]);
    365     commands = g_ptr_array_sized_new (n);
    366     for (i = 0 ; i < n ; i++) {
    367       GRegex *re = g_regex_new (command_patterns[i],
    368                                 G_REGEX_CASELESS |
    369                                 G_REGEX_ANCHORED |
    370                                 G_REGEX_RAW |
    371                                 G_REGEX_OPTIMIZE,
    372                                 0,
    373                                 &error);
    374       if (re == NULL) {
    375         g_warning ("Couldn't generate command regex: %s\n", error->message);
    376         g_error_free (error);
    377         exit (1);
    378       }
    379       g_ptr_array_add (commands, re);
    380     }
    381   }
    382 
    383   status = g_io_channel_read_line (source, &line, NULL, &term, &error);
    384   if (status == G_IO_STATUS_ERROR)
    385     return FALSE;
    386   line[term] = '\0';
    387 
    388   printf ("Line: '%s'\n", line);
    389 
    390   if (fakemodem->echo) {
    391     rval = write (masterfd, line, term);
    392     assert(term == rval);
    393     rval = write (masterfd, "\r\n", 2);
    394     assert(2 == rval);
    395   }
    396 
    397   if (g_ascii_strncasecmp (line, "AT", 2) != 0) {
    398     if (line[0] == '\0')
    399       goto out;
    400     response = "ERROR";
    401     goto done;
    402   }
    403 
    404   response = NULL;
    405   next = line + 2;
    406 
    407   while (!response && *next) {
    408     for (i = 0 ; i < commands->len; i++) {
    409       GMatchInfo *info;
    410       if (g_regex_match (g_ptr_array_index (commands, i), next, 0, &info)) {
    411         gint start, end;
    412         gchar *cmd;
    413         g_match_info_fetch_pos (info, 1, &start, &end);
    414         cmd = g_strndup (next + start, end - start);
    415         response = handle_cmd (fakemodem, cmd);
    416         g_free (cmd);
    417         g_match_info_free (info);
    418         next += end;
    419         break;
    420       }
    421       g_match_info_free (info);
    422     }
    423     if (i == commands->len) {
    424       response = "ERROR";
    425       break;
    426     }
    427   }
    428 
    429 
    430 done:
    431   if (fakemodem->verbose) {
    432     gchar *rstr;
    433     if (response == NULL)
    434       response = "OK";
    435     rstr = g_strdup_printf("\r\n%s\r\n", response);
    436     rval = write (masterfd, rstr, strlen (rstr));
    437     assert(strlen(rstr) == rval);
    438     g_free (rstr);
    439   } else {
    440     gchar *rstr;
    441     rstr = g_strdup_printf("%s\n", response);
    442     rval = write (masterfd, rstr, strlen (rstr));
    443     assert(strlen(rstr) == rval);
    444     g_free (rstr);
    445   }
    446 
    447 out:
    448   g_free (line);
    449   return TRUE;
    450 }
    451 
    452 static const gchar *
    453 handle_cmd(FakeModem *fakemodem, const gchar *cmd)
    454 {
    455   guint i;
    456   Pattern *pat = NULL;
    457 
    458   printf (" Cmd:  '%s'\n", cmd);
    459 
    460   if (toupper (cmd[0]) >= 'A' && toupper (cmd[0]) <= 'Z') {
    461     switch (toupper (cmd[0])) {
    462       case 'E':
    463         if (cmd[1] == '0')
    464           fakemodem->echo = FALSE;
    465         else if (cmd[1] == '1')
    466           fakemodem->echo = TRUE;
    467         else
    468           return "ERROR";
    469         return "OK";
    470       case 'V':
    471         if (cmd[1] == '0')
    472           fakemodem->verbose = FALSE;
    473         else if (cmd[1] == '1')
    474           fakemodem->verbose = TRUE;
    475         else
    476           return "ERROR";
    477         return "OK";
    478       case 'Z':
    479         fakemodem->echo = TRUE;
    480         fakemodem->verbose = TRUE;
    481         return "OK";
    482     }
    483   }
    484 
    485   for (i = 0 ; i < fakemodem->patterns->len; i++) {
    486     pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
    487     if (g_regex_match (pat->command, cmd, 0, NULL)) {
    488       break;
    489     }
    490   }
    491 
    492   if (i == fakemodem->patterns->len)
    493     return "ERROR";
    494 
    495   if (pat->reply && pat->reply[0]) {
    496     int rval;
    497     printf (" Reply: '%s'\n", pat->reply);
    498     rval = write (masterfd, pat->reply, strlen (pat->reply));
    499     assert(strlen(pat->reply) == rval);
    500     rval = write (masterfd, "\r\n", 2);
    501     assert(2 == rval);
    502   }
    503 
    504   return pat->responsetext; /* NULL implies "OK" and keep processing */
    505 }
    506 
    507 
    508 static gboolean
    509 send_unsolicited (FakeModem *fakemodem, const gchar* text)
    510 {
    511   int rval;
    512 
    513   rval = write (masterfd, "\r\n", 2);
    514   rval = write (masterfd, text, strlen (text));
    515   assert(strlen(text) == rval);
    516   rval = write (masterfd, "\r\n", 2);
    517   assert(2 == rval);
    518 
    519   return TRUE;
    520 }
    521 
    522 static gboolean
    523 set_response (FakeModem *fakemodem,
    524               const gchar* command,
    525               const gchar* reply,
    526               const gchar* response)
    527 {
    528   int i;
    529   Pattern *pat;
    530 
    531   if (strlen (response) == 0)
    532     response = "OK";
    533 
    534   for (i = 0 ; i < fakemodem->patterns->len; i++) {
    535     pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
    536     if (strcmp (g_regex_get_pattern (pat->command), command) == 0) {
    537       g_free (pat->reply);
    538       pat->reply = g_strdup (reply);
    539       g_free (pat->responsetext);
    540       pat->responsetext = g_strdup (response);
    541       break;
    542     }
    543   }
    544 
    545   if (i == fakemodem->patterns->len) {
    546     GError *error = NULL;
    547     pat = g_malloc (sizeof (*pat));
    548     pat->command = g_regex_new (command,
    549                                 G_REGEX_ANCHORED |
    550                                 G_REGEX_CASELESS |
    551                                 G_REGEX_RAW |
    552                                 G_REGEX_OPTIMIZE,
    553                                 0,
    554                                 &error);
    555     if (pat->command == NULL) {
    556       printf ("error: %s\n", error->message);
    557       g_free (pat);
    558       return FALSE;
    559     }
    560     pat->responsetext = g_strdup (response);
    561     pat->reply = g_strdup (reply);
    562     g_ptr_array_add (fakemodem->patterns, pat);
    563   }
    564 
    565   return TRUE;
    566 }
    567 
    568 static gboolean
    569 remove_response (FakeModem* fakemodem, const gchar* command)
    570 {
    571   int i;
    572   gboolean found;
    573   Pattern *pat;
    574 
    575   found = FALSE;
    576   for (i = 0 ; i < fakemodem->patterns->len; i++) {
    577     pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
    578     if (strcmp (g_regex_get_pattern (pat->command), command) == 0) {
    579       g_ptr_array_remove_index (fakemodem->patterns, i);
    580       found = TRUE;
    581       break;
    582     }
    583   }
    584 
    585   return found;
    586 }
    587