C++程序  |  587行  |  15.25 KB

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