/*
 * modem.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 */

#include "ppp.h"
#include <termios.h>
#include "chat.h"
#include "phys.h"
#include "modem.h"
#include "ngfunc.h"
#include "lcp.h"
#include "event.h"
#include "util.h"
#include "log.h"

#include <netgraph/ng_message.h>
#ifdef __DragonFly__
#include <netgraph/socket/ng_socket.h>
#include <netgraph/async/ng_async.h>
#include <netgraph/tty/ng_tty.h>
#else
#include <netgraph/ng_socket.h>
#include <netgraph/ng_async.h>
#include <netgraph/ng_tty.h>
#endif
#include <netgraph.h>

/*
 * DEFINITIONS
 */

#ifndef NETGRAPHDISC
  #define NETGRAPHDISC			7	/* XXX */
#endif

  #define MODEM_MTU			1600
  #define MODEM_MRU			1600

  #define MODEM_REOPEN_PAUSE		8
  #define MODEM_MIN_CLOSE_TIME		3
  #define MODEM_CONNECT_TIMEOUT		30
  #define MODEM_CHECK_INTERVAL		1
  #define MODEM_DEFAULT_SPEED		115200
  #define MODEM_MAX_SCRIPT_NAME		32
  #define MODEM_MAX_QUEUE		8192
  #define MODEM_ERR_REPORT_INTERVAL	60

  #define MODEM_IDLE_RESULT_ANSWER	"answer"
  #define MODEM_IDLE_RESULT_RINGBACK	"ringback"

  /* Special chat script variables we set/use */
  #define CHAT_VAR_LOGIN		"$Login"
  #define CHAT_VAR_PASSWORD		"$Password"
  #define CHAT_VAR_DEVICE		"$modemDevice"
  #define CHAT_VAR_IDLE_RESULT		"$IdleResult"
  #define CHAT_VAR_CONNECT_SPEED	"$ConnectionSpeed"

  /* Nominal link parameters */
  #define MODEM_DEFAULT_BANDWIDTH	28800	/* ~33.6 modem */
  #define MODEM_DEFAULT_LATENCY		10000	/* 10ms */

  /* Modem device state */
  struct modeminfo {
    int			fd;			/* Device file desc, or -1 */
    int			csock;			/* netgraph control socket */
    int			speed;			/* Port speed */
    u_int		watch;			/* Signals to watch */
    char		device[20];		/* Serial device name */
    char		ttynode[NG_NODELEN + 1];	/* TTY node name */
    char		connScript[CHAT_MAX_LABEL];	/* Connect script */
    char		idleScript[CHAT_MAX_LABEL];	/* Idle script */
    struct pppTimer	checkTimer;		/* Timer to check pins */
    struct pppTimer	reportTimer;		/* Timer to report errs */
    struct pppTimer	startTimer;		/* Timer for ModemStart() */
    struct optinfo	options;		/* Binary options */
    struct ng_async_cfg	acfg;			/* ng_async node config */
    ChatInfo		chat;			/* Chat script state */
    time_t		lastClosed;		/* Last time device closed */
    u_char		opened:1;		/* We have been opened */
    u_char		originated:1;		/* We originated current call */
    u_char		answering:1;		/* $IdleResult was "answer" */
  };
  typedef struct modeminfo	*ModemInfo;

  /* Set menu options */
  enum {
    SET_DEVICE,
    SET_SPEED,
    SET_CSCRIPT,
    SET_ISCRIPT,
    SET_SCRIPT_VAR,
    SET_WATCH,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int		ModemInit(PhysInfo p);
  static void		ModemOpen(PhysInfo p);
  static void		ModemClose(PhysInfo p);
  static int		ModemSetAccm(PhysInfo p, u_int32_t xmit, u_int32_t recv);
  static void		ModemStat(Context ctx);
  static int		ModemOriginated(PhysInfo p);
  static int		ModemIsSync(PhysInfo p);
  static int		ModemPeerAddr(PhysInfo p, void *buf, int buf_len);

  static void		ModemStart(void *arg);
  static void		ModemDoClose(PhysInfo p, int opened);

  /* Chat callbacks */
  static int		ModemChatSetBaudrate(void *arg, int baud);
  static void		ModemChatLog(void *arg,
				int level, const char *fmt, ...);
  static void		*ModemChatMalloc(void *arg, size_t size);
  static void		ModemChatFree(void *arg, void *mem);
  static void		ModemChatConnectResult(void *arg,
				int rslt, const char *msg);
  static void		ModemChatIdleResult(void *arg, int rslt,
				const char *msg);

  static int		ModemSetCommand(Context ctx, int ac, char *av[], void *arg);
  static int		ModemInstallNodes(PhysInfo p);
  static int		ModemGetNgStats(PhysInfo p, struct ng_async_stat *sp);

  static void		ModemCheck(void *arg);
  static void		ModemErrorCheck(void *arg);

/*
 * GLOBAL VARIABLES
 */

  const struct phystype gModemPhysType = {
    .name		= "modem",
    .minReopenDelay	= MODEM_REOPEN_PAUSE,
    .mtu		= MODEM_MTU,
    .mru		= MODEM_MRU,
    .init		= ModemInit,
    .open		= ModemOpen,
    .close		= ModemClose,
    .showstat		= ModemStat,
    .originate		= ModemOriginated,
    .issync		= ModemIsSync,
    .setaccm 		= ModemSetAccm,
    .peeraddr		= ModemPeerAddr,
    .callingnum		= NULL,
    .callednum		= NULL,
  };

  const struct cmdtab ModemSetCmds[] = {
    { "device name",			"Set modem device",
      ModemSetCommand, NULL, (void *) SET_DEVICE },
    { "speed port-speed",		"Set modem speed",
      ModemSetCommand, NULL, (void *) SET_SPEED },
    { "script [label]",			"Set connect script",
      ModemSetCommand, NULL, (void *) SET_CSCRIPT },
    { "idle-script [label]",		"Set idle script",
      ModemSetCommand, NULL, (void *) SET_ISCRIPT },
    { "var $var string",		"Set script variable",
      ModemSetCommand, NULL, (void *) SET_SCRIPT_VAR },
    { "watch [+|-cd] [+|-dsr]", 	"Set signals to monitor",
      ModemSetCommand, NULL, (void *) SET_WATCH },
    { NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static int	gSpeedList[] = {
    50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 
    38400, 7200, 14400, 28800, 57600, 76800, 115200, 230400, 460800, 921600, -1
  };

/*
 * ModemInit()
 *
 * Allocate and initialize device private info
 */

static int
ModemInit(PhysInfo p)
{
  char		defSpeed[32];
  ModemInfo	m;

  m = (ModemInfo) (p->info = Malloc(MB_PHYS, sizeof(*m)));
  m->watch = TIOCM_CAR;
  m->chat = ChatInit(p, ModemChatSetBaudrate,
		ModemChatLog, ModemChatMalloc, ModemChatFree);
  m->fd = -1;
  m->opened = FALSE;

  if (p->link) {
    /* Set nominal link speed and bandwith for a modem connection */
    p->link->latency = MODEM_DEFAULT_LATENCY;
    p->link->bandwidth = MODEM_DEFAULT_BANDWIDTH;
  }

    /* Set default speed */
    m->speed = MODEM_DEFAULT_SPEED;
    snprintf(defSpeed, sizeof(defSpeed), "%d", m->speed);
    ChatPresetVar(m->chat, CHAT_VAR_BAUDRATE, defSpeed);
    return(0);
}

/*
 * ModemOpen()
 */

static void
ModemOpen(PhysInfo p)
{
  ModemInfo	const m = (ModemInfo) p->info;

  assert(!m->opened);
  m->opened = TRUE;
  if (m->fd >= 0) {			/* Device is already open.. */
    if (m->answering) {			/* We just answered a call */
      m->originated = FALSE;
      m->answering = FALSE;
      ModemChatConnectResult(p, TRUE, NULL);
    } else
      ModemDoClose(p, TRUE);		/* Stop idle script then dial back */
  } else
    ModemStart(p);			/* Open device and try to dial */
}

/*
 * ModemStart()
 */

static void
ModemStart(void *arg)
{
  PhysInfo		const p = (PhysInfo) arg;
  ModemInfo		const m = (ModemInfo) p->info;
  const time_t		now = time(NULL);
  char			password[AUTH_MAX_PASSWORD];
  FILE			*scriptfp;

  /* If we're idle, and there's no idle script, there's nothing to do */
  assert(!m->answering);
  TimerStop(&m->startTimer);
  if (!m->opened && !*m->idleScript)
    return;

  /* Avoid brief hang from kernel enforcing minimum DTR hold time */
  if (now - m->lastClosed < MODEM_MIN_CLOSE_TIME) {
    TimerInit(&m->startTimer, "ModemStart",
      (MODEM_MIN_CLOSE_TIME - (now - m->lastClosed)) * SECONDS, ModemStart, p);
    TimerStart(&m->startTimer);
    return;
  }

  /* Open and configure serial port */
  if ((m->fd = OpenSerialDevice(p->name, m->device, m->speed)) < 0)
    goto fail;

  /* If connecting, but no connect script, then skip chat altogether */
  if (m->opened && !*m->connScript) {
    ModemChatConnectResult(p, TRUE, NULL);
    return;
  }

  /* Open chat script file */
  if ((scriptfp = OpenConfFile(SCRIPT_FILE, NULL)) == NULL) {
    Log(LG_ERR, ("[%s] MODEM: can't open chat script file", p->name));
    ExclusiveCloseDevice(p->name, m->fd, m->device);
    m->fd = -1;
fail:
    m->opened = FALSE;
    m->lastClosed = time(NULL);
    p->state = PHYS_STATE_DOWN;
    PhysDown(p, STR_ERROR, STR_DEV_NOT_READY);
    return;
  }

    /* Preset some special chat variables */
    ChatPresetVar(m->chat, CHAT_VAR_DEVICE, m->device);
    if (p->link) {
	ChatPresetVar(m->chat, CHAT_VAR_LOGIN, p->link->lcp.auth.conf.authname);
	if (p->link->lcp.auth.conf.password[0] != 0) {
	    ChatPresetVar(m->chat, CHAT_VAR_PASSWORD, p->link->lcp.auth.conf.password);
	} else if (AuthGetData(p->link->lcp.auth.conf.authname,
	    password, sizeof(password), NULL, NULL) >= 0) {
		ChatPresetVar(m->chat, CHAT_VAR_PASSWORD, password);
	}
    }

  /* Run connect or idle script as appropriate */
  if (!m->opened) {
    ChatPresetVar(m->chat, CHAT_VAR_IDLE_RESULT, "<unknown>");
    ChatStart(m->chat, m->fd, scriptfp, m->idleScript, ModemChatIdleResult);
  } else {
    m->originated = TRUE;
    p->state = PHYS_STATE_CONNECTING;
    ChatStart(m->chat, m->fd, scriptfp, m->connScript, ModemChatConnectResult);
  }
}

/*
 * ModemClose()
 */

static void
ModemClose(PhysInfo p)
{
  ModemInfo	const m = (ModemInfo) p->info;

  if (!m->opened)
    return;
  ModemDoClose(p, FALSE);
  p->state = PHYS_STATE_DOWN;
  PhysDown(p, 0, NULL);
}

/*
 * ModemDoClose()
 */

static void
ModemDoClose(PhysInfo p, int opened)
{
  ModemInfo     const m = (ModemInfo) p->info;
  char		path[NG_PATHLEN + 1];
  const char	ch = ' ';

  /* Shutdown everything */
  assert(m->fd >= 0);
  ChatAbort(m->chat);
  TimerStop(&m->checkTimer);
  TimerStop(&m->startTimer);
  TimerStop(&m->reportTimer);
  (void) write(m->fd, &ch, 1);	/* USR kludge to prevent dial lockup */
  if (*m->ttynode != '\0') {
    snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
    NgFuncShutdownNode(m->csock, p->name, path);
    *m->ttynode = '\0';
  }
  if (m->csock > 0) {
    close(m->csock);
    m->csock = -1;
  }

  ExclusiveCloseDevice(p->name, m->fd, m->device);
  m->lastClosed = time(NULL);
  m->answering = FALSE;
  m->fd = -1;
  m->opened = opened;
  ModemStart(p);
}

/*
 * ModemSetAccm()
 */

static int
ModemSetAccm(PhysInfo p, u_int32_t xmit, u_int32_t recv)
{
  ModemInfo		const m = (ModemInfo) p->info;
  char        		path[NG_PATHLEN+1];

  /* Update async config */
  m->acfg.accm = xmit|recv;
  snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
  if (NgSendMsg(m->csock, path, NGM_ASYNC_COOKIE,
      NGM_ASYNC_CMD_SET_CONFIG, &m->acfg, sizeof(m->acfg)) < 0) {
    Log(LG_PHYS, ("[%s] MODEM: can't update config for %s: %s",
      p->name, path, strerror(errno)));
      return (-1);
  }
  return (0);
}

/*
 * ModemChatConnectResult()
 *
 * Connect chat script returns here when finished.
 */

static void
ModemChatConnectResult(void *arg, int result, const char *msg)
{
  PhysInfo		const p = (PhysInfo) arg;
  ModemInfo		const m = (ModemInfo) p->info;
  const char	*cspeed;
  int		bw;

  /* Was the connect script successful? */
  Log(LG_PHYS, ("[%s] MODEM: chat script %s",
    p->name, result ? "succeeded" : "failed"));
  if (!result) {
failed:
    ModemDoClose(p, FALSE);
    p->state = PHYS_STATE_DOWN;
    PhysDown(p, STR_ERROR, "%s", msg);
    return;
  }

  /* Set modem's reported connection speed (if any) as the link bandwidth */
  if ((cspeed = ChatGetVar(m->chat, CHAT_VAR_CONNECT_SPEED)) != NULL) {
    if ((bw = (int) strtoul(cspeed, NULL, 10)) > 0) {
	if (p->link)
	    p->link->bandwidth = bw;
    }
    Freee(MB_CHAT, cspeed);
  }

  /* Do async <-> sync conversion via netgraph node */
  if (ModemInstallNodes(p) < 0) {
    msg = STR_DEV_NOT_READY;
    goto failed;
  }

  /* Start pin check and report timers */
  TimerInit(&m->checkTimer, "ModemCheck",
    MODEM_CHECK_INTERVAL * SECONDS, ModemCheck, p);
  TimerStart(&m->checkTimer);
  TimerStop(&m->reportTimer);
  TimerInit(&m->reportTimer, "ModemReport",
    MODEM_ERR_REPORT_INTERVAL * SECONDS, ModemErrorCheck, p);
  TimerStart(&m->reportTimer);

  /* Done */
  p->state = PHYS_STATE_UP;
  PhysUp(p);
}

/*
 * ModemChatIdleResult()
 *
 * Idle chat script returns here when finished. If the script returned
 * successfully, then one of two things happened: either we answered
 * an incoming call, or else we got a ring and want to do ringback.
 * We tell the difference by checking $IdleResult.
 */

static void
ModemChatIdleResult(void *arg, int result, const char *msg)
{
  PhysInfo		const p = (PhysInfo) arg;
  ModemInfo		const m = (ModemInfo) p->info;
  const char	*idleResult;

  /* If script failed, then do nothing */
  if (!result) {
    ModemDoClose(p, FALSE);
    return;
  }

  /* See what script wants us to do now by checking variable $IdleResult */
  if ((idleResult = ChatGetVar(m->chat, CHAT_VAR_IDLE_RESULT)) == NULL) {
    Log(LG_ERR, ("[%s] MODEM: idle script succeeded, but %s not defined",
      p->name, CHAT_VAR_IDLE_RESULT));
    ModemDoClose(p, FALSE);
    return;
  }

  /* Do whatever */
  Log(LG_PHYS, ("[%s] MODEM: idle script succeeded, action=%s",
    p->name, idleResult));

  if (gShutdownInProgress) {
    Log(LG_PHYS, ("Shutdown sequence in progress, ignoring"));
    ModemDoClose(p, FALSE);
  }
  else
  {
    if (strcasecmp(idleResult, MODEM_IDLE_RESULT_ANSWER) == 0) {
      Log(LG_PHYS, ("[%s] MODEM: opening link in %s mode", p->name, "answer"));
      if (p->link)
        RecordLinkUpDownReason(NULL, p->link, 1, STR_INCOMING_CALL, msg ? "%s" : NULL, msg);
      m->answering = TRUE;
      p->state = PHYS_STATE_READY;
      PhysIncoming(p);
    } else if (strcasecmp(idleResult, MODEM_IDLE_RESULT_RINGBACK) == 0) {
      Log(LG_PHYS, ("[%s] MODEM: opening link in %s mode", p->name, "ringback"));
      if (p->link)
        RecordLinkUpDownReason(NULL, p->link, 1, STR_RINGBACK, msg ? "%s" : NULL, msg);
      m->answering = FALSE;
      PhysIncoming(p);
    } else {
      Log(LG_ERR, ("[%s] MODEM: idle script succeeded, but action \"%s\" unknown",
        p->name, idleResult));
      ModemDoClose(p, FALSE);
    }
  }
  Freee(MB_CHAT, idleResult);
}

/*
 * ModemInstallNodes()
 */

static int
ModemInstallNodes(PhysInfo p)
{
  ModemInfo 		m = (ModemInfo) p->info;
  struct nodeinfo	ngtty;
  struct ngm_mkpeer	ngm;
  struct ngm_connect	cn;
  char        		path[NG_PATHLEN+1];
  int			hotchar = PPP_FLAG;
  int			ldisc = NETGRAPHDISC;

    /* Get a temporary netgraph socket node */
    if (NgMkSockNode(NULL, &m->csock, NULL) == -1) {
    	Log(LG_ERR, ("MODEM: NgMkSockNode: %s", strerror(errno)));
    	return(-1);
    }

  /* Install ng_tty line discipline */
  if (ioctl(m->fd, TIOCSETD, &ldisc) < 0) {

    /* Installation of the tty node type should be automatic, but isn't yet.
       The 'mkpeer' below will fail, because you can only create a ng_tty
       node via TIOCSETD; however, this will force a load of the node type. */
    if (errno == ENODEV) {
      (void)NgSendAsciiMsg(m->csock, ".",
	"mkpeer { type=\"%s\" ourhook=\"dummy\" peerhook=\"%s\" }",
	NG_TTY_NODE_TYPE, NG_TTY_HOOK);
    }
    if (ioctl(m->fd, TIOCSETD, &ldisc) < 0) {
      Log(LG_ERR, ("[%s] ioctl(TIOCSETD, %d): %s",
	p->name, ldisc, strerror(errno))); 
      close(m->csock);
      return(-1);
    }
  }

  /* Get the name of the ng_tty node */
  if (ioctl(m->fd, NGIOCGINFO, &ngtty) < 0) {
    Log(LG_ERR, ("[%s] MODEM: ioctl(NGIOCGINFO): %s", p->name, strerror(errno))); 
    return(-1);
  }
  snprintf(m->ttynode, sizeof(m->ttynode), "%s", ngtty.name);

  /* Set the ``hot char'' on the TTY node */
  snprintf(path, sizeof(path), "%s:", ngtty.name);
  if (NgSendMsg(m->csock, path, NGM_TTY_COOKIE,
      NGM_TTY_SET_HOTCHAR, &hotchar, sizeof(hotchar)) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't set hotchar", p->name));
    close(m->csock);
    return(-1);
  }

  /* Attach an async converter node */
  snprintf(ngm.type, sizeof(ngm.type), "%s", NG_ASYNC_NODE_TYPE);
  snprintf(ngm.ourhook, sizeof(ngm.ourhook), "%s", NG_TTY_HOOK);
  snprintf(ngm.peerhook, sizeof(ngm.peerhook), "%s", NG_ASYNC_HOOK_ASYNC);
  if (NgSendMsg(m->csock, path, NGM_GENERIC_COOKIE,
      NGM_MKPEER, &ngm, sizeof(ngm)) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't connect %s node", p->name, NG_ASYNC_NODE_TYPE));
    close(m->csock);
    return(-1);
  }

  /* Configure the async converter node */
  snprintf(path, sizeof(path), "%s:%s", ngtty.name, NG_TTY_HOOK);
  memset(&m->acfg, 0, sizeof(m->acfg));
  m->acfg.enabled = TRUE;
  m->acfg.accm = ~0;
  m->acfg.amru = MODEM_MRU;
  m->acfg.smru = MODEM_MTU;
  if (NgSendMsg(m->csock, path, NGM_ASYNC_COOKIE,
      NGM_ASYNC_CMD_SET_CONFIG, &m->acfg, sizeof(m->acfg)) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't config %s", p->name, path));
    close(m->csock);
    return(-1);
  }

    /* Attach async node to PPP node */
    if (!PhysGetUpperHook(p, cn.path, cn.peerhook)) {
        Log(LG_PHYS, ("[%s] MODEM: can't get upper hook", p->name));
	close(m->csock);
	return (-1);
    }
    snprintf(cn.ourhook, sizeof(cn.ourhook), NG_ASYNC_HOOK_SYNC);
    if (NgSendMsg(m->csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, 
        &cn, sizeof(cn)) < 0) {
    	    Log(LG_ERR, ("[%s] MODEM: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s",
	        p->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno)));
	    close(m->csock);
	    return (-1);
    }

    /* OK */
    return(0);
}

/*
 * ModemChatSetBaudrate()
 *
 * This callback changes the actual baudrate of the serial port.
 * Should only be called once the device is already open.
 * Returns -1 on failure.
 */

static int
ModemChatSetBaudrate(void *arg, int baud)
{
  PhysInfo		const p = (PhysInfo) arg;
  ModemInfo		const m = (ModemInfo) p->info;
  struct termios	attr;

  /* Change baud rate */
  if (tcgetattr(m->fd, &attr) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't tcgetattr \"%s\": %s",
      p->name, m->device, strerror(errno)));
    return(-1);
  }
  if (cfsetspeed(&attr, (speed_t) baud) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't set speed %d: %s",
      p->name, baud, strerror(errno)));
    return(-1);
  }
  if (tcsetattr(m->fd, TCSANOW, &attr) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't tcsetattr \"%s\": %s",
      p->name, m->device, strerror(errno)));
    return(-1);
  }
  return(0);
}

/*
 * ModemChatLog()
 */

static void
ModemChatLog(void *arg, int level, const char *fmt, ...)
{
  PhysInfo		const p = (PhysInfo) arg;
  char		buf[128];
  va_list	args;
  int		logLevel;

  /* Convert level */
  switch (level) {
    default:
    case CHAT_LG_NORMAL:
      logLevel = LG_CHAT;
      break;
    case CHAT_LG_ERROR:
      logLevel = LG_ERR;
      break;
    case CHAT_LG_DEBUG:
      logLevel = LG_CHAT2;
      break;
  }
  if ((gLogOptions & logLevel) == 0)
    return;

  /* Concat prefix and message */
  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  va_end(args);
  if (*buf != ' ')
    snprintf(buf, sizeof(buf), "[%s] chat: ", p->name);
  else
    *buf = '\0';
  va_start(args, fmt);
  vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, args);
  va_end(args);

  /* Log it */
  LogPrintf("%s", buf);
}

/*
 * ModemChatMalloc()
 */

static void *
ModemChatMalloc(void *arg, size_t size)
{
  return Malloc(MB_CHAT, size);
}

/*
 * ModemChatFree()
 */

static void
ModemChatFree(void *arg, void *mem)
{
  Freee(MB_CHAT, mem);
}

/*
 * ModemGetVar()
 */

const char *
ModemGetVar(PhysInfo p, const char *name)
{
  ModemInfo	const m = (ModemInfo) p->info;

  return ChatGetVar(m->chat, name);
}

/*
 * ModemCheck()
 */

static void
ModemCheck(void *arg)
{
  PhysInfo	const p = (PhysInfo)arg;
  ModemInfo	const m = (ModemInfo) p->info;
  int		state;

  if (ioctl(m->fd, TIOCMGET, &state) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't ioctl(%s) %s: %s",
      p->name, "TIOCMGET", m->device, strerror(errno)));
    p->state = PHYS_STATE_DOWN;
    PhysDown(p, STR_ERROR, "ioctl(%s): %s", "TIOCMGET", strerror(errno));
    ModemDoClose(p, FALSE);
    return;
  }
  if ((m->watch & TIOCM_CAR) && !(state & TIOCM_CAR)) {
    Log(LG_PHYS, ("[%s] MODEM: carrier detect (CD) signal lost", p->name));
    p->state = PHYS_STATE_DOWN;
    PhysDown(p, STR_DROPPED, "%s", STR_LOST_CD);
    ModemDoClose(p, FALSE);
    return;
  }
  if ((m->watch & TIOCM_DSR) && !(state & TIOCM_DSR)) {
    Log(LG_PHYS, ("[%s] MODEM: data-set ready (DSR) signal lost", p->name));
    p->state = PHYS_STATE_DOWN;
    PhysDown(p, STR_DROPPED, "%s", STR_LOST_DSR);
    ModemDoClose(p, FALSE);
    return;
  }
  TimerStart(&m->checkTimer);
}

/*
 * ModemErrorCheck()
 *
 * Called every second to record errors to the log
 */

static void
ModemErrorCheck(void *arg)
{
  PhysInfo		const p = (PhysInfo) arg;
  ModemInfo		const m = (ModemInfo) p->info;
  char			path[NG_PATHLEN + 1];
  struct ng_async_stat	stats;

  /* Check for errors */
  snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
  if (ModemGetNgStats(p, &stats) >= 0
      && (stats.asyncBadCheckSums
	|| stats.asyncRunts || stats.asyncOverflows)) {
    Log(LG_PHYS, ("[%s] NEW FRAME ERRS: FCS %u RUNT %u OVFL %u",
      p->name, stats.asyncBadCheckSums,
      stats.asyncRunts, stats.asyncOverflows));
    (void) NgSendMsg(m->csock, path,
      NGM_ASYNC_COOKIE, NGM_ASYNC_CMD_CLR_STATS, NULL, 0);
  }

  /* Restart timer */
  TimerStop(&m->reportTimer);
  TimerStart(&m->reportTimer);
}

/*
 * ModemGetNgStats()
 */

static int
ModemGetNgStats(PhysInfo p, struct ng_async_stat *sp)
{
  ModemInfo             const m = (ModemInfo) p->info;
  char			path[NG_PATHLEN + 1];
  union {
    u_char		buf[sizeof(struct ng_mesg) + sizeof(*sp)];
    struct ng_mesg	resp;
  } u;

  /* Get stats */
  snprintf(path, sizeof(path), "%s:%s", m->ttynode, NG_TTY_HOOK);
  if (NgFuncSendQuery(path, NGM_ASYNC_COOKIE, NGM_ASYNC_CMD_GET_STATS,
      NULL, 0, &u.resp, sizeof(u), NULL) < 0) {
    Log(LG_ERR, ("[%s] MODEM: can't get stats: %s", p->name, strerror(errno)));
    return(-1);
  }

  /* Done */
  memcpy(sp, u.resp.data, sizeof(*sp));
  return(0);
}

/*
 * ModemSetCommand()
 */

static int
ModemSetCommand(Context ctx, int ac, char *av[], void *arg)
{
  PhysInfo	const p = ctx->phys;
  ModemInfo	const m = (ModemInfo) p->info;

  switch ((intptr_t)arg) {
    case SET_DEVICE:
      if (ac == 1)
	snprintf(m->device, sizeof(m->device), "%s", av[0]);
      break;
    case SET_SPEED:
      {
	int	k, baud;

	if (ac != 1)
	  return(-1);
	baud = atoi(*av);
	for (k = 0; gSpeedList[k] != -1 && baud != gSpeedList[k]; k++);
	if (gSpeedList[k] == -1)
	  Log(LG_ERR, ("[%s] %s: invalid speed", p->name, *av));
	else
	{
	  char	buf[32];

	  m->speed = baud;
	  snprintf(buf, sizeof(buf), "%d", m->speed);
	  ChatPresetVar(m->chat, CHAT_VAR_BAUDRATE, buf);
	}
      }
      break;
    case SET_CSCRIPT:
      if (ac != 1)
	return(-1);
      *m->connScript = 0;
      snprintf(m->connScript, sizeof(m->connScript), "%s", av[0]);
      break;
    case SET_ISCRIPT:
      if (ac != 1)
	return(-1);
      *m->idleScript = 0;
      snprintf(m->idleScript, sizeof(m->idleScript), "%s", av[0]);
      if (m->opened || TimerRemain(&m->startTimer) >= 0)
	break;		/* nothing needs to be done right now */
      if (m->fd >= 0 && !*m->idleScript)
	ModemDoClose(p, FALSE);
      else if (m->fd < 0 && *m->idleScript)
	ModemStart(p);
      break;
    case SET_SCRIPT_VAR:
      if (ac != 2)
	return(-1);
      ChatPresetVar(m->chat, av[0], av[1]);
      break;
    case SET_WATCH:
      {
	int	bit, add;

	while (ac--)
	{
	  switch (**av)
	  {
	    case '+':
	      (*av)++;
	    default:
	      add = TRUE;
	      break;
	    case '-':
	      add = FALSE;
	      (*av)++;
	      break;
	  }
	  if (!strcasecmp(*av, "cd"))
	    bit = TIOCM_CAR;
	  else if (!strcasecmp(*av, "dsr"))
	    bit = TIOCM_DSR;
	  else
	  {
	    Printf("[%s] modem signal \"%s\" is unknown\r\n", p->name, *av);
	    bit = 0;
	  }
	  if (add)
	    m->watch |= bit;
	  else
	    m->watch &= ~bit;
	  av++;
	}
      }
      break;
    default:
      assert(0);
  }
  return(0);
}

/*
 * ModemOriginated()
 */

static int
ModemOriginated(PhysInfo p)
{
  ModemInfo	const m = (ModemInfo) p->info;

  return(m->originated ? LINK_ORIGINATE_LOCAL : LINK_ORIGINATE_REMOTE);
}

/*
 * ModemIsSync()
 */

static int
ModemIsSync(PhysInfo p)
{
  return (0);
}

/* XXX mbretter: the phone-number would be correct */
static int
ModemPeerAddr(PhysInfo p, void *buf, int buf_len)
{
  ModemInfo	const m = (ModemInfo) p->info;

  if (buf_len < sizeof(m->ttynode))
    return(-1);

  memcpy(buf, m->ttynode, sizeof(m->ttynode));

  return(0);
}


/*
 * ModemStat()
 */

void
ModemStat(Context ctx)
{
  ModemInfo		const m = (ModemInfo) ctx->phys->info;
  struct ng_async_stat	stats;
  const char		*cspeed;

  Printf("Modem info:\r\n");
  Printf("\tDevice       : %s\r\n", m->device);
  Printf("\tPort speed   : %d baud\r\n", m->speed);
  Printf("\tConn. script : \"%s\"\r\n", m->connScript);
  Printf("\tIdle script  : \"%s\"\r\n", m->idleScript);
  Printf("\tPins to watch: %s%s\r\n",
    (m->watch & TIOCM_CAR) ? "CD " : "",
    (m->watch & TIOCM_DSR) ? "DSR" : "");

    Printf("Modem status:\r\n");
    Printf("\tState        : %s\r\n", gPhysStateNames[ctx->phys->state]);
    if (ctx->phys->state != PHYS_STATE_DOWN) {
	Printf("\tOpened       : %s\r\n", (m->opened?"YES":"NO"));
	Printf("\tIncoming     : %s\r\n", (m->originated?"NO":"YES"));

	/* Set modem's reported connection speed (if any) as the link bandwidth */
	if ((cspeed = ChatGetVar(m->chat, CHAT_VAR_CONNECT_SPEED)) != NULL) {
	    Printf("\tConnect speed: %s baud\r\n", cspeed);
	    Freee(MB_CHAT, cspeed);
	}

	if (ctx->phys->state == PHYS_STATE_UP && 
    		ModemGetNgStats(ctx->phys, &stats) >= 0) {
    	    Printf("Async stats:\r\n");
    	    Printf("\t       syncOctets: %8u\r\n", stats.syncOctets);
    	    Printf("\t       syncFrames: %8u\r\n", stats.syncFrames);
    	    Printf("\t    syncOverflows: %8u\r\n", stats.syncOverflows);
	    Printf("\t      asyncOctets: %8u\r\n", stats.asyncOctets);
    	    Printf("\t      asyncFrames: %8u\r\n", stats.asyncFrames);
    	    Printf("\t       asyncRunts: %8u\r\n", stats.asyncRunts);
    	    Printf("\t   asyncOverflows: %8u\r\n", stats.asyncOverflows);
    	    Printf("\tasyncBadCheckSums: %8u\r\n", stats.asyncBadCheckSums);
      }
    }
}



syntax highlighted by Code2HTML, v. 0.9.1