blob: f9730a39d84bcd2825e03aae9eb094a3f3f66605 [file] [log] [blame]
/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "android/android.h"
#include "android_modem.h"
#include "android/config-file.h"
#include "android/config/config.h"
#include "android/snapshot.h"
#include "android/utils/debug.h"
#include "android/utils/timezone.h"
#include "android/utils/system.h"
#include "android/utils/bufprint.h"
#include "android/utils/path.h"
#include "hw/hw.h"
#include "qemu-common.h"
#include "sim_card.h"
#include "sysdeps.h"
#include <memory.h>
#include <stdarg.h>
#include <time.h>
#include <assert.h>
#include <stdio.h>
#include "sms.h"
#include "remote_call.h"
#define DEBUG 1
#if 1
# define D_ACTIVE VERBOSE_CHECK(modem)
#else
# define D_ACTIVE DEBUG
#endif
#if 1
# define R_ACTIVE VERBOSE_CHECK(radio)
#else
# define R_ACTIVE DEBUG
#endif
#if DEBUG
# define D(...) do { if (D_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0)
# define R(...) do { if (R_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0)
#else
# define D(...) ((void)0)
# define R(...) ((void)0)
#endif
#define CALL_DELAY_DIAL 1000
#define CALL_DELAY_ALERT 1000
/* the Android GSM stack checks that the operator's name has changed
* when roaming is on. If not, it will not update the Roaming status icon
*
* this means that we need to emulate two distinct operators:
* - the first one for the 'home' registration state, must also correspond
* to the emulated user's IMEI
*
* - the second one for the 'roaming' registration state, must have a
* different name and MCC/MNC
*/
#define OPERATOR_HOME_INDEX 0
#define OPERATOR_HOME_MCC 310
#define OPERATOR_HOME_MNC 260
#define OPERATOR_HOME_NAME "Android"
#define OPERATOR_HOME_MCCMNC STRINGIFY(OPERATOR_HOME_MCC) \
STRINGIFY(OPERATOR_HOME_MNC)
#define OPERATOR_ROAMING_INDEX 1
#define OPERATOR_ROAMING_MCC 310
#define OPERATOR_ROAMING_MNC 295
#define OPERATOR_ROAMING_NAME "TelKila"
#define OPERATOR_ROAMING_MCCMNC STRINGIFY(OPERATOR_ROAMING_MCC) \
STRINGIFY(OPERATOR_ROAMING_MNC)
static const char* _amodem_switch_technology(AModem modem, AModemTech newtech, int32_t newpreferred);
static int _amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss);
static int _amodem_set_cdma_prl_version( AModem modem, int prlVersion);
#if DEBUG
static const char* quote( const char* line )
{
static char temp[1024];
const char* hexdigits = "0123456789abcdef";
char* p = temp;
int c;
while ((c = *line++) != 0) {
c &= 255;
if (c >= 32 && c < 127) {
*p++ = c;
}
else if (c == '\r') {
memcpy( p, "<CR>", 4 );
p += 4;
}
else if (c == '\n') {
memcpy( p, "<LF>", 4 );strcat( p, "<LF>" );
p += 4;
}
else {
p[0] = '\\';
p[1] = 'x';
p[2] = hexdigits[ (c) >> 4 ];
p[3] = hexdigits[ (c) & 15 ];
p += 4;
}
}
*p = 0;
return temp;
}
#endif
extern AModemTech
android_parse_modem_tech( const char * tech )
{
const struct { const char* name; AModemTech tech; } techs[] = {
{ "gsm", A_TECH_GSM },
{ "wcdma", A_TECH_WCDMA },
{ "cdma", A_TECH_CDMA },
{ "evdo", A_TECH_EVDO },
{ "lte", A_TECH_LTE },
{ NULL, 0 }
};
int nn;
for (nn = 0; techs[nn].name; nn++) {
if (!strcmp(tech, techs[nn].name))
return techs[nn].tech;
}
/* not found */
return A_TECH_UNKNOWN;
}
extern ADataNetworkType
android_parse_network_type( const char* speed )
{
const struct { const char* name; ADataNetworkType type; } types[] = {
{ "gprs", A_DATA_NETWORK_GPRS },
{ "edge", A_DATA_NETWORK_EDGE },
{ "umts", A_DATA_NETWORK_UMTS },
{ "hsdpa", A_DATA_NETWORK_UMTS }, /* not handled yet by Android GSM framework */
{ "full", A_DATA_NETWORK_UMTS },
{ "lte", A_DATA_NETWORK_LTE },
{ "cdma", A_DATA_NETWORK_CDMA1X },
{ "evdo", A_DATA_NETWORK_EVDO },
{ NULL, 0 }
};
int nn;
for (nn = 0; types[nn].name; nn++) {
if (!strcmp(speed, types[nn].name))
return types[nn].type;
}
/* not found, be conservative */
return A_DATA_NETWORK_GPRS;
}
/* 'mode' for +CREG/+CGREG commands */
typedef enum {
A_REGISTRATION_UNSOL_DISABLED = 0,
A_REGISTRATION_UNSOL_ENABLED = 1,
A_REGISTRATION_UNSOL_ENABLED_FULL = 2
} ARegistrationUnsolMode;
/* Operator selection mode, see +COPS commands */
typedef enum {
A_SELECTION_AUTOMATIC,
A_SELECTION_MANUAL,
A_SELECTION_DEREGISTRATION,
A_SELECTION_SET_FORMAT,
A_SELECTION_MANUAL_AUTOMATIC
} AOperatorSelection;
/* Operator status, see +COPS commands */
typedef enum {
A_STATUS_UNKNOWN = 0,
A_STATUS_AVAILABLE,
A_STATUS_CURRENT,
A_STATUS_DENIED
} AOperatorStatus;
typedef struct {
AOperatorStatus status;
char name[3][16];
} AOperatorRec, *AOperator;
typedef struct AVoiceCallRec {
ACallRec call;
SysTimer timer;
AModem modem;
char is_remote;
} AVoiceCallRec, *AVoiceCall;
#define MAX_OPERATORS 4
typedef enum {
A_DATA_IP = 0,
A_DATA_PPP
} ADataType;
#define A_DATA_APN_SIZE 32
typedef struct {
int id;
int active;
ADataType type;
char apn[ A_DATA_APN_SIZE ];
} ADataContextRec, *ADataContext;
/* the spec says that there can only be a max of 4 contexts */
#define MAX_DATA_CONTEXTS 4
#define MAX_CALLS 4
#define MAX_EMERGENCY_NUMBERS 16
#define A_MODEM_SELF_SIZE 3
typedef struct AModemRec_
{
/* Legacy support */
char supportsNetworkDataType;
/* Radio state */
ARadioState radio_state;
int area_code;
int cell_id;
int base_port;
int rssi;
int ber;
/* SMS */
int wait_sms;
/* SIM card */
ASimCard sim;
/* voice and data network registration */
ARegistrationUnsolMode voice_mode;
ARegistrationState voice_state;
ARegistrationUnsolMode data_mode;
ARegistrationState data_state;
ADataNetworkType data_network;
/* operator names */
AOperatorSelection oper_selection_mode;
ANameIndex oper_name_index;
int oper_index;
int oper_count;
AOperatorRec operators[ MAX_OPERATORS ];
/* data connection contexts */
ADataContextRec data_contexts[ MAX_DATA_CONTEXTS ];
/* active calls */
AVoiceCallRec calls[ MAX_CALLS ];
int call_count;
/* unsolicited callback */ /* XXX: TODO: use this */
AModemUnsolFunc unsol_func;
void* unsol_opaque;
SmsReceiver sms_receiver;
int out_size;
char out_buff[1024];
/*
* Hold non-volatile ram configuration for modem
*/
AConfig *nvram_config;
char *nvram_config_filename;
AModemTech technology;
/*
* This is are really 4 byte-sized prioritized masks.
* Byte order gives the priority for the specific bitmask.
* Each bit position in each of the masks is indexed by the different
* A_TECH_XXXX values.
* e.g. 0x01 means only GSM is set (bit index 0), whereas 0x0f
* means that GSM,WCDMA,CDMA and EVDO are set
*/
int32_t preferred_mask;
ACdmaSubscriptionSource subscription_source;
ACdmaRoamingPref roaming_pref;
int in_emergency_mode;
int prl_version;
const char *emergency_numbers[MAX_EMERGENCY_NUMBERS];
} AModemRec;
static void
amodem_unsol( AModem modem, const char* format, ... )
{
if (modem->unsol_func) {
va_list args;
va_start(args, format);
vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args );
va_end(args);
modem->unsol_func( modem->unsol_opaque, modem->out_buff );
}
}
void
amodem_receive_sms( AModem modem, SmsPDU sms )
{
#define SMS_UNSOL_HEADER "+CMT: 0\r\n"
if (modem->unsol_func) {
int len, max;
char* p;
strcpy( modem->out_buff, SMS_UNSOL_HEADER );
p = modem->out_buff + (sizeof(SMS_UNSOL_HEADER)-1);
max = sizeof(modem->out_buff) - 3 - (sizeof(SMS_UNSOL_HEADER)-1);
len = smspdu_to_hex( sms, p, max );
if (len > max) /* too long */
return;
p[len] = '\r';
p[len+1] = '\n';
p[len+2] = 0;
R( "SMS>> %s\n", p );
modem->unsol_func( modem->unsol_opaque, modem->out_buff );
}
}
static const char*
amodem_printf( AModem modem, const char* format, ... )
{
va_list args;
va_start(args, format);
vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args );
va_end(args);
return modem->out_buff;
}
static void
amodem_begin_line( AModem modem )
{
modem->out_size = 0;
}
static void
amodem_add_line( AModem modem, const char* format, ... )
{
va_list args;
va_start(args, format);
modem->out_size += vsnprintf( modem->out_buff + modem->out_size,
sizeof(modem->out_buff) - modem->out_size,
format, args );
va_end(args);
}
static const char*
amodem_end_line( AModem modem )
{
modem->out_buff[ modem->out_size ] = 0;
return modem->out_buff;
}
#define NV_OPER_NAME_INDEX "oper_name_index"
#define NV_OPER_INDEX "oper_index"
#define NV_SELECTION_MODE "selection_mode"
#define NV_OPER_COUNT "oper_count"
#define NV_MODEM_TECHNOLOGY "modem_technology"
#define NV_PREFERRED_MODE "preferred_mode"
#define NV_CDMA_SUBSCRIPTION_SOURCE "cdma_subscription_source"
#define NV_CDMA_ROAMING_PREF "cdma_roaming_pref"
#define NV_IN_ECBM "in_ecbm"
#define NV_EMERGENCY_NUMBER_FMT "emergency_number_%d"
#define NV_PRL_VERSION "prl_version"
#define NV_SREGISTER "sregister"
#define MAX_KEY_NAME 40
static AConfig *
amodem_load_nvram( AModem modem )
{
AConfig* root = aconfig_node(NULL, NULL);
D("Using config file: %s\n", modem->nvram_config_filename);
if (aconfig_load_file(root, modem->nvram_config_filename)) {
D("Unable to load config\n");
aconfig_set(root, NV_MODEM_TECHNOLOGY, "gsm");
aconfig_save_file(root, modem->nvram_config_filename);
}
return root;
}
static int
amodem_nvram_get_int( AModem modem, const char *nvname, int defval)
{
int value;
char strval[MAX_KEY_NAME + 1];
char *newvalue;
value = aconfig_int(modem->nvram_config, nvname, defval);
snprintf(strval, MAX_KEY_NAME, "%d", value);
D("Setting value of %s to %d (%s)",nvname, value, strval);
newvalue = strdup(strval);
if (!newvalue) {
newvalue = "";
}
aconfig_set(modem->nvram_config, nvname, newvalue);
return value;
}
const char *
amodem_nvram_get_str( AModem modem, const char *nvname, const char *defval)
{
const char *value;
value = aconfig_str(modem->nvram_config, nvname, defval);
if (!value) {
if (!defval)
return NULL;
value = defval;
}
aconfig_set(modem->nvram_config, nvname, value);
return value;
}
static ACdmaSubscriptionSource _amodem_get_cdma_subscription_source( AModem modem )
{
int iss = -1;
iss = amodem_nvram_get_int( modem, NV_CDMA_SUBSCRIPTION_SOURCE, A_SUBSCRIPTION_RUIM );
if (iss >= A_SUBSCRIPTION_UNKNOWN || iss < 0) {
iss = A_SUBSCRIPTION_RUIM;
}
return iss;
}
static ACdmaRoamingPref _amodem_get_cdma_roaming_preference( AModem modem )
{
int rp = -1;
rp = amodem_nvram_get_int( modem, NV_CDMA_ROAMING_PREF, A_ROAMING_PREF_ANY );
if (rp >= A_ROAMING_PREF_UNKNOWN || rp < 0) {
rp = A_ROAMING_PREF_ANY;
}
return rp;
}
static void
amodem_reset( AModem modem )
{
const char *tmp;
int i;
modem->nvram_config = amodem_load_nvram(modem);
modem->radio_state = A_RADIO_STATE_OFF;
modem->wait_sms = 0;
modem->rssi= 7; // Two signal strength bars
modem->ber = 99; // Means 'unknown'
modem->oper_name_index = amodem_nvram_get_int(modem, NV_OPER_NAME_INDEX, 2);
modem->oper_selection_mode = amodem_nvram_get_int(modem, NV_SELECTION_MODE, A_SELECTION_AUTOMATIC);
modem->oper_index = amodem_nvram_get_int(modem, NV_OPER_INDEX, 0);
modem->oper_count = amodem_nvram_get_int(modem, NV_OPER_COUNT, 2);
modem->in_emergency_mode = amodem_nvram_get_int(modem, NV_IN_ECBM, 0);
modem->prl_version = amodem_nvram_get_int(modem, NV_PRL_VERSION, 0);
modem->emergency_numbers[0] = "911";
char key_name[MAX_KEY_NAME + 1];
for (i = 1; i < MAX_EMERGENCY_NUMBERS; i++) {
snprintf(key_name,MAX_KEY_NAME, NV_EMERGENCY_NUMBER_FMT, i);
modem->emergency_numbers[i] = amodem_nvram_get_str(modem,key_name, NULL);
}
modem->area_code = -1;
modem->cell_id = -1;
strcpy( modem->operators[0].name[0], OPERATOR_HOME_NAME );
strcpy( modem->operators[0].name[1], OPERATOR_HOME_NAME );
strcpy( modem->operators[0].name[2], OPERATOR_HOME_MCCMNC );
modem->operators[0].status = A_STATUS_AVAILABLE;
strcpy( modem->operators[1].name[0], OPERATOR_ROAMING_NAME );
strcpy( modem->operators[1].name[1], OPERATOR_ROAMING_NAME );
strcpy( modem->operators[1].name[2], OPERATOR_ROAMING_MCCMNC );
modem->operators[1].status = A_STATUS_AVAILABLE;
modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
modem->voice_state = A_REGISTRATION_HOME;
modem->data_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
modem->data_state = A_REGISTRATION_HOME;
modem->data_network = A_DATA_NETWORK_UMTS;
tmp = amodem_nvram_get_str( modem, NV_MODEM_TECHNOLOGY, "gsm" );
modem->technology = android_parse_modem_tech( tmp );
if (modem->technology == A_TECH_UNKNOWN) {
modem->technology = aconfig_int( modem->nvram_config, NV_MODEM_TECHNOLOGY, A_TECH_GSM );
}
// Support GSM, WCDMA, CDMA, EvDo
modem->preferred_mask = amodem_nvram_get_int( modem, NV_PREFERRED_MODE, 0x0f );
modem->subscription_source = _amodem_get_cdma_subscription_source( modem );
modem->roaming_pref = _amodem_get_cdma_roaming_preference( modem );
}
static AVoiceCall amodem_alloc_call( AModem modem );
static void amodem_free_call( AModem modem, AVoiceCall call );
#define MODEM_DEV_STATE_SAVE_VERSION 1
static void android_modem_state_save(QEMUFile *f, void *opaque)
{
AModem modem = opaque;
// TODO: save more than just calls and call_count - rssi, power, etc.
qemu_put_byte(f, modem->call_count);
int nn;
for (nn = modem->call_count - 1; nn >= 0; nn--) {
AVoiceCall vcall = modem->calls + nn;
// Note: not saving timers or remote calls.
ACall call = &vcall->call;
qemu_put_byte( f, call->dir );
qemu_put_byte( f, call->state );
qemu_put_byte( f, call->mode );
qemu_put_be32( f, call->multi );
qemu_put_buffer( f, (uint8_t *)call->number, A_CALL_NUMBER_MAX_SIZE+1 );
}
}
static int android_modem_state_load(QEMUFile *f, void *opaque, int version_id)
{
if (version_id != MODEM_DEV_STATE_SAVE_VERSION)
return -1;
AModem modem = opaque;
// In case there are timers or remote calls.
int nn;
for (nn = modem->call_count - 1; nn >= 0; nn--) {
amodem_free_call( modem, modem->calls + nn);
}
int call_count = qemu_get_byte(f);
for (nn = call_count; nn > 0; nn--) {
AVoiceCall vcall = amodem_alloc_call( modem );
ACall call = &vcall->call;
call->dir = qemu_get_byte( f );
call->state = qemu_get_byte( f );
call->mode = qemu_get_byte( f );
call->multi = qemu_get_be32( f );
qemu_get_buffer( f, (uint8_t *)call->number, A_CALL_NUMBER_MAX_SIZE+1 );
}
return 0; // >=0 Happy
}
static AModemRec _android_modem[1];
AModem
amodem_create( int base_port, AModemUnsolFunc unsol_func, void* unsol_opaque )
{
AModem modem = _android_modem;
char nvfname[MAX_PATH];
char *start = nvfname;
char *end = start + sizeof(nvfname);
modem->base_port = base_port;
start = bufprint_config_file( start, end, "modem-nv-ram-" );
start = bufprint( start, end, "%d", modem->base_port );
modem->nvram_config_filename = strdup( nvfname );
amodem_reset( modem );
modem->supportsNetworkDataType = 1;
modem->unsol_func = unsol_func;
modem->unsol_opaque = unsol_opaque;
modem->sim = asimcard_create(base_port);
sys_main_init();
register_savevm(NULL,
"android_modem",
0,
MODEM_DEV_STATE_SAVE_VERSION,
android_modem_state_save,
android_modem_state_load,
modem);
aconfig_save_file( modem->nvram_config, modem->nvram_config_filename );
return modem;
}
void
amodem_set_legacy( AModem modem )
{
modem->supportsNetworkDataType = 0;
}
void
amodem_destroy( AModem modem )
{
asimcard_destroy( modem->sim );
modem->sim = NULL;
}
static int
amodem_has_network( AModem modem )
{
return !(modem->radio_state == A_RADIO_STATE_OFF ||
modem->oper_index < 0 ||
modem->oper_index >= modem->oper_count ||
modem->oper_selection_mode == A_SELECTION_DEREGISTRATION );
}
ARadioState
amodem_get_radio_state( AModem modem )
{
return modem->radio_state;
}
void
amodem_set_radio_state( AModem modem, ARadioState state )
{
modem->radio_state = state;
}
ASimCard
amodem_get_sim( AModem modem )
{
return modem->sim;
}
ARegistrationState
amodem_get_voice_registration( AModem modem )
{
return modem->voice_state;
}
void
amodem_set_voice_registration( AModem modem, ARegistrationState state )
{
modem->voice_state = state;
if (state == A_REGISTRATION_HOME)
modem->oper_index = OPERATOR_HOME_INDEX;
else if (state == A_REGISTRATION_ROAMING)
modem->oper_index = OPERATOR_ROAMING_INDEX;
switch (modem->voice_mode) {
case A_REGISTRATION_UNSOL_ENABLED:
amodem_unsol( modem, "+CREG: %d,%d\r",
modem->voice_mode, modem->voice_state );
break;
case A_REGISTRATION_UNSOL_ENABLED_FULL:
amodem_unsol( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"\r",
modem->voice_mode, modem->voice_state,
modem->area_code & 0xffff, modem->cell_id & 0xffff);
break;
default:
;
}
}
ARegistrationState
amodem_get_data_registration( AModem modem )
{
return modem->data_state;
}
void
amodem_set_data_registration( AModem modem, ARegistrationState state )
{
modem->data_state = state;
switch (modem->data_mode) {
case A_REGISTRATION_UNSOL_ENABLED:
amodem_unsol( modem, "+CGREG: %d,%d\r",
modem->data_mode, modem->data_state );
break;
case A_REGISTRATION_UNSOL_ENABLED_FULL:
if (modem->supportsNetworkDataType)
amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"\r",
modem->data_mode, modem->data_state,
modem->area_code & 0xffff, modem->cell_id & 0xffff,
modem->data_network );
else
amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\"\r",
modem->data_mode, modem->data_state,
modem->area_code & 0xffff, modem->cell_id & 0xffff );
break;
default:
;
}
}
static int
amodem_nvram_set( AModem modem, const char *name, const char *value )
{
aconfig_set(modem->nvram_config, name, value);
return 0;
}
static AModemTech
tech_from_network_type( ADataNetworkType type )
{
switch (type) {
case A_DATA_NETWORK_GPRS:
case A_DATA_NETWORK_EDGE:
case A_DATA_NETWORK_UMTS:
// TODO: Add A_TECH_WCDMA
return A_TECH_GSM;
case A_DATA_NETWORK_LTE:
return A_TECH_LTE;
case A_DATA_NETWORK_CDMA1X:
case A_DATA_NETWORK_EVDO:
return A_TECH_CDMA;
case A_DATA_NETWORK_UNKNOWN:
return A_TECH_UNKNOWN;
}
return A_TECH_UNKNOWN;
}
void
amodem_set_data_network_type( AModem modem, ADataNetworkType type )
{
AModemTech modemTech;
modem->data_network = type;
amodem_set_data_registration( modem, modem->data_state );
modemTech = tech_from_network_type(type);
if (modem->unsol_func && modemTech != A_TECH_UNKNOWN) {
if (_amodem_switch_technology( modem, modemTech, modem->preferred_mask )) {
modem->unsol_func( modem->unsol_opaque, modem->out_buff );
}
}
}
int
amodem_get_operator_name ( AModem modem, ANameIndex index, char* buffer, int buffer_size )
{
AOperator oper;
int len;
if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count ||
(unsigned)index > 2 )
return 0;
oper = modem->operators + modem->oper_index;
len = strlen(oper->name[index]) + 1;
if (buffer_size > len)
buffer_size = len;
if (buffer_size > 0) {
memcpy( buffer, oper->name[index], buffer_size-1 );
buffer[buffer_size] = 0;
}
return len;
}
/* reset one operator name from a user-provided buffer, set buffer_size to -1 for zero-terminated strings */
void
amodem_set_operator_name( AModem modem, ANameIndex index, const char* buffer, int buffer_size )
{
AOperator oper;
int avail;
if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count ||
(unsigned)index > 2 )
return;
oper = modem->operators + modem->oper_index;
avail = sizeof(oper->name[0]);
if (buffer_size < 0)
buffer_size = strlen(buffer);
if (buffer_size > avail-1)
buffer_size = avail-1;
memcpy( oper->name[index], buffer, buffer_size );
oper->name[index][buffer_size] = 0;
}
/** CALLS
**/
int
amodem_get_call_count( AModem modem )
{
return modem->call_count;
}
ACall
amodem_get_call( AModem modem, int index )
{
if ((unsigned)index >= (unsigned)modem->call_count)
return NULL;
return &modem->calls[index].call;
}
static AVoiceCall
amodem_alloc_call( AModem modem )
{
AVoiceCall call = NULL;
int count = modem->call_count;
if (count < MAX_CALLS) {
int id;
/* find a valid id for this call */
for (id = 0; id < modem->call_count; id++) {
int found = 0;
int nn;
for (nn = 0; nn < count; nn++) {
if ( modem->calls[nn].call.id == (id+1) ) {
found = 1;
break;
}
}
if (!found)
break;
}
call = modem->calls + count;
call->call.id = id + 1;
call->modem = modem;
modem->call_count += 1;
}
return call;
}
static void
amodem_free_call( AModem modem, AVoiceCall call )
{
int nn;
if (call->timer) {
sys_timer_destroy( call->timer );
call->timer = NULL;
}
if (call->is_remote) {
remote_call_cancel( call->call.number, modem->base_port );
call->is_remote = 0;
}
for (nn = 0; nn < modem->call_count; nn++) {
if ( modem->calls + nn == call )
break;
}
assert( nn < modem->call_count );
memmove( modem->calls + nn,
modem->calls + nn + 1,
(modem->call_count - 1 - nn)*sizeof(*call) );
modem->call_count -= 1;
}
static AVoiceCall
amodem_find_call( AModem modem, int id )
{
int nn;
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall call = modem->calls + nn;
if (call->call.id == id)
return call;
}
return NULL;
}
static void
amodem_send_calls_update( AModem modem )
{
/* despite its name, this really tells the system that the call
* state has changed */
amodem_unsol( modem, "RING\r" );
}
int
amodem_add_inbound_call( AModem modem, const char* number )
{
AVoiceCall vcall = amodem_alloc_call( modem );
ACall call = &vcall->call;
int len;
if (call == NULL)
return -1;
call->dir = A_CALL_INBOUND;
call->state = A_CALL_INCOMING;
call->mode = A_CALL_VOICE;
call->multi = 0;
vcall->is_remote = (remote_number_string_to_port(number) > 0);
len = strlen(number);
if (len >= sizeof(call->number))
len = sizeof(call->number)-1;
memcpy( call->number, number, len );
call->number[len] = 0;
amodem_send_calls_update( modem );
return 0;
}
ACall
amodem_find_call_by_number( AModem modem, const char* number )
{
AVoiceCall vcall = modem->calls;
AVoiceCall vend = vcall + modem->call_count;
if (!number)
return NULL;
for ( ; vcall < vend; vcall++ )
if ( !strcmp(vcall->call.number, number) )
return &vcall->call;
return NULL;
}
void
amodem_set_signal_strength( AModem modem, int rssi, int ber )
{
modem->rssi = rssi;
modem->ber = ber;
}
static void
acall_set_state( AVoiceCall call, ACallState state )
{
if (state != call->call.state)
{
if (call->is_remote)
{
const char* number = call->call.number;
int port = call->modem->base_port;
switch (state) {
case A_CALL_HELD:
remote_call_other( number, port, REMOTE_CALL_HOLD );
break;
case A_CALL_ACTIVE:
remote_call_other( number, port, REMOTE_CALL_ACCEPT );
break;
default: ;
}
}
call->call.state = state;
}
}
int
amodem_update_call( AModem modem, const char* fromNumber, ACallState state )
{
AVoiceCall vcall = (AVoiceCall) amodem_find_call_by_number(modem, fromNumber);
if (vcall == NULL)
return -1;
acall_set_state( vcall, state );
amodem_send_calls_update(modem);
return 0;
}
int
amodem_disconnect_call( AModem modem, const char* number )
{
AVoiceCall vcall = (AVoiceCall) amodem_find_call_by_number(modem, number);
if (!vcall)
return -1;
amodem_free_call( modem, vcall );
amodem_send_calls_update(modem);
return 0;
}
/** COMMAND HANDLERS
**/
static const char*
unknownCommand( const char* cmd, AModem modem )
{
modem=modem;
fprintf(stderr, ">>> unknown command '%s'\n", cmd );
return "ERROR: unknown command\r";
}
/*
* Tell whether the specified tech is valid for the preferred mask.
* @pmask: The preferred mask
* @tech: The AModemTech we try to validate
* return: If the specified technology is not set in any of the 4
* bitmasks, return 0.
* Otherwise, return a non-zero value.
*/
static int matchPreferredMask( int32_t pmask, AModemTech tech )
{
int ret = 0;
int i;
for ( i=3; i >= 0 ; i-- ) {
if (pmask & (1 << (tech + i*8 ))) {
ret = 1;
break;
}
}
return ret;
}
static AModemTech
chooseTechFromMask( AModem modem, int32_t preferred )
{
int i, j;
/* TODO: Current implementation will only return the highest priority,
* lowest numbered technology that is set in the mask.
* However the implementation could be changed to consider currently
* available networks set from the console (or somewhere else)
*/
for ( i=3 ; i >= 0; i-- ) {
for ( j=0 ; j < A_TECH_UNKNOWN ; j++ ) {
if (preferred & (1 << (j + 8 * i)))
return (AModemTech) j;
}
}
assert("This should never happen" == 0);
// This should never happen. Just to please the compiler.
return A_TECH_UNKNOWN;
}
static const char*
_amodem_switch_technology( AModem modem, AModemTech newtech, int32_t newpreferred )
{
D("_amodem_switch_technology: oldtech: %d, newtech %d, preferred: %d. newpreferred: %d\n",
modem->technology, newtech, modem->preferred_mask,newpreferred);
const char *ret = "+CTEC: DONE";
assert( modem );
if (!newpreferred) {
return "ERROR: At least one technology must be enabled";
}
if (modem->preferred_mask != newpreferred) {
char value[MAX_KEY_NAME + 1];
modem->preferred_mask = newpreferred;
snprintf(value, MAX_KEY_NAME, "%d", newpreferred);
amodem_nvram_set(modem, NV_PREFERRED_MODE, value);
if (!matchPreferredMask(modem->preferred_mask, newtech)) {
newtech = chooseTechFromMask(modem, newpreferred);
}
}
if (modem->technology != newtech) {
modem->technology = newtech;
ret = amodem_printf(modem, "+CTEC: %d", modem->technology);
}
return ret;
}
static int
parsePreferred( const char *str, int *preferred )
{
char *endptr = NULL;
int result = 0;
if (!str || !*str) { *preferred = 0; return 0; }
if (*str == '"') str ++;
if (!*str) return 0;
result = strtol(str, &endptr, 16);
if (*endptr && *endptr != '"') {
return 0;
}
if (preferred)
*preferred = result;
return 1;
}
void
amodem_set_cdma_prl_version( AModem modem, int prlVersion)
{
D("amodem_set_prl_version()\n");
if (!_amodem_set_cdma_prl_version( modem, prlVersion)) {
amodem_unsol(modem, "+WPRL: %d", prlVersion);
}
}
static int
_amodem_set_cdma_prl_version( AModem modem, int prlVersion)
{
D("_amodem_set_cdma_prl_version");
if (modem->prl_version != prlVersion) {
modem->prl_version = prlVersion;
return 0;
}
return -1;
}
void
amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss)
{
D("amodem_set_cdma_subscription_source()\n");
if (!_amodem_set_cdma_subscription_source( modem, ss)) {
amodem_unsol(modem, "+CCSS: %d", (int)ss);
}
}
#define MAX_INT_DIGITS 10
static int
_amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss)
{
D("_amodem_set_cdma_subscription_source()\n");
char value[MAX_INT_DIGITS + 1];
if (ss != modem->subscription_source) {
snprintf( value, MAX_INT_DIGITS + 1, "%d", ss );
amodem_nvram_set( modem, NV_CDMA_SUBSCRIPTION_SOURCE, value );
modem->subscription_source = ss;
return 0;
}
return -1;
}
static const char*
handleSubscriptionSource( const char* cmd, AModem modem )
{
int newsource;
// TODO: Actually change subscription depending on source
D("handleSubscriptionSource(%s)\n",cmd);
assert( !memcmp( "+CCSS", cmd, 5 ) );
cmd += 5;
if (cmd[0] == '?') {
return amodem_printf( modem, "+CCSS: %d", modem->subscription_source );
} else if (cmd[0] == '=') {
switch (cmd[1]) {
case '0':
case '1':
newsource = (ACdmaSubscriptionSource)cmd[1] - '0';
_amodem_set_cdma_subscription_source( modem, newsource );
return amodem_printf( modem, "+CCSS: %d", modem->subscription_source );
break;
}
}
return amodem_printf( modem, "ERROR: Invalid subscription source");
}
static const char*
handleRoamPref( const char * cmd, AModem modem )
{
int roaming_pref = -1;
char *endptr = NULL;
D("handleRoamPref(%s)\n", cmd);
assert( !memcmp( "+WRMP", cmd, 5 ) );
cmd += 5;
if (cmd[0] == '?') {
return amodem_printf( modem, "+WRMP: %d", modem->roaming_pref );
}
if (!strcmp( cmd, "=?")) {
return amodem_printf( modem, "+WRMP: 0,1,2" );
} else if (cmd[0] == '=') {
cmd ++;
roaming_pref = strtol( cmd, &endptr, 10 );
// Make sure the rest of the command is the number
// (if *endptr is null, it means strtol processed the whole string as a number)
if(endptr && !*endptr) {
modem->roaming_pref = roaming_pref;
aconfig_set( modem->nvram_config, NV_CDMA_ROAMING_PREF, cmd );
aconfig_save_file( modem->nvram_config, modem->nvram_config_filename );
return NULL;
}
}
return amodem_printf( modem, "ERROR");
}
static const char*
handleTech( const char* cmd, AModem modem )
{
AModemTech newtech = modem->technology;
int pt = modem->preferred_mask;
int havenewtech = 0;
D("handleTech. cmd: %s\n", cmd);
assert( !memcmp( "+CTEC", cmd, 5 ) );
cmd += 5;
if (cmd[0] == '?') {
return amodem_printf( modem, "+CTEC: %d,%x",modem->technology, modem->preferred_mask );
}
amodem_begin_line( modem );
if (!strcmp( cmd, "=?")) {
return amodem_printf( modem, "+CTEC: 0,1,2,3" );
}
else if (cmd[0] == '=') {
switch (cmd[1]) {
case '0':
case '1':
case '2':
case '3':
havenewtech = 1;
newtech = cmd[1] - '0';
cmd += 1;
break;
}
cmd += 1;
}
if (havenewtech) {
D( "cmd: %s\n", cmd );
if (cmd[0] == ',' && ! parsePreferred( ++cmd, &pt ))
return amodem_printf( modem, "ERROR: invalid preferred mode" );
return _amodem_switch_technology( modem, newtech, pt );
}
return amodem_printf( modem, "ERROR: %s: Unknown Technology", cmd + 1 );
}
static const char*
handleEmergencyMode( const char* cmd, AModem modem )
{
long arg;
char *endptr = NULL;
assert ( !memcmp( "+WSOS", cmd, 5 ) );
cmd += 5;
if (cmd[0] == '?') {
return amodem_printf( modem, "+WSOS: %d", modem->in_emergency_mode);
}
if (cmd[0] == '=') {
if (cmd[1] == '?') {
return amodem_printf(modem, "+WSOS: (0)");
}
if (cmd[1] == 0) {
return amodem_printf(modem, "ERROR");
}
arg = strtol(cmd+1, &endptr, 10);
if (!endptr || endptr[0] != 0) {
return amodem_printf(modem, "ERROR");
}
arg = arg? 1 : 0;
if ((!arg) != (!modem->in_emergency_mode)) {
modem->in_emergency_mode = arg;
return amodem_printf(modem, "+WSOS: %d", arg);
}
}
return amodem_printf(modem, "ERROR");
}
static const char*
handlePrlVersion( const char* cmd, AModem modem )
{
assert ( !memcmp( "+WPRL", cmd, 5 ) );
cmd += 5;
if (cmd[0] == '?') {
return amodem_printf( modem, "+WPRL: %d", modem->prl_version);
}
return amodem_printf(modem, "ERROR");
}
static const char*
handleRadioPower( const char* cmd, AModem modem )
{
if ( !strcmp( cmd, "+CFUN=0" ) )
{
/* turn radio off */
modem->radio_state = A_RADIO_STATE_OFF;
}
else if ( !strcmp( cmd, "+CFUN=1" ) )
{
/* turn radio on */
modem->radio_state = A_RADIO_STATE_ON;
}
return NULL;
}
static const char*
handleRadioPowerReq( const char* cmd, AModem modem )
{
if (modem->radio_state != A_RADIO_STATE_OFF)
return "+CFUN: 1";
else
return "+CFUN: 0";
}
static const char*
handleSIMStatusReq( const char* cmd, AModem modem )
{
const char* answer = NULL;
switch (asimcard_get_status(modem->sim)) {
case A_SIM_STATUS_ABSENT: answer = "+CPIN: ABSENT"; break;
case A_SIM_STATUS_READY: answer = "+CPIN: READY"; break;
case A_SIM_STATUS_NOT_READY: answer = "+CMERROR: NOT READY"; break;
case A_SIM_STATUS_PIN: answer = "+CPIN: SIM PIN"; break;
case A_SIM_STATUS_PUK: answer = "+CPIN: SIM PUK"; break;
case A_SIM_STATUS_NETWORK_PERSONALIZATION: answer = "+CPIN: PH-NET PIN"; break;
default:
answer = "ERROR: internal error";
}
return answer;
}
/* TODO: Will we need this?
static const char*
handleSRegister( const char * cmd, AModem modem )
{
char *end;
assert( cmd[0] == 'S' || cmd[0] == 's' );
++ cmd;
int l = strtol(cmd, &end, 10);
} */
static const char*
handleNetworkRegistration( const char* cmd, AModem modem )
{
if ( !memcmp( cmd, "+CREG", 5 ) ) {
cmd += 5;
if (cmd[0] == '?') {
if (modem->voice_mode == A_REGISTRATION_UNSOL_ENABLED_FULL)
return amodem_printf( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"",
modem->voice_mode, modem->voice_state,
modem->area_code, modem->cell_id );
else
return amodem_printf( modem, "+CREG: %d,%d",
modem->voice_mode, modem->voice_state );
} else if (cmd[0] == '=') {
switch (cmd[1]) {
case '0':
modem->voice_mode = A_REGISTRATION_UNSOL_DISABLED;
break;
case '1':
modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED;
break;
case '2':
modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
break;
case '?':
return "+CREG: (0-2)";
default:
return "ERROR: BAD COMMAND";
}
} else {
assert( 0 && "unreachable" );
}
} else if ( !memcmp( cmd, "+CGREG", 6 ) ) {
cmd += 6;
if (cmd[0] == '?') {
if (modem->supportsNetworkDataType)
return amodem_printf( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"",
modem->data_mode, modem->data_state,
modem->area_code, modem->cell_id,
modem->data_network );
else
return amodem_printf( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\"",
modem->data_mode, modem->data_state,
modem->area_code, modem->cell_id );
} else if (cmd[0] == '=') {
switch (cmd[1]) {
case '0':
modem->data_mode = A_REGISTRATION_UNSOL_DISABLED;
break;
case '1':
modem->data_mode = A_REGISTRATION_UNSOL_ENABLED;
break;
case '2':
modem->data_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
break;
case '?':
return "+CGREG: (0-2)";
default:
return "ERROR: BAD COMMAND";
}
} else {
assert( 0 && "unreachable" );
}
}
return NULL;
}
static const char*
handleSetDialTone( const char* cmd, AModem modem )
{
/* XXX: TODO */
return NULL;
}
static const char*
handleDeleteSMSonSIM( const char* cmd, AModem modem )
{
/* XXX: TODO */
return NULL;
}
static const char*
handleSIM_IO( const char* cmd, AModem modem )
{
return asimcard_io( modem->sim, cmd );
}
static const char*
handleOperatorSelection( const char* cmd, AModem modem )
{
assert( !memcmp( "+COPS", cmd, 5 ) );
cmd += 5;
if (cmd[0] == '?') { /* ask for current operator */
AOperator oper = &modem->operators[ modem->oper_index ];
if ( !amodem_has_network( modem ) )
{
/* this error code means "no network" */
return amodem_printf( modem, "+CME ERROR: 30" );
}
oper = &modem->operators[ modem->oper_index ];
if ( modem->oper_name_index == 2 )
return amodem_printf( modem, "+COPS: %d,2,%s",
modem->oper_selection_mode,
oper->name[2] );
return amodem_printf( modem, "+COPS: %d,%d,\"%s\"",
modem->oper_selection_mode,
modem->oper_name_index,
oper->name[ modem->oper_name_index ] );
}
else if (cmd[0] == '=' && cmd[1] == '?') { /* ask for all available operators */
const char* comma = "+COPS: ";
int nn;
amodem_begin_line( modem );
for (nn = 0; nn < modem->oper_count; nn++) {
AOperator oper = &modem->operators[nn];
amodem_add_line( modem, "%s(%d,\"%s\",\"%s\",\"%s\")", comma,
oper->status, oper->name[0], oper->name[1], oper->name[2] );
comma = ", ";
}
return amodem_end_line( modem );
}
else if (cmd[0] == '=') {
switch (cmd[1]) {
case '0':
modem->oper_selection_mode = A_SELECTION_AUTOMATIC;
return NULL;
case '1':
{
int format, nn, len, found = -1;
if (cmd[2] != ',')
goto BadCommand;
format = cmd[3] - '0';
if ( (unsigned)format > 2 )
goto BadCommand;
if (cmd[4] != ',')
goto BadCommand;
cmd += 5;
len = strlen(cmd);
if (*cmd == '"') {
cmd++;
len -= 2;
}
if (len <= 0)
goto BadCommand;
for (nn = 0; nn < modem->oper_count; nn++) {
AOperator oper = modem->operators + nn;
char* name = oper->name[ format ];
if ( !memcpy( name, cmd, len ) && name[len] == 0 ) {
found = nn;
break;
}
}
if (found < 0) {
/* Selection failed */
return "+CME ERROR: 529";
} else if (modem->operators[found].status == A_STATUS_DENIED) {
/* network not allowed */
return "+CME ERROR: 32";
}
modem->oper_index = found;
/* set the voice and data registration states to home or roaming
* depending on the operator index
*/
if (found == OPERATOR_HOME_INDEX) {
modem->voice_state = A_REGISTRATION_HOME;
modem->data_state = A_REGISTRATION_HOME;
} else if (found == OPERATOR_ROAMING_INDEX) {
modem->voice_state = A_REGISTRATION_ROAMING;
modem->data_state = A_REGISTRATION_ROAMING;
}
return NULL;
}
case '2':
modem->oper_selection_mode = A_SELECTION_DEREGISTRATION;
return NULL;
case '3':
{
int format;
if (cmd[2] != ',')
goto BadCommand;
format = cmd[3] - '0';
if ( (unsigned)format > 2 )
goto BadCommand;
modem->oper_name_index = format;
return NULL;
}
default:
;
}
}
BadCommand:
return unknownCommand(cmd,modem);
}
static const char*
handleRequestOperator( const char* cmd, AModem modem )
{
AOperator oper;
cmd=cmd;
if ( !amodem_has_network(modem) )
return "+CME ERROR: 30";
oper = modem->operators + modem->oper_index;
modem->oper_name_index = 2;
return amodem_printf( modem, "+COPS: 0,0,\"%s\"\r"
"+COPS: 0,1,\"%s\"\r"
"+COPS: 0,2,\"%s\"",
oper->name[0], oper->name[1], oper->name[2] );
}
static const char*
handleSendSMStoSIM( const char* cmd, AModem modem )
{
/* XXX: TODO */
return "ERROR: unimplemented";
}
static const char*
handleSendSMS( const char* cmd, AModem modem )
{
modem->wait_sms = 1;
return "> ";
}
#if 0
static void
sms_address_dump( SmsAddress address, FILE* out )
{
int nn, len = address->len;
if (address->toa == 0x91) {
fprintf( out, "+" );
}
for (nn = 0; nn < len; nn += 2)
{
static const char dialdigits[16] = "0123456789*#,N%";
int c = address->data[nn/2];
fprintf( out, "%c", dialdigits[c & 0xf] );
if (nn+1 >= len)
break;
fprintf( out, "%c", dialdigits[(c >> 4) & 0xf] );
}
}
static void
smspdu_dump( SmsPDU pdu, FILE* out )
{
SmsAddressRec address;
unsigned char temp[256];
int len;
if (pdu == NULL) {
fprintf( out, "SMS PDU is (null)\n" );
return;
}
fprintf( out, "SMS PDU type: " );
switch (smspdu_get_type(pdu)) {
case SMS_PDU_DELIVER: fprintf(out, "DELIVER"); break;
case SMS_PDU_SUBMIT: fprintf(out, "SUBMIT"); break;
case SMS_PDU_STATUS_REPORT: fprintf(out, "STATUS_REPORT"); break;
default: fprintf(out, "UNKNOWN");
}
fprintf( out, "\n sender: " );
if (smspdu_get_sender_address(pdu, &address) < 0)
fprintf( out, "(N/A)" );
else
sms_address_dump(&address, out);
fprintf( out, "\n receiver: " );
if (smspdu_get_receiver_address(pdu, &address) < 0)
fprintf(out, "(N/A)");
else
sms_address_dump(&address, out);
fprintf( out, "\n text: " );
len = smspdu_get_text_message( pdu, temp, sizeof(temp)-1 );
if (len > sizeof(temp)-1 )
len = sizeof(temp)-1;
fprintf( out, "'%.*s'\n", len, temp );
}
#endif
static const char*
handleSendSMSText( const char* cmd, AModem modem )
{
#if 1
SmsAddressRec address;
char temp[16];
char number[16];
int numlen;
int len = strlen(cmd);
SmsPDU pdu;
/* get rid of trailing escape */
if (len > 0 && cmd[len-1] == 0x1a)
len -= 1;
pdu = smspdu_create_from_hex( cmd, len );
if (pdu == NULL) {
D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd);
return "+CMS ERROR: INVALID SMS PDU";
}
if (smspdu_get_receiver_address(pdu, &address) < 0) {
D("%s: could not get SMS receiver address from '%s'\n",
__FUNCTION__, cmd);
return "+CMS ERROR: BAD SMS RECEIVER ADDRESS";
}
do {
int index;
numlen = sms_address_to_str( &address, temp, sizeof(temp) );
if (numlen > sizeof(temp)-1)
break;
temp[numlen] = 0;
/* Converts 4, 7, and 10 digits number to 11 digits */
if (numlen == 10 && !strncmp(temp, PHONE_PREFIX+1, 6)) {
memcpy( number, PHONE_PREFIX, 1 );
memcpy( number+1, temp, numlen );
number[numlen+1] = 0;
} else if (numlen == 7 && !strncmp(temp, PHONE_PREFIX+4, 3)) {
memcpy( number, PHONE_PREFIX, 4 );
memcpy( number+4, temp, numlen );
number[numlen+4] = 0;
} else if (numlen == 4) {
memcpy( number, PHONE_PREFIX, 7 );
memcpy( number+7, temp, numlen );
number[numlen+7] = 0;
} else {
memcpy( number, temp, numlen );
number[numlen] = 0;
}
if ( remote_number_string_to_port( number ) < 0 )
break;
if (modem->sms_receiver == NULL) {
modem->sms_receiver = sms_receiver_create();
if (modem->sms_receiver == NULL) {
D( "%s: could not create SMS receiver\n", __FUNCTION__ );
break;
}
}
index = sms_receiver_add_submit_pdu( modem->sms_receiver, pdu );
if (index < 0) {
D( "%s: could not add submit PDU\n", __FUNCTION__ );
break;
}
/* the PDU is now owned by the receiver */
pdu = NULL;
if (index > 0) {
SmsAddressRec from[1];
char temp[12];
SmsPDU* deliver;
int nn;
snprintf( temp, sizeof(temp), PHONE_PREFIX "%d", modem->base_port );
sms_address_from_str( from, temp, strlen(temp) );
deliver = sms_receiver_create_deliver( modem->sms_receiver, index, from );
if (deliver == NULL) {
D( "%s: could not create deliver PDUs for SMS index %d\n",
__FUNCTION__, index );
break;
}
for (nn = 0; deliver[nn] != NULL; nn++) {
if ( remote_call_sms( number, modem->base_port, deliver[nn] ) < 0 ) {
D( "%s: could not send SMS PDU to remote emulator\n",
__FUNCTION__ );
break;
}
}
smspdu_free_list(deliver);
}
} while (0);
if (pdu != NULL)
smspdu_free(pdu);
#elif 1
SmsAddressRec address;
char number[16];
int numlen;
int len = strlen(cmd);
SmsPDU pdu;
/* get rid of trailing escape */
if (len > 0 && cmd[len-1] == 0x1a)
len -= 1;
pdu = smspdu_create_from_hex( cmd, len );
if (pdu == NULL) {
D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd);
return "+CMS ERROR: INVALID SMS PDU";
}
if (smspdu_get_receiver_address(pdu, &address) < 0) {
D("%s: could not get SMS receiver address from '%s'\n",
__FUNCTION__, cmd);
return "+CMS ERROR: BAD SMS RECEIVER ADDRESS";
}
do {
numlen = sms_address_to_str( &address, number, sizeof(number) );
if (numlen > sizeof(number)-1)
break;
number[numlen] = 0;
if ( remote_number_string_to_port( number ) < 0 )
break;
if ( remote_call_sms( number, modem->base_port, pdu ) < 0 )
{
D("%s: could not send SMS PDU to remote emulator\n",
__FUNCTION__);
return "+CMS ERROR: NO EMULATOR RECEIVER";
}
} while (0);
#else
fprintf(stderr, "SMS<< %s\n", cmd);
SmsPDU pdu = smspdu_create_from_hex( cmd, strlen(cmd) );
if (pdu == NULL) {
fprintf(stderr, "invalid SMS PDU ?: '%s'\n", cmd);
} else {
smspdu_dump(pdu, stderr);
}
#endif
return "+CMGS: 0\rOK\r";
}
static const char*
handleChangeOrEnterPIN( const char* cmd, AModem modem )
{
assert( !memcmp( cmd, "+CPIN=", 6 ) );
cmd += 6;
switch (asimcard_get_status(modem->sim)) {
case A_SIM_STATUS_ABSENT:
return "+CME ERROR: SIM ABSENT";
case A_SIM_STATUS_NOT_READY:
return "+CME ERROR: SIM NOT READY";
case A_SIM_STATUS_READY:
/* this may be a request to change the PIN */
{
if (strlen(cmd) == 9 && cmd[4] == ',') {
char pin[5];
memcpy( pin, cmd, 4 ); pin[4] = 0;
if ( !asimcard_check_pin( modem->sim, pin ) )
return "+CME ERROR: BAD PIN";
memcpy( pin, cmd+5, 4 );
asimcard_set_pin( modem->sim, pin );
return "+CPIN: READY";
}
}
break;
case A_SIM_STATUS_PIN: /* waiting for PIN */
if ( asimcard_check_pin( modem->sim, cmd ) )
return "+CPIN: READY";
else
return "+CME ERROR: BAD PIN";
case A_SIM_STATUS_PUK:
if (strlen(cmd) == 9 && cmd[4] == ',') {
char puk[5];
memcpy( puk, cmd, 4 );
puk[4] = 0;
if ( asimcard_check_puk( modem->sim, puk, cmd+5 ) )
return "+CPIN: READY";
else
return "+CME ERROR: BAD PUK";
}
return "+CME ERROR: BAD PUK";
default:
return "+CPIN: PH-NET PIN";
}
return "+CME ERROR: BAD FORMAT";
}
static const char*
handleListCurrentCalls( const char* cmd, AModem modem )
{
int nn;
amodem_begin_line( modem );
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (call->mode == A_CALL_VOICE)
amodem_add_line( modem, "+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n",
call->id, call->dir, call->state, call->mode,
call->multi, call->number, 129 );
}
return amodem_end_line( modem );
}
/* Add a(n unsolicited) time response.
*
* retrieve the current time and zone in a format suitable
* for %CTZV: unsolicited message
* "yy/mm/dd,hh:mm:ss(+/-)tz"
* mm is 0-based
* tz is in number of quarter-hours
*
* it seems reference-ril doesn't parse the comma (,) as anything else than a token
* separator, so use a column (:) instead, the Java parsing code won't see a difference
*
*/
static void
amodem_addTimeUpdate( AModem modem )
{
time_t now = time(NULL);
struct tm utc, local;
long e_local, e_utc;
long tzdiff;
char tzname[64];
tzset();
utc = *gmtime( &now );
local = *localtime( &now );
e_local = local.tm_min + 60*(local.tm_hour + 24*local.tm_yday);
e_utc = utc.tm_min + 60*(utc.tm_hour + 24*utc.tm_yday);
if ( utc.tm_year < local.tm_year )
e_local += 24*60;
else if ( utc.tm_year > local.tm_year )
e_utc += 24*60;
tzdiff = e_local - e_utc; /* timezone offset in minutes */
/* retrieve a zoneinfo-compatible name for the host timezone
*/
{
char* end = tzname + sizeof(tzname);
char* p = bufprint_zoneinfo_timezone( tzname, end );
if (p >= end)
strcpy(tzname, "Unknown/Unknown");
/* now replace every / in the timezone name by a "!"
* that's because the code that reads the CTZV line is
* dumb and treats a / as a field separator...
*/
p = tzname;
while (1) {
p = strchr(p, '/');
if (p == NULL)
break;
*p = '!';
p += 1;
}
}
/* as a special extension, we append the name of the host's time zone to the
* string returned with %CTZ. the system should contain special code to detect
* and deal with this case (since it normally relied on the operator's country code
* which is hard to simulate on a general-purpose computer
*/
amodem_add_line( modem, "%%CTZV: %02d/%02d/%02d:%02d:%02d:%02d%c%d:%d:%s\r\n",
(utc.tm_year + 1900) % 100, utc.tm_mon + 1, utc.tm_mday,
utc.tm_hour, utc.tm_min, utc.tm_sec,
(tzdiff >= 0) ? '+' : '-', (tzdiff >= 0 ? tzdiff : -tzdiff) / 15,
(local.tm_isdst > 0),
tzname );
}
static const char*
handleEndOfInit( const char* cmd, AModem modem )
{
amodem_begin_line( modem );
amodem_addTimeUpdate( modem );
return amodem_end_line( modem );
}
static const char*
handleListPDPContexts( const char* cmd, AModem modem )
{
int nn;
assert( !memcmp( cmd, "+CGACT?", 7 ) );
amodem_begin_line( modem );
for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) {
ADataContext data = modem->data_contexts + nn;
if (!data->active)
continue;
amodem_add_line( modem, "+CGACT: %d,%d\r\n", data->id, data->active );
}
return amodem_end_line( modem );
}
static const char*
handleDefinePDPContext( const char* cmd, AModem modem )
{
assert( !memcmp( cmd, "+CGDCONT=", 9 ) );
cmd += 9;
if (cmd[0] == '?') {
/* +CGDCONT=? is used to query the ranges of supported PDP Contexts.
* We only really support IP ones in the emulator, so don't try to
* fake PPP ones.
*/
return "+CGDCONT: (1-1),\"IP\",,,(0-2),(0-4)\r\n";
} else {
/* template is +CGDCONT=<id>,"<type>","<apn>",,0,0 */
int id = cmd[0] - '1';
ADataType type;
char apn[32];
ADataContext data;
if ((unsigned)id > 3)
goto BadCommand;
if ( !memcmp( cmd+1, ",\"IP\",\"", 7 ) ) {
type = A_DATA_IP;
cmd += 8;
} else if ( !memcmp( cmd+1, ",\"PPP\",\"", 8 ) ) {
type = A_DATA_PPP;
cmd += 9;
} else
goto BadCommand;
{
const char* p = strchr( cmd, '"' );
int len;
if (p == NULL)
goto BadCommand;
len = (int)( p - cmd );
if (len > sizeof(apn)-1 )
len = sizeof(apn)-1;
memcpy( apn, cmd, len );
apn[len] = 0;
}
data = modem->data_contexts + id;
data->id = id + 1;
data->active = 1;
data->type = type;
memcpy( data->apn, apn, sizeof(data->apn) );
}
return NULL;
BadCommand:
return "ERROR: BAD COMMAND";
}
static const char*
handleQueryPDPContext( const char* cmd, AModem modem )
{
int nn;
amodem_begin_line(modem);
for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) {
ADataContext data = modem->data_contexts + nn;
if (!data->active)
continue;
amodem_add_line( modem, "+CGDCONT: %d,\"%s\",\"%s\",\"%s\",0,0\r\n",
data->id,
data->type == A_DATA_IP ? "IP" : "PPP",
data->apn,
/* Note: For now, hard-code the IP address of our
* network interface
*/
data->type == A_DATA_IP ? "10.0.2.15" : "");
}
return amodem_end_line(modem);
}
static const char*
handleStartPDPContext( const char* cmd, AModem modem )
{
/* XXX: TODO: handle PDP start appropriately */
return NULL;
}
static void
remote_voice_call_event( void* _vcall, int success )
{
AVoiceCall vcall = _vcall;
AModem modem = vcall->modem;
/* NOTE: success only means we could send the "gsm in new" command
* to the remote emulator, nothing more */
if (!success) {
/* aargh, the remote emulator probably quitted at that point */
amodem_free_call(modem, vcall);
amodem_send_calls_update(modem);
}
}
static void
voice_call_event( void* _vcall )
{
AVoiceCall vcall = _vcall;
ACall call = &vcall->call;
switch (call->state) {
case A_CALL_DIALING:
call->state = A_CALL_ALERTING;
if (vcall->is_remote) {
if ( remote_call_dial( call->number,
vcall->modem->base_port,
remote_voice_call_event, vcall ) < 0 )
{
/* we could not connect, probably because the corresponding
* emulator is not running, so simply destroy this call.
* XXX: should we send some sort of message to indicate BAD NUMBER ? */
/* it seems the Android code simply waits for changes in the list */
amodem_free_call( vcall->modem, vcall );
}
} else {
/* this is not a remote emulator number, so just simulate
* a small ringing delay */
sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_ALERT,
voice_call_event, vcall );
}
break;
case A_CALL_ALERTING:
call->state = A_CALL_ACTIVE;
break;
default:
assert( 0 && "unreachable event call state" );
}
amodem_send_calls_update(vcall->modem);
}
static int amodem_is_emergency( AModem modem, const char *number )
{
int i;
if (!number) return 0;
for (i = 0; i < MAX_EMERGENCY_NUMBERS; i++) {
if ( modem->emergency_numbers[i] && !strcmp( number, modem->emergency_numbers[i] )) break;
}
if (i < MAX_EMERGENCY_NUMBERS) return 1;
return 0;
}
static const char*
handleDial( const char* cmd, AModem modem )
{
AVoiceCall vcall = amodem_alloc_call( modem );
ACall call = &vcall->call;
int len;
if (call == NULL)
return "ERROR: TOO MANY CALLS";
assert( cmd[0] == 'D' );
call->dir = A_CALL_OUTBOUND;
call->state = A_CALL_DIALING;
call->mode = A_CALL_VOICE;
call->multi = 0;
cmd += 1;
len = strlen(cmd);
if (len > 0 && cmd[len-1] == ';')
len--;
if (len >= sizeof(call->number))
len = sizeof(call->number)-1;
/* Converts 4, 7, and 10 digits number to 11 digits */
if (len == 10 && !strncmp(cmd, PHONE_PREFIX+1, 6)) {
memcpy( call->number, PHONE_PREFIX, 1 );
memcpy( call->number+1, cmd, len );
call->number[len+1] = 0;
} else if (len == 7 && !strncmp(cmd, PHONE_PREFIX+4, 3)) {
memcpy( call->number, PHONE_PREFIX, 4 );
memcpy( call->number+4, cmd, len );
call->number[len+4] = 0;
} else if (len == 4) {
memcpy( call->number, PHONE_PREFIX, 7 );
memcpy( call->number+7, cmd, len );
call->number[len+7] = 0;
} else {
memcpy( call->number, cmd, len );
call->number[len] = 0;
}
amodem_begin_line( modem );
if (amodem_is_emergency(modem, call->number)) {
modem->in_emergency_mode = 1;
amodem_add_line( modem, "+WSOS: 1" );
}
vcall->is_remote = (remote_number_string_to_port(call->number) > 0);
vcall->timer = sys_timer_create();
sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_DIAL,
voice_call_event, vcall );
return amodem_end_line( modem );
}
static const char*
handleAnswer( const char* cmd, AModem modem )
{
int nn;
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (cmd[0] == 'A') {
if (call->state == A_CALL_INCOMING) {
acall_set_state( vcall, A_CALL_ACTIVE );
}
else if (call->state == A_CALL_ACTIVE) {
acall_set_state( vcall, A_CALL_HELD );
}
} else if (cmd[0] == 'H') {
/* ATH: hangup, since user is busy */
if (call->state == A_CALL_INCOMING) {
amodem_free_call( modem, vcall );
break;
}
}
}
return NULL;
}
int android_snapshot_update_time = 1;
int android_snapshot_update_time_request = 0;
static const char*
handleSignalStrength( const char* cmd, AModem modem )
{
amodem_begin_line( modem );
/* Sneak time updates into the SignalStrength request, because it's periodic.
* Ideally, we'd be able to prod the guest into asking immediately on restore
* from snapshot, but that'd require a driver.
*/
if ( android_snapshot_update_time && android_snapshot_update_time_request ) {
amodem_addTimeUpdate( modem );
android_snapshot_update_time_request = 0;
}
// rssi = 0 (<-113dBm) 1 (<-111) 2-30 (<-109--53) 31 (>=-51) 99 (?!)
// ber (bit error rate) - always 99 (unknown), apparently.
// TODO: return 99 if modem->radio_state==A_RADIO_STATE_OFF, once radio_state is in snapshot.
int rssi = modem->rssi;
int ber = modem->ber;
rssi = (0 > rssi && rssi > 31) ? 99 : rssi ;
ber = (0 > ber && ber > 7 ) ? 99 : ber;
amodem_add_line( modem, "+CSQ: %i,%i,85,130,90,6,4,25,9,50,68,12\r\n", rssi, ber );
return amodem_end_line( modem );
}
static const char*
handleHangup( const char* cmd, AModem modem )
{
if ( !memcmp(cmd, "+CHLD=", 6) ) {
int nn;
cmd += 6;
switch (cmd[0]) {
case '0': /* release all held, and set busy for waiting calls */
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (call->mode != A_CALL_VOICE)
continue;
if (call->state == A_CALL_HELD ||
call->state == A_CALL_WAITING ||
call->state == A_CALL_INCOMING) {
amodem_free_call(modem, vcall);
nn--;
}
}
break;
case '1':
if (cmd[1] == 0) { /* release all active, accept held one */
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (call->mode != A_CALL_VOICE)
continue;
if (call->state == A_CALL_ACTIVE) {
amodem_free_call(modem, vcall);
nn--;
}
else if (call->state == A_CALL_HELD ||
call->state == A_CALL_WAITING) {
acall_set_state( vcall, A_CALL_ACTIVE );
}
}
} else { /* release specific call */
int id = cmd[1] - '0';
AVoiceCall vcall = amodem_find_call( modem, id );
if (vcall != NULL)
amodem_free_call( modem, vcall );
}
break;
case '2':
if (cmd[1] == 0) { /* place all active on hold, accept held or waiting one */
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (call->mode != A_CALL_VOICE)
continue;
if (call->state == A_CALL_ACTIVE) {
acall_set_state( vcall, A_CALL_HELD );
}
else if (call->state == A_CALL_HELD ||
call->state == A_CALL_WAITING) {
acall_set_state( vcall, A_CALL_ACTIVE );
}
}
} else { /* place all active on hold, except a specific one */
int id = cmd[1] - '0';
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (call->mode != A_CALL_VOICE)
continue;
if (call->state == A_CALL_ACTIVE && call->id != id) {
acall_set_state( vcall, A_CALL_HELD );
}
}
}
break;
case '3': /* add a held call to the conversation */
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (call->mode != A_CALL_VOICE)
continue;
if (call->state == A_CALL_HELD) {
acall_set_state( vcall, A_CALL_ACTIVE );
break;
}
}
break;
case '4': /* connect the two calls */
for (nn = 0; nn < modem->call_count; nn++) {
AVoiceCall vcall = modem->calls + nn;
ACall call = &vcall->call;
if (call->mode != A_CALL_VOICE)
continue;
if (call->state == A_CALL_HELD) {
acall_set_state( vcall, A_CALL_ACTIVE );
break;
}
}
break;
}
}
else
return "ERROR: BAD COMMAND";
return NULL;
}
/* a function used to deal with a non-trivial request */
typedef const char* (*ResponseHandler)(const char* cmd, AModem modem);
static const struct {
const char* cmd; /* command coming from libreference-ril.so, if first
character is '!', then the rest is a prefix only */
const char* answer; /* default answer, NULL if needs specific handling or
if OK is good enough */
ResponseHandler handler; /* specific handler, ignored if 'answer' is not NULL,
NULL if OK is good enough */
} sDefaultResponses[] =
{
/* see onRadioPowerOn() */
{ "%CPHS=1", NULL, NULL },
{ "%CTZV=1", NULL, NULL },
/* see onSIMReady() */
{ "+CSMS=1", "+CSMS: 1, 1, 1", NULL },
{ "+CNMI=1,2,2,1,1", NULL, NULL },
/* see requestRadioPower() */
{ "+CFUN=0", NULL, handleRadioPower },
{ "+CFUN=1", NULL, handleRadioPower },
{ "+CTEC=?", "+CTEC: 0,1,2,3", NULL }, /* Query available Techs */
{ "!+CTEC", NULL, handleTech }, /* Set/get current Technology and preferred mode */
{ "+WRMP=?", "+WRMP: 0,1,2", NULL }, /* Query Roam Preference */
{ "!+WRMP", NULL, handleRoamPref }, /* Set/get Roam Preference */
{ "+CCSS=?", "+CTEC: 0,1", NULL }, /* Query available subscription sources */
{ "!+CCSS", NULL, handleSubscriptionSource }, /* Set/Get current subscription source */
{ "+WSOS=?", "+WSOS: 0", NULL}, /* Query supported +WSOS values */
{ "!+WSOS=", NULL, handleEmergencyMode },
{ "+WPRL?", NULL, handlePrlVersion }, /* Query the current PRL version */
/* see requestOrSendPDPContextList() */
{ "+CGACT?", NULL, handleListPDPContexts },
/* see requestOperator() */
{ "+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?", NULL, handleRequestOperator },
/* see requestQueryNetworkSelectionMode() */
{ "!+COPS", NULL, handleOperatorSelection },
/* see requestGetCurrentCalls() */
{ "+CLCC", NULL, handleListCurrentCalls },
/* see requestWriteSmsToSim() */
{ "!+CMGW=", NULL, handleSendSMStoSIM },
/* see requestHangup() */
{ "!+CHLD=", NULL, handleHangup },
/* see requestSignalStrength() */
{ "+CSQ", NULL, handleSignalStrength },
/* see requestRegistrationState() */
{ "!+CREG", NULL, handleNetworkRegistration },
{ "!+CGREG", NULL, handleNetworkRegistration },
/* see requestSendSMS() */
{ "!+CMGS=", NULL, handleSendSMS },
/* see requestSetupDefaultPDP() */
{ "%CPRIM=\"GMM\",\"CONFIG MULTISLOT_CLASS=<10>\"", NULL, NULL },
{ "%DATA=2,\"UART\",1,,\"SER\",\"UART\",0", NULL, NULL },
{ "!+CGDCONT=", NULL, handleDefinePDPContext },
{ "+CGDCONT?", NULL, handleQueryPDPContext },
{ "+CGQREQ=1", NULL, NULL },
{ "+CGQMIN=1", NULL, NULL },
{ "+CGEREP=1,0", NULL, NULL },
{ "+CGACT=1,0", NULL, NULL },
{ "D*99***1#", NULL, handleStartPDPContext },
/* see requestDial() */
{ "!D", NULL, handleDial }, /* the code says that success/error is ignored, the call state will
be polled through +CLCC instead */
/* see requestSMSAcknowledge() */
{ "+CNMA=1", NULL, NULL },
{ "+CNMA=2", NULL, NULL },
/* see requestSIM_IO() */
{ "!+CRSM=", NULL, handleSIM_IO },
/* see onRequest() */
{ "+CHLD=0", NULL, handleHangup },
{ "+CHLD=1", NULL, handleHangup },
{ "+CHLD=2", NULL, handleHangup },
{ "+CHLD=3", NULL, handleHangup },
{ "A", NULL, handleAnswer }, /* answer the call */
{ "H", NULL, handleAnswer }, /* user is busy */
{ "!+VTS=", NULL, handleSetDialTone },
{ "+CIMI", OPERATOR_HOME_MCCMNC "000000000", NULL }, /* request internation subscriber identification number */
{ "+CGSN", "000000000000000", NULL }, /* request model version */
{ "+CUSD=2",NULL, NULL }, /* Cancel USSD */
{ "+COPS=0", NULL, handleOperatorSelection }, /* set network selection to automatic */
{ "!+CMGD=", NULL, handleDeleteSMSonSIM }, /* delete SMS on SIM */
{ "!+CPIN=", NULL, handleChangeOrEnterPIN },
/* see getSIMStatus() */
{ "+CPIN?", NULL, handleSIMStatusReq },
{ "+CNMI?", "+CNMI: 1,2,2,1,1", NULL },
/* see isRadioOn() */
{ "+CFUN?", NULL, handleRadioPowerReq },
/* see initializeCallback() */
{ "E0Q0V1", NULL, NULL },
{ "S0=0", NULL, NULL },
{ "+CMEE=1", NULL, NULL },
{ "+CCWA=1", NULL, NULL },
{ "+CMOD=0", NULL, NULL },
{ "+CMUT=0", NULL, NULL },
{ "+CSSN=0,1", NULL, NULL },
{ "+COLP=0", NULL, NULL },
{ "+CSCS=\"HEX\"", NULL, NULL },
{ "+CUSD=1", NULL, NULL },
{ "+CGEREP=1,0", NULL, NULL },
{ "+CMGF=0", NULL, handleEndOfInit }, /* now is a goof time to send the current tme and timezone */
{ "%CPI=3", NULL, NULL },
{ "%CSTAT=1", NULL, NULL },
/* end of list */
{NULL, NULL, NULL}
};
#define REPLY(str) do { const char* s = (str); R(">> %s\n", quote(s)); return s; } while (0)
const char* amodem_send( AModem modem, const char* cmd )
{
const char* answer;
if ( modem->wait_sms != 0 ) {
modem->wait_sms = 0;
R( "SMS<< %s\n", quote(cmd) );
answer = handleSendSMSText( cmd, modem );
REPLY(answer);
}
/* everything that doesn't start with 'AT' is not a command, right ? */
if ( cmd[0] != 'A' || cmd[1] != 'T' || cmd[2] == 0 ) {
/* R( "-- %s\n", quote(cmd) ); */
return NULL;
}
R( "<< %s\n", quote(cmd) );
cmd += 2;
/* TODO: implement command handling */
{
int nn, found = 0;
for (nn = 0; ; nn++) {
const char* scmd = sDefaultResponses[nn].cmd;
if (!scmd) /* end of list */
break;
if (scmd[0] == '!') { /* prefix match */
int len = strlen(++scmd);
if ( !memcmp( scmd, cmd, len ) ) {
found = 1;
break;
}
} else { /* full match */
if ( !strcmp( scmd, cmd ) ) {
found = 1;
break;
}
}
}
if ( !found )
{
D( "** UNSUPPORTED COMMAND **\n" );
REPLY( "ERROR: UNSUPPORTED" );
}
else
{
const char* answer = sDefaultResponses[nn].answer;
ResponseHandler handler = sDefaultResponses[nn].handler;
if ( answer != NULL ) {
REPLY( amodem_printf( modem, "%s\rOK", answer ) );
}
if (handler == NULL) {
REPLY( "OK" );
}
answer = handler( cmd, modem );
if (answer == NULL)
REPLY( "OK" );
if ( !memcmp( answer, "> ", 2 ) ||
!memcmp( answer, "ERROR", 5 ) ||
!memcmp( answer, "+CME ERROR", 6 ) )
{
REPLY( answer );
}
if (answer != modem->out_buff)
REPLY( amodem_printf( modem, "%s\rOK", answer ) );
strcat( modem->out_buff, "\rOK" );
REPLY( answer );
}
}
}