Phone
/* $Id$ */
/* Copyright (c) 2011-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Phone */
/* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/* TODO:
* - implement priorities again
* - parse messages from within +CMGL already
* - add MCT callbacks/buttons to change the SIM code (via a helper in phone.c)
* - implement new contacts
* - implement +PBREADY?
* - implement AT+CTZR=1 (timezone updates)
* - implement AT+CLCK="PN",2? (is SIM-locked)
* - implement AT+XREG? (band frequency, Sierra Wireless only?)
* - implement AT+XACT? (band frequency again, same) */
#ifdef __linux__
# include <sys/file.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <termios.h>
#include <errno.h>
#include <libgen.h>
#include <glib.h>
#include <System.h>
#include <Phone/modem.h>
#include "hayes/channel.h"
#include "hayes/command.h"
#include "hayes/common.h"
#include "hayes/pdu.h"
#include "hayes/quirks.h"
#include "hayes.h"
/* constants */
#ifndef PROGNAME_PPPD
# define PROGNAME_PPPD "pppd"
#endif
/* macros */
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
/* Hayes */
/* private */
/* types */
typedef struct _ModemPlugin
{
ModemPluginHelper * helper;
unsigned int retry;
/* modem */
HayesChannel channel;
} Hayes;
#ifdef DEBUG
static const char * _hayes_command_status[HCS_COUNT] =
{
"HCS_UNKNOWN", "HCS_QUEUED", "HCS_UNKNOWN", "HCS_ACTIVE", "HCS_TIMEOUT",
"HCS_ERROR", "HCS_SUCCESS"
};
#endif
typedef struct _HayesRequestContactList
{
unsigned int from;
unsigned int to;
} HayesRequestContactList;
typedef struct _HayesRequestMessageData
{
unsigned int id;
ModemMessageFolder folder;
ModemMessageStatus status;
} HayesRequestMessageData;
typedef struct _HayesRequestHandler
{
unsigned int type;
char const * attention;
HayesCommandCallback callback;
} HayesRequestHandler;
typedef struct _HayesCodeHandler
{
char const * code;
void (*callback)(HayesChannel * channel, char const * answer);
} HayesCodeHandler;
/* constants */
enum
{
HAYES_REQUEST_ALIVE = MODEM_REQUEST_COUNT,
HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_DISABLE,
HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_ENABLE,
HAYES_REQUEST_CONNECTED_LINE_DISABLE,
HAYES_REQUEST_CONNECTED_LINE_ENABLE,
HAYES_REQUEST_CONTACT_LIST,
HAYES_REQUEST_EXTENDED_ERRORS,
HAYES_REQUEST_EXTENDED_RING_REPORTS,
HAYES_REQUEST_FUNCTIONAL,
HAYES_REQUEST_FUNCTIONAL_DISABLE,
HAYES_REQUEST_FUNCTIONAL_ENABLE,
HAYES_REQUEST_FUNCTIONAL_ENABLE_RESET,
HAYES_REQUEST_GPRS_ATTACHED,
HAYES_REQUEST_LOCAL_ECHO_DISABLE,
HAYES_REQUEST_LOCAL_ECHO_ENABLE,
HAYES_REQUEST_MESSAGE_FORMAT_PDU,
HAYES_REQUEST_MESSAGE_LIST_INBOX_READ,
HAYES_REQUEST_MESSAGE_LIST_INBOX_UNREAD,
HAYES_REQUEST_MESSAGE_LIST_SENT_READ,
HAYES_REQUEST_MESSAGE_LIST_SENT_UNREAD,
HAYES_REQUEST_MESSAGE_UNSOLLICITED_DISABLE,
HAYES_REQUEST_MESSAGE_UNSOLLICITED_ENABLE,
HAYES_REQUEST_MODEL,
HAYES_REQUEST_OPERATOR,
HAYES_REQUEST_OPERATOR_FORMAT_SHORT,
HAYES_REQUEST_OPERATOR_FORMAT_LONG,
HAYES_REQUEST_OPERATOR_FORMAT_NUMERIC,
HAYES_REQUEST_PHONE_ACTIVE,
HAYES_REQUEST_REGISTRATION,
HAYES_REQUEST_REGISTRATION_AUTOMATIC,
HAYES_REQUEST_REGISTRATION_DISABLED,
HAYES_REQUEST_REGISTRATION_UNSOLLICITED_DISABLE,
HAYES_REQUEST_REGISTRATION_UNSOLLICITED_ENABLE,
HAYES_REQUEST_SERIAL_NUMBER,
HAYES_REQUEST_SIM_PIN_VALID,
HAYES_REQUEST_SUBSCRIBER_IDENTITY,
HAYES_REQUEST_SUPPLEMENTARY_SERVICE_DATA_CANCEL,
HAYES_REQUEST_SUPPLEMENTARY_SERVICE_DATA_ENABLE,
HAYES_REQUEST_SUPPLEMENTARY_SERVICE_DATA_DISABLE,
HAYES_REQUEST_VENDOR,
HAYES_REQUEST_VERBOSE_DISABLE,
HAYES_REQUEST_VERBOSE_ENABLE,
HAYES_REQUEST_VERSION
};
/* prototypes */
/* plug-in */
static ModemPlugin * _hayes_init(ModemPluginHelper * helper);
static void _hayes_destroy(Hayes * hayes);
static int _hayes_start(Hayes * hayes, unsigned int retry);
static int _hayes_stop(Hayes * hayes);
static int _hayes_request(Hayes * hayes, ModemRequest * request);
static int _hayes_trigger(Hayes * hayes, ModemEventType event);
/* accessors */
static void _hayes_set_mode(Hayes * hayes, HayesChannel * channel,
HayesChannelMode mode);
/* useful */
/* conversions */
static unsigned char _hayes_convert_gsm_to_iso(unsigned char c);
static void _hayes_convert_gsm_string_to_iso(char * str);
static unsigned char _hayes_convert_iso_to_gsm(unsigned char c);
static void _hayes_convert_iso_string_to_gsm(char * str);
/* messages */
static char * _hayes_message_to_pdu(HayesChannel * channel, char const * number,
ModemMessageEncoding encoding, size_t length,
char const * content);
/* logging */
static void _hayes_log(Hayes * hayes, HayesChannel * channel,
char const * prefix, char const * buf, size_t cnt);
/* parser */
static int _hayes_parse(Hayes * hayes, HayesChannel * channel);
static int _hayes_parse_pdu(Hayes * hayes, HayesChannel * channel);
static int _hayes_parse_trigger(HayesChannel * channel, char const * answer,
HayesCommand * command);
/* queue */
static int _hayes_queue_command(Hayes * hayes, HayesChannel * channel,
HayesCommand * command);
#if 0 /* XXX no longer used */
static int _hayes_queue_command_full(Hayes * hayes,
char const * attention, HayesCommandCallback callback);
#endif
static int _hayes_queue_push(Hayes * hayes, HayesChannel * channel);
/* requests */
static int _hayes_request_channel(Hayes * hayes, HayesChannel * channel,
ModemRequest * request, void * data);
static int _hayes_request_type(Hayes * hayes, HayesChannel * channel,
ModemRequestType type);
/* reset */
static void _hayes_reset(Hayes * hayes);
/* callbacks */
static gboolean _on_channel_authenticate(gpointer data);
static gboolean _on_channel_reset(gpointer data);
static gboolean _on_channel_timeout(gpointer data);
static gboolean _on_queue_timeout(gpointer data);
static gboolean _on_reset_settle(gpointer data);
static gboolean _on_reset_settle2(gpointer data);
static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
gpointer data);
static gboolean _on_watch_can_read_ppp(GIOChannel * source,
GIOCondition condition, gpointer data);
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
gpointer data);
static gboolean _on_watch_can_write_ppp(GIOChannel * source,
GIOCondition condition, gpointer data);
static HayesCommandStatus _on_request_authenticate(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_battery_level(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_call(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_call_incoming(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_call_outgoing(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_call_status(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_contact_delete(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_contact_list(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_functional(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_functional_enable(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_functional_enable_reset(
HayesCommand * command, HayesCommandStatus status,
HayesChannel * channel);
static HayesCommandStatus _on_request_generic(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_message(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_message_delete(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_message_list(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_message_send(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_model(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_registration(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_registration_automatic(
HayesCommand * command, HayesCommandStatus status,
HayesChannel * channel);
static HayesCommandStatus _on_request_registration_disabled(
HayesCommand * command, HayesCommandStatus status,
HayesChannel * channel);
static HayesCommandStatus _on_request_sim_pin_valid(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static HayesCommandStatus _on_request_unsupported(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static void _on_code_call_error(HayesChannel * channel, char const * answer);
static void _on_code_cbc(HayesChannel * channel, char const * answer);
static void _on_code_cfun(HayesChannel * channel, char const * answer);
static void _on_code_cgatt(HayesChannel * channel, char const * answer);
static void _on_code_cgmi(HayesChannel * channel, char const * answer);
static void _on_code_cgmm(HayesChannel * channel, char const * answer);
static void _on_code_cgmr(HayesChannel * channel, char const * answer);
static void _on_code_cgsn(HayesChannel * channel, char const * answer);
static void _on_code_cimi(HayesChannel * channel, char const * answer);
static void _on_code_clip(HayesChannel * channel, char const * answer);
static void _on_code_cme_error(HayesChannel * channel, char const * answer);
static void _on_code_cmgl(HayesChannel * channel, char const * answer);
static void _on_code_cmgr(HayesChannel * channel, char const * answer);
static void _on_code_cmgs(HayesChannel * channel, char const * answer);
static void _on_code_cms_error(HayesChannel * channel, char const * answer);
static void _on_code_cmti(HayesChannel * channel, char const * answer);
static void _on_code_connect(HayesChannel * channel, char const * answer);
static void _on_code_colp(HayesChannel * channel, char const * answer);
static void _on_code_cops(HayesChannel * channel, char const * answer);
static void _on_code_cpas(HayesChannel * channel, char const * answer);
static void _on_code_cpbr(HayesChannel * channel, char const * answer);
static void _on_code_cpin(HayesChannel * channel, char const * answer);
static void _on_code_creg(HayesChannel * channel, char const * answer);
static void _on_code_cring(HayesChannel * channel, char const * answer);
static void _on_code_csq(HayesChannel * channel, char const * answer);
static void _on_code_cusd(HayesChannel * channel, char const * answer);
static void _on_code_ext_error(HayesChannel * channel, char const * answer);
/* helpers */
static int _is_ussd_code(char const * number);
/* variables */
static ModemConfig _hayes_config[] =
{
{ "device", "Device", MCT_FILENAME },
{ "baudrate", "Baudrate", MCT_UINT32 },
{ "hwflow", "Hardware flow control",MCT_BOOLEAN },
{ NULL, "Advanced", MCT_SUBSECTION },
{ "logfile", "Log file", MCT_FILENAME },
{ NULL, NULL, MCT_NONE },
};
static struct
{
unsigned char gsm;
unsigned char iso;
} _hayes_gsm_iso[] =
{
{ '\0', '@' },
{ 0x01, 163 }, /* £ */
{ 0x02, '$' },
{ 0x03, 165 }, /* ¥ */
{ 0x04, 232 }, /* è */
{ 0x05, 233 }, /* é */
{ 0x06, 249 }, /* ù */
{ 0x07, 236 }, /* ì */
{ 0x08, 242 }, /* ò */
{ 0x09, 199 }, /* Ç */
{ 0x0b, 216 }, /* Ø */
{ 0x0c, 248 }, /* ø */
{ 0x0e, 197 }, /* Å */
{ 0x0f, 229 }, /* å */
{ 0x10, ' ' }, /* XXX delta */
{ 0x11, '_' },
{ 0x12, ' ' }, /* XXX phi */
{ 0x13, ' ' }, /* XXX gamma */
{ 0x14, ' ' }, /* XXX lambda */
{ 0x15, ' ' }, /* XXX omega */
{ 0x16, ' ' }, /* XXX pi */
{ 0x17, ' ' }, /* XXX psi */
{ 0x18, ' ' }, /* XXX sigma */
{ 0x19, ' ' }, /* XXX theta */
{ 0x1a, ' ' }, /* XXX xi */
{ 0x1b, ' ' }, /* FIXME escape */
{ 0x1c, 198 },
{ 0x1d, 230 },
{ 0x1e, 223 },
{ 0x1f, 201 },
{ 0x24, 164 }, /* $ */
{ 0x40, 161 }, /* @ */
{ 0x5b, 196 }, /* [ */
{ 0x5c, 214 }, /* \ */
{ 0x5d, 209 }, /* ] */
{ 0x5e, 220 }, /* ^ */
{ 0x5f, 167 }, /* _ */
{ 0x60, 191 }, /* ` */
{ 0x7b, 228 }, /* { */
{ 0x7c, 246 }, /* | */
{ 0x7d, 241 }, /* } */
{ 0x7e, 252 }, /* ~ */
{ 0x7f, 224 }
};
static HayesRequestHandler _hayes_request_handlers[] =
{
{ HAYES_REQUEST_ALIVE, "AT",
_on_request_generic },
{ HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_DISABLE,"AT+CCWA=1",
_on_request_generic },
{ HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_ENABLE,"AT+CCWA=1",
_on_request_generic },
{ HAYES_REQUEST_CONNECTED_LINE_DISABLE, "AT+COLP=0",
_on_request_generic },
{ HAYES_REQUEST_CONNECTED_LINE_ENABLE, "AT+COLP=1",
_on_request_generic },
{ HAYES_REQUEST_CONTACT_LIST, NULL,
_on_request_generic },
{ HAYES_REQUEST_EXTENDED_ERRORS, "AT+CMEE=1",
_on_request_generic },
{ HAYES_REQUEST_EXTENDED_RING_REPORTS, "AT+CRC=1",
_on_request_generic },
{ HAYES_REQUEST_FUNCTIONAL, "AT+CFUN?",
_on_request_functional },
{ HAYES_REQUEST_FUNCTIONAL_DISABLE, "AT+CFUN=0",
_on_request_generic },
{ HAYES_REQUEST_FUNCTIONAL_ENABLE, "AT+CFUN=1",
_on_request_functional_enable },
/* XXX AT+CFUN=16 on Sierra Wireless? */
{ HAYES_REQUEST_FUNCTIONAL_ENABLE_RESET, "AT+CFUN=1,1",
_on_request_functional_enable_reset },
{ HAYES_REQUEST_GPRS_ATTACHED, "AT+CGATT?",
_on_request_generic },
{ HAYES_REQUEST_LOCAL_ECHO_DISABLE, "ATE0",
_on_request_generic },
{ HAYES_REQUEST_LOCAL_ECHO_ENABLE, "ATE1",
_on_request_generic },
{ HAYES_REQUEST_MESSAGE_FORMAT_PDU, "AT+CMGF=0",
_on_request_generic },
{ HAYES_REQUEST_MESSAGE_LIST_INBOX_UNREAD, "AT+CMGL=0",
_on_request_message_list },
{ HAYES_REQUEST_MESSAGE_LIST_INBOX_READ, "AT+CMGL=1",
_on_request_message_list },
{ HAYES_REQUEST_MESSAGE_LIST_SENT_UNREAD, "AT+CMGL=2",
_on_request_message_list },
{ HAYES_REQUEST_MESSAGE_LIST_SENT_READ, "AT+CMGL=3",
_on_request_message_list },
{ HAYES_REQUEST_MESSAGE_UNSOLLICITED_DISABLE, "AT+CNMI=0",
_on_request_generic },
{ HAYES_REQUEST_MESSAGE_UNSOLLICITED_ENABLE, "AT+CNMI=1,1",
_on_request_generic }, /* XXX report error? */
{ HAYES_REQUEST_MODEL, "AT+CGMM",
_on_request_model },
{ HAYES_REQUEST_OPERATOR, "AT+COPS?",
_on_request_generic },
{ HAYES_REQUEST_OPERATOR_FORMAT_LONG, "AT+COPS=3,0",
_on_request_registration },
{ HAYES_REQUEST_OPERATOR_FORMAT_NUMERIC, "AT+COPS=3,2",
_on_request_registration },
{ HAYES_REQUEST_OPERATOR_FORMAT_SHORT, "AT+COPS=3,1",
_on_request_registration },
{ HAYES_REQUEST_PHONE_ACTIVE, "AT+CPAS",
_on_request_call },
{ HAYES_REQUEST_REGISTRATION, "AT+CREG?",
_on_request_generic },
{ HAYES_REQUEST_REGISTRATION_AUTOMATIC, "AT+COPS=0",
_on_request_registration_automatic },
{ HAYES_REQUEST_REGISTRATION_DISABLED, "AT+COPS=2",
_on_request_registration_disabled },
{ HAYES_REQUEST_REGISTRATION_UNSOLLICITED_DISABLE,"AT+CREG=0",
_on_request_generic },
{ HAYES_REQUEST_REGISTRATION_UNSOLLICITED_ENABLE,"AT+CREG=2",
_on_request_registration },
{ HAYES_REQUEST_SERIAL_NUMBER, "AT+CGSN",
_on_request_generic },
{ HAYES_REQUEST_SIM_PIN_VALID, "AT+CPIN?",
_on_request_sim_pin_valid },
{ HAYES_REQUEST_SUBSCRIBER_IDENTITY, "AT+CIMI",
_on_request_generic },
{ HAYES_REQUEST_SUPPLEMENTARY_SERVICE_DATA_CANCEL,"AT+CUSD=2",
_on_request_generic },
{ HAYES_REQUEST_SUPPLEMENTARY_SERVICE_DATA_DISABLE,"AT+CUSD=0",
_on_request_generic },
{ HAYES_REQUEST_SUPPLEMENTARY_SERVICE_DATA_ENABLE,"AT+CUSD=1",
_on_request_generic },
{ HAYES_REQUEST_VENDOR, "AT+CGMI",
_on_request_generic },
{ HAYES_REQUEST_VERBOSE_DISABLE, "ATV0",
_on_request_generic },
{ HAYES_REQUEST_VERBOSE_ENABLE, "ATV1",
_on_request_generic },
{ HAYES_REQUEST_VERSION, "AT+CGMR",
_on_request_generic },
{ MODEM_REQUEST_AUTHENTICATE, NULL,
_on_request_authenticate },
{ MODEM_REQUEST_BATTERY_LEVEL, "AT+CBC",
_on_request_battery_level },
{ MODEM_REQUEST_CALL, NULL,
_on_request_call_outgoing },
{ MODEM_REQUEST_CALL_ANSWER, "ATA",
_on_request_call_incoming },
{ MODEM_REQUEST_CALL_HANGUP, NULL,
_on_request_call_status },
{ MODEM_REQUEST_CALL_PRESENTATION, NULL,
_on_request_generic },
{ MODEM_REQUEST_CONNECTIVITY, NULL,
_on_request_generic },
{ MODEM_REQUEST_CONTACT_DELETE, NULL,
_on_request_contact_delete },
{ MODEM_REQUEST_CONTACT_EDIT, NULL,
_on_request_contact_list },
{ MODEM_REQUEST_CONTACT_LIST, "AT+CPBR=?",
_on_request_generic },
{ MODEM_REQUEST_CONTACT_NEW, NULL,
_on_request_contact_list },
{ MODEM_REQUEST_DTMF_SEND, NULL,
_on_request_generic },
{ MODEM_REQUEST_MESSAGE, NULL,
_on_request_message },
{ MODEM_REQUEST_MESSAGE_DELETE, NULL,
_on_request_message_delete },
{ MODEM_REQUEST_MESSAGE_LIST, NULL,
_on_request_message_list },
{ MODEM_REQUEST_MESSAGE_SEND, NULL,
_on_request_message_send },
{ MODEM_REQUEST_PASSWORD_SET, NULL,
_on_request_generic },
{ MODEM_REQUEST_REGISTRATION, NULL,
_on_request_registration },
{ MODEM_REQUEST_SIGNAL_LEVEL, "AT+CSQ",
_on_request_generic },
{ MODEM_REQUEST_UNSUPPORTED, NULL,
_on_request_unsupported }
};
static HayesCodeHandler _hayes_code_handlers[] =
{
{ "+CBC", _on_code_cbc },
{ "+CFUN", _on_code_cfun },
{ "+CGATT", _on_code_cgatt },
{ "+CGMI", _on_code_cgmi },
{ "+CGMM", _on_code_cgmm },
{ "+CGMR", _on_code_cgmr },
{ "+CGSN", _on_code_cgsn },
{ "+CIMI", _on_code_cimi },
{ "+CLIP", _on_code_clip },
{ "+CME ERROR", _on_code_cme_error },
{ "+CMGL", _on_code_cmgl },
{ "+CMGR", _on_code_cmgr },
{ "+CMGS", _on_code_cmgs },
{ "+CMS ERROR", _on_code_cms_error },
{ "+CMTI", _on_code_cmti },
{ "+COLP", _on_code_colp },
{ "+COPS", _on_code_cops },
{ "+CPAS", _on_code_cpas },
{ "+CPBR", _on_code_cpbr },
{ "+CPIN", _on_code_cpin },
{ "+CREG", _on_code_creg },
{ "+CRING", _on_code_cring },
{ "+CSQ", _on_code_csq },
{ "+CUSD", _on_code_cusd },
{ "+EXT ERROR", _on_code_ext_error },
{ "BUSY", _on_code_call_error },
{ "CONNECT", _on_code_connect },
{ "NO CARRIER", _on_code_call_error },
{ "NO DIALTONE",_on_code_call_error },
{ "RING", _on_code_cring }
};
/* public */
/* variables */
ModemPluginDefinition plugin =
{
"Hayes",
"phone",
_hayes_config,
_hayes_init,
_hayes_destroy,
_hayes_start,
_hayes_stop,
_hayes_request,
_hayes_trigger
};
/* private */
/* plug-in */
/* functions */
static ModemPlugin * _hayes_init(ModemPluginHelper * helper)
{
Hayes * hayes;
if((hayes = object_new(sizeof(*hayes))) == NULL)
return NULL;
memset(hayes, 0, sizeof(*hayes));
hayes->helper = helper;
hayeschannel_init(&hayes->channel, hayes);
return hayes;
}
/* hayes_destroy */
static void _hayes_destroy(Hayes * hayes)
{
_hayes_stop(hayes);
hayeschannel_destroy(&hayes->channel);
object_delete(hayes);
}
/* hayes_request */
static int _hayes_request(Hayes * hayes, ModemRequest * request)
{
return _hayes_request_channel(hayes, &hayes->channel, request, NULL);
}
/* hayes_start */
static int _start_is_started(Hayes * hayes);
static int _hayes_start(Hayes * hayes, unsigned int retry)
{
hayes->retry = retry;
if(_start_is_started(hayes))
return 0;
if(hayes->channel.source != 0)
g_source_remove(hayes->channel.source);
hayes->channel.source = g_idle_add(_on_channel_reset, &hayes->channel);
return 0;
}
static int _start_is_started(Hayes * hayes)
{
if(hayes->channel.source != 0)
return 1;
if(hayeschannel_is_started(&hayes->channel))
return 1;
return 0;
}
/* hayes_stop */
static int _hayes_stop(Hayes * hayes)
{
HayesChannel * channel = &hayes->channel;
ModemEvent * event;
hayescommon_source_reset(&channel->source);
hayeschannel_stop(channel);
/* report disconnection if already connected */
event = &channel->events[MODEM_EVENT_TYPE_CONNECTION];
if(event->connection.connected)
{
event->connection.connected = 0;
event->connection.in = 0;
event->connection.out = 0;
hayes->helper->event(hayes->helper->modem, event);
}
/* reset battery information */
event = &channel->events[MODEM_EVENT_TYPE_BATTERY_LEVEL];
if(event->battery_level.status != MODEM_BATTERY_STATUS_UNKNOWN)
{
event->battery_level.status = MODEM_BATTERY_STATUS_UNKNOWN;
event->battery_level.level = 0.0 / 0.0;
event->battery_level.charging = 0;
hayes->helper->event(hayes->helper->modem, event);
}
return 0;
}
/* hayes_trigger */
static int _hayes_trigger(Hayes * hayes, ModemEventType event)
{
int ret = 0;
HayesChannel * channel = &hayes->channel;
ModemEvent * e;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%u)\n", __func__, event);
#endif
switch(event)
{
case MODEM_EVENT_TYPE_BATTERY_LEVEL: /* use the existing data */
case MODEM_EVENT_TYPE_CALL:
case MODEM_EVENT_TYPE_CONNECTION:
case MODEM_EVENT_TYPE_STATUS:
e = &channel->events[event];
hayes->helper->event(hayes->helper->modem, e);
break;
case MODEM_EVENT_TYPE_AUTHENTICATION:
return _hayes_request_type(hayes, channel,
HAYES_REQUEST_SIM_PIN_VALID);
case MODEM_EVENT_TYPE_CONTACT:
return _hayes_request_type(hayes, channel,
MODEM_REQUEST_CONTACT_LIST);
case MODEM_EVENT_TYPE_MESSAGE:
return _hayes_request_type(hayes, channel,
MODEM_REQUEST_MESSAGE_LIST);
case MODEM_EVENT_TYPE_MODEL:
ret |= _hayes_request_type(hayes, channel,
HAYES_REQUEST_VENDOR);
ret |= _hayes_request_type(hayes, channel,
HAYES_REQUEST_VERSION);
ret |= _hayes_request_type(hayes, channel,
HAYES_REQUEST_SERIAL_NUMBER);
ret |= _hayes_request_type(hayes, channel,
HAYES_REQUEST_SUBSCRIBER_IDENTITY);
ret |= _hayes_request_type(hayes, channel,
HAYES_REQUEST_MODEL);
break;
case MODEM_EVENT_TYPE_REGISTRATION:
e = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
if(e->registration.status
== MODEM_REGISTRATION_STATUS_UNKNOWN)
ret |= _hayes_request_type(hayes, channel,
HAYES_REQUEST_REGISTRATION);
else
hayes->helper->event(hayes->helper->modem, e);
break;
case MODEM_EVENT_TYPE_CONTACT_DELETED: /* do not make sense */
case MODEM_EVENT_TYPE_ERROR:
case MODEM_EVENT_TYPE_NOTIFICATION:
case MODEM_EVENT_TYPE_MESSAGE_DELETED:
case MODEM_EVENT_TYPE_MESSAGE_SENT:
ret = -1;
break;
}
return ret;
}
/* accessors */
/* hayes_set_mode */
static void _hayes_set_mode(Hayes * hayes, HayesChannel * channel,
HayesChannelMode mode)
{
ModemEvent * event;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%u)\n", __func__, mode);
#endif
if(channel->mode == mode)
return;
switch(channel->mode)
{
case HAYESCHANNEL_MODE_INIT:
case HAYESCHANNEL_MODE_COMMAND:
case HAYESCHANNEL_MODE_PDU:
break; /* nothing to do */
case HAYESCHANNEL_MODE_DATA:
hayescommon_source_reset(&channel->rd_ppp_source);
hayescommon_source_reset(&channel->wr_ppp_source);
/* reset registration media */
event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
free(channel->registration_media);
channel->registration_media = NULL;
event->registration.media = NULL;
/* reset modem */
_hayes_reset(hayes);
break;
}
switch(mode)
{
case HAYESCHANNEL_MODE_INIT:
case HAYESCHANNEL_MODE_COMMAND:
case HAYESCHANNEL_MODE_PDU:
break; /* nothing to do */
case HAYESCHANNEL_MODE_DATA:
/* report GPRS registration */
event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
free(channel->registration_media);
channel->registration_media = NULL;
event->registration.media = "GPRS";
hayes->helper->event(hayes->helper->modem, event);
break;
}
channel->mode = mode;
}
/* conversions */
/* hayes_convert_gsm_to_iso */
static unsigned char _hayes_convert_gsm_to_iso(unsigned char c)
{
size_t i;
for(i = 0; i < sizeof(_hayes_gsm_iso) / sizeof(*_hayes_gsm_iso); i++)
if(_hayes_gsm_iso[i].gsm == c)
return _hayes_gsm_iso[i].iso;
return c;
}
/* hayes_convert_gsm_string_to_iso */
static void _hayes_convert_gsm_string_to_iso(char * str)
{
unsigned char * ustr = (unsigned char *)str;
size_t i;
for(i = 0; str[i] != '\0'; i++)
ustr[i] = _hayes_convert_gsm_to_iso(ustr[i]);
}
/* hayes_convert_iso_to_gsm */
static unsigned char _hayes_convert_iso_to_gsm(unsigned char c)
{
size_t i;
for(i = 0; i < sizeof(_hayes_gsm_iso) / sizeof(*_hayes_gsm_iso); i++)
if(_hayes_gsm_iso[i].iso == c)
return _hayes_gsm_iso[i].gsm;
return c;
}
/* hayes_convert_iso_string_to_gsm */
static void _hayes_convert_iso_string_to_gsm(char * str)
{
unsigned char * ustr = (unsigned char *)str;
size_t i;
for(i = 0; str[i] != '\0'; i++)
ustr[i] = _hayes_convert_iso_to_gsm(ustr[i]);
}
/* logging */
/* hayes_log */
static void _hayes_log(Hayes * hayes, HayesChannel * channel,
char const * prefix, char const * buf, size_t cnt)
{
ModemPluginHelper * helper = hayes->helper;
if(channel->fp == NULL)
return;
if(fprintf(channel->fp, "\n%s", (prefix != NULL) ? prefix : "") == EOF
|| fwrite(buf, sizeof(*buf), cnt, channel->fp) < cnt)
{
helper->error(NULL, strerror(errno), 1);
fclose(channel->fp);
channel->fp = NULL;
}
}
/* messages */
/* hayes_message_to_pdu */
static char * _hayes_message_to_pdu(HayesChannel * channel, char const * number,
ModemMessageEncoding encoding, size_t length,
char const * content)
{
unsigned int flags = 0;
flags |= hayeschannel_has_quirks(channel, HAYES_QUIRK_WANT_SMSC_IN_PDU)
? HAYESPDU_FLAG_WANT_SMSC : 0;
return hayespdu_encode(number, encoding, length, content, flags);
}
/* parser */
/* hayes_parse */
static int _parse_do(Hayes * hayes, HayesChannel * channel);
static int _hayes_parse(Hayes * hayes, HayesChannel * channel)
{
int ret = 0;
size_t i;
char * p;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() cnt=%zu\n", __func__, channel->rd_buf_cnt);
#endif
for(i = 0; i < channel->rd_buf_cnt;)
{
if(channel->rd_buf[i] == '\r')
{
if(i + 1 < channel->rd_buf_cnt
&& channel->rd_buf[i + 1] == '\n')
channel->rd_buf[i++] = '\0';
}
else if(channel->rd_buf[i] != '\n')
{
i++;
continue;
}
channel->rd_buf[i++] = '\0';
if(channel->rd_buf[0] != '\0')
ret |= _parse_do(hayes, channel);
channel->rd_buf_cnt -= i;
memmove(channel->rd_buf, &channel->rd_buf[i],
channel->rd_buf_cnt);
i = 0;
}
if((p = realloc(channel->rd_buf, channel->rd_buf_cnt)) != NULL)
/* we can ignore errors... */
channel->rd_buf = p;
else if(channel->rd_buf_cnt == 0)
/* ...except when it's not one */
channel->rd_buf = NULL;
return ret;
}
static int _parse_do(Hayes * hayes, HayesChannel * channel)
{
char const * line = channel->rd_buf;
HayesCommand * command = (channel->queue != NULL) ? channel->queue->data
: NULL;
HayesCommandStatus status;
if(command == NULL || hayes_command_get_status(command) != HCS_ACTIVE)
/* this was most likely unsollicited */
return _hayes_parse_trigger(channel, line, NULL);
_hayes_parse_trigger(channel, line, command);
if(hayes_command_answer_append(command, line) != 0)
return -1;
if((status = hayes_command_get_status(command)) == HCS_ACTIVE)
hayes_command_callback(command);
/* unqueue if complete */
if(hayes_command_is_complete(command))
{
hayeschannel_queue_pop(channel);
_hayes_queue_push(hayes, channel);
}
return 0;
}
/* hayes_parse_pdu */
static int _parse_pdu_resume(Hayes * hayes, HayesChannel * channel);
static int _parse_pdu_send(Hayes * hayes, HayesChannel * channel);
static int _hayes_parse_pdu(Hayes * hayes, HayesChannel * channel)
{
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() cnt=%zu\n", __func__, channel->rd_buf_cnt);
for(i = 0; i < channel->rd_buf_cnt; i++)
fprintf(stderr, " %02x", channel->rd_buf[i]);
fputc('\n', stderr);
#endif
for(i = 0; i < channel->rd_buf_cnt;)
{
if(channel->rd_buf[i] == '\r'
|| channel->rd_buf[i] == '\n')
{
/* ignore carriage returns */
i++;
continue;
}
/* look for the PDU prompt */
if(channel->rd_buf[i] != '>')
return _parse_pdu_resume(hayes, channel);
if(i + 1 >= channel->rd_buf_cnt)
/* we need more data */
break;
if(channel->rd_buf[++i] != ' ')
return _parse_pdu_resume(hayes, channel);
_parse_pdu_send(hayes, channel);
i++;
break;
}
channel->rd_buf_cnt -= i;
memmove(channel->rd_buf, &channel->rd_buf[i], channel->rd_buf_cnt);
return 0;
}
static int _parse_pdu_resume(Hayes * hayes, HayesChannel * channel)
{
char eop = 0x1a;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
/* XXX check for errors and report them */
hayeschannel_queue_data(channel, &eop, sizeof(eop));
if(channel->channel != NULL && channel->wr_source == 0)
channel->wr_source = g_io_add_watch(channel->channel, G_IO_OUT,
_on_watch_can_write, channel);
_hayes_set_mode(hayes, channel, HAYESCHANNEL_MODE_COMMAND);
return 0;
}
static int _parse_pdu_send(Hayes * hayes, HayesChannel * channel)
{
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
char * pdu;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(command != NULL && hayes_command_get_status(command) == HCS_ACTIVE
&& (pdu = hayes_command_get_data(command)) != NULL)
{
/* XXX check for errors and report them */
hayeschannel_queue_data(channel, pdu, strlen(pdu));
free(pdu);
hayes_command_set_data(command, NULL);
}
return _parse_pdu_resume(hayes, channel);
}
/* hayes_parse_trigger */
static int _hayes_parse_trigger(HayesChannel * channel, char const * answer,
HayesCommand * command)
{
size_t i;
const size_t count = sizeof(_hayes_code_handlers)
/ sizeof(*_hayes_code_handlers);
size_t len;
HayesCodeHandler * hch;
char const * p;
int j;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", command)\n", __func__, answer);
#endif
/* if the handler is obvious return directly */
for(i = 0; i < count; i++)
{
hch = &_hayes_code_handlers[i];
len = strlen(hch->code);
if(strncmp(hch->code, answer, len) != 0)
continue;
if(answer[len] == ':')
{
if(answer[++len] == ' ') /* skip the optional space */
len++;
}
else if(answer[len] != '\0')
continue;
hch->callback(channel, &answer[len]);
return 0;
}
/* if the answer has no prefix choose it from the command issued */
if(command == NULL
|| (p = hayes_command_get_attention(command)) == NULL
|| strncmp(p, "AT", 2) != 0)
return 0;
for(i = 0; i < count; i++)
{
hch = &_hayes_code_handlers[i];
len = strlen(hch->code);
if(strncmp(hch->code, &p[2], len) != 0
|| isalnum((j = p[2 + len])))
continue;
hch->callback(channel, answer);
return 0;
}
return 0;
}
/* queue */
/* hayes_queue_command */
static int _hayes_queue_command(Hayes * hayes, HayesChannel * channel,
HayesCommand * command)
{
GSList * queue;
switch(channel->mode)
{
case HAYESCHANNEL_MODE_INIT:
/* ignore commands besides initialization */
if(hayes_command_get_priority(command)
!= HCP_IMMEDIATE)
return -1;
/* fallthrough */
case HAYESCHANNEL_MODE_COMMAND:
case HAYESCHANNEL_MODE_DATA:
case HAYESCHANNEL_MODE_PDU:
if(hayes_command_set_status(command, HCS_QUEUED)
!= HCS_QUEUED)
return -1;
queue = channel->queue;
channel->queue = g_slist_append(channel->queue,
command);
if(queue == NULL)
_hayes_queue_push(hayes, channel);
break;
}
return 0;
}
#if 0 /* XXX no longer used */
/* hayes_queue_command_full */
static int _hayes_queue_command_full(Hayes * hayes,
char const * attention, HayesCommandCallback callback)
{
HayesCommand * command;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, attention);
#endif
if((command = hayes_command_new(attention)) == NULL)
return -hayes->helper->error(hayes->helper->modem,
error_get(NULL), 1);
hayes_command_set_callback(command, callback, hayes);
if(_hayes_queue_command(hayes, command) != 0)
{
hayes_command_delete(command);
return -1;
}
return 0;
}
#endif
/* hayes_queue_push */
static int _queue_push_do(Hayes * hayes, HayesChannel * channel);
static int _hayes_queue_push(Hayes * hayes, HayesChannel * channel)
{
while(_queue_push_do(hayes, channel) != 0);
return 0;
}
static int _queue_push_do(Hayes * hayes, HayesChannel * channel)
{
HayesCommand * command;
char const * prefix = "";
char const * attention;
const char suffix[] = "\r\n";
size_t size;
char * buf;
guint timeout;
if(channel->queue == NULL) /* nothing to send */
return 0;
command = channel->queue->data;
if(channel->mode == HAYESCHANNEL_MODE_DATA)
#if 0 /* FIXME does not seem to work (see ATS2, ATS12) */
prefix = "+++\r\n";
#else
return 0; /* XXX keep commands in the queue in DATA mode */
#endif
if(hayes_command_set_status(command, HCS_PENDING) != HCS_PENDING)
{
/* no longer push the command */
hayeschannel_queue_pop(channel);
return -1;
}
attention = hayes_command_get_attention(command);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() pushing \"%s\"\n", __func__, attention);
#endif
size = strlen(prefix) + strlen(attention) + sizeof(suffix);
if((buf = malloc(size)) == NULL
|| snprintf(buf, size, "%s%s%s", prefix, attention,
suffix) != (int)size - 1
|| hayeschannel_queue_data(channel, buf, size - 1) != 0)
{
free(buf);
hayes_command_set_status(command, HCS_ERROR);
hayeschannel_queue_pop(channel);
return -hayes->helper->error(hayes->helper->modem, strerror(
errno), 1);
}
free(buf);
if(channel->channel != NULL && channel->wr_source == 0)
channel->wr_source = g_io_add_watch(channel->channel, G_IO_OUT,
_on_watch_can_write, channel);
hayescommon_source_reset(&channel->timeout);
if((timeout = hayes_command_get_timeout(command)) != 0)
channel->timeout = g_timeout_add(timeout, _on_channel_timeout,
channel);
return 0;
}
/* hayes_request_channel */
static char * _request_attention(Hayes * hayes, HayesChannel * channel,
ModemRequest * request, void ** data);
static char * _request_attention_apn(char const * protocol, char const * apn);
static char * _request_attention_call(HayesChannel * channel,
ModemRequest * request);
static char * _request_attention_call_data(HayesChannel * channel,
ModemRequest * request);
static char * _request_attention_call_ussd(ModemRequest * request);
static char * _request_attention_call_hangup(Hayes * hayes,
HayesChannel * channel);
static char * _request_attention_connectivity(Hayes * hayes,
HayesChannel * channel, unsigned int enabled);
static char * _request_attention_contact_delete(HayesChannel * channel,
unsigned int id);
static char * _request_attention_contact_edit(unsigned int id,
char const * name, char const * number);
static char * _request_attention_contact_list(ModemRequest * request);
static char * _request_attention_contact_new(char const * name,
char const * number);
static char * _request_attention_dtmf_send(ModemRequest * request);
static char * _request_attention_gprs(Hayes * hayes, HayesChannel * channel,
char const * username, char const * password);
static char * _request_attention_message(unsigned int id);
static char * _request_attention_message_delete(HayesChannel * channel,
unsigned int id);
static char * _request_attention_message_list(Hayes * hayes,
HayesChannel * channel);
static char * _request_attention_message_send(Hayes * hayes,
HayesChannel * channel, char const * number,
ModemMessageEncoding encoding, size_t length,
char const * content, void ** data);
static char * _request_attention_password_set(Hayes * hayes, char const * name,
char const * oldpassword, char const * newpassword);
static char * _request_attention_registration(Hayes * hayes,
HayesChannel * channel, ModemRegistrationMode mode,
char const * _operator);
static char * _request_attention_sim_pin(Hayes * hayes, HayesChannel * channel,
char const * password);
static char * _request_attention_sim_puk(Hayes * hayes, HayesChannel * channel,
char const * password);
static char * _request_attention_unsupported(Hayes * hayes,
ModemRequest * request);
static int _request_channel_handler(Hayes * hayes, HayesChannel * channel,
ModemRequest * request, void * data,
HayesRequestHandler * handler);
static int _hayes_request_channel(Hayes * hayes, HayesChannel * channel,
ModemRequest * request, void * data)
{
unsigned int type = request->type;
size_t i;
const size_t count = sizeof(_hayes_request_handlers)
/ sizeof(*_hayes_request_handlers);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%u)\n", __func__,
(request != NULL) ? request->type : (unsigned)-1);
#endif
if(request == NULL)
return -1;
if(hayeschannel_has_quirks(channel, HAYES_QUIRK_CONNECTED_LINE_DISABLED)
&& type == HAYES_REQUEST_CONNECTED_LINE_ENABLE)
request->type = HAYES_REQUEST_CONNECTED_LINE_DISABLE;
for(i = 0; i < count; i++)
if(_hayes_request_handlers[i].type == request->type)
break;
if(i == count)
#ifdef DEBUG
return -hayes->helper->error(hayes->helper->modem,
"Unable to handle request", 1);
#else
return -hayes->helper->error(NULL, "Unable to handle request",
1);
#endif
return _request_channel_handler(hayes, channel, request, data,
&_hayes_request_handlers[i]);
}
static int _request_channel_handler(Hayes * hayes, HayesChannel * channel,
ModemRequest * request, void * data,
HayesRequestHandler * handler)
{
HayesCommand * command;
char const * attention;
char * p = NULL;
if((attention = handler->attention) == NULL)
{
if((p = _request_attention(hayes, channel, request, &data))
== NULL)
return 0; /* XXX errors should not be ignored */
attention = p;
}
/* XXX using _hayes_queue_command_full() was more elegant */
command = hayes_command_new(attention);
free(p);
if(command == NULL)
return -1;
hayes_command_set_callback(command, handler->callback, channel);
if(_hayes_queue_command(hayes, channel, command) != 0)
{
hayes_command_delete(command);
return -1;
}
if(data != NULL)
hayes_command_set_data(command, data);
return 0;
}
static char * _request_attention(Hayes * hayes, HayesChannel * channel,
ModemRequest * request, void ** data)
{
unsigned int type = request->type;
char buf[32];
switch(type)
{
case HAYES_REQUEST_CONTACT_LIST:
return _request_attention_contact_list(request);
case MODEM_REQUEST_AUTHENTICATE:
if(strcmp(request->authenticate.name, "APN") == 0)
return _request_attention_apn(
request->authenticate.username,
request->authenticate.password);
if(strcmp(request->authenticate.name, "GPRS") == 0)
return _request_attention_gprs(hayes, channel,
request->authenticate.username,
request->authenticate.password);
if(strcmp(request->authenticate.name, "SIM PIN") == 0)
return _request_attention_sim_pin(hayes,
channel,
request->authenticate.password);
if(strcmp(request->authenticate.name, "SIM PUK") == 0)
return _request_attention_sim_puk(hayes,
channel,
request->authenticate.password);
break;
case MODEM_REQUEST_CALL:
if(request->call.call_type == MODEM_CALL_TYPE_VOICE
&& _is_ussd_code(request->call.number))
return _request_attention_call_ussd(request);
else if(request->call.call_type == MODEM_CALL_TYPE_DATA)
return _request_attention_call_data(channel,
request);
else
return _request_attention_call(channel, request);
case MODEM_REQUEST_CALL_HANGUP:
return _request_attention_call_hangup(hayes, channel);
case MODEM_REQUEST_CALL_PRESENTATION:
snprintf(buf, sizeof(buf), "%s%u", "AT+CLIP=",
request->call_presentation.enabled
? 1 : 0);
return strdup(buf);
case MODEM_REQUEST_CONNECTIVITY:
return _request_attention_connectivity(hayes, channel,
request->connectivity.enabled);
case MODEM_REQUEST_CONTACT_DELETE:
return _request_attention_contact_delete(channel,
request->contact_delete.id);
case MODEM_REQUEST_CONTACT_EDIT:
return _request_attention_contact_edit(
request->contact_edit.id,
request->contact_edit.name,
request->contact_edit.number);
case MODEM_REQUEST_CONTACT_NEW:
return _request_attention_contact_new(
request->contact_new.name,
request->contact_new.number);
case MODEM_REQUEST_DTMF_SEND:
return _request_attention_dtmf_send(request);
case MODEM_REQUEST_MESSAGE:
return _request_attention_message(request->message.id);
case MODEM_REQUEST_MESSAGE_LIST:
return _request_attention_message_list(hayes, channel);
case MODEM_REQUEST_MESSAGE_DELETE:
return _request_attention_message_delete(channel,
request->message_delete.id);
case MODEM_REQUEST_MESSAGE_SEND:
return _request_attention_message_send(hayes, channel,
request->message_send.number,
request->message_send.encoding,
request->message_send.length,
request->message_send.content, data);
case MODEM_REQUEST_PASSWORD_SET:
return _request_attention_password_set(hayes,
request->password_set.name,
request->password_set.oldpassword,
request->password_set.newpassword);
case MODEM_REQUEST_REGISTRATION:
return _request_attention_registration(hayes, channel,
request->registration.mode,
request->registration._operator);
case MODEM_REQUEST_UNSUPPORTED:
return _request_attention_unsupported(hayes, request);
default:
break;
}
return NULL;
}
static char * _request_attention_apn(char const * protocol, char const * apn)
{
char * ret;
const char cmd[] = "AT+CGDCONT=1,";
size_t len;
if(protocol == NULL || apn == NULL)
return NULL;
len = sizeof(cmd) + strlen(protocol) + 2 + strlen(apn) + 3;
if((ret = malloc(len)) == NULL)
return NULL;
snprintf(ret, len, "%s\"%s\",\"%s\"", cmd, protocol, apn);
return ret;
}
static char * _request_attention_call(HayesChannel * channel,
ModemRequest * request)
{
char * ret;
char const * number = request->call.number;
ModemEvent * event;
const char cmd[] = "ATD";
const char anonymous[] = "I";
const char voice[] = ";";
size_t len;
if(request->call.number == NULL)
request->call.number = "";
if(request->call.number[0] == '\0')
number = "L";
else if(!hayescommon_number_is_valid(request->call.number))
return NULL;
event = &channel->events[MODEM_EVENT_TYPE_CALL];
/* XXX should really be set at the time of the call */
event->call.call_type = request->call.call_type;
free(channel->call_number);
if(request->call.call_type == MODEM_CALL_TYPE_DATA)
channel->call_number = NULL;
else if((channel->call_number = strdup(request->call.number)) == NULL)
return NULL;
event->call.number = channel->call_number;
len = sizeof(cmd) + strlen(number) + sizeof(anonymous) + sizeof(voice);
if((ret = malloc(len)) == NULL)
return NULL;
snprintf(ret, len, "%s%s%s%s", cmd, number,
(request->call.anonymous) ? anonymous : "",
(request->call.call_type == MODEM_CALL_TYPE_VOICE)
? voice : "");
return ret;
}
static char * _request_attention_call_data(HayesChannel * channel,
ModemRequest * request)
{
if(request->call.number == NULL)
return strdup("AT+CGDATA=\"PPP\"");
return _request_attention_call(channel, request);
}
static char * _request_attention_call_ussd(ModemRequest * request)
{
char * ret;
char const * number = request->call.number;
const char cmd[] = "AT+CUSD=1,";
size_t len;
if(request->call.number == NULL || request->call.number[0] == '\0')
return NULL;
len = sizeof(cmd) + strlen(number) + 2;
if((ret = malloc(len)) == NULL)
return NULL;
/* XXX may also require setting dcs */
snprintf(ret, len, "%s\"%s\"", cmd, number);
return ret;
}
static char * _request_attention_call_hangup(Hayes * hayes,
HayesChannel * channel)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CONNECTION];
/* FIXME check that this works on all phones, including:
* - while calling:
* . still ringing => simply inject "\r\n"?
* . in the queue => simply remove?
* - while ringing (incoming) */
if(channel->mode == HAYESCHANNEL_MODE_DATA)
{
event->connection.connected = 0;
event->connection.in = 0;
event->connection.out = 0;
hayes->helper->event(hayes->helper->modem, event);
_hayes_set_mode(hayes, channel, HAYESCHANNEL_MODE_INIT);
return NULL;
}
/* return "ATH" if currently ringing */
event = &channel->events[MODEM_EVENT_TYPE_CALL];
if(event->call.direction == MODEM_CALL_DIRECTION_INCOMING
&& event->call.status == MODEM_CALL_STATUS_RINGING)
return strdup("ATH");
/* force all calls to terminate */
return strdup("AT+CHUP");
}
static char * _request_attention_connectivity(Hayes * hayes,
HayesChannel * channel, unsigned int enabled)
{
_hayes_request_type(hayes, channel, enabled
? HAYES_REQUEST_FUNCTIONAL_ENABLE
: HAYES_REQUEST_FUNCTIONAL_DISABLE);
return NULL;
}
static char * _request_attention_contact_delete(HayesChannel * channel,
unsigned int id)
{
char const cmd[] = "AT+CPBW=";
char buf[32];
/* FIXME store in the command itself */
channel->events[MODEM_EVENT_TYPE_CONTACT_DELETED].contact_deleted.id
= id;
snprintf(buf, sizeof(buf), "%s%u%s", cmd, id, ",");
return strdup(buf);
}
static char * _request_attention_contact_edit(unsigned int id,
char const * name, char const * number)
{
char const cmd[] = "AT+CPBW=";
char buf[128];
char * p;
if(!hayescommon_number_is_valid(number)
|| name == NULL || strlen(name) == 0)
/* XXX report error */
return NULL;
if((p = g_convert(name, -1, "ISO-8859-1", "UTF-8", NULL, NULL, NULL))
!= NULL)
{
_hayes_convert_iso_string_to_gsm(p);
name = p;
}
if(snprintf(buf, sizeof(buf), "%s%u%s\"%s\"%s%u%s\"%s\"", cmd, id, ",",
(number[0] == '+') ? &number[1] : number, ",",
(number[0] == '+') ? 145 : 129, ",", name)
>= (int)sizeof(buf))
{
g_free(p);
/* XXX report error */
return NULL;
}
g_free(p);
return strdup(buf);
}
static char * _request_attention_contact_list(ModemRequest * request)
{
HayesRequestContactList * list = request->plugin.data;
const char cmd[] = "AT+CPBR=";
char buf[32];
if(list->to < list->from)
list->to = list->from;
snprintf(buf, sizeof(buf), "%s%u,%u", cmd, list->from, list->to);
return strdup(buf);
}
static char * _request_attention_contact_new(char const * name,
char const * number)
{
char const cmd[] = "AT+CPBW=";
char buf[128];
char * p;
if(!hayescommon_number_is_valid(number)
|| name == NULL || strlen(name) == 0)
/* XXX report error */
return NULL;
if((p = g_convert(name, -1, "ISO-8859-1", "UTF-8", NULL, NULL, NULL))
!= NULL)
{
_hayes_convert_iso_string_to_gsm(p);
name = p;
}
if(snprintf(buf, sizeof(buf), "%s%s\"%s\"%s%u%s\"%s\"", cmd, ",",
(number[0] == '+') ? &number[1] : number, ",",
(number[0] == '+') ? 145 : 129, ",", name)
>= (int)sizeof(buf))
{
g_free(p);
/* XXX report error */
return NULL;
}
g_free(p);
return strdup(buf);
}
static char * _request_attention_dtmf_send(ModemRequest * request)
{
const char cmd[] = "AT+VTS=";
char buf[32];
unsigned int dtmf = request->dtmf_send.dtmf;
if((dtmf < '0' || dtmf > '9') && (dtmf < 'A' || dtmf > 'D')
&& dtmf != '*' && dtmf != '#')
return NULL;
snprintf(buf, sizeof(buf), "%s%c", cmd, dtmf);
return strdup(buf);
}
static char * _request_attention_gprs(Hayes * hayes, HayesChannel * channel,
char const * username, char const * password)
{
free(channel->gprs_username);
channel->gprs_username = (username != NULL) ? strdup(username) : NULL;
free(channel->gprs_password);
channel->gprs_password = (password != NULL) ? strdup(password) : NULL;
/* check for errors */
if((username != NULL && channel->gprs_username == NULL)
|| (password != NULL && channel->gprs_password == NULL))
hayes->helper->error(NULL, strerror(errno), 1);
return NULL; /* we don't need to issue any command */
}
static char * _request_attention_message(unsigned int id)
{
char const cmd[] = "AT+CMGR=";
char buf[32];
/* FIXME force the message format to be in PDU mode? */
snprintf(buf, sizeof(buf), "%s%u", cmd, id);
return strdup(buf);
}
static char * _request_attention_message_delete(HayesChannel * channel,
unsigned int id)
{
char const cmd[] = "AT+CMGD=";
char buf[32];
/* FIXME store in the command itself */
channel->events[MODEM_EVENT_TYPE_MESSAGE_DELETED].message_deleted.id
= id;
snprintf(buf, sizeof(buf), "%s%u", cmd, id);
return strdup(buf);
}
static char * _request_attention_message_list(Hayes * hayes,
HayesChannel * channel)
{
ModemRequest request;
HayesRequestMessageData * data;
memset(&request, 0, sizeof(request));
/* request received unread messages */
request.type = HAYES_REQUEST_MESSAGE_LIST_INBOX_UNREAD;
if((data = malloc(sizeof(*data))) != NULL)
{
data->id = 0;
data->folder = MODEM_MESSAGE_FOLDER_INBOX;
data->status = MODEM_MESSAGE_STATUS_UNREAD;
}
if(_hayes_request_channel(hayes, channel, &request, data) != 0)
free(data);
/* request received read messages */
request.type = HAYES_REQUEST_MESSAGE_LIST_INBOX_READ;
if((data = malloc(sizeof(*data))) != NULL)
{
data->id = 0;
data->folder = MODEM_MESSAGE_FOLDER_INBOX;
data->status = MODEM_MESSAGE_STATUS_READ;
}
if(_hayes_request_channel(hayes, channel, &request, data) != 0)
free(data);
/* request sent unread messages */
request.type = HAYES_REQUEST_MESSAGE_LIST_SENT_UNREAD;
if((data = malloc(sizeof(*data))) != NULL)
{
data->id = 0;
data->folder = MODEM_MESSAGE_FOLDER_OUTBOX;
data->status = MODEM_MESSAGE_STATUS_UNREAD;
}
if(_hayes_request_channel(hayes, channel, &request, data) != 0)
free(data);
/* request sent read messages */
request.type = HAYES_REQUEST_MESSAGE_LIST_SENT_READ;
if((data = malloc(sizeof(*data))) != NULL)
{
data->id = 0;
data->folder = MODEM_MESSAGE_FOLDER_OUTBOX;
data->status = MODEM_MESSAGE_STATUS_READ;
}
if(_hayes_request_channel(hayes, channel, &request, data) != 0)
free(data);
return NULL;
}
static char * _request_attention_message_send(Hayes * hayes,
HayesChannel * channel, char const * number,
ModemMessageEncoding encoding, size_t length,
char const * content, void ** data)
{
char * ret;
char const cmd[] = "AT+CMGS=";
char * pdu;
size_t pdulen;
size_t len;
if(_hayes_request_type(hayes, channel, HAYES_REQUEST_MESSAGE_FORMAT_PDU)
!= 0)
return NULL;
if((pdu = _hayes_message_to_pdu(channel, number, encoding, length,
content)) == NULL)
return NULL;
len = sizeof(cmd) + 11;
if((ret = malloc(len)) == NULL)
{
free(pdu);
return NULL;
}
pdulen = strlen(pdu);
if(hayeschannel_has_quirks(channel, HAYES_QUIRK_WANT_SMSC_IN_PDU))
pdulen -= 2;
snprintf(ret, len, "%s%zu", cmd, pdulen / 2);
*data = pdu;
return ret;
}
static char * _request_attention_password_set(Hayes * hayes, char const * name,
char const * oldpassword, char const * newpassword)
{
char * ret;
size_t len;
char const cpwd[] = "AT+CPWD=";
char const * n;
if(name == NULL || oldpassword == NULL || newpassword == NULL)
return NULL;
if(strcmp(name, "SIM PIN") == 0)
n = "SC";
else
return NULL;
len = sizeof(cpwd) + strlen(n) + 2 + strlen(oldpassword) + 3
+ strlen(newpassword) + 3;
if((ret = malloc(len)) == NULL)
{
hayes->helper->error(NULL, strerror(errno), 1);
return NULL;
}
snprintf(ret, len, "%s\"%s\",\"%s\",\"%s\"", cpwd, n, oldpassword,
newpassword);
return ret;
}
static char * _request_attention_registration(Hayes * hayes,
HayesChannel * channel, ModemRegistrationMode mode,
char const * _operator)
{
char const cops[] = "AT+COPS=";
size_t len = sizeof(cops) + 5;
char * p;
switch(mode)
{
case MODEM_REGISTRATION_MODE_AUTOMATIC:
_hayes_request_type(hayes, channel,
HAYES_REQUEST_REGISTRATION_AUTOMATIC);
break;
case MODEM_REGISTRATION_MODE_DISABLED:
_hayes_request_type(hayes, channel,
HAYES_REQUEST_REGISTRATION_DISABLED);
break;
case MODEM_REGISTRATION_MODE_MANUAL:
if(_operator == NULL)
return NULL;
len += strlen(_operator);
if((p = malloc(len)) == NULL)
return NULL;
snprintf(p, len, "%s=1,0,%s", cops, _operator);
return p;
case MODEM_REGISTRATION_MODE_UNKNOWN:
break;
}
return NULL;
}
static char * _request_attention_sim_pin(Hayes * hayes, HayesChannel * channel,
char const * password)
{
char * ret;
const char cmd[] = "AT+CPIN=";
size_t len;
char const * format;
if(password == NULL)
return NULL;
len = sizeof(cmd) + strlen(password) + 2;
if((ret = malloc(len)) == NULL)
{
hayes->helper->error(NULL, strerror(errno), 1);
return NULL;
}
format = hayeschannel_has_quirks(channel, HAYES_QUIRK_CPIN_NO_QUOTES)
? "%s%s" : "%s\"%s\"";
snprintf(ret, len, format, cmd, password);
return ret;
}
static char * _request_attention_sim_puk(Hayes * hayes, HayesChannel * channel,
char const * password)
{
char * ret;
const char cmd[] = "AT+CPIN=";
size_t len;
char const * format;
if(password == NULL)
return NULL;
len = sizeof(cmd) + strlen(password) + 3;
if((ret = malloc(len)) == NULL)
{
hayes->helper->error(NULL, strerror(errno), 1);
return NULL;
}
format = hayeschannel_has_quirks(channel, HAYES_QUIRK_CPIN_NO_QUOTES)
? "%s%s," : "%s\"%s\",";
snprintf(ret, len, format, cmd, password);
return ret;
}
static char * _request_attention_unsupported(Hayes * hayes,
ModemRequest * request)
{
HayesRequest * hrequest = request->unsupported.request;
(void) hayes;
if(strcmp(request->unsupported.modem, plugin.name) != 0)
return NULL;
if(request->unsupported.size != sizeof(*hrequest))
return NULL;
switch(request->unsupported.request_type)
{
case HAYES_REQUEST_COMMAND_QUEUE:
return strdup(hrequest->command_queue.command);
default:
return NULL;
}
}
/* hayes_request_type */
static int _hayes_request_type(Hayes * hayes, HayesChannel * channel,
ModemRequestType type)
{
ModemRequest request;
memset(&request, 0, sizeof(request));
request.type = type;
return _hayes_request_channel(hayes, channel, &request, NULL);
}
/* hayes_reset */
static void _hayes_reset(Hayes * hayes)
{
_hayes_stop(hayes);
_hayes_start(hayes, hayes->retry);
}
/* callbacks */
/* on_channel_authenticate */
static gboolean _on_channel_authenticate(gpointer data)
{
const guint timeout = 1000;
HayesChannel * channel = data;
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_AUTHENTICATION];
if(channel->authenticate_count++ < 10)
{
channel->authenticate_source = g_timeout_add(timeout,
_on_channel_authenticate, channel);
/* FIXME this must stop the "checking for SIM PIN" dialog */
_hayes_trigger(hayes, MODEM_EVENT_TYPE_AUTHENTICATION);
}
else
{
channel->authenticate_count = 0;
channel->authenticate_source = 0;
event->authentication.status
= MODEM_AUTHENTICATION_STATUS_ERROR;
hayes->helper->event(hayes->helper->modem, event);
}
return FALSE;
}
/* on_channel_reset */
static int _reset_open(Hayes * hayes);
static int _reset_configure(Hayes * hayes, char const * device, int fd);
static unsigned int _reset_configure_baudrate(Hayes * hayes,
unsigned int baudrate);
static gboolean _on_channel_reset(gpointer data)
{
HayesChannel * channel = data;
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_STATUS];
GError * error = NULL;
int fd;
char const * logfile;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
_hayes_stop(hayes);
if((fd = _reset_open(hayes)) < 0)
{
if(event->status.status != MODEM_STATUS_UNAVAILABLE)
{
event->status.status = MODEM_STATUS_UNAVAILABLE;
hayes->helper->event(hayes->helper->modem, event);
}
hayes->helper->error(NULL, error_get(NULL), 1);
if(hayes->retry > 0)
channel->source = g_timeout_add(hayes->retry,
_on_channel_reset, channel);
return FALSE;
}
event->status.status = MODEM_STATUS_UNKNOWN;
/* logging */
logfile = helper->config_get(helper->modem, "logfile");
if(logfile != NULL)
{
if((channel->fp = fopen(logfile, "w")) == NULL)
hayes->helper->error(NULL, strerror(errno), 1);
else
setvbuf(channel->fp, NULL, _IONBF, BUFSIZ);
}
channel->channel = g_io_channel_unix_new(fd);
if(g_io_channel_set_encoding(channel->channel, NULL, &error)
!= G_IO_STATUS_NORMAL)
{
hayes->helper->error(hayes->helper->modem, error->message, 1);
g_error_free(error);
}
g_io_channel_set_buffered(channel->channel, FALSE);
channel->rd_source = g_io_add_watch(channel->channel, G_IO_IN,
_on_watch_can_read, channel);
channel->source = g_idle_add(_on_reset_settle, channel);
return FALSE;
}
static int _reset_open(Hayes * hayes)
{
ModemPluginHelper * helper = hayes->helper;
char const * device;
int fd;
if((device = helper->config_get(helper->modem, "device")) == NULL)
device = "/dev/modem";
if((fd = open(device, O_RDWR | O_NONBLOCK)) < 0)
return -error_set_code(1, "%s: %s", device, strerror(errno));
if(_reset_configure(hayes, device, fd) != 0)
{
close(fd);
return -1;
}
return fd;
}
static int _reset_configure(Hayes * hayes, char const * device, int fd)
{
ModemPluginHelper * helper = hayes->helper;
unsigned int baudrate;
unsigned int hwflow;
struct stat st;
int fl;
struct termios term;
char const * p;
/* baud rate */
if((p = helper->config_get(helper->modem, "baudrate")) == NULL
|| (baudrate = strtoul(p, NULL, 10)) == 0)
baudrate = 115200;
baudrate = _reset_configure_baudrate(hayes, baudrate);
/* hardware flow */
if((p = helper->config_get(helper->modem, "hwflow")) == NULL
|| (hwflow = strtoul(p, NULL, 10)) != 0)
hwflow = 1;
/* lock the port for exclusive access */
if(flock(fd, LOCK_EX | LOCK_NB) != 0)
return 1;
/* set the port as blocking */
fl = fcntl(fd, F_GETFL, 0);
if((fl & ~O_NONBLOCK) != fl
&& fcntl(fd, F_SETFL, fl & ~O_NONBLOCK) == -1)
return 1;
if(fstat(fd, &st) != 0)
return 1;
if(st.st_mode & S_IFCHR) /* character special */
{
if(tcgetattr(fd, &term) != 0)
return 1;
term.c_cflag &= ~(CSIZE | PARENB);
term.c_cflag |= CS8;
term.c_cflag |= CREAD;
term.c_cflag |= CLOCAL;
if(hwflow != 0)
term.c_cflag |= CRTSCTS;
else
term.c_cflag &= ~CRTSCTS;
term.c_iflag = (IGNPAR | IGNBRK);
term.c_lflag = 0;
term.c_oflag = 0;
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
if(cfsetispeed(&term, 0) != 0) /* same speed as output speed */
error_set("%s", device); /* go on anyway */
if(cfsetospeed(&term, baudrate) != 0)
error_set("%s", device); /* go on anyway */
if(tcsetattr(fd, TCSAFLUSH, &term) != 0)
return 1;
}
return 0;
}
static unsigned int _reset_configure_baudrate(Hayes * hayes,
unsigned int baudrate)
{
switch(baudrate)
{
case 1200:
return B1200;
case 2400:
return B2400;
case 4800:
return B4800;
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
#ifdef B76800
case 76800:
return B76800;
#endif
#ifdef B14400
case 14400:
return B14400;
#endif
#ifdef B28800
case 28800:
return B28800;
#endif
case 57600:
return B57600;
case 115200:
return B115200;
case 230400:
return B230400;
#ifdef B460800
case 460800:
return B460800;
#endif
#ifdef B921600
case 921600:
return B921600;
#endif
default:
error_set("%u%s%u%s", baudrate,
": Unsupported baudrate (using ",
115200, ")");
hayes->helper->error(NULL, error_get(NULL), 1);
return B115200;
}
}
/* on_channel_timeout */
static gboolean _on_channel_timeout(gpointer data)
{
HayesChannel * channel = data;
Hayes * hayes = channel->hayes;
HayesCommand * command;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
channel->timeout = 0;
if(channel->queue == NULL || (command = channel->queue->data) == NULL)
return FALSE;
hayes_command_set_status(command, HCS_TIMEOUT);
hayeschannel_queue_pop(channel);
_hayes_queue_push(hayes, channel);
return FALSE;
}
/* on_queue_timeout */
static gboolean _on_queue_timeout(gpointer data)
{
const guint timeout = 1000;
HayesChannel * channel = data;
Hayes * hayes = channel->hayes;
HayesCommand * command;
channel->source = 0;
if(channel->queue_timeout == NULL) /* nothing to send */
return FALSE;
command = channel->queue_timeout->data;
_hayes_queue_command(hayes, channel, command);
channel->queue_timeout = g_slist_remove(channel->queue_timeout,
command);
if(channel->queue_timeout != NULL)
channel->source = g_timeout_add(timeout, _on_queue_timeout,
channel);
else
/* XXX check the registration again to be safe */
_hayes_request_type(hayes, channel, HAYES_REQUEST_REGISTRATION);
return FALSE;
}
/* on_reset_settle */
static void _reset_settle_command(HayesChannel * channel, char const * string);
static HayesCommandStatus _on_reset_settle_callback(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel);
static gboolean _on_reset_settle(gpointer data)
{
HayesChannel * channel = data;
channel->source = 0;
_reset_settle_command(channel, "ATZE0V1");
return FALSE;
}
static gboolean _on_reset_settle2(gpointer data)
{
HayesChannel * channel = data;
channel->source = 0;
/* try an alternative initialization string */
_reset_settle_command(channel, "ATE0V1");
return FALSE;
}
static void _reset_settle_command(HayesChannel * channel, char const * string)
{
const unsigned int timeout = 500;
Hayes * hayes = channel->hayes;
HayesCommand * command;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, string);
#endif
if((command = hayes_command_new(string)) == NULL)
{
hayes->helper->error(hayes->helper->modem, error_get(NULL), 1);
return;
}
hayes_command_set_callback(command, _on_reset_settle_callback, channel);
hayes_command_set_priority(command, HCP_IMMEDIATE);
hayes_command_set_timeout(command, timeout);
if(_hayes_queue_command(hayes, channel, command) != 0)
{
hayes->helper->error(hayes->helper->modem, error_get(NULL), 1);
hayes_command_delete(command);
}
}
static HayesCommandStatus _on_reset_settle_callback(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%s (%u))\n", __func__,
_hayes_command_status[status], status);
#endif
status = _on_request_generic(command, status, channel);
switch(status)
{
case HCS_UNKNOWN: /* ignore */
case HCS_QUEUED:
case HCS_PENDING:
break;
case HCS_ACTIVE: /* give it another chance */
break;
case HCS_SUCCESS: /* we can initialize */
_hayes_set_mode(hayes, channel,
HAYESCHANNEL_MODE_COMMAND);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_LOCAL_ECHO_DISABLE);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_VERBOSE_ENABLE);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_VENDOR);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_MODEL);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_EXTENDED_ERRORS);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_FUNCTIONAL);
break;
case HCS_TIMEOUT: /* try again */
case HCS_ERROR:
if(channel->source != 0)
g_source_remove(channel->source);
channel->source = g_timeout_add(hayes->retry,
_on_reset_settle2, channel);
break;
}
return status;
}
/* on_watch_can_read */
static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
gpointer data)
{
HayesChannel * channel = data;
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
const int inc = 256;
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
char * p;
if(condition != G_IO_IN || source != channel->channel)
return FALSE; /* should not happen */
if((p = realloc(channel->rd_buf, channel->rd_buf_cnt + inc)) == NULL)
return TRUE; /* XXX retries immediately (delay?) */
channel->rd_buf = p;
status = g_io_channel_read_chars(source,
&channel->rd_buf[channel->rd_buf_cnt], inc, &cnt,
&error);
_hayes_log(hayes, channel, "MODEM: ",
&channel->rd_buf[channel->rd_buf_cnt], cnt);
channel->rd_buf_cnt += cnt;
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
helper->error(helper->modem, error->message, 1);
g_error_free(error);
/* fallthrough */
case G_IO_STATUS_EOF:
default: /* should not happen... */
channel->rd_source = 0;
if(hayes->retry > 0)
_hayes_reset(hayes);
return FALSE;
}
switch(channel->mode)
{
case HAYESCHANNEL_MODE_INIT:
case HAYESCHANNEL_MODE_COMMAND:
_hayes_parse(hayes, channel);
break;
case HAYESCHANNEL_MODE_PDU:
_hayes_parse_pdu(hayes, channel);
break;
case HAYESCHANNEL_MODE_DATA:
if(channel->wr_ppp_channel == NULL
|| channel->wr_ppp_source != 0)
break;
channel->wr_ppp_source = g_io_add_watch(
channel->wr_ppp_channel, G_IO_OUT,
_on_watch_can_write_ppp, channel);
break;
}
return TRUE;
}
/* on_watch_can_read_ppp */
static gboolean _on_watch_can_read_ppp(GIOChannel * source,
GIOCondition condition, gpointer data)
{
HayesChannel * channel = data;
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CONNECTION];
const int inc = 256;
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
char * p;
if(condition != G_IO_IN || source != channel->rd_ppp_channel)
return FALSE; /* should not happen */
if((p = realloc(channel->wr_buf, channel->wr_buf_cnt + inc)) == NULL)
return TRUE; /* XXX retries immediately (delay?) */
channel->wr_buf = p;
status = g_io_channel_read_chars(source,
&channel->wr_buf[channel->wr_buf_cnt], inc, &cnt,
&error);
channel->wr_buf_cnt += cnt;
event->connection.out += cnt;
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
helper->error(helper->modem, error->message, 1);
g_error_free(error);
/* fallthrough */
case G_IO_STATUS_EOF:
default:
channel->rd_ppp_source = 0;
event->connection.connected = 0;
helper->event(helper->modem, event);
_hayes_set_mode(hayes, channel, HAYESCHANNEL_MODE_INIT);
return FALSE;
}
if(channel->channel != NULL && channel->wr_source == 0)
channel->wr_source = g_io_add_watch(channel->channel, G_IO_OUT,
_on_watch_can_write, channel);
return TRUE;
}
/* on_watch_can_write */
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
gpointer data)
{
HayesChannel * channel = data;
HayesCommand * command = (channel->queue) != NULL
? channel->queue->data : NULL;
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
char * p;
if(condition != G_IO_OUT || source != channel->channel)
return FALSE; /* should not happen */
status = g_io_channel_write_chars(source, channel->wr_buf,
channel->wr_buf_cnt, &cnt, &error);
_hayes_log(hayes, channel, "PHONE: ", channel->wr_buf, cnt);
if(cnt != 0) /* some data may have been written anyway */
{
channel->wr_buf_cnt -= cnt;
memmove(channel->wr_buf, &channel->wr_buf[cnt],
channel->wr_buf_cnt);
if((p = realloc(channel->wr_buf, channel->wr_buf_cnt)) != NULL)
/* we can ignore errors... */
channel->wr_buf = p;
else if(channel->wr_buf_cnt == 0)
/* ...except when it's not one */
channel->wr_buf = NULL;
}
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
helper->error(helper->modem, error->message, 1);
g_error_free(error);
/* fallthrough */
case G_IO_STATUS_EOF:
default: /* should not happen */
channel->wr_source = 0;
if(hayes->retry > 0)
_hayes_reset(hayes);
return FALSE;
}
if(channel->wr_buf_cnt > 0) /* there is more data to write */
return TRUE;
channel->wr_source = 0;
if(command != NULL)
hayes_command_set_status(command, HCS_ACTIVE);
return FALSE;
}
/* on_watch_can_write_ppp */
static gboolean _on_watch_can_write_ppp(GIOChannel * source,
GIOCondition condition, gpointer data)
{
HayesChannel * channel = data;
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CONNECTION];
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
char * p;
if(condition != G_IO_OUT || source != channel->wr_ppp_channel)
return FALSE; /* should not happen */
status = g_io_channel_write_chars(source, channel->rd_buf,
channel->rd_buf_cnt, &cnt, &error);
event->connection.in += cnt;
if(cnt != 0) /* some data may have been written anyway */
{
channel->rd_buf_cnt -= cnt;
memmove(channel->rd_buf, &channel->rd_buf[cnt],
channel->rd_buf_cnt);
if((p = realloc(channel->rd_buf, channel->rd_buf_cnt)) != NULL)
/* we can ignore errors... */
channel->rd_buf = p;
else if(channel->rd_buf_cnt == 0)
/* ...except when it's not one */
channel->rd_buf = NULL;
}
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
helper->error(helper->modem, error->message, 1);
g_error_free(error);
/* fallthrough */
case G_IO_STATUS_EOF:
default:
channel->wr_ppp_source = 0;
event->connection.connected = 0;
helper->event(helper->modem, event);
_hayes_set_mode(hayes, channel, HAYESCHANNEL_MODE_INIT);
return FALSE;
}
if(channel->rd_buf_cnt > 0) /* there is more data to write */
return TRUE;
channel->wr_ppp_source = 0;
return FALSE;
}
/* on_request_authenticate */
static HayesCommandStatus _on_request_authenticate(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
const char sim_pin[] = "SIM PIN";
const char sim_puk[] = "SIM PUK";
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_AUTHENTICATION];
guint timeout;
switch((status = _on_request_generic(command, status, channel)))
{
case HCS_SUCCESS:
break;
case HCS_ERROR:
event->authentication.status
= MODEM_AUTHENTICATION_STATUS_ERROR;
hayes->helper->event(hayes->helper->modem, event);
/* fallthrough */
default:
return status;
}
/* XXX it should be bound to the request instead */
if(event->authentication.name != NULL && (strcmp(sim_pin,
event->authentication.name) == 0
|| strcmp(sim_puk,
event->authentication.name) == 0))
{
/* verify that it really worked */
timeout = hayeschannel_has_quirks(channel,
HAYES_QUIRK_CPIN_SLOW) ? 1000 : 0;
channel->authenticate_count = 0;
if(channel->authenticate_source != 0)
g_source_remove(channel->authenticate_source);
channel->authenticate_source = g_timeout_add(timeout,
_on_channel_authenticate, channel);
}
else
{
event->authentication.status = MODEM_AUTHENTICATION_STATUS_OK;
hayes->helper->event(hayes->helper->modem, event);
}
return status;
}
/* on_request_battery_level */
static HayesCommandStatus _on_request_battery_level(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_BATTERY_LEVEL];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
/* on_request_call */
static HayesCommandStatus _on_request_call(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CALL];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
/* on_request_call_incoming */
static HayesCommandStatus _on_request_call_incoming(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CALL];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS && status != HCS_ERROR)
return status;
event->call.direction = MODEM_CALL_DIRECTION_INCOMING;
event->call.status = (status == HCS_SUCCESS)
? MODEM_CALL_STATUS_ACTIVE : MODEM_CALL_STATUS_NONE;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
/* on_request_call_outgoing */
static HayesCommandStatus _on_request_call_outgoing(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CALL];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS && status != HCS_ERROR)
return status;
event->call.direction = MODEM_CALL_DIRECTION_OUTGOING;
event->call.status = (status == HCS_SUCCESS)
? MODEM_CALL_STATUS_ACTIVE : MODEM_CALL_STATUS_NONE;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
/* on_request_call_status */
static HayesCommandStatus _on_request_call_status(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS && status != HCS_ERROR)
return status;
_hayes_request_type(hayes, channel, HAYES_REQUEST_PHONE_ACTIVE);
return status;
}
/* on_request_contact_delete */
static HayesCommandStatus _on_request_contact_delete(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CONTACT_DELETED];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
/* on_request_contact_list */
static HayesCommandStatus _on_request_contact_list(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
/* XXX could probably be more efficient */
_hayes_request_type(channel->hayes, channel,
MODEM_REQUEST_CONTACT_LIST);
return status;
}
/* on_request_functional */
static HayesCommandStatus _on_request_functional(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
switch((status = _on_request_generic(command, status, channel)))
{
case HCS_ERROR:
/* try to enable */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_FUNCTIONAL_ENABLE);
break;
default:
break;
}
return status;
}
/* on_request_functional_enable */
static HayesCommandStatus _on_request_functional_enable(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
switch((status = _on_request_generic(command, status, channel)))
{
case HCS_ERROR:
#if 0 /* XXX ignore for now (may simply be missing the PIN code) */
/* force a reset */
_hayes_request_type(hayes,
HAYES_REQUEST_FUNCTIONAL_ENABLE_RESET);
#endif
break;
case HCS_SUCCESS:
_on_code_cfun(channel, "1"); /* XXX ugly workaround */
break;
case HCS_TIMEOUT:
/* repeat request */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_FUNCTIONAL_ENABLE);
break;
default:
break;
}
return status;
}
/* on_request_functional_enable_reset */
static HayesCommandStatus _on_request_functional_enable_reset(
HayesCommand * command, HayesCommandStatus status,
HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
switch((status = _on_request_generic(command, status, channel)))
{
case HCS_SUCCESS:
_on_code_cfun(channel, "1"); /* XXX ugly workaround */
break;
case HCS_TIMEOUT:
/* repeat request */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_FUNCTIONAL_ENABLE);
break;
default:
break;
}
return status;
}
/* on_request_generic */
static HayesCommandStatus _on_request_generic(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
char const * answer;
char const * p;
(void) channel;
if(status != HCS_ACTIVE)
return status;
if((answer = hayes_command_get_answer(command)) == NULL)
return status;
/* look for the last line */
while((p = strchr(answer, '\n')) != NULL)
answer = ++p;
if(strcmp(answer, "OK") == 0)
return HCS_SUCCESS;
else if(strcmp(answer, "ERROR") == 0)
return HCS_ERROR;
return status;
}
/* on_request_message */
static HayesCommandStatus _on_request_message(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
HayesRequestMessageData * data;
if((status = _on_request_generic(command, status, channel))
== HCS_SUCCESS
|| status == HCS_ERROR || status == HCS_TIMEOUT)
if((data = hayes_command_get_data(command)) != NULL)
{
free(data);
hayes_command_set_data(command, NULL);
}
return status;
}
/* on_request_message_delete */
static HayesCommandStatus _on_request_message_delete(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MESSAGE_DELETED];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
/* on_request_message_list */
static HayesCommandStatus _on_request_message_list(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
HayesRequestMessageData * data;
if((status = _on_request_generic(command, status, channel))
== HCS_SUCCESS
|| status == HCS_ERROR || status == HCS_TIMEOUT)
if((data = hayes_command_get_data(command)) != NULL)
{
free(data);
hayes_command_set_data(command, NULL);
}
return status;
}
/* on_request_message_send */
static HayesCommandStatus _on_request_message_send(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MESSAGE_SENT];
char * pdu;
if((pdu = hayes_command_get_data(command)) != NULL
&& (status = _on_request_generic(command, status,
channel)) == HCS_ACTIVE)
_hayes_set_mode(hayes, channel, HAYESCHANNEL_MODE_PDU);
if(status == HCS_SUCCESS || status == HCS_ERROR
|| status == HCS_TIMEOUT)
{
free(pdu);
hayes_command_set_data(command, NULL);
}
if(status == HCS_ERROR)
{
event->message_sent.error = "Could not send message";
event->message_sent.id = 0;
hayes->helper->event(hayes->helper->modem, event);
}
return status;
}
/* on_request_model */
static HayesCommandStatus _on_request_model(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MODEL];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
/* on_request_registration */
static HayesCommandStatus _on_request_registration(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
/* force a registration status */
_hayes_request_type(hayes, channel, HAYES_REQUEST_REGISTRATION);
return status;
}
/* on_request_registration_automatic */
static HayesCommandStatus _on_request_registration_automatic(
HayesCommand * command, HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
status = _on_request_generic(command, status, channel);
switch(status)
{
case HCS_UNKNOWN:
case HCS_QUEUED:
case HCS_PENDING:
break;
case HCS_ACTIVE:
event->registration.mode
= MODEM_REGISTRATION_MODE_AUTOMATIC;
event->registration.status
= MODEM_REGISTRATION_STATUS_SEARCHING;
hayes->helper->event(hayes->helper->modem, event);
break;
case HCS_ERROR:
case HCS_TIMEOUT:
event->registration.mode
= MODEM_REGISTRATION_MODE_UNKNOWN;
event->registration.status
= MODEM_REGISTRATION_STATUS_UNKNOWN;
hayes->helper->event(hayes->helper->modem, event);
break;
case HCS_SUCCESS:
/* force a registration status */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_REGISTRATION);
break;
}
return status;
}
/* on_request_registration_disabled */
static HayesCommandStatus _on_request_registration_disabled(
HayesCommand * command, HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
if((status = _on_request_generic(command, status, channel))
!= HCS_SUCCESS)
return status;
event->registration.mode = MODEM_REGISTRATION_MODE_DISABLED;
/* force a registration status */
_hayes_request_type(hayes, channel, HAYES_REQUEST_REGISTRATION);
return status;
}
/* on_request_sim_pin_valid */
static HayesCommandStatus _on_request_sim_pin_valid(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_AUTHENTICATION];
ModemRequest request;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%u)\n", __func__, status);
#endif
if((status = _on_request_generic(command, status, channel)) == HCS_ERROR
|| status == HCS_TIMEOUT)
{
event->authentication.status
= MODEM_AUTHENTICATION_STATUS_ERROR;
hayes->helper->event(hayes->helper->modem, event);
return status;
}
else if(status != HCS_SUCCESS)
return status;
hayes->helper->event(hayes->helper->modem, event);
/* return if not successful */
if(event->authentication.status != MODEM_AUTHENTICATION_STATUS_OK)
return status;
/* apply useful settings */
_hayes_request_type(hayes, channel, HAYES_REQUEST_EXTENDED_ERRORS);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_EXTENDED_RING_REPORTS);
memset(&request, 0, sizeof(request));
request.type = MODEM_REQUEST_CALL_PRESENTATION;
request.call_presentation.enabled = 1;
_hayes_request(hayes, &request);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_ENABLE);
_hayes_request_type(hayes, channel,
HAYES_REQUEST_CONNECTED_LINE_ENABLE);
/* report new messages */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_MESSAGE_UNSOLLICITED_ENABLE);
/* report new notifications */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_SUPPLEMENTARY_SERVICE_DATA_ENABLE);
/* refresh the registration status */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_REGISTRATION_UNSOLLICITED_ENABLE);
/* refresh the current call status */
_hayes_trigger(hayes, MODEM_EVENT_TYPE_CALL);
/* refresh the contact list */
_hayes_request_type(hayes, channel, MODEM_REQUEST_CONTACT_LIST);
/* refresh the message list */
_hayes_request_type(hayes, channel, MODEM_REQUEST_MESSAGE_LIST);
return status;
}
/* on_request_unsupported */
static HayesCommandStatus _on_request_unsupported(HayesCommand * command,
HayesCommandStatus status, HayesChannel * channel)
{
/* FIXME report an unsupported event with the result of the command */
return _on_request_generic(command, status, channel);
}
/* on_code_call_error */
static void _on_code_call_error(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
(void) answer;
if(command != NULL)
hayes_command_set_status(command, HCS_ERROR);
_hayes_request_type(hayes, channel, HAYES_REQUEST_PHONE_ACTIVE);
}
/* on_code_cbc */
static void _on_code_cbc(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_BATTERY_LEVEL];
int res;
unsigned int u;
unsigned int v;
double f;
if((res = sscanf(answer, "%u,%u", &u, &v)) != 2)
return;
event->battery_level.status = MODEM_BATTERY_STATUS_UNKNOWN;
event->battery_level.charging = 0;
if(u == 0)
u = MODEM_BATTERY_STATUS_CONNECTED;
else if(u == 1)
u = MODEM_BATTERY_STATUS_CHARGING;
else if(u == 2)
u = MODEM_BATTERY_STATUS_NONE;
else if(u == 3)
u = MODEM_BATTERY_STATUS_ERROR;
else
u = MODEM_BATTERY_STATUS_UNKNOWN;
switch((event->battery_level.status = u))
{
case MODEM_BATTERY_STATUS_CHARGING:
event->battery_level.charging = 1;
/* fallthrough */
case MODEM_BATTERY_STATUS_CONNECTED:
f = v;
if(hayeschannel_has_quirks(channel,
HAYES_QUIRK_BATTERY_70))
f /= 70.0;
else
f /= 100.0;
f = max(f, 0.0);
event->battery_level.level = min(f, 1.0);
break;
default:
event->battery_level.level = 0.0 / 0.0;
break;
}
}
/* on_code_cfun */
static void _on_code_cfun(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_STATUS];
unsigned int u;
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 1:
/* report being online */
event->status.status = MODEM_STATUS_ONLINE;
break;
case 4: /* antennas disabled */
case 0: /* telephony disabled */
default:
/* FIXME this is maybe not the right event type */
event->status.status = MODEM_STATUS_OFFLINE;
break;
}
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cgatt */
static void _on_code_cgatt(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
unsigned int u;
if(sscanf(answer, "%u", &u) != 1)
return;
free(channel->registration_media);
channel->registration_media = NULL;
if(u == 1)
event->registration.media = "GPRS";
else
event->registration.media = NULL;
/* this is usually worth an event */
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cgmi */
static void _on_code_cgmi(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MODEL];
char * p;
if(answer[0] == '\0' || strcmp(answer, "OK") == 0
|| (p = strdup(answer)) == NULL) /* XXX report error? */
return;
free(channel->model_vendor);
channel->model_vendor = p;
event->model.vendor = p;
}
/* on_code_cgmm */
static void _on_code_cgmm(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MODEL];
char * p;
if(answer[0] == '\0' || strcmp(answer, "OK") == 0
|| (p = strdup(answer)) == NULL) /* XXX report error? */
return;
free(channel->model_name);
channel->model_name = p;
event->model.name = p;
hayeschannel_set_quirks(channel, hayes_quirks(event->model.vendor, p));
}
/* on_code_cgmr */
static void _on_code_cgmr(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MODEL];
char * p;
if(answer[0] == '\0' || strcmp(answer, "OK") == 0
|| (p = strdup(answer)) == NULL) /* XXX report error? */
return;
free(channel->model_version);
channel->model_version = p;
event->model.version = p;
}
/* on_code_cgsn */
static void _on_code_cgsn(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MODEL];
char * p;
if(answer[0] == '\0' || strcmp(answer, "OK") == 0
|| (p = strdup(answer)) == NULL) /* XXX report error? */
return;
free(channel->model_serial);
channel->model_serial = p;
event->model.serial = p;
}
/* on_code_cimi */
static void _on_code_cimi(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MODEL];
char * p;
if(answer[0] == '\0' || strcmp(answer, "OK") == 0
|| (p = strdup(answer)) == NULL) /* XXX report error? */
return;
free(channel->model_identity);
channel->model_identity = p;
event->model.identity = p;
}
/* on_code_clip */
static void _on_code_clip(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CALL];
char buf[32];
unsigned int u;
if(sscanf(answer, "\"%31[^\"]\",%u", buf, &u) != 2)
return;
buf[sizeof(buf) - 1] = '\0';
free(channel->call_number);
switch(u)
{
case 145:
if((channel->call_number = malloc(sizeof(buf) + 1))
== NULL)
break;
snprintf(channel->call_number, sizeof(buf) + 1, "%s%s",
"+", buf);
break;
default:
channel->call_number = strdup(buf);
break;
}
/* this is always an unsollicited event */
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cme_error */
static void _cme_command_repeat(HayesChannel * channel, HayesCommand * command);
static void _cme_error_registration(HayesChannel * channel, char const * error);
static void _on_code_cme_error(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
/* XXX ugly */
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
unsigned int u;
ModemEvent * event;
if(command != NULL)
hayes_command_set_status(command, HCS_ERROR);
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 10: /* SIM not inserted */
/* FIXME also display an icon in the UI */
_cme_error_registration(channel, "SIM not inserted");
break;
case 11: /* SIM PIN required */
_on_code_cpin(channel, "SIM PIN");
_hayes_trigger(hayes, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
case 12: /* SIM PUK required */
_on_code_cpin(channel, "SIM PUK");
_hayes_trigger(hayes, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
case 100: /* unknown error */
if(hayeschannel_has_quirks(channel,
HAYES_QUIRK_REPEAT_ON_UNKNOWN_ERROR) == 0)
break;
/* fallthrough */
case 14: /* SIM busy */
_cme_command_repeat(channel, command);
break;
case 30: /* No network service */
_cme_error_registration(channel, "No network service");
break;
case 31: /* Network timeout */
event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
event->registration.signal = 0.0 / 0.0;
helper->event(helper->modem, event);
break;
case 32: /* emergency calls only */
event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
free(channel->registration_media);
channel->registration_media = NULL;
event->registration.media = NULL;
free(channel->registration_operator);
channel->registration_operator = NULL;
event->registration._operator = "SOS";
event->registration.status
= MODEM_REGISTRATION_STATUS_REGISTERED;
helper->event(helper->modem, event);
/* verify the SIM card */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_SIM_PIN_VALID);
break;
case 112: /* Location area not allowed */
case 113: /* Roaming not allowed in this location area */
event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
event->registration.status
= MODEM_REGISTRATION_STATUS_DENIED;
helper->event(helper->modem, event);
break;
case 262: /* SIM blocked */
_cme_error_registration(channel, "SIM blocked");
break;
default: /* FIXME implement the rest */
case 3: /* Operation not allowed */
case 4: /* Operation not supported */
case 16: /* Incorrect SIM PIN/PUK */
case 20: /* Memory full */
case 107: /* GPRS services not allowed */
case 148: /* Unspecified GPRS error */
case 263: /* Invalid block */
break;
}
}
static void _cme_command_repeat(HayesChannel * channel, HayesCommand * command)
{
const guint timeout = 5000;
HayesCommand * p;
if(command == NULL)
return;
if((p = hayes_command_new_copy(command)) == NULL)
return;
hayes_command_set_data(p, hayes_command_get_data(command));
hayes_command_set_data(command, NULL);
channel->queue_timeout = g_slist_append(channel->queue_timeout, p);
if(channel->source == 0)
channel->source = g_timeout_add(timeout, _on_queue_timeout,
channel);
}
static void _cme_error_registration(HayesChannel * channel, char const * error)
{
Hayes * hayes = channel->hayes;
ModemEvent * event;
ModemPluginHelper * helper = hayes->helper;
/* update the authentication status */
event = &channel->events[MODEM_EVENT_TYPE_AUTHENTICATION];
free(channel->authentication_error);
channel->authentication_error = NULL;
event->authentication.error = error;
/* report the registration error */
event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
free(channel->registration_media);
channel->registration_media = NULL;
event->registration.media = NULL;
free(channel->registration_operator);
channel->registration_operator = NULL;
event->registration._operator = NULL;
event->registration.signal = 0.0 / 0.0;
event->registration.status = MODEM_REGISTRATION_STATUS_DENIED;
helper->event(helper->modem, event);
}
/* on_code_cmgl */
static void _on_code_cmgl(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
/* XXX ugly */
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
ModemRequest request;
unsigned int id;
unsigned int u;
HayesRequestMessageData * data;
ModemMessageFolder folder = MODEM_MESSAGE_FOLDER_UNKNOWN;
ModemMessageStatus status = MODEM_MESSAGE_STATUS_READ;
/* XXX we could already be reading the message at this point */
if(sscanf(answer, "%u,%u,%u,%u", &id, &u, &u, &u) != 4
&& sscanf(answer, "%u,%u,,%u", &id, &u, &u) != 3)
/* XXX we may be stuck in PDU mode at this point */
return;
request.type = MODEM_REQUEST_MESSAGE;
request.message.id = id;
if(command != NULL && (data = hayes_command_get_data(command)) != NULL)
{
folder = data->folder;
status = data->status;
}
if((data = malloc(sizeof(*data))) != NULL)
{
data->id = id;
data->folder = folder;
data->status = status;
}
if(_hayes_request_channel(hayes, channel, &request, data) != 0)
free(data);
}
/* on_code_cmgr */
static char * _cmgr_pdu_parse(char const * pdu, time_t * timestamp,
char * number, ModemMessageEncoding * encoding,
size_t * length);
static char * _cmgr_pdu_parse_encoding_data(char const * pdu, size_t len,
size_t i, size_t hdr, ModemMessageEncoding * encoding,
size_t * length);
static char * _cmgr_pdu_parse_encoding_default(char const * pdu, size_t len,
size_t i, size_t hdr, ModemMessageEncoding * encoding,
size_t * length);
static void _cmgr_pdu_parse_number(unsigned int type, char const * number,
size_t length, char * buf);
static time_t _cmgr_pdu_parse_timestamp(char const * timestamp);
static void _on_code_cmgr(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
/* XXX ugly */
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MESSAGE];
char buf[32];
char number[32];
char date[32];
struct tm t;
unsigned int mbox;
unsigned int alpha = 0;
unsigned int length;
char * p;
HayesRequestMessageData * data;
/* text mode support */
if(sscanf(answer, "\"%31[^\"]\",\"%31[^\"]\",,\"%31[^\"]\"", buf,
number, date) == 3)
{
number[sizeof(number) - 1] = '\0';
string_delete(channel->message_number);
channel->message_number = strdup(number);
event->message.number = channel->message_number;
date[sizeof(date) - 1] = '\0';
memset(&t, 0, sizeof(t));
if(strptime(date, "%y/%m/%d,%H:%M:%S", &t) == NULL)
/* XXX also parse the timezone? */
localtime_r(NULL, &t);
event->message.date = mktime(&t);
event->message.length = 0;
return; /* we need to wait for the next line */
}
/* PDU mode support */
if(sscanf(answer, "%u,%u,%u", &mbox, &alpha, &length) == 3
|| sscanf(answer, "%u,,%u", &mbox, &length) == 2)
return; /* we need to wait for the next line */
/* message content */
if(event->message.length == 0) /* XXX assumes this is text mode */
{
/* FIXME guarantee this would not happen */
if(command == NULL || (data = hayes_command_get_data(command))
== NULL)
return;
event->message.id = data->id;
event->message.folder = data->folder;
event->message.status = data->status;
event->message.encoding = MODEM_MESSAGE_ENCODING_UTF8;
event->message.content = answer;
event->message.length = strlen(answer);
hayes->helper->event(hayes->helper->modem, event);
return;
}
if((p = _cmgr_pdu_parse(answer, &event->message.date, number,
&event->message.encoding,
&event->message.length)) == NULL)
return;
/* FIXME guarantee this would not happen */
if(command == NULL || (data = hayes_command_get_data(command)) == NULL)
return;
event->message.id = data->id;
event->message.folder = data->folder;
event->message.status = data->status;
event->message.number = number; /* XXX */
event->message.content = p;
hayes->helper->event(hayes->helper->modem, event);
free(p);
}
static char * _cmgr_pdu_parse(char const * pdu, time_t * timestamp,
char * number, ModemMessageEncoding * encoding, size_t * length)
{
size_t len;
unsigned int smscl;
unsigned int tp;
unsigned int hdr;
unsigned int addrl;
unsigned int pid;
unsigned int dcs;
unsigned int datal;
unsigned int u;
char const * q;
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, pdu);
#endif
len = strlen(pdu);
if(sscanf(pdu, "%02X", &smscl) != 1) /* SMSC length */
return NULL;
if((smscl * 2) + 2 > len)
return NULL;
q = pdu + (smscl * 2) + 2;
if(sscanf(q, "%02X", &tp) != 1)
return NULL;
if((tp & 0x03) != 0x00) /* TP-MTI not SMS-DELIVER */
return NULL;
hdr = ((tp & 0x40) == 0x40) ? 1 : 0; /* TP-UDHI header present */
if((smscl * 2) + 4 > len)
return NULL;
q = pdu + (smscl * 2) + 4;
if(sscanf(q, "%02X", &addrl) != 1) /* address length */
return NULL;
if((smscl * 2) + 6 > len)
return NULL;
q = pdu + (smscl * 2) + 6;
if(sscanf(q, "%02X", &u) != 1) /* type of address */
return NULL;
/* FIXME this probably depends on the type of address */
if(addrl % 2 == 1)
addrl++;
if((smscl * 2) + 2 + 4 + addrl + 2 > len)
return NULL;
_cmgr_pdu_parse_number(u, q + 2, addrl, number);
q = pdu + (smscl * 2) + 2 + 4 + addrl + 2;
if(sscanf(q, "%02X", &pid) != 1) /* PID */
return NULL;
if((smscl * 2) + 2 + 4 + addrl + 4 > len)
return NULL;
q = pdu + (smscl * 2) + 2 + 4 + addrl + 4;
if(sscanf(q, "%02X", &dcs) != 1) /* DCS */
return NULL;
if((smscl * 2) + 2 + 4 + addrl + 6 > len)
return NULL;
q = pdu + (smscl * 2) + 2 + 4 + addrl + 6;
if(timestamp != NULL)
*timestamp = _cmgr_pdu_parse_timestamp(q);
if((smscl * 2) + 2 + 4 + addrl + 6 + 14 > len)
return NULL;
q = pdu + (smscl * 2) + 2 + 4 + addrl + 6 + 14;
if(sscanf(q, "%02X", &datal) != 1) /* data length */
return NULL;
/* XXX check the data length */
if((i = (smscl * 2) + 2 + 4 + addrl + 6 + 16) > len)
return NULL;
if(hdr != 0 && sscanf(&pdu[i], "%02X", &hdr) != 1)
return NULL;
if(dcs == 0x00)
return _cmgr_pdu_parse_encoding_default(pdu, len, i, hdr,
encoding, length);
if(dcs == 0x04)
return _cmgr_pdu_parse_encoding_data(pdu, len, i, hdr,
encoding, length);
return NULL;
}
static char * _cmgr_pdu_parse_encoding_data(char const * pdu, size_t len,
size_t i, size_t hdr, ModemMessageEncoding * encoding,
size_t * length)
{
unsigned char * p;
size_t j;
unsigned int u;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if((p = malloc(len - i + 1)) == NULL) /* XXX 2 times big enough? */
return NULL;
/* FIXME actually parse the header */
if(hdr != 0)
i += 2 + (hdr * 2);
for(j = 0; i + 1 < len; i+=2)
{
if(sscanf(&pdu[i], "%02X", &u) != 1)
{
free(p);
return NULL;
}
p[j++] = u;
}
*encoding = MODEM_MESSAGE_ENCODING_DATA;
*length = j;
p[j] = '\0';
return (char *)p;
}
static char * _cmgr_pdu_parse_encoding_default(char const * pdu, size_t len,
size_t i, size_t hdr, ModemMessageEncoding * encoding,
size_t * length)
{
unsigned char * p;
size_t j;
unsigned char rest;
int shift = 0;
char const * q;
unsigned int u;
unsigned char byte;
char * r;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%zu, %zu)\n", __func__, i, hdr);
#endif
if((p = malloc(len - i + 1)) == NULL)
return NULL;
if(hdr != 0)
{
/* FIXME actually parse the header */
i += 2 + (hdr * 2);
shift = (hdr + 1) % 7;
}
p[0] = '\0';
for(j = 0, rest = 0; i + 1 < len; i+=2)
{
q = &pdu[i];
if(sscanf(q, "%02X", &u) != 1)
break; /* FIXME report an error instead? */
byte = u;
p[j] = (((byte << (shift + 1)) >> (shift + 1)) << shift) & 0x7f;
p[j] |= rest;
p[j] = _hayes_convert_gsm_to_iso(p[j]);
/* ignore the first character if there is a header */
if(hdr == 0 || j != 0)
j++;
rest = (byte >> (7 - shift)) & 0x7f;
if(++shift == 7)
{
shift = 0;
p[j++] = rest;
rest = 0;
}
}
*encoding = MODEM_MESSAGE_ENCODING_UTF8;
if((r = g_convert((char *)p, j, "UTF-8", "ISO-8859-1", NULL, NULL,
NULL)) != NULL)
{
free(p);
p = (unsigned char *)r;
j = strlen(r);
}
*length = j;
return (char *)p;
}
static void _cmgr_pdu_parse_number(unsigned int type, char const * number,
size_t length, char * buf)
{
char * b = buf;
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(type == 0x91)
*(b++) = '+';
for(i = 0; i < length - 1 && i < 32 - 1; i+=2)
{
if((number[i] != 'F' && (number[i] < '0' || number[i] > '9'))
|| number[i + 1] < '0' || number[i + 1] > '9')
break;
b[i] = number[i + 1];
if((b[i + 1] = number[i]) == 'F')
b[i + 1] = '\0';
}
b[i] = '\0';
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", %zu) => \"%s\"\n", __func__, number,
length, b);
#endif
}
static time_t _cmgr_pdu_parse_timestamp(char const * timestamp)
{
char const * p = timestamp;
size_t i;
struct tm tm;
time_t t;
#if defined(__NetBSD__)
timezone_t tz;
#endif
#ifdef DEBUG
char buf[32];
#endif
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, timestamp);
#endif
if(strlen(p) < 14)
return 0;
for(i = 0; i < 14; i++)
if(p[i] < '0' || p[i] > '9')
return 0;
memset(&tm, 0, sizeof(tm));
tm.tm_year = (p[0] - '0') + ((p[1] - '0') * 10);
tm.tm_year = (tm.tm_year > 70) ? tm.tm_year : (100 + tm.tm_year);
tm.tm_mon = (p[2] - '0') + ((p[3] - '0') * 10);
if(tm.tm_mon > 0)
tm.tm_mon--;
tm.tm_mday = (p[4] - '0') + ((p[5] - '0') * 10);
tm.tm_hour = (p[6] - '0') + ((p[7] - '0') * 10);
tm.tm_min = (p[8] - '0') + ((p[9] - '0') * 10);
tm.tm_sec = (p[10] - '0') + ((p[11] - '0') * 10);
#ifdef DEBUG
strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M:%S", &tm);
fprintf(stderr, "DEBUG: %s() => \"%s\"\n", __func__, buf);
#endif
#if !defined(__NetBSD__)
t = mktime(&tm);
#else
/* XXX assumes UTC */
tz = tzalloc("UTC");
t = mktime_z(tz, &tm);
tzfree(tz);
#endif
return t;
}
/* on_code_cmgs */
static void _on_code_cmgs(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_MESSAGE_SENT];
unsigned int u;
if(sscanf(answer, "%u", &u) != 1)
return;
event->message_sent.error = NULL;
event->message_sent.id = u;
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cms_error */
static void _on_code_cms_error(HayesChannel * channel, char const * answer)
{
const guint timeout = 5000;
Hayes * hayes = channel->hayes;
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
unsigned int u;
HayesCommand * p;
if(command != NULL)
hayes_command_set_status(command, HCS_ERROR);
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 311: /* SIM PIN required */
_on_code_cpin(channel, "SIM PIN");
_hayes_trigger(hayes, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
case 316: /* SIM PUK required */
_on_code_cpin(channel, "SIM PUK");
_hayes_trigger(hayes, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
case 317: /* SIM PIN2 required */
_on_code_cpin(channel, "SIM PIN2");
_hayes_trigger(hayes, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
case 318: /* SIM PUK2 required */
_on_code_cpin(channel, "SIM PUK2");
_hayes_trigger(hayes, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
case 500: /* Unknown error */
if(hayeschannel_has_quirks(channel,
HAYES_QUIRK_REPEAT_ON_UNKNOWN_ERROR) == 0)
break;
/* fallthrough */
case 314: /* SIM busy */
case 532: /* SIM not ready */
/* repeat the command */
/* FIXME duplicated from _on_code_cme_error() */
if(command == NULL)
break;
if((p = hayes_command_new_copy(command)) == NULL)
break;
hayes_command_set_data(p,
hayes_command_get_data(command));
hayes_command_set_data(command, NULL);
channel->queue_timeout = g_slist_append(
channel->queue_timeout, p);
if(channel->source == 0)
channel->source = g_timeout_add(timeout,
_on_queue_timeout, channel);
break;
case 21: /* Short message transfer rejected */
case 96: /* Invalid mandatory information */
case 303: /* Operation not supported */
case 321: /* Invalid memory index */
default: /* FIXME implement the rest */
break;
}
}
/* on_code_cmti */
static void _on_code_cmti(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
char buf[32];
unsigned int u;
ModemRequest request;
if(sscanf(answer, "\"%31[^\"]\",%u", buf, &u) != 2)
return;
buf[sizeof(buf) - 1] = '\0';
/* fetch the new message directly */
memset(&request, 0, sizeof(request));
request.type = MODEM_REQUEST_MESSAGE;
request.message.id = u;
_hayes_request(hayes, &request);
}
/* on_code_connect */
static void _on_code_connect(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CONNECTION];
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
char * argv[] = { "/usr/sbin/" PROGNAME_PPPD, PROGNAME_PPPD,
"call", "phone", "user", "", "password", "", NULL };
char const * p;
gboolean res;
const GSpawnFlags flags = G_SPAWN_FILE_AND_ARGV_ZERO;
int wfd;
int rfd;
GError * error = NULL;
(void) answer;
if(command != NULL) /* XXX else report error? */
hayes_command_set_status(command, HCS_SUCCESS);
_hayes_set_mode(hayes, channel, HAYESCHANNEL_MODE_DATA);
/* pppd */
if((p = helper->config_get(helper->modem, "pppd")) != NULL)
{
if((argv[0] = strdup(p)) == NULL)
{
hayes->helper->error(NULL, strerror(errno), 1);
_hayes_reset(hayes);
return;
}
argv[1] = basename(argv[0]);
}
/* username */
if(channel->gprs_username != NULL)
argv[5] = channel->gprs_username;
/* password */
if(channel->gprs_password != NULL)
argv[7] = channel->gprs_password;
res = g_spawn_async_with_pipes(NULL, argv, NULL, flags, NULL, NULL,
NULL, &wfd, &rfd, NULL, &error);
if(p != NULL)
free(argv[0]);
if(res == FALSE)
{
hayes->helper->error(NULL, error->message, 1);
g_error_free(error);
_hayes_reset(hayes);
return;
}
channel->rd_ppp_channel = g_io_channel_unix_new(rfd);
if(g_io_channel_set_encoding(channel->rd_ppp_channel, NULL, &error)
!= G_IO_STATUS_NORMAL)
{
hayes->helper->error(NULL, error->message, 1);
g_error_free(error);
error = NULL;
}
g_io_channel_set_buffered(channel->rd_ppp_channel, FALSE);
channel->rd_ppp_source = g_io_add_watch(channel->rd_ppp_channel,
G_IO_IN, _on_watch_can_read_ppp, channel);
channel->wr_ppp_channel = g_io_channel_unix_new(wfd);
if(g_io_channel_set_encoding(channel->wr_ppp_channel, NULL, &error)
!= G_IO_STATUS_NORMAL)
{
hayes->helper->error(NULL, error->message, 1);
g_error_free(error);
}
g_io_channel_set_buffered(channel->wr_ppp_channel, FALSE);
channel->wr_ppp_source = 0;
event->connection.connected = 1;
event->connection.in = 0;
event->connection.out = 0;
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_colp */
static void _on_code_colp(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CALL];
char buf[32];
unsigned int u;
if(sscanf(answer, "\"%31[^\"]\",%u", buf, &u) != 2)
return; /* FIXME there may be different or more information */
buf[sizeof(buf) - 1] = '\0';
free(channel->call_number);
switch(u)
{
case 145:
if((channel->call_number = malloc(sizeof(buf) + 1))
== NULL)
break;
snprintf(channel->call_number, sizeof(buf) + 1, "%s%s",
"+", buf);
break;
default:
channel->call_number = strdup(buf);
break;
}
event->call.number = channel->call_number;
}
/* on_code_cops */
static void _on_code_cops(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
unsigned int u;
unsigned int v = 0;
char buf[32] = "";
unsigned int w;
if(sscanf(answer, "%u,%u,\"%31[^\"]\",%u", &u, &v, buf, &w) < 1)
return;
switch(u)
{
case 0:
u = MODEM_REGISTRATION_MODE_AUTOMATIC;
break;
case 1:
u = MODEM_REGISTRATION_MODE_MANUAL;
break;
case 2:
u = MODEM_REGISTRATION_MODE_DISABLED;
break;
case 3: /* only for setting the format */
default:
u = event->registration.mode;
break;
}
event->registration.mode = u;
free(channel->registration_operator);
channel->registration_operator = NULL;
event->registration._operator = NULL;
if(v != 0)
/* force alphanumeric format */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_OPERATOR_FORMAT_LONG);
else
{
buf[sizeof(buf) - 1] = '\0';
channel->registration_operator = strdup(buf);
event->registration._operator = channel->registration_operator;
}
/* refresh registration data */
_hayes_request_type(hayes, channel, MODEM_REQUEST_SIGNAL_LEVEL);
_hayes_request_type(hayes, channel, HAYES_REQUEST_GPRS_ATTACHED);
/* this is usually worth an event */
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cpas */
static void _on_code_cpas(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemPluginHelper * helper = hayes->helper;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CALL];
unsigned int u;
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 0:
event->call.status = MODEM_CALL_STATUS_NONE;
event->call.direction = MODEM_CALL_DIRECTION_NONE;
/* report connection status */
event = &channel->events[MODEM_EVENT_TYPE_CONNECTION];
event->connection.connected = 0;
event->connection.in = 0;
event->connection.out = 0;
helper->event(helper->modem, event);
break;
case 3:
event->call.status = MODEM_CALL_STATUS_RINGING;
/* report event */
helper->event(helper->modem, event);
break;
case 4:
event->call.status = MODEM_CALL_STATUS_ACTIVE;
event->call.direction = MODEM_CALL_DIRECTION_NONE;
break;
case 2: /* XXX unknown */
default:
break;
}
}
/* on_code_cpbr */
static void _on_code_cpbr(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemRequest request;
HayesRequestContactList list;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CONTACT];
unsigned int u;
unsigned int v;
char number[32];
char name[32];
char * p;
if(sscanf(answer, "(%u-%u)", &u, &v) == 2)
{
memset(&request, 0, sizeof(request));
request.type = HAYES_REQUEST_CONTACT_LIST;
list.from = u;
list.to = v;
request.plugin.data = &list;
_hayes_request(hayes, &request);
return;
}
if(sscanf(answer, "%u,\"%31[^\"]\",%u,\"%31[^\"]\"",
&event->contact.id, number, &u, name) != 4)
return;
switch(u)
{
case 145:
if(number[0] == '+')
break;
/* prefix the number with a "+" */
memmove(&number[1], number, sizeof(number) - 1);
number[0] = '+';
break;
}
number[sizeof(number) - 1] = '\0';
free(channel->contact_number);
channel->contact_number = strdup(number);
event->contact.number = channel->contact_number;
name[sizeof(name) - 1] = '\0';
#if 1 /* FIXME is it really always in GSM? */
_hayes_convert_gsm_string_to_iso(name);
#endif
if((p = g_convert(name, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL))
!= NULL)
{
snprintf(name, sizeof(name), "%s", p);
g_free(p);
}
free(channel->contact_name);
channel->contact_name = strdup(name);
event->contact.name = channel->contact_name;
event->contact.status = MODEM_CONTACT_STATUS_OFFLINE;
/* send event */
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cpin */
static void _on_code_cpin(HayesChannel * channel, char const * answer)
{
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_AUTHENTICATION];
char * p;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, answer);
#endif
if(strcmp(answer, "READY") == 0)
{
event->authentication.status = MODEM_AUTHENTICATION_STATUS_OK;
hayescommon_source_reset(&channel->authenticate_source);
channel->authenticate_count = 0;
}
else if(strcmp(answer, "SIM PIN") == 0
|| strcmp(answer, "SIM PUK") == 0)
{
free(channel->authentication_name);
p = strdup(answer);
channel->authentication_name = p;
event->authentication.name = p;
event->authentication.method = MODEM_AUTHENTICATION_METHOD_PIN;
event->authentication.status
= MODEM_AUTHENTICATION_STATUS_REQUIRED;
/* FIXME also provide remaining retries */
}
}
/* on_code_creg */
static void _on_code_creg(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
int res;
unsigned int u[4] = { 0, 0, 0, 0 };
res = sscanf(answer, "%u,%u,%X,%X", &u[0], &u[1], &u[2], &u[3]);
if(res == 1)
res = sscanf(answer, "%u,\"%X\",\"%X\"", &u[1], &u[2], &u[3]);
else if(res == 2)
res = sscanf(answer, "%u,%u,\"%X\",\"%X\"", &u[0], &u[1], &u[2],
&u[3]);
else if(res == 3)
res = sscanf(answer, "%u,%X,%X", &u[1], &u[2], &u[3]);
if(res == 0)
return;
u[0] = event->registration.mode;
event->registration.roaming = 0;
switch(u[1])
{
case 0:
u[0] = MODEM_REGISTRATION_MODE_DISABLED;
u[1] = MODEM_REGISTRATION_STATUS_NOT_SEARCHING;
break;
case 1:
if(u[0] != MODEM_REGISTRATION_MODE_MANUAL)
u[0] = MODEM_REGISTRATION_MODE_AUTOMATIC;
u[1] = MODEM_REGISTRATION_STATUS_REGISTERED;
break;
case 2:
if(u[0] != MODEM_REGISTRATION_MODE_MANUAL)
u[0] = MODEM_REGISTRATION_MODE_AUTOMATIC;
u[1] = MODEM_REGISTRATION_STATUS_SEARCHING;
break;
case 3:
u[1] = MODEM_REGISTRATION_STATUS_DENIED;
break;
case 5:
if(u[0] != MODEM_REGISTRATION_MODE_MANUAL)
u[0] = MODEM_REGISTRATION_MODE_AUTOMATIC;
u[1] = MODEM_REGISTRATION_STATUS_REGISTERED;
event->registration.roaming = 1;
break;
case 4: /* unknown */
default:
#ifdef DEBUG
if(u[1] != 4)
fprintf(stderr, "DEBUG: %s() Unknown CREG %u\n",
__func__, u[1]);
#endif
u[0] = MODEM_REGISTRATION_MODE_UNKNOWN;
u[1] = MODEM_REGISTRATION_STATUS_UNKNOWN;
break;
}
event->registration.mode = u[0];
switch((event->registration.status = u[1]))
{
case MODEM_REGISTRATION_STATUS_REGISTERED:
/* refresh registration data */
_hayes_request_type(hayes, channel,
HAYES_REQUEST_OPERATOR);
break;
default:
free(channel->registration_media);
channel->registration_media = NULL;
event->registration.media = NULL;
free(channel->registration_operator);
channel->registration_operator = NULL;
event->registration._operator = NULL;
event->registration.signal = 0.0 / 0.0;
break;
}
/* this is usually an unsollicited event */
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cring */
static void _on_code_cring(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_CALL];
if(strcmp(answer, "VOICE") == 0)
event->call.call_type = MODEM_CALL_TYPE_VOICE;
event->call.status = MODEM_CALL_STATUS_RINGING;
event->call.direction = MODEM_CALL_DIRECTION_INCOMING;
event->call.number = "";
/* this is always an unsollicited event */
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_csq */
static void _on_code_csq(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_REGISTRATION];
unsigned int u;
unsigned int v;
if(sscanf(answer, "%u,%u", &u, &v) != 2)
return;
if(u > 31)
event->registration.signal = 0.0 / 0.0;
else if(u >= 20)
event->registration.signal = 1.0;
else
{
event->registration.signal = (u > 4) ? (u - 4) : 0;
event->registration.signal = event->registration.signal / 16.0;
}
/* this is usually worth an event */
hayes->helper->event(hayes->helper->modem, event);
}
/* on_code_cusd */
static void _on_code_cusd(HayesChannel * channel, char const * answer)
{
Hayes * hayes = channel->hayes;
ModemEvent * event = &channel->events[MODEM_EVENT_TYPE_NOTIFICATION];
unsigned int u;
char buf[32];
if(sscanf(answer, "%u,\"%31[^\"]\",%u", &u, buf, &u) >= 2)
{
event->notification.ntype = MODEM_NOTIFICATION_TYPE_INFO;
event->notification.title = NULL;
buf[sizeof(buf) - 1] = '\0';
event->notification.content = buf;
hayes->helper->event(hayes->helper->modem, event);
}
}
/* on_code_ext_error */
static void _on_code_ext_error(HayesChannel * channel, char const * answer)
{
/* XXX ugly */
HayesCommand * command = (channel->queue != NULL)
? channel->queue->data : NULL;
unsigned int u;
if(command != NULL)
hayes_command_set_status(command, HCS_ERROR);
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 0:
default: /* FIXME implement */
break;
}
}
/* helpers */
/* is_ussd_code */
static int _is_ussd_code(char const * number)
{
if(number == NULL || number[0] != '*')
return 0;
for(number++; *number != '\0'; number++)
if(*number == '#')
{
if(*(number + 1) == '\0')
return 1;
continue;
}
else if((*number >= '0' && *number <= '9') || *number == '*')
continue;
else
return 0;
return 0;
}