From 55f4e4a5ec657a017e3bf75299ad71fd1c968dd3 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- telephony/Jamfile | 13 + telephony/android_modem.c | 1869 +++++++++++++++++++++++++++++++++++++++++++++ telephony/android_modem.h | 137 ++++ telephony/gsm.c | 1217 +++++++++++++++++++++++++++++ telephony/gsm.h | 196 +++++ telephony/modem_driver.c | 147 ++++ telephony/modem_driver.h | 29 + telephony/remote_call.c | 429 +++++++++++ telephony/remote_call.h | 55 ++ telephony/sim_card.c | 432 +++++++++++ telephony/sim_card.h | 54 ++ telephony/simulator.c | 195 +++++ telephony/sms.c | 1655 +++++++++++++++++++++++++++++++++++++++ telephony/sms.h | 117 +++ telephony/sysdeps.h | 80 ++ telephony/sysdeps_posix.c | 645 ++++++++++++++++ telephony/sysdeps_qemu.c | 376 +++++++++ telephony/test1.c | 49 ++ telephony/test2.c | 215 ++++++ 19 files changed, 7910 insertions(+) create mode 100644 telephony/Jamfile create mode 100644 telephony/android_modem.c create mode 100644 telephony/android_modem.h create mode 100644 telephony/gsm.c create mode 100644 telephony/gsm.h create mode 100644 telephony/modem_driver.c create mode 100644 telephony/modem_driver.h create mode 100644 telephony/remote_call.c create mode 100644 telephony/remote_call.h create mode 100644 telephony/sim_card.c create mode 100644 telephony/sim_card.h create mode 100644 telephony/simulator.c create mode 100644 telephony/sms.c create mode 100644 telephony/sms.h create mode 100644 telephony/sysdeps.h create mode 100644 telephony/sysdeps_posix.c create mode 100644 telephony/sysdeps_qemu.c create mode 100644 telephony/test1.c create mode 100644 telephony/test2.c (limited to 'telephony') diff --git a/telephony/Jamfile b/telephony/Jamfile new file mode 100644 index 0000000..0f2b7b9 --- /dev/null +++ b/telephony/Jamfile @@ -0,0 +1,13 @@ +Main telephony : telephony.c ; + +Library sysdeps : sysdeps_posix.c ; +Library android_modem : android_modem.c sim_card.c ; + +for prog in test1 test2 { + Main $(prog) : $(prog).c ; + LinkLibraries $(prog) : sysdeps ; +} + +Main simulator : simulator.c ; +LinkLibraries simulator : sysdeps android_modem ; + diff --git a/telephony/android_modem.c b/telephony/android_modem.c new file mode 100644 index 0000000..2321c87 --- /dev/null +++ b/telephony/android_modem.c @@ -0,0 +1,1869 @@ +/* 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.h" +#include "android_modem.h" +#include "android_timezone.h" +#include "sim_card.h" +#include "sysdeps.h" +#include +#include +#include +#include +#include +#include "sms.h" +#include "remote_call.h" +#include "vl.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) + +#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, "", 4 ); + p += 4; + } + else if (c == '\n') { + memcpy( p, "", 4 );strcat( p, "" ); + 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 AGprsNetworkType +android_parse_network_type( const char* speed ) +{ + const struct { const char* name; AGprsNetworkType type; } types[] = { + { "gprs", A_GPRS_NETWORK_GPRS }, + { "edge", A_GPRS_NETWORK_EDGE }, + { "umts", A_GPRS_NETWORK_UMTS }, + { "hsdpa", A_GPRS_NETWORK_UMTS }, /* not handled yet by Android GSM framework */ + { "full", A_GPRS_NETWORK_UMTS }, + { 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_GPRS_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 A_MODEM_SELF_SIZE 3 + +typedef struct AModemRec_ +{ + /* Radio state */ + ARadioState radio_state; + int area_code; + int cell_id; + int base_port; + + /* 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; + AGprsNetworkType 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]; + +} 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; +} + +static void +amodem_reset( AModem modem ) +{ + modem->radio_state = A_RADIO_STATE_OFF; + modem->wait_sms = 0; + + modem->oper_name_index = 2; + modem->oper_selection_mode = A_SELECTION_AUTOMATIC; + modem->oper_index = 0; + modem->oper_count = 2; + + 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_GPRS_NETWORK_UMTS; +} + +static AModemRec _android_modem[1]; + +AModem +amodem_create( int base_port, AModemUnsolFunc unsol_func, void* unsol_opaque ) +{ + AModem modem = _android_modem; + + amodem_reset( modem ); + modem->base_port = base_port; + modem->unsol_func = unsol_func; + modem->unsol_opaque = unsol_opaque; + + modem->sim = asimcard_create(); + + return modem; +} + +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, modem->cell_id ); + 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: + amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"\r", + modem->data_mode, modem->data_state, + modem->area_code, modem->cell_id, + modem->data_network ); + break; + + default: + ; + } +} + +void +amodem_set_data_network_type( AModem modem, AGprsNetworkType type ) +{ + modem->data_network = type; + amodem_set_data_registration( modem, modem->data_state ); +} + +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; +} + + +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"; +} + +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; +} + +static const char* +handleNetworkRegistration( const char* cmd, AModem modem ) +{ + if ( !memcmp( cmd, "+CREG", 5 ) ) { + cmd += 5; + if (cmd[0] == '?') { + return amodem_printf( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"", + modem->voice_mode, modem->voice_state, + modem->area_code, modem->cell_id ); + } 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] == '?') {\ + 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 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 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, number, sizeof(number) ); + if (numlen > sizeof(number)-1) + break; + + 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[10]; + SmsPDU* deliver; + int nn; + + sprintf( temp, "%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 ); +} + +/* 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 const char* +handleEndOfInit( const char* cmd, 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 + */ + return amodem_printf( modem, "%%CTZV: %02d/%02d/%02d:%02d:%02d:%02d%c%d:%d:%s", + (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* +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", 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] == '?') { + 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\",,0,0\r\n", + data->id, + data->type == A_DATA_IP ? "IP" : "PPP", + data->apn ); + } + return amodem_end_line(modem); + } else { + /* template is +CGDCONT=,"","",,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* +handleStartPDPContext( const char* cmd, AModem modem ) +{ + /* XXX: TODO: handle PDP start appropriately */ + /* for the moment, always return success */ +#if 0 + AVoiceCall vcall = amodem_alloc_call( modem ); + ACall call = (ACall) vcall; + if (call == NULL) { + return "ERROR: TOO MANY CALLS"; + } + call->id = 1; + call->dir = A_CALL_OUTBOUND; + /* XXX: it would be better to delay this */ + call->state = A_CALL_ACTIVE; + call->mode = A_CALL_DATA; + call->multi = 0; + strcpy( call->number, "012345" ); +#endif + 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 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; + + memcpy( call->number, cmd, len ); + call->number[len] = 0; + + 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 NULL; +} + + +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; +} + +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 }, + + /* see requestOrSendPDPContextList() */ + { "+CGACT?", "", 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", "+CSQ: 7,99", NULL }, /* XXX: TODO: implement variable signal strength and error rates */ + + /* 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 }, + + { "+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 }, + { "+CREG=2", NULL, handleNetworkRegistration }, + { "+CREG=1", NULL, handleNetworkRegistration }, + { "+CGREG=1", NULL, handleNetworkRegistration }, + { "+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 ); + } + } +} diff --git a/telephony/android_modem.h b/telephony/android_modem.h new file mode 100644 index 0000000..80d22c5 --- /dev/null +++ b/telephony/android_modem.h @@ -0,0 +1,137 @@ +/* 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. +*/ +#ifndef _android_modem_h_ +#define _android_modem_h_ + +#include "sim_card.h" +#include "sms.h" + +/** MODEM OBJECT + **/ +typedef struct AModemRec_* AModem; + +/* a function used by the modem to send unsolicited messages to the channel controller */ +typedef void (*AModemUnsolFunc)( void* opaque, const char* message ); + +extern AModem amodem_create( int base_port, AModemUnsolFunc unsol_func, void* unsol_opaque ); +extern void amodem_destroy( AModem modem ); + +/* send a command to the modem */ +extern const char* amodem_send( AModem modem, const char* cmd ); + +/* simulate the receipt on an incoming SMS message */ +extern void amodem_receive_sms( AModem modem, SmsPDU pdu ); + +/** RADIO STATE + **/ +typedef enum { + A_RADIO_STATE_OFF = 0, /* Radio explictly powered off (eg CFUN=0) */ + A_RADIO_STATE_ON, /* Radio on */ +} ARadioState; + +extern ARadioState amodem_get_radio_state( AModem modem ); +extern void amodem_set_radio_state( AModem modem, ARadioState state ); + +/** SIM CARD STATUS + **/ +extern ASimCard amodem_get_sim( AModem modem ); + +/** VOICE AND DATA NETWORK REGISTRATION + **/ + +/* 'stat' for +CREG/+CGREG commands */ +typedef enum { + A_REGISTRATION_UNREGISTERED = 0, + A_REGISTRATION_HOME = 1, + A_REGISTRATION_SEARCHING, + A_REGISTRATION_DENIED, + A_REGISTRATION_UNKNOWN, + A_REGISTRATION_ROAMING +} ARegistrationState; + +typedef enum { + A_GPRS_NETWORK_UNKNOWN = 0, + A_GPRS_NETWORK_GPRS, + A_GPRS_NETWORK_EDGE, + A_GPRS_NETWORK_UMTS +} AGprsNetworkType; + +extern ARegistrationState amodem_get_voice_registration( AModem modem ); +extern void amodem_set_voice_registration( AModem modem, ARegistrationState state ); + +extern ARegistrationState amodem_get_data_registration( AModem modem ); +extern void amodem_set_data_registration( AModem modem, ARegistrationState state ); +extern void amodem_set_data_network_type( AModem modem, AGprsNetworkType type ); + +extern AGprsNetworkType android_parse_network_type( const char* speed ); + + +/** OPERATOR NAMES + **/ +typedef enum { + A_NAME_LONG = 0, + A_NAME_SHORT, + A_NAME_NUMERIC, + A_NAME_MAX /* don't remove */ +} ANameIndex; + +/* retrieve operator name into user-provided buffer. returns number of writes written, including terminating zero */ +extern int amodem_get_operator_name ( AModem modem, ANameIndex index, char* buffer, int buffer_size ); + +/* reset one operator name from a user-provided buffer, set buffer_size to -1 for zero-terminated strings */ +extern void amodem_set_operator_name( AModem modem, ANameIndex index, const char* buffer, int buffer_size ); + +/** CALL STATES + **/ + +typedef enum { + A_CALL_OUTBOUND = 0, + A_CALL_INBOUND = 1, +} ACallDir; + +typedef enum { + A_CALL_ACTIVE = 0, + A_CALL_HELD, + A_CALL_DIALING, + A_CALL_ALERTING, + A_CALL_INCOMING, + A_CALL_WAITING +} ACallState; + +typedef enum { + A_CALL_VOICE = 0, + A_CALL_DATA, + A_CALL_FAX, + A_CALL_UNKNOWN = 9 +} ACallMode; + +#define A_CALL_NUMBER_MAX_SIZE 16 + +typedef struct { + int id; + ACallDir dir; + ACallState state; + ACallMode mode; + int multi; + char number[ A_CALL_NUMBER_MAX_SIZE+1 ]; +} ACallRec, *ACall; + +extern int amodem_get_call_count( AModem modem ); +extern ACall amodem_get_call( AModem modem, int index ); +extern ACall amodem_find_call_by_number( AModem modem, const char* number ); +extern int amodem_add_inbound_call( AModem modem, const char* number ); +extern int amodem_update_call( AModem modem, const char* number, ACallState state ); +extern int amodem_disconnect_call( AModem modem, const char* number ); + +/**/ + +#endif /* _android_modem_h_ */ diff --git a/telephony/gsm.c b/telephony/gsm.c new file mode 100644 index 0000000..89ff79e --- /dev/null +++ b/telephony/gsm.c @@ -0,0 +1,1217 @@ +/* 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 "gsm.h" +#include +#include + +/** UTILITIES + **/ +byte_t +gsm_int_to_bcdi( int value ) +{ + return (byte_t)((value / 10) | ((value % 10) << 4)); +} + +int +gsm_int_from_bcdi( byte_t val ) +{ + int ret = 0; + + if ((val & 0xf0) <= 0x90) + ret = (val >> 4); + + if ((val & 0x0f) <= 0x90) + ret |= (val % 0xf)*10; + + return ret; +} + + +static int +gsm_bcdi_to_ascii( cbytes_t bcd, int bcdlen, bytes_t dst ) +{ + static byte_t bcdichars[14] = "0123456789*#,N"; + + int result = 0; + int shift = 0; + + while (bcdlen > 0) { + int c = (bcd[0] >> shift) & 0xf; + + if (c == 0xf && bcdlen == 1) + break; + + if (c < 14) { + if (dst) dst[result] = bcdichars[c]; + result += 1; + } + bcdlen --; + shift += 4; + if (shift == 8) { + bcd++; + shift = 0; + } + } + return result; +} + + +static int +gsm_bcdi_from_ascii( cbytes_t ascii, int asciilen, bytes_t dst ) +{ + cbytes_t end = ascii + asciilen; + int result = 0; + int phase = 0x01; + + while (ascii < end) { + int c = *ascii++; + + if (c == '*') + c = 11; + else if (c == '#') + c = 12; + else if (c == ',') + c = 13; + else if (c == 'N') + c = 14; + else { + c -= '0'; + if ((unsigned)c >= 10) + break; + } + phase = (phase << 4) | c; + if (phase & 0x100) { + if (dst) dst[result] = (byte_t) phase; + result += 1; + phase = 0x01; + } + } + if (phase != 0x01) { + if (dst) dst[result] = (byte_t)( phase | 0xf0 ); + result += 1; + } + return result; +} + + +int +gsm_hexchar_to_int( char c ) +{ + if ((unsigned)(c - '0') < 10) + return c - '0'; + if ((unsigned)(c - 'a') < 6) + return 10 + (c - 'a'); + if ((unsigned)(c - 'A') < 6) + return 10 + (c - 'A'); + return -1; +} + +int +gsm_hexchar_to_int0( char c ) +{ + int ret = gsm_hexchar_to_int(c); + + return (ret < 0) ? 0 : ret; +} + +int +gsm_hex2_to_byte( const char* hex ) +{ + int hi = gsm_hexchar_to_int(hex[0]); + int lo = gsm_hexchar_to_int(hex[1]); + + if (hi < 0 || lo < 0) + return -1; + + return ( (hi << 4) | lo ); +} + +int +gsm_hex4_to_short( const char* hex ) +{ + int hi = gsm_hex2_to_byte(hex); + int lo = gsm_hex2_to_byte(hex+2); + + if (hi < 0 || lo < 0) + return -1; + + return ((hi << 8) | lo); +} + +int +gsm_hex2_to_byte0( const char* hex ) +{ + int hi = gsm_hexchar_to_int0(hex[0]); + int lo = gsm_hexchar_to_int0(hex[1]); + + return (byte_t)( (hi << 4) | lo ); +} + +void +gsm_hex_from_byte( char* hex, int val ) +{ + static const char hexdigits[] = "0123456789abcdef"; + + hex[0] = hexdigits[(val >> 4) & 15]; + hex[1] = hexdigits[val & 15]; +} + +void +gsm_hex_from_short( char* hex, int val ) +{ + gsm_hex_from_byte( hex, (val >> 8) ); + gsm_hex_from_byte( hex+2, val ); +} + + + +/** HEX + **/ +void +gsm_hex_to_bytes0( cbytes_t hex, int hexlen, bytes_t dst ) +{ + int nn; + + for (nn = 0; nn < hexlen/2; nn++ ) { + dst[nn] = (byte_t) gsm_hex2_to_byte0( (const char*)hex+2*nn ); + } + if (hexlen & 1) { + dst[nn] = gsm_hexchar_to_int0( hex[2*nn] ) << 4; + } +} + +int +gsm_hex_to_bytes( cbytes_t hex, int hexlen, bytes_t dst ) +{ + int nn; + + if (hexlen & 1) /* must be even */ + return -1; + + for (nn = 0; nn < hexlen/2; nn++ ) { + int c = gsm_hex2_to_byte( (const char*)hex+2*nn ); + if (c < 0) return -1; + dst[nn] = (byte_t) c; + } + return hexlen/2; +} + +void +gsm_hex_from_bytes( char* hex, cbytes_t src, int srclen ) +{ + int nn; + + for (nn = 0; nn < srclen; nn++) { + gsm_hex_from_byte( hex + 2*nn, src[nn] ); + } +} + +/** ROPES + **/ + +void +gsm_rope_init( GsmRope rope ) +{ + rope->data = NULL; + rope->pos = 0; + rope->max = 0; + rope->error = 0; +} + +void +gsm_rope_init_alloc( GsmRope rope, int count ) +{ + rope->data = rope->data0; + rope->pos = 0; + rope->max = sizeof(rope->data0); + rope->error = 0; + + if (count > 0) { + rope->data = calloc( count, 1 ); + rope->max = count; + + if (rope->data == NULL) { + rope->error = 1; + rope->max = 0; + } + } +} + +int +gsm_rope_done( GsmRope rope ) +{ + int result = rope->error; + + if (rope->data && rope->data != rope->data0) + free(rope->data); + + rope->data = NULL; + rope->pos = 0; + rope->max = 0; + rope->error = 0; + + return result; +} + + +bytes_t +gsm_rope_done_acquire( GsmRope rope, int *psize ) +{ + bytes_t result = rope->data; + + *psize = rope->pos; + if (result == rope->data0) { + result = malloc( rope->pos ); + if (result != NULL) + memcpy( result, rope->data, rope->pos ); + } + return result; +} + + +int +gsm_rope_ensure( GsmRope rope, int new_count ) +{ + if (rope->data != NULL) { + int old_max = rope->max; + bytes_t old_data = rope->data == rope->data0 ? NULL : rope->data; + int new_max = old_max; + bytes_t new_data; + + while (new_max < new_count) { + new_max += (new_max >> 1) + 4; + } + new_data = realloc( old_data, new_max ); + if (new_data == NULL) { + rope->error = 1; + return -1; + } + rope->data = new_data; + rope->max = new_max; + } else { + rope->max = new_count; + } + return 0; +} + +static int +gsm_rope_can_grow( GsmRope rope, int count ) +{ + if (!rope->data || rope->error) + return 0; + + if (rope->pos + count > rope->max) + { + if (rope->data == NULL) + rope->max = rope->pos + count; + + else if (rope->error || + gsm_rope_ensure( rope, rope->pos + count ) < 0) + return 0; + } + return 1; +} + +void +gsm_rope_add_c( GsmRope rope, char c ) +{ + if (gsm_rope_can_grow(rope, 1)) { + rope->data[ rope->pos ] = (byte_t) c; + } + rope->pos += 1; +} + +void +gsm_rope_add( GsmRope rope, const void* buf, int buflen ) +{ + if (gsm_rope_can_grow(rope, buflen)) { + memcpy( rope->data + rope->pos, (const char*)buf, buflen ); + } + rope->pos += buflen; +} + +void* +gsm_rope_reserve( GsmRope rope, int count ) +{ + void* result = NULL; + + if (gsm_rope_can_grow(rope, count)) + { + if (rope->data != NULL) + result = rope->data + rope->pos; + } + rope->pos += count; + + return result; +} + +/* skip a given number of Unicode characters in a utf-8 byte string */ +cbytes_t +utf8_skip( cbytes_t utf8, + cbytes_t utf8end, + int count) +{ + cbytes_t p = utf8; + cbytes_t end = utf8end; + + for ( ; count > 0; count-- ) { + int c; + + if (p >= end) + break; + + c = *p++; + if (c > 128) { + while (p < end && (p[0] & 0xc0) == 0x80) + p++; + } + } + return p; +} + + +static __inline__ int +utf8_next( cbytes_t *pp, cbytes_t end ) +{ + cbytes_t p = *pp; + int result = -1; + + if (p < end) { + int c= *p++; + if (c >= 128) { + if ((c & 0xe0) == 0xc0) + c &= 0x1f; + else if ((c & 0xf0) == 0xe0) + c &= 0x0f; + else + c &= 0x07; + + while (p < end && (p[0] & 0xc0) == 0x80) { + c = (c << 6) | (p[0] & 0x3f); + p ++; + } + } + result = c; + *pp = p; + } + return result; +} + + +__inline__ int +utf8_write( bytes_t utf8, int offset, int v ) +{ + int result; + + if (v < 128) { + result = 1; + if (utf8) + utf8[offset] = (byte_t) v; + } else if (v < 0x800) { + result = 2; + if (utf8) { + utf8[offset+0] = (byte_t)( 0xc0 | (v >> 6) ); + utf8[offset+1] = (byte_t)( 0x80 | (v & 0x3f) ); + } + } else if (v < 0x10000) { + result = 3; + if (utf8) { + utf8[offset+0] = (byte_t)( 0xe0 | (v >> 12) ); + utf8[offset+1] = (byte_t)( 0x80 | ((v >> 6) & 0x3f) ); + utf8[offset+2] = (byte_t)( 0x80 | (v & 0x3f) ); + } + } else { + result = 4; + if (utf8) { + utf8[offset+0] = (byte_t)( 0xf0 | ((v >> 18) & 0x7) ); + utf8[offset+1] = (byte_t)( 0x80 | ((v >> 12) & 0x3f) ); + utf8[offset+2] = (byte_t)( 0x80 | ((v >> 6) & 0x3f) ); + utf8[offset+3] = (byte_t)( 0x80 | (v & 0x3f) ); + } + } + return result; +} + +static __inline__ int +ucs2_write( bytes_t ucs2, int offset, int v ) +{ + if (ucs2) { + ucs2[offset+0] = (byte_t) (v >> 8); + ucs2[offset+1] = (byte_t) (v); + } + return 2; +} + +int +utf8_check( cbytes_t p, int utf8len ) +{ + cbytes_t end = p + utf8len; + int result = 0; + + if (p) { + while (p < end) { + int c = *p++; + if (c >= 128) { + int len; + if ((c & 0xe0) == 0xc0) { + len = 1; + } + else if ((c & 0xf0) == 0xe0) { + len = 2; + } + else if ((c & 0xf8) == 0xf0) { + len = 3; + } + else + goto Exit; /* malformed utf-8 */ + + if (p+len > end) /* string too short */ + goto Exit; + + for ( ; len > 0; len--, p++ ) { + if ((p[0] & 0xc0) != 0x80) + goto Exit; + } + } + } + result = 1; + } +Exit: + return result; +} + +/** UCS2 to UTF8 + **/ + +/* convert a UCS2 string into a UTF8 byte string, assumes 'buf' is correctly sized */ +int +ucs2_to_utf8( cbytes_t ucs2, + int ucs2len, + bytes_t buf ) +{ + int nn; + int result = 0; + + for (nn = 0; nn < ucs2len; ucs2 += 2, nn++) { + int c= (ucs2[0] << 8) | ucs2[1]; + result += utf8_write(buf, result, c); + } + return result; +} + +/* count the number of UCS2 chars contained in a utf8 byte string */ +int +utf8_to_ucs2( cbytes_t utf8, + int utf8len, + bytes_t ucs2 ) +{ + cbytes_t p = utf8; + cbytes_t end = p + utf8len; + int result = 0; + + while (p < end) { + int c = utf8_next(&p, end); + + if (c < 0) + break; + + result += ucs2_write(ucs2, result, c); + } + return result/2; +} + + + +/** GSM ALPHABET + **/ + +#define GSM_7BITS_ESCAPE 0x1b +#define GSM_7BITS_UNKNOWN 0 + +static const unsigned short gsm7bits_to_unicode[128] = { + '@', 0xa3, '$', 0xa5, 0xe8, 0xe9, 0xf9, 0xec, 0xf2, 0xc7, '\n', 0xd8, 0xf8, '\r', 0xc5, 0xe5, +0x394, '_',0x3a6,0x393,0x39b,0x3a9,0x3a0,0x3a8,0x3a3,0x398,0x39e, 0, 0xc6, 0xe6, 0xdf, 0xc9, + ' ', '!', '"', '#', 0xa4, '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', + 0xa1, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0xc4, 0xd6,0x147, 0xdc, 0xa7, + 0xbf, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0xe4, 0xf6, 0xf1, 0xfc, 0xe0, +}; + +static const unsigned short gsm7bits_extend_to_unicode[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\f', 0, 0, 0, 0, 0, + 0, 0, 0, 0, '^', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, '{', '}', 0, 0, 0, 0, 0,'\\', + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '[', '~', ']', 0, + '|', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,0x20ac, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + +static int +unichar_to_gsm7( int unicode ) +{ + int nn; + for (nn = 0; nn < 128; nn++) { + if (gsm7bits_to_unicode[nn] == unicode) { + return nn; + } + } + return -1; +} + +static int +unichar_to_gsm7_extend( int unichar ) +{ + int nn; + for (nn = 0; nn < 128; nn++) { + if (gsm7bits_extend_to_unicode[nn] == unichar) { + return nn; + } + } + return -1; +} + + +/* return the number of septets needed to encode a unicode charcode */ +static int +unichar_to_gsm7_count( int unicode ) +{ + int nn; + + nn = unichar_to_gsm7(unicode); + if (nn >= 0) + return 1; + + nn = unichar_to_gsm7_extend(unicode); + if (nn >= 0) + return 2; + + return 0; +} + + +cbytes_t +utf8_skip_gsm7( cbytes_t utf8, cbytes_t utf8end, int gsm7len ) +{ + cbytes_t p = utf8; + cbytes_t end = utf8end; + + while (gsm7len >0) { + cbytes_t q = p; + int c = utf8_next( &q, end ); + int len; + + if (c < 0) + break; + + len = unichar_to_gsm7_count( c ); + if (len == 0) /* unknown chars are replaced by spaces */ + len = 1; + + if (len > gsm7len) + break; + + gsm7len -= len; + p = q; + } + return p; +} + + +int +utf8_check_gsm7( cbytes_t utf8, + int utf8len ) +{ + cbytes_t utf8end = utf8 + utf8len; + + while (utf8 < utf8end) { + int c = utf8_next( &utf8, utf8end ); + if (unichar_to_gsm7_count(c) == 0) + return 0; + } + return 1; +} + + +int +utf8_from_gsm7( cbytes_t src, + int septet_offset, + int septet_count, + bytes_t utf8 ) +{ + int shift = (septet_offset & 7); + int escaped = 0; + int result = 0; + + src += (septet_offset >> 3); + for ( ; septet_count > 0; septet_count-- ) + { + int c = (src[0] >> shift) & 0x7f; + int v; + + if (shift > 1) { + c = ((src[1] << (8-shift)) | c) & 0x7f; + } + + if (escaped) { + v = gsm7bits_extend_to_unicode[c]; + } else if (c == GSM_7BITS_ESCAPE) { + escaped = 1; + goto NextSeptet; + } else { + v = gsm7bits_to_unicode[c]; + } + + result += utf8_write( utf8, result, v ); + + NextSeptet: + shift += 7; + if (shift >= 8) { + shift -= 8; + src += 1; + } + } + return result; +} + + +int +utf8_from_gsm8( cbytes_t src, int count, bytes_t utf8 ) +{ + int result = 0; + int escaped = 0; + + + for ( ; count > 0; count-- ) + { + int c = *src++; + + if (c == 0xff) + break; + + if (c == GSM_7BITS_ESCAPE) { + if (escaped) { /* two escape characters => one space */ + c = 0x20; + escaped = 0; + } else { + escaped = 1; + continue; + } + } + else + { + if (c >= 0x80) { + c = 0x20; + escaped = 0; + } else if (escaped) { + c = gsm7bits_extend_to_unicode[c]; + } else + c = gsm7bits_to_unicode[c]; + } + + result += utf8_write( utf8, result, c ); + } + return result; +} + +/* convert a GSM 7-bit message into a unicode character array + * the 'dst' array must contain at least 160 chars. the function + * returns the number of characters decoded + * + * assumes the 'dst' array has at least septet_count items, returns the + * number of unichars really written + */ +int +ucs2_from_gsm7( bytes_t ucs2, + cbytes_t src, + int septet_offset, + int septet_count ) +{ + const unsigned char* p = src + (septet_offset >> 3); + int shift = (septet_offset & 7); + int escaped = 0; + int result = 0; + + for ( ; septet_count > 0; septet_count-- ) + { + unsigned val = (p[0] >> shift) & 0x7f; + + if (shift > 1) + val = (val | (p[1] << (8-shift))) & 0x7f; + + if (escaped) { + int c = gsm7bits_to_unicode[val]; + + result += ucs2_write(ucs2, result, c); + escaped = 0; + } + else if (val == GSM_7BITS_ESCAPE) { + escaped = 1; + } + else { + val = gsm7bits_extend_to_unicode[val]; + if (val == 0) + val = 0x20; + + result += ucs2_write( ucs2, result, val ); + } + } + return result/2; +} + + +/* count the number of septets required to write a utf8 string */ +static int +utf8_to_gsm7_count( cbytes_t utf8, int utf8len ) +{ + cbytes_t utf8end = utf8 + utf8len; + int result = 0; + + while ( utf8 < utf8end ) { + int len; + int c = utf8_next( &utf8, utf8end ); + + if (c < 0) + break; + + len = unichar_to_gsm7_count(c); + if (len == 0) /* replace non-representables with space */ + len = 1; + + result += len; + } + return result; +} + +typedef struct { + bytes_t dst; + unsigned pad; + int bits; + int offset; +} BWriterRec, *BWriter; + +static void +bwriter_init( BWriter writer, bytes_t dst, int start ) +{ + int shift = start & 7; + + writer->dst = dst + (start >> 3); + writer->pad = 0; + writer->bits = shift; + writer->offset = start; + + if (shift > 0) { + writer->pad = writer->dst[0] & ~(0xFF << shift); + } +} + +static void +bwriter_add7( BWriter writer, unsigned value ) +{ + writer->pad |= (unsigned)(value << writer->bits); + writer->bits += 7; + if (writer->bits >= 8) { + writer->dst[0] = (byte_t)writer->pad; + writer->bits -= 8; + writer->pad >>= 8; + writer->dst += 1; + } + writer->offset += 7; +} + +static int +bwriter_done( BWriter writer ) +{ + if (writer->bits > 0) { + writer->dst[0] = (byte_t)writer->pad; + writer->pad = 0; + writer->bits = 0; + writer->dst += 1; + } + return writer->offset; +} + +/* convert a utf8 string to a gsm7 byte string - return the number of septets written */ +int +utf8_to_gsm7( cbytes_t utf8, int utf8len, bytes_t dst, int offset ) +{ + const unsigned char* utf8end = utf8 + utf8len; + BWriterRec writer[1]; + + if (dst == NULL) + return utf8_to_gsm7_count(utf8, utf8len); + + bwriter_init( writer, dst, offset ); + while ( utf8 < utf8end ) { + int c = utf8_next( &utf8, utf8end ); + int nn; + + if (c < 0) + break; + + nn = unichar_to_gsm7(c); + if (nn >= 0) { + bwriter_add7( writer, nn ); + continue; + } + + nn = unichar_to_gsm7_extend(c); + if (nn >= 0) { + bwriter_add7( writer, GSM_7BITS_ESCAPE ); + bwriter_add7( writer, nn ); + continue; + } + + /* unknown => replaced by space */ + bwriter_add7( writer, 0x20 ); + } + return bwriter_done( writer ); +} + + +int +utf8_to_gsm8( cbytes_t utf8, int utf8len, bytes_t dst ) +{ + const unsigned char* utf8end = utf8 + utf8len; + int result = 0; + + while ( utf8 < utf8end ) { + int c = utf8_next( &utf8, utf8end ); + int nn; + + if (c < 0) + break; + + nn = unichar_to_gsm7(c); + if (nn >= 0) { + if (dst) + dst[result] = (byte_t)nn; + result += 1; + continue; + } + + nn = unichar_to_gsm7_extend(c); + if (nn >= 0) { + if (dst) { + dst[result+0] = (byte_t) GSM_7BITS_ESCAPE; + dst[result+1] = (byte_t) nn; + } + result += 2; + continue; + } + + /* unknown => space */ + if (dst) + dst[result] = 0x20; + result += 1; + } + return result; +} + + +int +ucs2_to_gsm7( cbytes_t ucs2, int ucs2len, bytes_t dst, int offset ) +{ + const unsigned char* ucs2end = ucs2 + ucs2len*2; + BWriterRec writer[1]; + + bwriter_init( writer, dst, offset ); + while ( ucs2 < ucs2end ) { + int c = *ucs2++; + int nn; + + for (nn = 0; nn < 128; nn++) { + if ( gsm7bits_to_unicode[nn] == c ) { + bwriter_add7( writer, nn ); + goto NextUnicode; + } + } + for (nn = 0; nn < 128; nn++) { + if ( gsm7bits_extend_to_unicode[nn] == c ) { + bwriter_add7( writer, GSM_7BITS_ESCAPE ); + bwriter_add7( writer, nn ); + goto NextUnicode; + } + } + + /* unknown */ + bwriter_add7( writer, 0x20 ); + + NextUnicode: + ; + } + return bwriter_done( writer ); +} + + +int +ucs2_to_gsm8( cbytes_t ucs2, int ucs2len, bytes_t dst ) +{ + const unsigned char* ucs2end = ucs2 + ucs2len*2; + bytes_t dst0 = dst; + + while ( ucs2 < ucs2end ) { + int c = *ucs2++; + int nn; + + for (nn = 0; nn < 128; nn++) { + if ( gsm7bits_to_unicode[nn] == c ) { + *dst++ = (byte_t)nn; + goto NextUnicode; + } + } + for (nn = 0; nn < 128; nn++) { + if ( gsm7bits_extend_to_unicode[nn] == c ) { + dst[0] = (byte_t) GSM_7BITS_ESCAPE; + dst[1] = (byte_t) nn; + dst += 2; + goto NextUnicode; + } + } + + /* unknown */ + *dst++ = 0x20; + + NextUnicode: + ; + } + return (dst - dst0); +} + +int +gsm_bcdnum_to_ascii( cbytes_t bcd, int count, bytes_t dst ) +{ + int result = 0; + int shift = 0; + + while (count > 0) { + int c = (bcd[0] >> shift) & 0xf; + + if (c == 15 && count == 1) /* ignore trailing 0xf */ + break; + + if (c >= 14) + c = 0; + + if (dst) dst[result] = "0123456789*#,N"[c]; + result += 1; + + shift += 4; + if (shift == 8) { + shift = 0; + bcd += 1; + } + } + return result; +} + + +int +gsm_bcdnum_from_ascii( cbytes_t ascii, int asciilen, bytes_t dst ) +{ + cbytes_t end = ascii + asciilen; + int result = 0; + int phase = 0x01; + + while (ascii < end) { + int c = *ascii++; + + if (c == '*') + c = 10; + else if (c == '#') + c = 11; + else if (c == ',') + c = 12; + else if (c == 'N') + c = 13; + else { + c -= '0'; + if ((unsigned)c >= 10U) + return -1; + } + phase = (phase << 4) | c; + result += 1; + if (phase & 0x100) { + if (dst) dst[result/2] = (byte_t) phase; + phase = 0x01; + } + } + + if (result & 1) { + if (dst) dst[result/2] = (byte_t)(phase | 0xf0); + } + return result; +} + +/** ADN: Abbreviated Dialing Number + **/ + +#define ADN_FOOTER_SIZE 14 +#define ADN_OFFSET_NUMBER_LENGTH 0 +#define ADN_OFFSET_TON_NPI 1 +#define ADN_OFFSET_NUMBER_START 2 +#define ADN_OFFSET_NUMBER_END 11 +#define ADN_OFFSET_CAPABILITY_ID 12 +#define ADN_OFFSET_EXTENSION_ID 13 + +/* see 10.5.1 of 3GPP 51.011 */ +static int +sim_adn_alpha_to_utf8( cbytes_t alpha, cbytes_t end, bytes_t dst ) +{ + int result = 0; + + /* ignore trailing 0xff */ + while (alpha < end && end[-1] == 0xff) + end--; + + if (alpha >= end) + return 0; + + if (alpha[0] == 0x80) { /* UCS/2 source encoding */ + alpha += 1; + result = ucs2_to_utf8( alpha, (end-alpha)/2, dst ); + } + else + { + int is_ucs2 = 0; + int len = 0, base = 0; + + if (alpha+3 <= end && alpha[0] == 0x81) { + is_ucs2 = 1; + len = alpha[1]; + base = alpha[2] << 7; + alpha += 3; + if (len > end-alpha) + len = end-alpha; + } else if (alpha+4 <= end && alpha[0] == 0x82) { + is_ucs2 = 1; + len = alpha[1]; + base = (alpha[2] << 8) | alpha[3]; + alpha += 4; + if (len > end-alpha) + len = end-alpha; + } + + if (is_ucs2) { + end = alpha + len; + while (alpha < end) { + int c = alpha[0]; + if (c >= 0x80) { + result += utf8_write(dst, result, base + (c & 0x7f)); + alpha += 1; + } else { + /* GSM character set */ + int count; + for (count = 0; alpha+count < end && alpha[count] < 128; count++) + ; + result += utf8_from_gsm8(alpha, count, (dst ? dst+result : NULL)); + alpha += count; + } + } + } + else { + result = utf8_from_gsm8(alpha, end-alpha, dst); + } + } + return result; +} + +static int +sim_adn_alpha_from_utf8( cbytes_t utf8, int utf8len, bytes_t dst ) +{ + int result = 0; + + if (utf8_check_gsm7(utf8, utf8len)) { + /* GSM 7-bit compatible, encode directly as 8-bit string */ + result = utf8_to_gsm8(utf8, utf8len, dst); + } else { + /* otherwise, simply try UCS-2 encoding, nothing more serious at the moment */ + if (dst) { + dst[0] = 0x80; + } + result = 1 + utf8_to_ucs2(utf8, utf8len, dst ? (dst+1) : NULL)*2; + } + return result; +} + +int +sim_adn_record_from_bytes( SimAdnRecord rec, cbytes_t data, int len ) +{ + cbytes_t end = data + len; + cbytes_t footer = end - ADN_FOOTER_SIZE; + int num_len; + + rec->adn.alpha[0] = 0; + rec->adn.number[0] = 0; + rec->ext_record = 0xff; + + if (len < ADN_FOOTER_SIZE) + return -1; + + /* alpha is optional */ + if (len > ADN_FOOTER_SIZE) { + cbytes_t dataend = data + len - ADN_FOOTER_SIZE; + int count = sim_adn_alpha_to_utf8(data, dataend, NULL); + + if (count > sizeof(rec->adn.alpha)-1) /* too long */ + return -1; + + sim_adn_alpha_to_utf8(data, dataend, rec->adn.alpha); + rec->adn.alpha[count] = 0; + } + + num_len = footer[ADN_OFFSET_NUMBER_LENGTH]; + if (num_len > 11) + return -1; + + /* decode TON and number to ASCII, NOTE: this is lossy !! */ + { + int ton = footer[ADN_OFFSET_TON_NPI]; + bytes_t number = (bytes_t) rec->adn.number; + int len = sizeof(rec->adn.number)-1; + int count; + + if (ton != 0x81 && ton != 0x91) + return -1; + + if (ton == 0x91) { + *number++ = '+'; + len -= 1; + } + + count = gsm_bcdnum_to_ascii( footer + ADN_OFFSET_NUMBER_START, + num_len*2, number ); + number[count] = 0; + } + return 0; +} + +int +sim_adn_record_to_bytes( SimAdnRecord rec, bytes_t data, int datalen ) +{ + bytes_t end = data + datalen; + bytes_t footer = end - ADN_FOOTER_SIZE; + int ton = 0x81; + cbytes_t number = (cbytes_t) rec->adn.number; + + if (number[0] == '+') { + ton = 0x91; + number += 1; + } + footer[0] = (strlen((const char*)number)+1)/2 + 1; + /* XXXX: TODO */ + return 0; +} diff --git a/telephony/gsm.h b/telephony/gsm.h new file mode 100644 index 0000000..f799dea --- /dev/null +++ b/telephony/gsm.h @@ -0,0 +1,196 @@ +/* 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. +*/ +#ifndef _android_gsm_h +#define _android_gsm_h + +/** USEFUL TYPES + **/ + +typedef unsigned char byte_t; +typedef byte_t* bytes_t; +typedef const byte_t* cbytes_t; + +/** BCD + **/ + +/* convert a 8-bit value into the corresponding nibble-bcd byte */ +extern byte_t gsm_int_to_bcdi( int value ); + +/* convert a nibble-bcd byte into an int, invalid nibbles are silently converted to 0 */ +extern int gsm_int_from_bcdi( byte_t value ); + +/** HEX + **/ + +/* try to convert a hex string into a byte string, assumes 'dst' is properly sized, and hexlen is even. + * returns the number of bytes on exit, or -1 in case of badly formatted data */ +extern int gsm_hex_to_bytes ( cbytes_t hex, int hexlen, bytes_t dst ); + +/* convert a hex string into a byte string, assumes 'dst' is properly sized, and hexlen is even. + * no checks are performed */ +extern void gsm_hex_to_bytes0 ( cbytes_t hex, int hexlen, bytes_t dst ); + +/* convert a byte string into a hex string, assumes 'hex' is properly sized */ +extern void gsm_hex_from_bytes( char* hex, cbytes_t src, int srclen ); + +/* convert a hexchar to an int, returns -1 on error */ +extern int gsm_hexchar_to_int( char c ); + +/* convert a hexchar to an int, returns 0 on error */ +extern int gsm_hexchar_to_int0( char c ); + +/* convert a 2-char hex value into an int, returns -1 on error */ +extern int gsm_hex2_to_byte( const char* hex ); + +/* convert a 2-char hex value into an int, returns 0 on error */ +extern int gsm_hex2_to_byte0( const char* hex ); + +/* convert a 4-char hex value into an int, returns -1 on error */ +extern int gsm_hex4_to_short( const char* hex ); + +/* convert a 4-char hex value into an int, returns 0 on error */ +extern int gsm_hex4_to_short0( const char* hex ); + +/* write a byte to a 2-byte hex string */ +extern void gsm_hex_from_byte( char* hex, int val ); + +extern void gsm_hex_from_short( char* hex, int val ); + +/** UTF-8 and GSM Alphabet + **/ + +/* check that a given utf8 string is well-formed, returns 1 on success, 0 otherwise */ +extern int utf8_check( cbytes_t utf8, int utf8len ); + +/* check that all characters in a given utf8 string can be encoded into the GSM alphabet. + returns 1 if TRUE, 0 otherwise */ +extern int utf8_check_gsm7( cbytes_t utf8, int utf8len ); + +/* try to skip enough utf8 characters to generate gsm7len GSM septets */ +extern cbytes_t utf8_skip_gsm7( cbytes_t utf8, cbytes_t utf8end, int gsm7len ); + +/* convert a utf-8 string into a GSM septet string, assumes 'dst' is NULL or is properly sized, + and that all characters are representable. 'offset' is the starting bit offset in 'dst'. + non-representable characters are replaced by spaces. + returns the number of septets, */ +extern int utf8_to_gsm7( cbytes_t utf8, int utf8len, bytes_t dst, int offset ); + +/* convert a utf8 string into an array of 8-bit unpacked GSM septets, + * assumes 'dst' is NULL or is properly sized, returns the number of GSM bytes */ +extern int utf8_to_gsm8( cbytes_t utf8, int utf8len, bytes_t dst ); + +/* convert a GSM septets string into a utf-8 byte string. assumes that 'utf8' is NULL or properly + sized. 'offset' is the starting bit offset in 'src', 'count' is the number of input septets. + return the number of utf8 bytes. */ +extern int utf8_from_gsm7( cbytes_t src, int offset, int count, bytes_t utf8 ); + +/* convert an unpacked 8-bit GSM septets string into a utf-8 byte string. assumes that 'utf8' + is NULL or properly sized. 'count' is the number of input bytes. + returns the number of utf8 bytes */ +extern int utf8_from_gsm8( cbytes_t src, int count, bytes_t utf8 ); + + +/** UCS-2 and GSM Alphabet + ** + ** Note that here, 'ucs2' really refers to non-aligned UCS2-BE, as used by the GSM standard + **/ + +/* check that all characters in a given ucs2 string can be encoded into the GSM alphabet. + returns 1 if TRUE, 0 otherwise */ +extern int ucs2_check_gsm7( cbytes_t ucs2, int ucs2len ); + +/* convert a ucs2 string into a GSM septet string, assumes 'dst' is NULL or properly sized, + 'offset' is the starting bit offset in 'dst'. non-representable characters are replaced + by spaces. returns the number of septets */ +extern int ucs2_to_gsm7( cbytes_t ucs2, int ucs2len, bytes_t dst, int offset ); + +/* convert a ucs2 string into a GSM septet string, assumes 'dst' is NULL or properly sized, + non-representable characters are replaced by spaces. returns the number of bytes */ +extern int ucs2_to_gsm8( cbytes_t ucs2, int ucs2len, bytes_t dst ); + +/* convert a GSM septets string into a ucs2 string. assumes that 'ucs2' is NULL or + properly sized. 'offset' is the starting bit offset in 'src', 'count' is the number + of input septets. return the number of ucs2 characters (not bytes) */ +extern int ucs2_from_gsm7( bytes_t ucs2, cbytes_t src, int offset, int count ); + +/* convert an 8-bit unpacked GSM septets string into a ucs2 string. assumes that 'ucs2' + is NULL or properly sized. 'count' is the number of input septets. return the number + of ucs2 characters (not bytes) */ +extern int ucs2_from_gsm8( bytes_t ucs2, cbytes_t src, int count ); + + +/** UCS2 to/from UTF8 + **/ + +/* convert a ucs2 string into a utf8 byte string, assumes 'utf8' NULL or properly sized. + returns the number of utf8 bytes*/ +extern int ucs2_to_utf8( cbytes_t ucs2, int ucs2len, bytes_t utf8 ); + +/* convert a utf8 byte string into a ucs2 string, assumes 'ucs2' NULL or properly sized. + returns the number of ucs2 chars */ +extern int utf8_to_ucs2( cbytes_t utf8, int utf8len, bytes_t ucs2 ); + +/* try to skip a given number of characters in a utf-8 byte string, return new position */ +extern cbytes_t utf8_skip( cbytes_t utf8, cbytes_t utf8end, int count); + +/** Dial Numbers: TON byte + 'count' bcd numbers + **/ + +/* convert a bcd-coded GSM dial number into an ASCII string (not zero-terminated) + assumes 'dst' is NULL or properly sized, returns 0 in case of success, -1 in case of error. + 'num_digits' is the number of digits, not input bytes. a trailing 0xf0 is ignored automatically + return the number of ASCII chars */ +extern int gsm_bcdnum_to_ascii ( cbytes_t bcd, int num_digits, bytes_t dst ); + +/* convert an ASCII dial-number into a bcd-coded string, returns the number of 4-bit nibbles written, */ +extern int gsm_bcdnum_from_ascii( cbytes_t ascii, int asciilen, bytes_t dst ); + +/** ADN: Abbreviated Dialing Numbers + **/ +#define SIM_ADN_MAX_ALPHA 20 /* maximum number of characters in ADN alpha tag */ +#define SIM_ADN_MAX_NUMBER 20 /* maximum digits in ADN number */ + +typedef struct { + byte_t alpha [ SIM_ADN_MAX_ALPHA*3+1 ]; /* alpha tag in zero-terminated utf-8 */ + char number[ SIM_ADN_MAX_NUMBER+1 ]; /* dialing number in zero-terminated ASCII */ +} +SimAdnRec, *SimAdn; + +typedef struct { + SimAdnRec adn; + byte_t ext_record; /* 0 or 0xFF means no extension */ +} +SimAdnRecordRec, *SimAdnRecord; + +extern int sim_adn_record_from_bytes( SimAdnRecord rec, cbytes_t data, int datalen ); +extern int sim_adn_record_to_bytes ( SimAdnRecord rec, bytes_t data, int datalen ); + +/** ROPES + **/ + +typedef struct { + bytes_t data; + int max; + int pos; + int error; + unsigned char data0[16]; +} GsmRopeRec, *GsmRope; + +extern void gsm_rope_init( GsmRope rope ); +extern void gsm_rope_init_alloc( GsmRope rope, int alloc ); +extern int gsm_rope_done( GsmRope rope ); +extern bytes_t gsm_rope_done_acquire( GsmRope rope, int *psize ); +extern void gsm_rope_add_c( GsmRope rope, char c ); +extern void gsm_rope_add( GsmRope rope, const void* str, int len ); +extern void* gsm_rope_reserve( GsmRope rope, int len ); + +#endif /* _android_gsm_h */ diff --git a/telephony/modem_driver.c b/telephony/modem_driver.c new file mode 100644 index 0000000..7de475f --- /dev/null +++ b/telephony/modem_driver.c @@ -0,0 +1,147 @@ +/* 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. +*/ +/* implement the modem character device for Android within the QEMU event loop. + * it communicates through a serial port with "rild" (Radio Interface Layer Daemon) + * on the emulated device. + */ +#include "modem_driver.h" + +#define xxDEBUG + +#ifdef DEBUG +# include +# define D(...) ( fprintf( stderr, __VA_ARGS__ ) ) +#else +# define D(...) ((void)0) +#endif + +AModem android_modem; +CharDriverState* android_modem_cs; + +typedef struct { + CharDriverState* cs; + AModem modem; + char in_buff[ 1024 ]; + int in_pos; + int in_sms; +} ModemDriver; + +/* send unsollicited messages to the device */ +static void +modem_driver_unsol( void* _md, const char* message) +{ + ModemDriver* md = _md; + int len = strlen(message); + + qemu_chr_write(md->cs, (const uint8_t*)message, len); +} + +static int +modem_driver_can_read( void* _md ) +{ + ModemDriver* md = _md; + int ret = sizeof(md->in_buff) - md->in_pos; + + return ret; +} + +/* despite its name, this function is called when the device writes to the modem */ +static void +modem_driver_read( void* _md, const uint8_t* src, int len ) +{ + ModemDriver* md = _md; + const uint8_t* end = src + len; + int nn; + + D( "%s: reading %d from %p bytes:", __FUNCTION__, len, src ); + for (nn = 0; nn < len; nn++) { + int c = src[nn]; + if (c >= 32 && c < 127) + D( "%c", c ); + else if (c == '\n') + D( "" ); + else if (c == '\r') + D( "" ); + else + D( "\\x%02x", c ); + } + D( "\n" ); + + for ( ; src < end; src++ ) { + char c = src[0]; + + if (md->in_sms) { + if (c != 26) + goto AppendChar; + + md->in_buff[ md->in_pos ] = c; + md->in_pos++; + md->in_sms = 0; + c = '\n'; + } + + if (c == '\n' || c == '\r') { + const char* answer; + + if (md->in_pos == 0) /* skip empty lines */ + continue; + + md->in_buff[ md->in_pos ] = 0; + md->in_pos = 0; + + D( "%s: << %s\n", __FUNCTION__, md->in_buff ); + answer = amodem_send(android_modem, md->in_buff); + if (answer != NULL) { + D( "%s: >> %s\n", __FUNCTION__, answer ); + len = strlen(answer); + if (len == 2 && answer[0] == '>' && answer[1] == ' ') + md->in_sms = 1; + + qemu_chr_write(md->cs, (const uint8_t*)answer, len); + qemu_chr_write(md->cs, "\r", 1); + } else + D( "%s: -- NO ANSWER\n", __FUNCTION__ ); + + continue; + } + AppendChar: + md->in_buff[ md->in_pos++ ] = c; + if (md->in_pos == sizeof(md->in_buff)) { + /* input is too long !! */ + md->in_pos = 0; + } + } + D( "%s: done\n", __FUNCTION__ ); +} + + +static void +modem_driver_init( int base_port, ModemDriver* dm, CharDriverState* cs ) +{ + dm->cs = cs; + dm->in_pos = 0; + dm->in_sms = 0; + dm->modem = amodem_create( base_port, modem_driver_unsol, dm ); + + qemu_chr_add_read_handler( cs, modem_driver_can_read, modem_driver_read, dm ); +} + + +void android_modem_init( int base_port ) +{ + static ModemDriver modem_driver[1]; + + if (android_modem_cs != NULL) { + modem_driver_init( base_port, modem_driver, android_modem_cs ); + android_modem = modem_driver->modem; + } +} diff --git a/telephony/modem_driver.h b/telephony/modem_driver.h new file mode 100644 index 0000000..9acf23f --- /dev/null +++ b/telephony/modem_driver.h @@ -0,0 +1,29 @@ +/* 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. +*/ +#ifndef _modem_driver_h +#define _modem_driver_h + +#include "android_modem.h" +#include "vl.h" + +/** in telephony/modem_driver.c */ +/* this is the internal character driver used to communicate with the + * emulated GSM modem. see qemu_chr_open() in vl.c */ +extern CharDriverState* android_modem_cs; + +/* the emulated GSM modem itself */ +extern AModem android_modem; + +/* must be called before the VM runs if there is a modem to emulate */ +extern void android_modem_init( int base_port ); + +#endif /* _modem_driver_h */ diff --git a/telephony/remote_call.c b/telephony/remote_call.c new file mode 100644 index 0000000..d5b58eb --- /dev/null +++ b/telephony/remote_call.c @@ -0,0 +1,429 @@ +/* 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 "remote_call.h" +#include "android_utils.h" +#include "sysdeps.h" +#include "gsm.h" +#include "android.h" +#include "sockets.h" +#include + +#define DEBUG 1 + +#if 1 +# define D_ACTIVE VERBOSE_CHECK(modem) +#else +# define D_ACTIVE DEBUG +#endif + +#if 1 +# define S_ACTIVE VERBOSE_CHECK(socket) +#else +# define S_ACTIVE DEBUG +#endif + +#if DEBUG +# include +# define D(...) do { if (D_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0) +# define S(...) do { if (S_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0) +#else +# define D(...) ((void)0) +# define S(...) ((void)0) +#endif + +/** By convention, remote numbers are the console ports, i.e. 5554, 5556, etc... + **/ +#define REMOTE_NUMBER_BASE 5554 +#define REMOTE_NUMBER_MAX 16 +#define REMOTE_NUMBER_MAX_CHARS 4 +#define REMOTE_CONSOLE_PORT 5554 + +int +remote_number_from_port( int port ) +{ + if (port & 1) /* must be even */ + return -1; + + port = (port - REMOTE_CONSOLE_PORT) >> 1; + if ((unsigned)port >= REMOTE_NUMBER_MAX) + return -1; + + return REMOTE_NUMBER_BASE + port*2; +} + +int +remote_number_to_port( int number ) +{ + if (number & 1) /* must be even */ + return -1; + + number = (number - REMOTE_NUMBER_BASE) >> 1; + if ((unsigned)number >= REMOTE_NUMBER_MAX) + return -1; + + return REMOTE_CONSOLE_PORT + number*2; +} + +int +remote_number_string_to_port( const char* number ) +{ + char* end; + long num = strtol( number, &end, 10 ); + + if (end == NULL || *end || (int)num != num ) + return -1; + + return remote_number_to_port( (int)num ); +} + +/** REMOTE CALL OBJECTS + **/ + +typedef struct RemoteCallRec { + struct RemoteCallRec* next; + struct RemoteCallRec** pref; + RemoteCallType type; + int to_port; + int from_port; + SysChannel channel; + RemoteResultFunc result_func; + void* result_opaque; + + char quitting; + + /* the output buffer */ + char* buff; + int buff_pos; + int buff_len; + int buff_size; + char buff0[32]; + +} RemoteCallRec, *RemoteCall; + +static void +remote_call_done( RemoteCall call ) +{ + call->pref[0] = call->next; + call->next = NULL; + call->pref = &call->next; + + if (call->buff && call->buff != call->buff0) { + free(call->buff); + call->buff = call->buff0; + call->buff_size = (int) sizeof(call->buff0); + } + + if ( call->channel ) { + sys_channel_close( call->channel ); + call->channel = NULL; + } + + call->buff_pos = 0; + call->buff_len = 0; +} + + +static void +remote_call_free( RemoteCall call ) +{ + if (call) { + remote_call_done( call ); + free(call); + } +} + + +static void remote_call_event( void* opaque, int events ); /* forward */ + +static RemoteCall +remote_call_alloc( RemoteCallType type, int to_port, int from_port ) +{ + RemoteCall rcall = calloc( sizeof(*rcall), 1 ); + int from_num = remote_number_from_port(from_port); + + if (rcall != NULL) { + char *p, *end; + + rcall->pref = &rcall->next; + rcall->type = type; + rcall->to_port = to_port; + rcall->from_port = from_port; + rcall->buff = rcall->buff0; + rcall->buff_size = sizeof(rcall->buff0); + rcall->buff_pos = 0; + + p = rcall->buff; + end = p + rcall->buff_size; + + switch (type) { + case REMOTE_CALL_DIAL: + p = bufprint(p, end, "gsm call %d\n", from_num ); + break; + + case REMOTE_CALL_BUSY: + p = bufprint(p, end, "gsm busy %d\n", from_num); + break; + + case REMOTE_CALL_HOLD: + p = bufprint(p, end, "gsm hold %d\n", from_num); + break; + + case REMOTE_CALL_ACCEPT: + p = bufprint(p, end, "gsm accept %d\n", from_num); + break; + + case REMOTE_CALL_HANGUP: + p = bufprint(p, end, "gsm cancel %d\n", from_num ); + break; + + default: + ; + } + if (p >= end) { + D("%s: buffer too short\n", __FUNCTION__ ); + remote_call_free(rcall); + return NULL; + } + + rcall->buff_len = p - rcall->buff; + + rcall->channel = sys_channel_create_tcp_client( "localhost", to_port ); + if (rcall->channel == NULL) { + D("%s: could not create channel to port %d\n", __FUNCTION__, to_port); + remote_call_free(rcall); + return NULL; + } + + sys_channel_on( rcall->channel, SYS_EVENT_WRITE, remote_call_event, rcall ); + } + return rcall; +} + + +static int +remote_call_set_sms_pdu( RemoteCall call, + SmsPDU pdu ) +{ + char *p, *end; + int msg2len; + + msg2len = 32 + smspdu_to_hex( pdu, NULL, 0 ); + if (msg2len > call->buff_size) { + char* old_buff = call->buff == call->buff0 ? NULL : call->buff; + char* new_buff = realloc( old_buff, msg2len ); + if (new_buff == NULL) { + D("%s: not enough memory to alloc %d bytes", __FUNCTION__, msg2len); + return -1; + } + call->buff = new_buff; + call->buff_size = msg2len; + } + + p = call->buff; + end = p + call->buff_size; + + p = bufprint(p, end, "sms pdu "); + p += smspdu_to_hex( pdu, p, end-p ); + *p++ = '\n'; + *p = 0; + + call->buff_len = p - call->buff; + call->buff_pos = 0; + return 0; +} + + +static void +remote_call_add( RemoteCall call, + RemoteCall *plist ) +{ + RemoteCall first = *plist; + + call->next = first; + call->pref = plist; + + if (first) + first->pref = &call->next; +} + +static void +remote_call_event( void* opaque, int events ) +{ + RemoteCall call = opaque; + + S("%s: called for call (%d,%d), events=%02x\n", __FUNCTION__, + call->from_port, call->to_port, events); + + if (events & SYS_EVENT_READ) { + /* simply drain the channel */ + char temp[32]; + int n = sys_channel_read( call->channel, temp, sizeof(temp) ); + if (n <= 0) { + /* remote emulator probably quitted */ + //S("%s: emulator %d quitted with %d: %s\n", __FUNCTION__, call->to_port, socket_errno, socket_errstr()); + remote_call_free( call ); + return; + } + } + + if (events & SYS_EVENT_WRITE) { + int n; + + if (S_ACTIVE) { + int nn; + S("%s: call (%d,%d) sending %d bytes '", __FUNCTION__, + call->from_port, call->to_port, call->buff_len - call->buff_pos ); + for (nn = call->buff_pos; nn < call->buff_len; nn++) { + int c = call->buff[nn]; + if (c < 32) { + if (c == '\n') + S("\\n"); + else if (c == '\t') + S("\\t"); + else if (c == '\r') + S("\\r"); + else + S("\\x%02x", c); + } else + S("%c", c); + } + S("'\n"); + } + + n = sys_channel_write( call->channel, + call->buff + call->buff_pos, + call->buff_len - call->buff_pos ); + if (n <= 0) { + /* remote emulator probably quitted */ + S("%s: emulator %d quitted unexpectedly with error %d: %s\n", + __FUNCTION__, call->to_port, socket_errno, socket_errstr()); + if (call->result_func) + call->result_func( call->result_opaque, 0 ); + remote_call_free( call ); + return; + } + call->buff_pos += n; + + if (call->buff_pos >= call->buff_len) { + /* cool, we sent everything */ + S("%s: finished sending data to %d\n", __FUNCTION__, call->to_port); + if (!call->quitting) { + call->quitting = 1; + sprintf( call->buff, "quit\n" ); + call->buff_len = strlen(call->buff); + call->buff_pos = 0; + } else { + call->quitting = 0; + if (call->result_func) + call->result_func( call->result_opaque, 1 ); + + sys_channel_on( call->channel, SYS_EVENT_READ, remote_call_event, call ); + } + } + } +} + +static RemoteCall _the_remote_calls; + + +static int +remote_from_number( const char* from ) +{ + char* end; + long num = strtol( from, &end, 10 ); + + if (end == NULL || *end) + return -1; + + if ((unsigned)(num - REMOTE_NUMBER_BASE) >= REMOTE_NUMBER_MAX) + return -1; + + return (int) num; +} + + +static RemoteCall +remote_call_generic( RemoteCallType type, const char* to_number, int from_port ) +{ + int to_port = remote_number_string_to_port(to_number); + RemoteCall call; + + if ( remote_number_from_port(from_port) < 0 ) { + D("%s: from_port value %d is not valid", __FUNCTION__, from_port); + return NULL; + } + if ( to_port < 0 ) { + D("%s: phone number '%s' is not decimal or remote", __FUNCTION__, to_number); + return NULL; + } + if (to_port == from_port) { + D("%s: trying to call self\n", __FUNCTION__); + return NULL; + } + call = remote_call_alloc( type, to_port, from_port ); + if (call == NULL) { + return NULL; + } + remote_call_add( call, &_the_remote_calls ); + D("%s: adding new call from port %d to port %d\n", __FUNCTION__, from_port, to_port); + return call; +} + + +int +remote_call_dial( const char* number, + int from, + RemoteResultFunc result_func, + void* result_opaque ) +{ + RemoteCall call = remote_call_generic( REMOTE_CALL_DIAL, number, from ); + + if (call != NULL) { + call->result_func = result_func; + call->result_opaque = result_opaque; + } + return call ? 0 : -1; +} + + +void +remote_call_other( const char* to_number, int from_port, RemoteCallType type ) +{ + remote_call_generic( type, to_number, from_port ); +} + +/* call this function to send a SMS to a remote emulator */ +int +remote_call_sms( const char* number, + int from, + SmsPDU pdu ) +{ + RemoteCall call = remote_call_generic( REMOTE_CALL_SMS, number, from ); + + if (call == NULL) + return -1; + + if (call != NULL) { + if ( remote_call_set_sms_pdu( call, pdu ) < 0 ) { + remote_call_free(call); + return -1; + } + } + return call ? 0 : -1; +} + + +void +remote_call_cancel( const char* to_number, int from_port ) +{ + remote_call_generic( REMOTE_CALL_HANGUP, to_number, from_port ); +} diff --git a/telephony/remote_call.h b/telephony/remote_call.h new file mode 100644 index 0000000..c6891b8 --- /dev/null +++ b/telephony/remote_call.h @@ -0,0 +1,55 @@ +/* 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. +*/ +#ifndef _REMOTE_CALL_H +#define _REMOTE_CALL_H + +#include "sms.h" + +/* convert a base console port into a remote phone number, -1 on error */ +extern int remote_number_from_port( int port ); + +/* convert a remote phone number into a remote console port, -1 on error */ +extern int remote_number_to_port( int number ); + +extern int remote_number_string_to_port( const char* number ); + +typedef void (*RemoteResultFunc)( void* opaque, int success ); + +typedef enum { + REMOTE_CALL_DIAL = 0, + REMOTE_CALL_BUSY, + REMOTE_CALL_HANGUP, + REMOTE_CALL_HOLD, + REMOTE_CALL_ACCEPT, + REMOTE_CALL_SMS +} RemoteCallType; + +/* call this function when you need to dial a remote voice call. + * this will try to connect to a remote emulator. the result function + * is called to indicate success or failure after some time. + * + * returns 0 if the number is to a remote phone, or -1 otherwise + */ +extern int remote_call_dial( const char* to_number, + int from_port, + RemoteResultFunc result_func, + void* result_opaque ); + +/* call this function to send a SMS to a remote emulator */ +extern int remote_call_sms( const char* number, int from_port, SmsPDU pdu ); + +/* call this function to indicate that you're busy to a remote caller */ +extern void remote_call_other( const char* to_number, int from_port, RemoteCallType type ); + +extern void remote_call_cancel( const char* to_number, int from_port ); + +#endif /* _REMOTE_CALL_H */ diff --git a/telephony/sim_card.c b/telephony/sim_card.c new file mode 100644 index 0000000..9e48200 --- /dev/null +++ b/telephony/sim_card.c @@ -0,0 +1,432 @@ +/* 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 "sim_card.h" +#include +#include + +#define A_SIM_PIN_SIZE 4 +#define A_SIM_PUK_SIZE 8 + +typedef struct ASimCardRec_ { + ASimStatus status; + char pin[ A_SIM_PIN_SIZE+1 ]; + char puk[ A_SIM_PUK_SIZE+1 ]; + int pin_retries; + + char out_buff[ 256 ]; + int out_size; + +} ASimCardRec; + +static ASimCardRec _s_card[1]; + +ASimCard +asimcard_create( void ) +{ + ASimCard card = _s_card; + card->status = A_SIM_STATUS_READY; + card->pin_retries = 0; + strncpy( card->pin, "0000", sizeof(card->pin) ); + strncpy( card->puk, "12345678", sizeof(card->puk) ); + return card; +} + +void +asimcard_destroy( ASimCard card ) +{ + /* nothing really */ + card=card; +} + +static __inline__ int +asimcard_ready( ASimCard card ) +{ + return card->status == A_SIM_STATUS_READY; +} + +ASimStatus +asimcard_get_status( ASimCard sim ) +{ + return sim->status; +} + +void +asimcard_set_status( ASimCard sim, ASimStatus status ) +{ + sim->status = status; +} + +const char* +asimcard_get_pin( ASimCard sim ) +{ + return sim->pin; +} + +const char* +asimcard_get_puk( ASimCard sim ) +{ + return sim->puk; +} + +void +asimcard_set_pin( ASimCard sim, const char* pin ) +{ + strncpy( sim->pin, pin, A_SIM_PIN_SIZE ); + sim->pin_retries = 0; +} + +void +asimcard_set_puk( ASimCard sim, const char* puk ) +{ + strncpy( sim->puk, puk, A_SIM_PUK_SIZE ); + sim->pin_retries = 0; +} + + +int +asimcard_check_pin( ASimCard sim, const char* pin ) +{ + if (sim->status != A_SIM_STATUS_PIN && + sim->status != A_SIM_STATUS_READY ) + return 0; + + if ( !strcmp( sim->pin, pin ) ) { + sim->status = A_SIM_STATUS_READY; + sim->pin_retries = 0; + return 1; + } + + if (sim->status != A_SIM_STATUS_READY) { + if (++sim->pin_retries == 3) + sim->status = A_SIM_STATUS_PUK; + } + return 0; +} + + +int +asimcard_check_puk( ASimCard sim, const char* puk, const char* pin ) +{ + if (sim->status != A_SIM_STATUS_PUK) + return 0; + + if ( !strcmp( sim->puk, puk ) ) { + strncpy( sim->puk, puk, A_SIM_PUK_SIZE ); + strncpy( sim->pin, pin, A_SIM_PIN_SIZE ); + sim->status = A_SIM_STATUS_READY; + sim->pin_retries = 0; + return 1; + } + + if ( ++sim->pin_retries == 6 ) { + sim->status = A_SIM_STATUS_ABSENT; + } + return 0; +} + +typedef enum { + SIM_FILE_DM = 0, + SIM_FILE_DF, + SIM_FILE_EF_DEDICATED, + SIM_FILE_EF_LINEAR, + SIM_FILE_EF_CYCLIC +} SimFileType; + +typedef enum { + SIM_FILE_READ_ONLY = (1 << 0), + SIM_FILE_NEED_PIN = (1 << 1), +} SimFileFlags; + +/* descriptor for a known SIM File */ +#define SIM_FILE_HEAD \ + SimFileType type; \ + unsigned short id; \ + unsigned short flags; + +typedef struct { + SIM_FILE_HEAD +} SimFileAnyRec, *SimFileAny; + +typedef struct { + SIM_FILE_HEAD + cbytes_t data; + int length; +} SimFileEFDedicatedRec, *SimFileEFDedicated; + +typedef struct { + SIM_FILE_HEAD + byte_t rec_count; + byte_t rec_len; + cbytes_t records; +} SimFileEFLinearRec, *SimFileEFLinear; + +typedef SimFileEFLinearRec SimFileEFCyclicRec; +typedef SimFileEFCyclicRec* SimFileEFCyclic; + +typedef union { + SimFileAnyRec any; + SimFileEFDedicatedRec dedicated; + SimFileEFLinearRec linear; + SimFileEFCyclicRec cyclic; +} SimFileRec, *SimFile; + + +/* convert a SIM File descriptor into an ASCII string, + assumes 'dst' is NULL or properly sized. + return the number of chars, or -1 on error */ +static int +sim_file_to_hex( SimFile file, bytes_t dst ) +{ + SimFileType type = file->any.type; + int result = 0; + + /* see 9.2.1 in TS 51.011 */ + switch (type) { + case SIM_FILE_EF_DEDICATED: + case SIM_FILE_EF_LINEAR: + case SIM_FILE_EF_CYCLIC: + { + if (dst) { + int file_size, file_type, perm; + + memcpy(dst, "0000", 4); /* bytes 1-2 are RFU */ + dst += 4; + + /* bytes 3-4 are the file size */ + if (type == SIM_FILE_EF_DEDICATED) + file_size = file->dedicated.length; + else + file_size = file->linear.rec_count * file->linear.rec_len; + + gsm_hex_from_short( dst, file_size ); + dst += 4; + + /* bytes 5-6 are the file id */ + gsm_hex_from_short( dst, file->any.id ); + dst += 4; + + /* byte 7 is the file type - always EF, i.e. 0x04 */ + dst[0] = '0'; + dst[1] = '4'; + dst += 2; + + /* byte 8 is RFU, except bit 7 for cyclic files, which indicates + that INCREASE is allowed. Since we don't support this yet... */ + dst[0] = '0'; + dst[1] = '0'; + dst += 2; + + /* byte 9-11 are access conditions */ + if (file->any.flags & SIM_FILE_READ_ONLY) { + if (file->any.flags & SIM_FILE_NEED_PIN) + perm = 0x1a; + else + perm = 0x0a; + } else { + if (file->any.flags & SIM_FILE_NEED_PIN) + perm = 0x11; + else + perm = 0x00; + } + gsm_hex_from_byte(dst, perm); + memcpy( dst+2, "a0aa", 4 ); + dst += 6; + + /* byte 12 is file status, we don't support invalidation */ + dst[0] = '0'; + dst[1] = '0'; + dst += 2; + + /* byte 13 is length of the following data, always 2 */ + dst[0] = '0'; + dst[1] = '2'; + dst += 2; + + /* byte 14 is struct of EF */ + dst[0] = '0'; + if (type == SIM_FILE_EF_DEDICATED) + dst[1] = '0'; + else if (type == SIM_FILE_EF_LINEAR) + dst[1] = '1'; + else + dst[1] = '3'; + + /* byte 15 is lenght of record, or 0 */ + if (type == SIM_FILE_EF_DEDICATED) { + dst[0] = '0'; + dst[1] = '0'; + } else + gsm_hex_from_byte( dst, file->linear.rec_len ); + } + result = 30; + } + break; + + default: + result = -1; + } + return result; +} + + +static const byte_t _const_spn_cphs[20] = { + 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static const byte_t _const_voicemail_cphs[1] = { + 0x55 +}; + +static const byte_t _const_iccid[10] = { + 0x98, 0x10, 0x14, 0x30, 0x12, 0x11, 0x81, 0x15, 0x70, 0x02 +}; + +static const byte_t _const_cff_cphs[1] = { + 0x55 +}; + +static SimFileEFDedicatedRec _const_files_dedicated[] = +{ + { SIM_FILE_EF_DEDICATED, 0x6f14, SIM_FILE_READ_ONLY | SIM_FILE_NEED_PIN, + _const_spn_cphs, sizeof(_const_spn_cphs) }, + + { SIM_FILE_EF_DEDICATED, 0x6f11, SIM_FILE_NEED_PIN, + _const_voicemail_cphs, sizeof(_const_voicemail_cphs) }, + + { SIM_FILE_EF_DEDICATED, 0x2fe2, SIM_FILE_READ_ONLY, + _const_iccid, sizeof(_const_iccid) }, + + { SIM_FILE_EF_DEDICATED, 0x6f13, SIM_FILE_NEED_PIN, + _const_cff_cphs, sizeof(_const_cff_cphs) }, + + { 0, 0, 0, NULL, 0 } /* end of list */ +}; + + +const char* +asimcard_io( ASimCard sim, const char* cmd ) +{ + int nn; + int command, id, p1, p2, p3; + + static const struct { const char* cmd; const char* answer; } answers[] = + { + { "+CRSM=192,28436,0,0,15", "+CRSM: 144,0,000000146f1404001aa0aa01020000" }, + { "+CRSM=176,28436,0,0,20", "+CRSM: 144,0,416e64726f6964ffffffffffffffffffffffffff" }, + + { "+CRSM=192,28433,0,0,15", "+CRSM: 144,0,000000016f11040011a0aa01020000" }, + { "+CRSM=176,28433,0,0,1", "+CRSM: 144,0,55" }, + + { "+CRSM=192,12258,0,0,15", "+CRSM: 144,0,0000000a2fe204000fa0aa01020000" }, + { "+CRSM=176,12258,0,0,10", "+CRSM: 144,0,98101430121181157002" }, + + { "+CRSM=192,28435,0,0,15", "+CRSM: 144,0,000000016f13040011a0aa01020000" }, + { "+CRSM=176,28435,0,0,1", "+CRSM: 144,0,55" }, + + { "+CRSM=192,28472,0,0,15", "+CRSM: 144,0,0000000f6f3804001aa0aa01020000" }, + { "+CRSM=176,28472,0,0,15", "+CRSM: 144,0,ff30ffff3c003c03000c0000f03f00" }, + + { "+CRSM=192,28617,0,0,15", "+CRSM: 144,0,000000086fc9040011a0aa01020104" }, + { "+CRSM=178,28617,1,4,4", "+CRSM: 144,0,01000000" }, + + { "+CRSM=192,28618,0,0,15", "+CRSM: 144,0,0000000a6fca040011a0aa01020105" }, + { "+CRSM=178,28618,1,4,5", "+CRSM: 144,0,0000000000" }, + + { "+CRSM=192,28589,0,0,15", "+CRSM: 144,0,000000046fad04000aa0aa01020000" }, + { "+CRSM=176,28589,0,0,4", "+CRSM: 144,0,00000003" }, + + { "+CRSM=192,28438,0,0,15", "+CRSM: 144,0,000000026f1604001aa0aa01020000" }, + { "+CRSM=176,28438,0,0,2", "+CRSM: 144,0,0233" }, + + { "+CRSM=192,28486,0,0,15", "+CRSM: 148,4" }, + { "+CRSM=192,28621,0,0,15", "+CRSM: 148,4" }, + + { "+CRSM=192,28613,0,0,15", "+CRSM: 144,0,000000f06fc504000aa0aa01020118" }, + { "+CRSM=178,28613,1,4,24", "+CRSM: 144,0,43058441aa890affffffffffffffffffffffffffffffffff" }, + + { "+CRSM=192,28480,0,0,15", "+CRSM: 144,0,000000806f40040011a0aa01020120" }, + { "+CRSM=178,28480,1,4,32", "+CRSM: 144,0,ffffffffffffffffffffffffffffffffffff07815155258131f5ffffffffffff" }, + + { "+CRSM=192,28615,0,0,15", "+CRSM: 144,0,000000406fc7040011a0aa01020120" }, + { "+CRSM=178,28615,1,4,32", "+CRSM: 144,0,566f6963656d61696cffffffffffffffffff07915155125740f9ffffffffffff" }, + + { NULL, NULL } + }; + + assert( memcmp( cmd, "+CRSM=", 6 ) == 0 ); + +#if 0 /* this code officially disabled in the depot until properly tested and debugged */ + if ( sscanf(cmd, "+CRSM=%d,%d,%d,%d,%d", &command, &id, &p1, &p2, &p3) == 5 ) { + switch (command) { + case A_SIM_CMD_GET_RESPONSE: + { + const SimFileEFDedicatedRec* file = _const_files_dedicated; + + assert(p1 == 0 && p2 == 0 && p3 == 15); + + for ( ; file->id != 0; file++ ) { + if (file->id == id) { + int count; + char* out = sim->out_buff; + strcpy( out, "+CRSM: 144,0," ); + out += strlen(out); + count = sim_file_to_hex( (SimFile) file, out ); + if (count < 0) + return "ERROR: INTERNAL SIM ERROR"; + out[count] = 0; + return sim->out_buff; + } + } + break; + } + + case A_SIM_CMD_READ_BINARY: + { + const SimFileEFDedicatedRec* file = _const_files_dedicated; + + assert(p1 == 0 && p2 == 0); + + for ( ; file->id != 0; file++ ) { + if (file->id == id) { + char* out = sim->out_buff; + + if (p3 > file->length) + return "ERROR: BINARY LENGTH IS TOO LONG"; + + strcpy( out, "+CRSM: 144,0," ); + out += strlen(out); + gsm_hex_from_bytes( out, file->data, p3 ); + out[p3*2] = 0; + return sim->out_buff; + } + } + break; + } + + case A_SIM_CMD_READ_RECORD: + break; + + default: + return "ERROR: UNSUPPORTED SIM COMMAND"; + } + } +#endif + + for (nn = 0; answers[nn].cmd != NULL; nn++) { + if ( !strcmp( answers[nn].cmd, cmd ) ) { + return answers[nn].answer; + } + } + return "ERROR: BAD COMMAND"; +} + diff --git a/telephony/sim_card.h b/telephony/sim_card.h new file mode 100644 index 0000000..af78237 --- /dev/null +++ b/telephony/sim_card.h @@ -0,0 +1,54 @@ +/* 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. +*/ +#ifndef _android_sim_card_h +#define _android_sim_card_h + +#include "gsm.h" + +typedef struct ASimCardRec_* ASimCard; + +extern ASimCard asimcard_create( void ); +extern void asimcard_destroy( ASimCard sim ); + +typedef enum { + A_SIM_STATUS_ABSENT = 0, + A_SIM_STATUS_NOT_READY, + A_SIM_STATUS_READY, + A_SIM_STATUS_PIN, + A_SIM_STATUS_PUK, + A_SIM_STATUS_NETWORK_PERSONALIZATION +} ASimStatus; + +extern ASimStatus asimcard_get_status( ASimCard sim ); +extern void asimcard_set_status( ASimCard sim, ASimStatus status ); + +extern const char* asimcard_get_pin( ASimCard sim ); +extern const char* asimcard_get_puk( ASimCard sim ); +extern void asimcard_set_pin( ASimCard sim, const char* pin ); +extern void asimcard_set_puk( ASimCard sim, const char* puk ); + +extern int asimcard_check_pin( ASimCard sim, const char* pin ); +extern int asimcard_check_puk( ASimCard sim, const char* puk, const char* pin ); + +/* Restricted SIM Access command, as defined by 8.18 of 3GPP 27.007 */ +typedef enum { + A_SIM_CMD_READ_BINARY = 176, + A_SIM_CMD_READ_RECORD = 178, + A_SIM_CMD_GET_RESPONSE = 192, + A_SIM_CMD_UPDATE_BINARY = 214, + A_SIM_CMD_UPDATE_RECORD = 220, + A_SIM_CMD_STATUS = 242 +} ASimCommand; + +extern const char* asimcard_io( ASimCard sim, const char* cmd ); + +#endif /* _android_sim_card_h */ diff --git a/telephony/simulator.c b/telephony/simulator.c new file mode 100644 index 0000000..43f267a --- /dev/null +++ b/telephony/simulator.c @@ -0,0 +1,195 @@ +/* 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_modem.h" +#include "sysdeps.h" +#include +#include +#include +#include + +#define DEFAULT_PORT 6703 + +static AModem modem; + +typedef struct { + SysChannel channel; + char in_buff[ 128 ]; + int in_pos; + + char out_buff[ 128 ]; + int out_pos; + int out_size; +} ClientRec, *Client; + +static Client +client_alloc( SysChannel channel ) +{ + Client client = calloc( sizeof(*client), 1 ); + + client->channel = channel; + return client; +} + +static void +client_free( Client client ) +{ + sys_channel_close( client->channel ); + client->channel = NULL; + free( client ); +} + +static void +client_append( Client client, const char* str, int len ); + +static void +dump_line( const char* line, const char* prefix ) +{ + if (prefix) + printf( "%s", prefix ); + + for ( ; *line; line++ ) { + int c = line[0]; + + if (c >= 32 && c < 127) + printf( "%c", c ); + else if (c == '\r') + printf( "" ); + else if (c == '\n') + printf( "" ); + else + printf( "\\x%02x", c ); + } + printf( "\n" ); +} + +static void +client_handle_line( Client client, const char* cmd ) +{ + const char* answer; + + dump_line( cmd, "<< " ); + answer = amodem_send( modem, cmd ); + if (answer == NULL) /* not an AT command, ignored */ { + printf( "-- NO ANSWER\n" ); + return; + } + + dump_line( answer, ">> " ); + client_append( client, answer, -1 ); + client_append( client, "\r", 1 ); +} + +static void +client_handler( void* _client, int events ) +{ + Client client = _client; + + if (events & SYS_EVENT_READ) { + int ret; + /* read into buffer, one character at a time */ + ret = sys_channel_read( client->channel, client->in_buff + client->in_pos, 1 ); + if (ret != 1) { + fprintf(stderr, "client %p could not read byte, result = %d, error: %s\n", + client, ret, strerror(errno) ); + goto ExitClient; + } + if (client->in_buff[client->in_pos] == '\r' || + client->in_buff[client->in_pos] == '\n' ) { + const char* cmd = client->in_buff; + client->in_buff[client->in_pos] = 0; + + if (client->in_pos > 0) { + client_handle_line( client, cmd ); + client->in_pos = 0; + } + } else + client->in_pos += 1; + } + + if (events & SYS_EVENT_WRITE) { + int ret; + /* write from output buffer, one char at a time */ + ret = sys_channel_write( client->channel, client->out_buff + client->out_pos, 1 ); + if (ret != 1) { + fprintf(stderr, "client %p could not write byte, result = %d, error: %s\n", + client, ret, strerror(errno) ); + goto ExitClient; + } + client->out_pos += 1; + if (client->out_pos == client->out_size) { + client->out_size = 0; + client->out_pos = 0; + /* we don't need to write */ + sys_channel_on( client->channel, SYS_EVENT_READ, client_handler, client ); + } + } + return; + +ExitClient: + printf( "client %p exiting\n", client ); + client_free( client ); +} + + +static void +client_append( Client client, const char* str, int len ) +{ + int avail; + + if (len < 0) + len = strlen(str); + + avail = sizeof(client->out_buff) - client->out_size; + if (len > avail) + len = avail; + + memcpy( client->out_buff + client->out_size, str, len ); + if (client->out_size == 0) { + sys_channel_on( client->channel, SYS_EVENT_READ | SYS_EVENT_WRITE, client_handler, client ); + } + client->out_size += len; +} + + +static void +accept_func( void* _server, int events ) +{ + SysChannel server = _server; + SysChannel handler; + Client client; + + printf( "connection accepted for server channel, getting handler socket\n" ); + handler = sys_channel_create_tcp_handler( server ); + client = client_alloc( handler ); + printf( "got one. created client %p\n", client ); + + events=events; + sys_channel_on( handler, SYS_EVENT_READ, client_handler, client ); +} + + +int main( void ) +{ + int port = DEFAULT_PORT; + SysChannel server; + + sys_main_init(); + modem = amodem_create( NULL, NULL ); + + server = sys_channel_create_tcp_server( port ); + printf( "GSM simulator listening on local port %d\n", port ); + + sys_channel_on( server, SYS_EVENT_READ, accept_func, server ); + sys_main_loop(); + printf( "GSM simulator exiting\n" ); + return 0; +} diff --git a/telephony/sms.c b/telephony/sms.c new file mode 100644 index 0000000..7a394d4 --- /dev/null +++ b/telephony/sms.c @@ -0,0 +1,1655 @@ +/* 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 "sms.h" +#include "gsm.h" +#include +#include +#include + +#define DEBUG 1 + +#if 1 +# include "android_debug.h" +# define D_ACTIVE VERBOSE_CHECK(modem) +#else +# define D_ACTIVE DEBUG +#endif + +#if DEBUG +# define D(...) VERBOSE_PRINT(modem,__VA_ARGS__) +#else +# define D(...) ((void)0) +#endif + +/* maximum number of data bytes in a SMS data message */ +#define MAX_USER_DATA_BYTES 140 + +/* maximum number of 7-bit septets in a SMS text message */ +#define MAX_USER_DATA_SEPTETS 160 + +/* size of the user data header in bytes */ +#define USER_DATA_HEADER_SIZE 6 + +/** MESSAGE TEXT + **/ +int +sms_utf8_from_message_str( const char* str, int strlen, unsigned char* utf8, int utf8len ) +{ + cbytes_t p = (cbytes_t)str; + cbytes_t end = p + strlen; + int count = 0; + int escaped = 0; + + while (p < end) + { + int c = p[0]; + + /* read the value from the string */ + p += 1; + if (c >= 128) { + if ((c & 0xe0) == 0xc0) + c &= 0x1f; + else if ((c & 0xf0) == 0xe0) + c &= 0x0f; + else + c &= 0x07; + p++; + while (p < end && (p[0] & 0xc0) == 0x80) { + c = (c << 6) | (p[0] & 0x3f); + p++; + } + } + if (escaped) { + switch (c) { + case '\\': + break; + case 'n': /* \n is line feed */ + c = 10; + break; + + case 'x': /* \xNN, where NN is a 2-digit hexadecimal value */ + if (p+2 > end) + return -1; + c = gsm_hex2_to_byte( (const char*)p ); + if (c < 0) + return -1; + p += 2; + break; + + case 'u': /* \uNNNN where NNNN is a 4-digiti hexadecimal value */ + if (p + 4 > end) + return -1; + c = gsm_hex4_to_short( (const char*)p ); + if (c < 0) + return -1; + p += 4; + break; + + default: /* invalid escape, return -1 */ + return -1; + } + escaped = 0; + } + else if (c == '\\') + { + escaped = 1; + continue; + } + + /* now, try to write it to the destination */ + if (c < 128) { + if (count < utf8len) + utf8[count] = (byte_t) c; + count += 1; + } + else if (c < 0x800) { + if (count < utf8len) + utf8[count] = (byte_t)(0xc0 | ((c >> 6) & 0x1f)); + if (count+1 < utf8len) + utf8[count+1] = (byte_t)(0x80 | (c & 0x3f)); + count += 2; + } + else { + if (count < utf8len) + utf8[count] = (byte_t)(0xc0 | ((c >> 12) & 0xf)); + if (count+1 < utf8len) + utf8[count+1] = (byte_t)(0x80 | ((c >> 6) & 0x3f)); + if (count+2 < utf8len) + utf8[count+2] = (byte_t)(0x80 | (c & 0x3f)); + count += 3; + } + } + + if (escaped) /* bad final escape */ + return -1; + + return count; +} + +/* to convert utf-8 to a message string, we only need to deal with control characters + * and that's it */ +int sms_utf8_to_message_str( const unsigned char* utf8, int utf8len, char* str, int strlen ) +{ + cbytes_t p = utf8; + cbytes_t end = p + utf8len; + int count = 0; + + while (p < end) + { + int c = p[0]; + int escape = 0; + + /* read the value from the string */ + p += 1; + if (c >= 128) { + if ((c & 0xe0) == 0xc0) + c &= 0x1f; + else if ((c & 0xf0) == 0xe0) + c &= 0x0f; + else + c &= 0x07; + p++; + while (p < end && (p[0] & 0xc0) == 0x80) { + c = (c << 6) | (p[0] & 0x3f); + p++; + } + } + + if (c < ' ') { + escape = 1; + if (c == '\n') { + c = 'n'; + escape = 2; + } + } + else if (c == '\\') + escape = 2; + + switch (escape) { + case 0: + if (c < 128) { + if (count < strlen) + str[count] = (char) c; + count += 1; + } + else if (c < 0x800) { + if (count < strlen) + str[count] = (byte_t)(0xc0 | ((c >> 6) & 0x1f)); + if (count+1 < strlen) + str[count+1] = (byte_t)(0x80 | (c & 0x3f)); + count += 2; + } + else { + if (count < strlen) + str[count] = (byte_t)(0xc0 | ((c >> 12) & 0xf)); + if (count+1 < strlen) + str[count+1] = (byte_t)(0x80 | ((c >> 6) & 0x3f)); + if (count+2 < strlen) + str[count+2] = (byte_t)(0x80 | (c & 0x3f)); + count += 3; + } + break; + + case 1: + if (count+3 < strlen) { + str[count+0] = '\\'; + str[count+1] = 'x'; + gsm_hex_from_byte(str + count + 2, c); + } + count += 4; + break; + + default: + if (count+2 < strlen) { + str[count+0] = '\\'; + str[count+1] = (char) c; + } + count += 2; + } + } + return count; +} + + +/** TIMESTAMPS + **/ +void +sms_timestamp_now( SmsTimeStamp stamp ) +{ + time_t now_time = time(NULL); + struct tm gm = *(gmtime(&now_time)); + struct tm local = *(localtime(&now_time)); + int tzdiff = 0; + + stamp->data[0] = gsm_int_to_bcdi( local.tm_year % 100 ); + stamp->data[1] = gsm_int_to_bcdi( local.tm_mon+1 ); + stamp->data[2] = gsm_int_to_bcdi( local.tm_mday ); + stamp->data[3] = gsm_int_to_bcdi( local.tm_hour ); + stamp->data[4] = gsm_int_to_bcdi( local.tm_min ); + stamp->data[5] = gsm_int_to_bcdi( local.tm_sec ); + + tzdiff = (local.tm_hour*4 + local.tm_min/15) - (gm.tm_hour*4 + gm.tm_min/15); + if (local.tm_yday > gm.tm_yday) + tzdiff += 24*4; + else if (local.tm_yday < gm.tm_yday) + tzdiff -= 24*4; + + stamp->data[6] = gsm_int_to_bcdi( tzdiff >= 0 ? tzdiff : -tzdiff ); + if (tzdiff < 0) + stamp->data[6] |= 0x08; +} + +int +sms_timestamp_to_tm( SmsTimeStamp stamp, struct tm* tm ) +{ + int tzdiff; + + tm->tm_year = gsm_int_from_bcdi( stamp->data[0] ); + if (tm->tm_year < 50) + tm->tm_year += 100; + tm->tm_mon = gsm_int_from_bcdi( stamp->data[1] ) -1; + tm->tm_mday = gsm_int_from_bcdi( stamp->data[2] ); + tm->tm_hour = gsm_int_from_bcdi( stamp->data[3] ); + tm->tm_min = gsm_int_from_bcdi( stamp->data[4] ); + tm->tm_sec = gsm_int_from_bcdi( stamp->data[5] ); + + tm->tm_isdst = -1; + + tzdiff = gsm_int_from_bcdi( stamp->data[6] & 0xf7 ); + if (stamp->data[6] & 0x8) + tzdiff = -tzdiff; + + return tzdiff; +} + +static void +gsm_rope_add_timestamp( GsmRope rope, const SmsTimeStampRec* ts ) +{ + gsm_rope_add( rope, ts->data, 7 ); +} + + +/** SMS ADDRESSES + **/ + +int +sms_address_from_str( SmsAddress address, const char* src, int srclen ) +{ + const char* end = src + srclen; + int shift = 0, len = 0; + bytes_t data = address->data; + + address->len = 0; + address->toa = 0x81; + + if (src >= end) + return -1; + + if ( src[0] == '+' ) { + address->toa = 0x91; + if (++src == end) + goto Fail; + } + + memset( address->data, 0, sizeof(address->data) ); + + shift = 0; + + while (src < end) { + int c = *src++ - '0'; + + if ( (unsigned)c >= 10 || + data >= address->data + sizeof(address->data) ) + goto Fail; + + data[0] |= c << shift; + len += 1; + shift += 4; + if (shift == 8) { + shift = 0; + data += 1; + } + } + if (shift != 0) + data[0] |= 0xf0; + + address->len = len; + return 0; + +Fail: + return -1; +} + +int +sms_address_to_str( SmsAddress address, char* str, int strlen ) +{ + static const char dialdigits[16] = "0123456789*#,N%"; + int n, count = 0; + + if (address->toa == 0x91) { + if (count < strlen) + str[count] = '+'; + count++; + } + for (n = 0; n < address->len; n += 2) + { + int c = address->data[n/2]; + + if (count < strlen) + str[count] = dialdigits[c & 0xf]; + count += 1; + + if (n+1 > address->len) + break; + + if (count < strlen) + str[count] = dialdigits[(c >> 4) & 0xf]; + count += 1; + } + return count; +} + +int +sms_address_from_bytes( SmsAddress address, const unsigned char* buf, int buflen ) +{ + int len = sizeof(address->data), num_digits; + + if (buflen < 2) + return -1; + + address->len = num_digits = buf[0]; + address->toa = buf[1]; + + len = (num_digits+1)/2; + if ( len > sizeof(address->data) ) + return -1; + + memcpy( address->data, buf+2, len ); + return 0; +} + +int +sms_address_to_bytes( SmsAddress address, unsigned char* buf, int bufsize ) +{ + int len = (address->len + 1)/2 + 2; + + if (buf == NULL) + bufsize = 0; + + if (bufsize < 1) goto Exit; + buf[0] = address->len; + + if (bufsize < 2) goto Exit; + buf[1] = address->toa; + + buf += 2; + bufsize -= 2; + if (bufsize > len-2) + bufsize = len - 2; + + memcpy( buf, address->data, bufsize ); +Exit: + return len; +} + +int +sms_address_from_hex ( SmsAddress address, const char* hex, int hexlen ) +{ + const char* hexend = hex + hexlen; + int nn, len, num_digits; + + if (hexlen < 4) + return -1; + + address->len = num_digits = gsm_hex2_to_byte( hex ); + address->toa = gsm_hex2_to_byte( hex+2 ); + hex += 4; + + len = (num_digits + 1)/2; + if (hex + len*2 > hexend) + return -1; + + for ( nn = 0; nn < len; nn++ ) + address->data[nn] = gsm_hex2_to_byte( hex + nn*2 ); + + return 0; +} + +int +sms_address_to_hex ( SmsAddress address, char* hex, int hexlen ) +{ + int len = (address->len + 1)/2 + 2; + int nn; + + if (hex == NULL) + hexlen = 0; + + if (hexlen < 2) goto Exit; + gsm_hex_from_byte( hex, address->len ); + if (hexlen < 4) goto Exit; + gsm_hex_from_byte( hex+2, address->toa ); + hex += 4; + hexlen -= 4; + if ( hexlen > 2*(len - 2) ) + hexlen = (len - 2)/2; + + for ( nn = 0; nn < hexlen; nn += 2 ) + gsm_hex_from_byte( hex+nn, address->data[nn/2] ); + +Exit: + return len*2; +} + +static void +gsm_rope_add_address( GsmRope rope, const SmsAddressRec* addr ) +{ + gsm_rope_add_c( rope, addr->len ); + gsm_rope_add_c( rope, addr->toa ); + gsm_rope_add( rope, addr->data, (addr->len+1)/2 ); + if (addr->len & 1) { + if (!rope->error && rope->data != NULL) + rope->data[ rope->pos-1 ] |= 0xf0; + } +} + +static int +sms_address_eq( const SmsAddressRec* addr1, const SmsAddressRec* addr2 ) +{ + if ( addr1->toa != addr2->toa || + addr1->len != addr2->len ) + return 0; + + return ( !memcmp( addr1->data, addr2->data, addr1->len ) ); +} + +/** SMS PARSER + **/ +static int +sms_get_byte( cbytes_t *pcur, cbytes_t end ) +{ + cbytes_t cur = *pcur; + int result = -1; + + if (cur < end) { + result = cur[0]; + *pcur = cur + 1; + } + return result; +} + +/* parse a service center address, returns -1 in case of error */ +static int +sms_get_sc_address( cbytes_t *pcur, + cbytes_t end, + SmsAddress address ) +{ + cbytes_t cur = *pcur; + int result = -1; + + if (cur < end) { + int len = cur[0]; + int dlen, adjust = 0; + + cur += 1; + + if (len == 0) { /* empty address */ + address->len = 0; + address->toa = 0x00; + result = 0; + goto Exit; + } + + if (cur + len > end) { + goto Exit; + } + + address->toa = *cur++; + len -= 1; + result = 0; + + for (dlen = 0; dlen < len; dlen+=1) + { + int c = cur[dlen]; + int v; + + adjust = 0; + if (dlen >= sizeof(address->data)) { + result = -1; + break; + } + + v = (c & 0xf); + if (v >= 0xe) + break; + + adjust = 1; + address->data[dlen] = (byte_t) c; + + v = (c >> 4) & 0xf; + if (v >= 0xe) { + break; + } + } + address->len = 2*dlen + adjust; + } +Exit: + if (!result) + *pcur = cur; + + return result; +} + +static int +sms_skip_sc_address( cbytes_t *pcur, + cbytes_t end ) +{ + cbytes_t cur = *pcur; + int result = -1; + int len; + + if (cur >= end) + goto Exit; + + len = cur[0]; + cur += 1 + len; + if (cur > end) + goto Exit; + + *pcur = cur; + result = 0; +Exit: + return result; +} + +/* parse a sender/receiver address, returns -1 in case of error */ +static int +sms_get_address( cbytes_t *pcur, + cbytes_t end, + SmsAddress address ) +{ + cbytes_t cur = *pcur; + int result = -1; + int len, dlen; + + if (cur >= end) + goto Exit; + + dlen = *cur++; + + if (dlen == 0) { + address->len = 0; + address->toa = 0; + result = 0; + goto Exit; + } + + if (cur + 1 + (dlen+1)/2 > end) + goto Exit; + + address->len = dlen; + address->toa = *cur++; + + len = (dlen + 1)/2; + if (len > sizeof(address->data)) + goto Exit; + + memcpy( address->data, cur, len ); + cur += len; + result = 0; + +Exit: + if (!result) + *pcur = cur; + + return result; +} + +static int +sms_skip_address( cbytes_t *pcur, + cbytes_t end ) +{ + cbytes_t cur = *pcur; + int result = -1; + int dlen; + + if (cur + 2 > end) + goto Exit; + + dlen = cur[0]; + cur += 2 + (dlen + 1)/2; + if (cur > end) + goto Exit; + + result = 0; +Exit: + return result; +} + +/* parse a service center timestamp */ +static int +sms_get_timestamp( cbytes_t *pcur, + cbytes_t end, + SmsTimeStamp ts ) +{ + cbytes_t cur = *pcur; + + if (cur + 7 > end) + return -1; + + memcpy( ts->data, cur, 7 ); + *pcur = cur + 7; + return 0; +} + +static int +sms_skip_timestamp( cbytes_t *pcur, + cbytes_t end ) +{ + cbytes_t cur = *pcur; + + if (cur + 7 > end) + return -1; + + *pcur = cur + 7; + return 0; +} + + +static int +sms_skip_validity_period( cbytes_t *pcur, + cbytes_t end, + int mtiByte ) +{ + cbytes_t cur = *pcur; + + switch ((mtiByte >> 3) & 3) { + case 1: /* relative format */ + cur += 1; + break; + + case 2: /* enhanced format */ + case 3: /* absolute format */ + cur += 7; + } + if (cur > end) + return -1; + + *pcur = cur; + return 0; +} + +/** SMS PDU + **/ + +typedef struct SmsPDURec { + bytes_t base; + bytes_t end; + bytes_t tpdu; +} SmsPDURec; + +void +smspdu_free( SmsPDU pdu ) +{ + if (pdu) { + free( pdu->base ); + pdu->base = NULL; + pdu->end = NULL; + pdu->tpdu = NULL; + } +} + +SmsPduType +smspdu_get_type( SmsPDU pdu ) +{ + cbytes_t data = pdu->tpdu; + cbytes_t end = pdu->end; + int mtiByte = sms_get_byte(&data, end); + + switch (mtiByte & 3) { + case 0: return SMS_PDU_DELIVER; + case 1: return SMS_PDU_SUBMIT; + case 2: return SMS_PDU_STATUS_REPORT; + default: return SMS_PDU_INVALID; + } +} + +int +smspdu_get_sender_address( SmsPDU pdu, SmsAddress address ) +{ + cbytes_t data = pdu->tpdu; + cbytes_t end = pdu->end; + int mtiByte = sms_get_byte(&data, end); + + switch (mtiByte & 3) { + case 0: /* SMS_PDU_DELIVER; */ + return sms_get_sc_address( &data, end, address ); + + default: return -1; + } +} + +int +smspdu_get_sc_timestamp( SmsPDU pdu, SmsTimeStamp ts ) +{ + cbytes_t data = pdu->tpdu; + cbytes_t end = pdu->end; + int mtiByte = sms_get_byte( &data, end ); + + switch (mtiByte & 3) { + case 0: /* SMS_PDU_DELIVER */ + { + SmsAddressRec address; + + if ( sms_get_sc_address( &data, end, &address ) < 0 ) + return -1; + + data += 2; /* skip protocol identifer + coding scheme */ + + return sms_get_timestamp( &data, end, ts ); + } + + default: return -1; + } +} + +int +smspdu_get_receiver_address( SmsPDU pdu, SmsAddress address ) +{ + cbytes_t data = pdu->tpdu; + cbytes_t end = pdu->end; + int mtiByte = sms_get_byte( &data, end ); + + switch (mtiByte & 3) { + case 1: /* SMS_PDU_SUBMIT */ + { + data += 1; /* skip message reference */ + return sms_get_address( &data, end, address ); + } + + default: return -1; + } +} + +typedef enum { + SMS_CODING_SCHEME_UNKNOWN = 0, + SMS_CODING_SCHEME_GSM7, + SMS_CODING_SCHEME_UCS2 + +} SmsCodingScheme; + +/* see TS 23.038 Section 5 for details */ +static SmsCodingScheme +sms_get_coding_scheme( cbytes_t *pcur, + cbytes_t end ) +{ + cbytes_t cur = *pcur; + int dataCoding; + + if (cur >= end) + return SMS_CODING_SCHEME_UNKNOWN; + + dataCoding = *cur++; + *pcur = cur; + + switch (dataCoding >> 4) { + case 0x00: + case 0x02: + case 0x03: + return SMS_CODING_SCHEME_GSM7; + + case 0x01: + if (dataCoding == 0x10) return SMS_CODING_SCHEME_GSM7; + if (dataCoding == 0x11) return SMS_CODING_SCHEME_UCS2; + break; + + case 0x04: case 0x05: case 0x06: case 0x07: + if (dataCoding & 0x20) return SMS_CODING_SCHEME_UNKNOWN; /* compressed 7-bits */ + if (((dataCoding >> 2) & 3) == 0) return SMS_CODING_SCHEME_GSM7; + if (((dataCoding >> 2) & 3) == 2) return SMS_CODING_SCHEME_UCS2; + break; + + case 0xF: + if (!(dataCoding & 4)) return SMS_CODING_SCHEME_GSM7; + break; + } + return SMS_CODING_SCHEME_UNKNOWN; +} + + +/* see TS 23.040 section 9.2.3.24 for details */ +static int +sms_get_text_utf8( cbytes_t *pcur, + cbytes_t end, + int hasUDH, + SmsCodingScheme coding, + GsmRope rope ) +{ + cbytes_t cur = *pcur; + int result = -1; + int len; + + if (cur >= end) + goto Exit; + + len = *cur++; + + /* skip user data header if any */ + if ( hasUDH ) + { + int hlen; + + if (cur >= end) + goto Exit; + + hlen = *cur++; + if (cur + hlen > end) + goto Exit; + + cur += hlen; + + if (coding == SMS_CODING_SCHEME_GSM7) + len -= 2*(hlen+1); + else + len -= hlen+1; + + if (len < 0) + goto Exit; + } + + /* switch the user data header if any */ + if (coding == SMS_CODING_SCHEME_GSM7) + { + int count = utf8_from_gsm7( cur, 0, len, NULL ); + + if (rope != NULL) + { + bytes_t dst = gsm_rope_reserve( rope, count ); + if (dst != NULL) + utf8_from_gsm7( cur, 0, len, dst ); + } + cur += (len+1)/2; + } + else if (coding == SMS_CODING_SCHEME_UCS2) + { + int count = ucs2_to_utf8( cur, len/2, NULL ); + + if (rope != NULL) + { + bytes_t dst = gsm_rope_reserve( rope, count ); + if (dst != NULL) + ucs2_to_utf8( cur, len/2, dst ); + } + cur += len; + } + result = 0; + +Exit: + if (!result) + *pcur = cur; + + return result; +} + +/* get the message embedded in a SMS PDU as a utf8 byte array, returns the length of the message in bytes */ +/* or -1 in case of error */ +int +smspdu_get_text_message( SmsPDU pdu, unsigned char* utf8, int utf8len ) +{ + cbytes_t data = pdu->tpdu; + cbytes_t end = pdu->end; + int mtiByte = sms_get_byte( &data, end ); + + switch (mtiByte & 3) { + case 0: /* SMS_PDU_DELIVER */ + { + SmsAddressRec address; + SmsTimeStampRec timestamp; + SmsCodingScheme coding; + GsmRopeRec rope[1]; + int result; + + if ( sms_get_sc_address( &data, end, &address ) < 0 ) + goto Fail; + + data += 1; /* skip protocol identifier */ + coding = sms_get_coding_scheme( &data, end ); + if (coding == SMS_CODING_SCHEME_UNKNOWN) + goto Fail; + + if ( sms_get_timestamp( &data, end, ×tamp ) < 0 ) + goto Fail; + + if ( sms_get_text_utf8( &data, end, (mtiByte & 0x40), coding, rope ) < 0 ) + goto Fail; + + result = rope->pos; + if (utf8len > result) + utf8len = result; + + if (utf8len > 0) + memcpy( utf8, rope->data, utf8len ); + + gsm_rope_done( rope ); + return result; + } + + case 1: /* SMS_PDU_SUBMIT */ + { + SmsAddressRec address; + SmsCodingScheme coding; + GsmRopeRec rope[1]; + int result; + + data += 1; /* message reference */ + + if ( sms_get_address( &data, end, &address ) < 0 ) + goto Fail; + + data += 1; /* skip protocol identifier */ + coding = sms_get_coding_scheme( &data, end ); + if (coding == SMS_CODING_SCHEME_UNKNOWN) + goto Fail; + + gsm_rope_init_alloc( rope, 0 ); + if ( sms_get_text_utf8( &data, end, (mtiByte & 0x40), coding, rope ) < 0 ) { + gsm_rope_done( rope ); + goto Fail; + } + + result = rope->pos; + if (utf8len > result) + utf8len = result; + + if (utf8len > 0) + memcpy( utf8, rope->data, utf8len ); + + gsm_rope_done( rope ); + return result; + } + } +Fail: + return -1; +} + +static cbytes_t +smspdu_get_user_data_ref( SmsPDU pdu ) +{ + cbytes_t data = pdu->tpdu; + cbytes_t end = pdu->end; + int mtiByte = sms_get_byte( &data, end ); + int len; + + /* if there is no user-data-header, there is no message reference here */ + if ((mtiByte & 0x40) == 0) + goto Fail; + + switch (mtiByte & 3) { + case 0: /* SMS_PDU_DELIVER */ + if ( sms_skip_address( &data, end ) < 0 ) + goto Fail; + + data += 2; /* skip protocol identifier + coding scheme */ + + if ( sms_skip_timestamp( &data, end ) < 0 ) + goto Fail; + + break; + + case 1: /* SMS_PDU_SUBMIT */ + data += 1; /* skip message reference */ + + if ( sms_skip_address( &data, end ) < 0 ) + goto Fail; + + data += 2; /* protocol identifier + oding schene */ + if ( sms_skip_validity_period( &data, end, mtiByte ) < 0 ) + goto Fail; + + break; + + default: + goto Fail; + } + + /* skip user-data length */ + if (data+1 >= end) + goto Fail; + + len = data[1]; + data += 2; + + while (len >= 2 && data + 2 <= end) { + int htype = data[0]; + int hlen = data[1]; + + if (htype == 00 && hlen == 3 && data + 5 <= end) { + return data + 2; + } + + data += hlen; + len -= hlen - 2; + } +Fail: + return NULL; +} + +int +smspdu_get_ref( SmsPDU pdu ) +{ + cbytes_t user_ref = smspdu_get_user_data_ref( pdu ); + + if (user_ref != NULL) + { + return user_ref[0]; + } + else + { + cbytes_t data = pdu->tpdu; + cbytes_t end = pdu->end; + int mtiByte = sms_get_byte( &data, end ); + + if ((mtiByte & 3) == 1) { + /* try to extract directly the reference for a SMS-SUBMIT */ + if (data < end) + return data[0]; + } + } + return -1; +} + +int +smspdu_get_max_index( SmsPDU pdu ) +{ + cbytes_t user_ref = smspdu_get_user_data_ref( pdu ); + + if (user_ref != NULL) { + return user_ref[1]; + } else { + return 1; + } +} + +int +smspdu_get_cur_index( SmsPDU pdu ) +{ + cbytes_t user_ref = smspdu_get_user_data_ref( pdu ); + + if (user_ref != NULL) { + return user_ref[2] - 1; + } else { + return 0; + } +} + + +static void +gsm_rope_add_sms_user_header( GsmRope rope, + int ref_number, + int pdu_count, + int pdu_index ) +{ + gsm_rope_add_c( rope, 0x05 ); /* total header length == 5 bytes */ + gsm_rope_add_c( rope, 0x00 ); /* element id: concatenated message reference number */ + gsm_rope_add_c( rope, 0x03 ); /* element len: 3 bytes */ + gsm_rope_add_c( rope, (byte_t)ref_number ); /* reference number */ + gsm_rope_add_c( rope, (byte_t)pdu_count ); /* max pdu index */ + gsm_rope_add_c( rope, (byte_t)pdu_index+1 ); /* current pdu index */ +} + +/* write a SMS-DELIVER PDU into a rope */ +static void +gsm_rope_add_sms_deliver_pdu( GsmRope rope, + cbytes_t utf8, + int utf8len, + int use_gsm7, + const SmsAddressRec* sender_address, + const SmsTimeStampRec* timestamp, + int ref_num, + int pdu_count, + int pdu_index) +{ + int coding; + int mtiByte = 0x20; /* message type - SMS DELIVER */ + + if (pdu_count > 1) + mtiByte |= 0x40; /* user data header indicator */ + + gsm_rope_add_c( rope, 0 ); /* no SC Address */ + gsm_rope_add_c( rope, mtiByte ); /* message type - SMS-DELIVER */ + gsm_rope_add_address( rope, sender_address ); + gsm_rope_add_c( rope, 0 ); /* protocol identifier */ + + /* data coding scheme - GSM 7 bits / no class - or - 16-bit UCS2 / class 1 */ + coding = (use_gsm7 ? 0x00 : 0x09); + + gsm_rope_add_c( rope, coding ); /* data coding scheme */ + gsm_rope_add_timestamp( rope, timestamp ); /* service center timestamp */ + + if (use_gsm7) { + bytes_t dst; + int count = utf8_to_gsm7( utf8, utf8len, NULL, 0 ); + int pad = 0; + + assert( count <= MAX_USER_DATA_SEPTETS - USER_DATA_HEADER_SIZE ); + + if (pdu_count > 1) + { + int headerBits = 6*8; /* 6 is size of header in bytes */ + int headerSeptets = headerBits / 7; + if (headerBits % 7 > 0) + headerSeptets += 1; + + pad = headerSeptets*7 - headerBits; + + gsm_rope_add_c( rope, count + headerSeptets ); + gsm_rope_add_sms_user_header(rope, ref_num, pdu_count, pdu_index); + } + else + gsm_rope_add_c( rope, count ); + + count = (count*7+pad+7)/8; /* convert to byte count */ + + dst = gsm_rope_reserve( rope, count ); + if (dst != NULL) { + utf8_to_gsm7( utf8, utf8len, dst, pad ); + } + } else { + bytes_t dst; + int count = utf8_to_ucs2( utf8, utf8len, NULL ); + + assert( count*2 <= MAX_USER_DATA_BYTES - USER_DATA_HEADER_SIZE ); + + if (pdu_count > 1) + { + gsm_rope_add_c( rope, count*2 + 6 ); + gsm_rope_add_sms_user_header( rope, ref_num, pdu_count, pdu_index ); + } + else + gsm_rope_add_c( rope, count*2 ); + + gsm_rope_add_c( rope, count*2 ); + dst = gsm_rope_reserve( rope, count*2 ); + if (dst != NULL) { + utf8_to_ucs2( utf8, utf8len, dst ); + } + } +} + + +static SmsPDU +smspdu_create_deliver( cbytes_t utf8, + int utf8len, + int use_gsm7, + const SmsAddressRec* sender_address, + const SmsTimeStampRec* timestamp, + int ref_num, + int pdu_count, + int pdu_index ) +{ + SmsPDU p; + GsmRopeRec rope[1]; + int size; + + p = calloc( sizeof(*p), 1 ); + if (!p) goto Exit; + + gsm_rope_init( rope ); + gsm_rope_add_sms_deliver_pdu( rope, utf8, utf8len, use_gsm7, + sender_address, timestamp, + ref_num, pdu_count, pdu_index); + if (rope->error) + goto Fail; + + gsm_rope_init_alloc( rope, rope->pos ); + + gsm_rope_add_sms_deliver_pdu( rope, utf8, utf8len, use_gsm7, + sender_address, timestamp, + ref_num, pdu_count, pdu_index ); + + p->base = gsm_rope_done_acquire( rope, &size ); + if (p->base == NULL) + goto Fail; + + p->end = p->base + size; + p->tpdu = p->base + 1; +Exit: + return p; + +Fail: + free(p); + return NULL; +} + + +void +smspdu_free_list( SmsPDU* pdus ) +{ + if (pdus) { + int nn; + for (nn = 0; pdus[nn] != NULL; nn++) + smspdu_free( pdus[nn] ); + + free( pdus ); + } +} + + + +SmsPDU* +smspdu_create_deliver_utf8( const unsigned char* utf8, + int utf8len, + const SmsAddressRec* sender_address, + const SmsTimeStampRec* timestamp ) +{ + SmsTimeStampRec ts0; + int use_gsm7; + int count, block; + int num_pdus = 0; + int leftover = 0; + SmsPDU* list = NULL; + + static unsigned char ref_num = 0; + + if (timestamp == NULL) { + sms_timestamp_now( &ts0 ); + timestamp = &ts0; + } + + /* can we encode the message with the GSM 7-bit alphabet ? */ + use_gsm7 = utf8_check_gsm7( utf8, utf8len ); + + /* count the number of SMS PDUs we'll need */ + block = MAX_USER_DATA_SEPTETS - USER_DATA_HEADER_SIZE; + + if (use_gsm7) { + count = utf8_to_gsm7( utf8, utf8len, NULL, 0 ); + } else { + count = utf8_to_ucs2( utf8, utf8len, NULL ); + block = MAX_USER_DATA_BYTES - USER_DATA_HEADER_SIZE; + } + + num_pdus = count / block; + leftover = count - num_pdus*block; + if (leftover > 0) + num_pdus += 1; + + list = calloc( sizeof(SmsPDU*), num_pdus + 1 ); + if (list == NULL) + return NULL; + + /* now create each SMS PDU */ + { + cbytes_t src = utf8; + cbytes_t src_end = utf8 + utf8len; + int nn; + + for (nn = 0; nn < num_pdus; nn++) + { + int skip = block; + cbytes_t src_next; + + if (leftover > 0 && nn == num_pdus-1) + skip = leftover; + + src_next = utf8_skip_gsm7( src, src_end, skip ); + + list[nn] = smspdu_create_deliver( src, src_next - src, use_gsm7, sender_address, timestamp, + ref_num, num_pdus, nn ); + if (list[nn] == NULL) + goto Fail; + + src = src_next; + } + } + + ref_num++; + return list; + +Fail: + smspdu_free_list(list); + return NULL; +} + + +SmsPDU +smspdu_create_from_hex( const char* hex, int hexlen ) +{ + SmsPDU p; + cbytes_t data; + + p = calloc( sizeof(*p), 1 ); + if (!p) goto Exit; + + p->base = malloc( (hexlen+1)/2 ); + if (p->base == NULL) { + free(p); + p = NULL; + goto Exit; + } + + if ( gsm_hex_to_bytes( (cbytes_t)hex, hexlen, p->base ) < 0 ) + goto Fail; + + p->end = p->base + (hexlen+1)/2; + + data = p->base; + if ( sms_skip_sc_address( &data, p->end ) < 0 ) + goto Fail; + + p->tpdu = (bytes_t) data; + +Exit: + return p; + +Fail: + free(p->base); + free(p); + return NULL; +} + +int +smspdu_to_hex( SmsPDU pdu, char* hex, int hexlen ) +{ + int result = (pdu->end - pdu->base)*2; + int nn; + + if (hexlen > result) + hexlen = result; + + for (nn = 0; nn*2 < hexlen; nn++) { + gsm_hex_from_byte( &hex[nn*2], pdu->base[nn] ); + } + return result; +} + + +/** SMS SUBMIT RECEIVER + ** collects one or more SMS-SUBMIT PDUs to generate a single message to deliver + **/ + +typedef struct SmsFragmentRec { + struct SmsFragmentRec* next; + SmsAddressRec from[1]; + byte_t ref; + byte_t max; + byte_t count; + int index; + SmsPDU* pdus; + +} SmsFragmentRec, *SmsFragment; + + +typedef struct SmsReceiverRec { + int last; + SmsFragment fragments; + +} SmsReceiverRec; + + +static void +sms_fragment_free( SmsFragment frag ) +{ + int nn; + + for (nn = 0; nn < frag->max; nn++) { + if (frag->pdus[nn] != NULL) { + smspdu_free( frag->pdus[nn] ); + frag->pdus[nn] = NULL; + } + } + frag->pdus = NULL; + frag->count = 0; + frag->max = 0; + frag->index = 0; + free( frag ); +} + +static SmsFragment +sms_fragment_alloc( SmsReceiver rec, const SmsAddressRec* from, int ref, int max ) +{ + SmsFragment frag = calloc(sizeof(*frag) + max*sizeof(SmsPDU), 1 ); + + if (frag != NULL) { + frag->from[0] = from[0]; + frag->ref = ref; + frag->max = max; + frag->pdus = (SmsPDU*)(frag + 1); + frag->index = ++rec->last; + } + return frag; +} + + + +SmsReceiver sms_receiver_create( void ) +{ + SmsReceiver rec = calloc(sizeof(*rec),1); + return rec; +} + +void +sms_receiver_destroy( SmsReceiver rec ) +{ + while (rec->fragments) { + SmsFragment frag = rec->fragments; + rec->fragments = frag->next; + sms_fragment_free(frag); + } +} + +static SmsFragment* +sms_receiver_find_p( SmsReceiver rec, const SmsAddressRec* from, int ref ) +{ + SmsFragment* pnode = &rec->fragments; + SmsFragment node; + + for (;;) { + node = *pnode; + if (node == NULL) + break; + if (node->ref == ref && sms_address_eq( node->from, from )) + break; + pnode = &node->next; + } + return pnode; +} + +static SmsFragment* +sms_receiver_find_index_p( SmsReceiver rec, int index ) +{ + SmsFragment* pnode = &rec->fragments; + SmsFragment node; + + for (;;) { + node = *pnode; + if (node == NULL) + break; + if (node->index == index) + break; + pnode = &node->next; + } + return pnode; +} + +int +sms_receiver_add_submit_pdu( SmsReceiver rec, SmsPDU submit_pdu ) +{ + SmsAddressRec from[1]; + int ref, max, cur; + SmsFragment* pnode; + SmsFragment frag; + + if ( smspdu_get_receiver_address( submit_pdu, from ) < 0 ) { + D( "%s: could not extract receiver address\n", __FUNCTION__ ); + return -1; + } + + ref = smspdu_get_ref( submit_pdu ); + if (ref < 0) { + D( "%s: could not extract message reference from pdu\n", __FUNCTION__ ); + return -1; + } + max = smspdu_get_max_index( submit_pdu ); + if (max < 0) { + D( "%s: invalid max fragment value: %d should be >= 1\n", + __FUNCTION__, max ); + return -1; + } + pnode = sms_receiver_find_p( rec, from, ref ); + frag = *pnode; + if (frag == NULL) { + frag = sms_fragment_alloc( rec, from, ref, max ); + if (frag == NULL) { + D("%s: not enough memory to allocate new fragment\n", __FUNCTION__ ); + return -1; + } + if (D_ACTIVE) { + char tmp[32]; + int len; + + len = sms_address_to_str( from, tmp, sizeof(tmp) ); + if (len < 0) { + strcpy( tmp, "" ); + len = strlen(tmp); + } + D("%s: created SMS index %d, from %.*s, ref %d, max %d\n", __FUNCTION__, + frag->index, len, tmp, frag->ref, frag->max); + } + *pnode = frag; + } + + cur = smspdu_get_cur_index( submit_pdu ); + if (cur < 0) { + D("%s: SMS fragment index is too small: %d should be >= 1\n", __FUNCTION__, cur+1 ); + return -1; + } + if (cur >= max) { + D("%s: SMS fragment index is too large (%d >= %d)\n", __FUNCTION__, cur, max); + return -1; + } + if ( frag->pdus[cur] != NULL ) { + D("%s: receiving duplicate SMS fragment for %d/%d, ref=%d, discarding old one\n", + __FUNCTION__, cur+1, max, ref); + smspdu_free( frag->pdus[cur] ); + frag->count -= 1; + } + frag->pdus[cur] = submit_pdu; + frag->count += 1; + + if (frag->count >= frag->max) { + /* yes, we received all fragments for this SMS */ + D( "%s: SMS index %d, received all %d fragments\n", __FUNCTION__, frag->index, frag->count ); + return frag->index; + } + else { + /* still waiting for more */ + D( "%s: SMS index %d, received %d/%d, waiting for %d more\n", __FUNCTION__, + frag->index, cur+1, max, frag->max - frag->count ); + return 0; + } +} + + +int +sms_receiver_get_text_message( SmsReceiver rec, int index, bytes_t utf8, int utf8len ) +{ + SmsFragment* pnode = sms_receiver_find_index_p( rec, index ); + SmsFragment frag = *pnode; + int nn, total; + + if (frag == NULL) { + D( "%s: invalid SMS index %d\n", __FUNCTION__, index ); + return -1; + } + if (frag->count != frag->max) { + D( "%s: SMS index %d still needs %d fragments\n", __FUNCTION__, + frag->index, frag->max - frag->count ); + return -1; + } + /* get the size of all combined text */ + total = 0; + for ( nn = 0; nn < frag->count; nn++ ) { + int partial; + if (utf8 && utf8len > 0) { + partial = smspdu_get_text_message( frag->pdus[nn], utf8, utf8len ); + utf8 += partial; + utf8len -= partial; + } else { + partial = smspdu_get_text_message( frag->pdus[nn], NULL, 0 ); + } + total += partial; + } + return total; +} + + +static void +sms_receiver_remove( SmsReceiver rec, int index ) +{ + SmsFragment* pnode = sms_receiver_find_index_p( rec, index ); + SmsFragment frag = *pnode; + if (frag != NULL) { + *pnode = frag->next; + sms_fragment_free(frag); + } +} + + +SmsPDU* +sms_receiver_create_deliver( SmsReceiver rec, int index, const SmsAddressRec* from ) +{ + SmsPDU* result = NULL; + SmsFragment* pnode = sms_receiver_find_index_p( rec, index ); + SmsFragment frag = *pnode; + SmsTimeStampRec now[1]; + int nn, total; + bytes_t utf8; + int utf8len; + + if (frag == NULL) { + D( "%s: invalid SMS index %d\n", __FUNCTION__, index ); + return NULL; + } + if (frag->count != frag->max) { + D( "%s: SMS index %d still needs %d fragments\n", __FUNCTION__, + frag->index, frag->max - frag->count ); + return NULL; + } + + /* get the combined text message */ + utf8len = sms_receiver_get_text_message( rec, index, NULL, 0 ); + if (utf8len < 0) + goto Exit; + + utf8 = malloc( utf8len + 1 ); + if (utf8 == NULL) { + D( "%s: not enough memory to allocate %d bytes\n", + __FUNCTION__, utf8len+1 ); + goto Exit; + } + + total = 0; + for ( nn = 0; nn < frag->count; nn++ ) { + total += smspdu_get_text_message( frag->pdus[nn], utf8 + total, utf8len - total ); + } + + sms_timestamp_now( now ); + + result = smspdu_create_deliver_utf8( utf8, utf8len, from, now ); + + free(utf8); + +Exit: + sms_receiver_remove( rec, index ); + return result; +} + diff --git a/telephony/sms.h b/telephony/sms.h new file mode 100644 index 0000000..7059ee3 --- /dev/null +++ b/telephony/sms.h @@ -0,0 +1,117 @@ +/* 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. +*/ +#ifndef _android_sms_h +#define _android_sms_h + +#include + +/** MESSAGE TEXT + **/ +/* convert a quoted message text into a utf8 string. Note: you can use 'str' as the destination buffer + * with the current implementation. always return the number of utf8 bytes corresponding to the original + * message string, even if utf8 is NULL and utf8len is 0 + */ +extern int sms_utf8_from_message_str( const char* str, int strlen, unsigned char* utf8, int utf8len ); + +/* the equivalent in the opposite direction + */ +extern int sms_utf8_to_message_str( const unsigned char* utf8, int utf8len, char* str, int strlen ); + +/** TIMESTAMPS + **/ + +/* An SMS timestamp structure */ +typedef struct { + unsigned char data[7]; +} SmsTimeStampRec, *SmsTimeStamp; + +extern void sms_timestamp_now( SmsTimeStamp stamp ); +extern int sms_timestamp_to_tm( SmsTimeStamp stamp, struct tm* tm ); + +/** SMS ADDRESSES + **/ + +#define SMS_ADDRESS_MAX_SIZE 16 + +typedef struct { + unsigned char len; + unsigned char toa; + unsigned char data[ SMS_ADDRESS_MAX_SIZE ]; +} SmsAddressRec, *SmsAddress; + +extern int sms_address_from_str( SmsAddress address, const char* src, int srclen ); +extern int sms_address_to_str( SmsAddress address, char* src, int srclen ); + +extern int sms_address_from_bytes( SmsAddress address, const unsigned char* buf, int buflen ); +extern int sms_address_to_bytes ( SmsAddress address, unsigned char* buf, int bufsize ); +extern int sms_address_from_hex ( SmsAddress address, const char* hex, int hexlen ); +extern int sms_address_to_hex ( SmsAddress address, char* hex, int hexsize ); + +/** SMS PROTOCOL DATA UNITS + **/ + +typedef struct SmsPDURec* SmsPDU; + +extern SmsPDU* smspdu_create_deliver_utf8( const unsigned char* utf8, + int utf8len, + const SmsAddressRec* sender_address, + const SmsTimeStampRec* timestamp ); + +extern void smspdu_free_list( SmsPDU* pdus ); + +extern SmsPDU smspdu_create_from_hex( const char* hex, int hexlen ); + +extern int smspdu_to_hex( SmsPDU pdu, char* hex, int hexsize ); + +/* free a given SMS PDU */ +extern void smspdu_free( SmsPDU pdu ); + +typedef enum { + SMS_PDU_INVALID = 0, + SMS_PDU_DELIVER, + SMS_PDU_SUBMIT, + SMS_PDU_STATUS_REPORT +} SmsPduType; + +extern SmsPduType smspdu_get_type( SmsPDU pdu ); + +/* retrieve the sender address of a SMS-DELIVER pdu, returns -1 otherwise */ +extern int smspdu_get_sender_address( SmsPDU pdu, SmsAddress address ); + +/* retrieve the service center timestamp of a SMS-DELIVER pdu, return -1 otherwise */ +extern int smspdu_get_sc_timestamp( SmsPDU pdu, SmsTimeStamp timestamp ); + +/* retrieve the receiver address of a SMS-SUBMIT pdu, return -1 otherwise */ +extern int smspdu_get_receiver_address( SmsPDU pdu, SmsAddress address ); + +extern int smspdu_get_ref ( SmsPDU pdu ); +extern int smspdu_get_max_index( SmsPDU pdu ); +extern int smspdu_get_cur_index( SmsPDU pdu ); + +/* get the message embedded in a SMS PDU as a utf8 byte array, returns the length of the message in bytes */ +/* or -1 in case of error */ +extern int smspdu_get_text_message( SmsPDU pdu, unsigned char* utf8, int utf8len ); + +/** SMS SUBMIT RECEIVER + ** collects one or more SMS-SUBMIT PDUs to generate a single message to deliver + **/ + +typedef struct SmsReceiverRec *SmsReceiver; + +extern SmsReceiver sms_receiver_create( void ); +extern void sms_receiver_destroy( SmsReceiver rec ); + +extern int sms_receiver_add_submit_pdu( SmsReceiver rec, SmsPDU submit_pdu ); +extern int sms_receiver_get_text_message( SmsReceiver rec, int index, unsigned char* utf8, int utf8len ); +extern SmsPDU* sms_receiver_create_deliver( SmsReceiver rec, int index, const SmsAddressRec* from ); + +#endif /* _android_sms_h */ diff --git a/telephony/sysdeps.h b/telephony/sysdeps.h new file mode 100644 index 0000000..19ca8d3 --- /dev/null +++ b/telephony/sysdeps.h @@ -0,0 +1,80 @@ +/* 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. +*/ +#ifndef __sysdeps_h__ +#define __sysdeps_h__ + +/* system-dependent platform abstraction used by the emulated GSM modem + */ + +/* to be called before anything else */ + +extern void sys_main_init( void ); + +/** callbacks + **/ +typedef void (*SysCallback)( void* opaque ); + +/** events + **/ +enum { + SYS_EVENT_READ = 0x01, + SYS_EVENT_WRITE = 0x02, + SYS_EVENT_ERROR = 0x04, + SYS_EVENT_ALL = 0x07 +}; + +/** channels + **/ +typedef struct SysChannelRec_* SysChannel; + +typedef void (*SysChannelCallback)( void* opaque, int event_flags ); + +/* XXX: TODO: channel creation functions */ +extern SysChannel sys_channel_create_tcp_server( int port ); +extern SysChannel sys_channel_create_tcp_handler( SysChannel server_channel ); +extern SysChannel sys_channel_create_tcp_client( const char* hostname, int port ); +extern int sys_channel_set_non_block( SysChannel channel ); + +extern void sys_channel_on( SysChannel channel, + int event_flags, + SysChannelCallback event_callback, + void* event_opaqe ); + +extern int sys_channel_read( SysChannel channel, void* buffer, int size ); + +extern int sys_channel_write( SysChannel channel, const void* buffer, int size ); + +extern void sys_channel_close( SysChannel channel ); + + +/** time measurement + **/ +typedef long long SysTime; + +extern SysTime sys_time_now( void ); + +/** timers + **/ +typedef struct SysTimerRec_* SysTimer; + +extern SysTimer sys_timer_create( void ); +extern void sys_timer_set( SysTimer timer, SysTime when, SysCallback callback, void* opaque ); +extern void sys_timer_unset( SysTimer timer ); +extern void sys_timer_destroy( SysTimer timer ); + +extern long long sys_time_ms( void ); + +/** main loop (may return immediately on some platform) + **/ +extern int sys_main_loop( void ); + +#endif /* __sysdeps_h__ */ diff --git a/telephony/sysdeps_posix.c b/telephony/sysdeps_posix.c new file mode 100644 index 0000000..8c5eb12 --- /dev/null +++ b/telephony/sysdeps_posix.c @@ -0,0 +1,645 @@ +/* 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 "sysdeps.h" +#include +#include +#include +#include +#include +#include +#ifndef HAVE_WINSOCK +#include +#include +#include +#include +#include +#include +#include +#endif + +/** QUEUE + **/ +#define SYS_MAX_QUEUE 16 + +typedef struct { + int start; + int end; + void* pending[ SYS_MAX_QUEUE ]; +} +SysQueueRec, *SysQueue; + +static void +sys_queue_reset( SysQueue queue ) +{ + queue->start = queue->end = 0; +} + +static void +sys_queue_add( SysQueue queue, void* item ) +{ + assert( queue->end - queue->start < SYS_MAX_QUEUE ); + assert( queue->start == 0 ); + assert( item != NULL ); + queue->pending[ queue->end++ ] = item; +} + +#if 0 +static void +sys_queue_remove( SysQueue queue, void* item ) +{ + int nn, count; + assert( queue->end > queue->start ); + assert( item != NULL ); + count = queue->end - queue->start; + for ( nn = queue->start; count > 0; ++nn, --count ) { + if ( queue->pending[nn] == item ) { + queue->pending[nn] = queue->pending[nn+count-1]; + queue->end -= 1; + break; + } + } + assert( 0 && "sys_queue_remove: item not found" ); +} +#endif + +static void* +sys_queue_get( SysQueue queue ) +{ + if (queue->end > queue->start) { + return queue->pending[ queue->start++ ]; + } + return NULL; +} + +/** CHANNELS + **/ +typedef struct SysChannelRec_ { + SysChannel next; + int fd; + char active; + char pending; + char closed; + int wanted; + int ready; + SysChannelCallback callback; + void* opaque; +} SysChannelRec; + + +/*** channel allocation ***/ +#define SYS_EVENT_MAX 3 +#define SYS_MAX_CHANNELS 16 + +static SysChannelRec _s_channels0[ SYS_MAX_CHANNELS ]; +static SysChannel _s_free_channels; + +static SysChannel +sys_channel_alloc( void ) +{ + SysChannel channel = _s_free_channels; + assert( channel != NULL && "out of free channels" ); + _s_free_channels = channel->next; + channel->next = NULL; + channel->active = 0; + channel->closed = 0; + channel->pending = 0; + channel->wanted = 0; + return channel; +} + +static void +sys_channel_free( SysChannel channel ) +{ + if (channel->fd >= 0) { +#ifdef _WIN32 + shutdown( channel->fd, SD_BOTH ); +#else + shutdown( channel->fd, SHUT_RDWR ); +#endif + close(channel->fd); + channel->fd = -1; + } + channel->wanted = 0; + channel->ready = 0; + channel->callback = NULL; + + channel->next = _s_free_channels; + _s_free_channels = channel; +} + + +/* list of active channels */ +static SysChannel _s_channels; + +/* used by select to wait on channel events */ +static fd_set _s_fdsets[SYS_EVENT_MAX]; +static int _s_maxfd; + +static void +sys_channel_deactivate( SysChannel channel ) +{ + assert( channel->active != 0 ); + SysChannel *pnode = &_s_channels; + for (;;) { + SysChannel node = *pnode; + assert( node != NULL ); + if (node == channel) + break; + pnode = &node->next; + } + *pnode = channel->next; + channel->next = NULL; + channel->active = 0; +} + +static void +sys_channel_activate( SysChannel channel ) +{ + assert( channel->active == 0 ); + channel->next = _s_channels; + _s_channels = channel; + channel->active = 1; + if (channel->fd > _s_maxfd) + _s_maxfd = channel->fd; +} + + +/* queue of pending channels */ +static SysQueueRec _s_pending_channels[1]; + + +static void +sys_init_channels( void ) +{ + int nn; + + for (nn = 0; nn < SYS_MAX_CHANNELS-1; nn++) + _s_channels0[nn].next = &_s_channels0[nn+1]; + _s_free_channels = &_s_channels0[0]; + + for (nn = 0; nn < SYS_EVENT_MAX; nn++) + FD_ZERO( &_s_fdsets[nn] ); + + _s_maxfd = -1; + + sys_queue_reset( _s_pending_channels ); +} + + +void +sys_channel_on( SysChannel channel, + int events, + SysChannelCallback callback, + void* opaque ) +{ + int adds = events & ~channel->wanted; + int removes = channel->wanted & ~events; + + channel->wanted = events; + channel->callback = callback; + channel->opaque = opaque; + + /* update global fdsets */ + if (adds) { + int ee; + for (ee = 0; ee < SYS_EVENT_MAX; ee++) + if (adds & (1 << ee)) + FD_SET( channel->fd, &_s_fdsets[ee] ); + } + if (removes) { + int ee; + for (ee = 0; ee < SYS_EVENT_MAX; ee++) + if (removes & (1 << ee)) + FD_CLR( channel->fd, &_s_fdsets[ee] ); + } + if (events && !channel->active) { + sys_channel_activate( channel ); + } + else if (!events && channel->active) { + sys_channel_deactivate( channel ); + } +} + +int +sys_channel_read( SysChannel channel, void* buffer, int size ) +{ + char* buff = buffer; + int count = 0; + + assert( !channel->closed ); + + while (size > 0) { + int len = read(channel->fd, buff, size); + if (len < 0) { + if (errno == EINTR) + continue; + if (count == 0) + count = -1; + break; + } + buff += len; + size -= len; + count += len; + } + return count; +} + + +int +sys_channel_write( SysChannel channel, const void* buffer, int size ) +{ + const char* buff = buffer; + int count = 0; + + assert( !channel->closed ); + + while (size > 0) { + int len = write(channel->fd, buff, size); + if (len < 0) { + if (errno == EINTR) + continue; + if (count == 0) + count = -1; + break; + } + buff += len; + size -= len; + count += len; + } + return count; +} + + +void +sys_channel_close( SysChannel channel ) +{ + if (channel->active) { + sys_channel_on( channel, 0, NULL, NULL ); + } + + if (channel->pending) { + /* we can't free the channel right now because it */ + /* is in the pending list, set a flag */ + channel->closed = 1; + return; + } + + if (!channel->closed) { + channel->closed = 1; + } + + sys_channel_free( channel ); +} + +/** time measurement + **/ +SysTime sys_time_ms( void ) +{ + struct timeval tv; + gettimeofday( &tv, NULL ); + return (SysTime)(tv.tv_usec / 1000) + (SysTime)tv.tv_sec * 1000; +} + +/** timers + **/ +typedef struct SysTimerRec_ +{ + SysTimer next; + SysTime when; + SysCallback callback; + void* opaque; +} SysTimerRec; + +#define SYS_MAX_TIMERS 16 + +static SysTimerRec _s_timers0[ SYS_MAX_TIMERS ]; +static SysTimer _s_free_timers; +static SysTimer _s_timers; + +static SysQueueRec _s_pending_timers[1]; + + +static void +sys_init_timers( void ) +{ + int nn; + for (nn = 0; nn < SYS_MAX_TIMERS-1; nn++) { + _s_timers0[nn].next = & _s_timers0[nn+1]; + } + _s_free_timers = &_s_timers0[0]; + + sys_queue_reset( _s_pending_timers ); +} + + +SysTimer sys_timer_create( void ) +{ + SysTimer timer = _s_free_timers; + assert( timer != NULL && "too many timers allocated" ); + _s_free_timers = timer->next; + timer->next = NULL; + return timer; +} + + +void sys_timer_unset( SysTimer timer ) +{ + if (timer->callback != NULL) { + SysTimer *pnode, node; + pnode = &_s_timers; + for (;;) { + node = *pnode; + if (node == NULL) + break; + if (node == timer) { + *pnode = node->next; + break; + } + pnode = &node->next; + } + timer->next = NULL; + timer->callback = NULL; + timer->opaque = NULL; + } +} + + +void sys_timer_set( SysTimer timer, + SysTime when, + SysCallback callback, + void* opaque ) +{ + if (timer->callback != NULL) + sys_timer_unset(timer); + + if (callback != NULL) { + SysTime now = sys_time_ms(); + + if (now >= when) { + callback( opaque ); + } else { + SysTimer *pnode, node; + pnode = &_s_timers; + for (;;) { + node = *pnode; + if (node == NULL || node->when >= when) { + break; + } + pnode = &node->next; + } + timer->next = *pnode; + *pnode = timer; + timer->when = when; + timer->callback = callback; + timer->opaque = opaque; + } + } +} + + +void sys_timer_destroy( SysTimer timer ) +{ + assert( timer != NULL && "sys_timer_destroy: bad argument" ); + if (timer->callback != NULL) + sys_timer_unset(timer); + + timer->next = _s_free_timers; + _s_free_timers = timer; +} + + +static void +sys_single_loop( void ) +{ + fd_set rfd, wfd, efd; + struct timeval timeout_tv, *timeout = NULL; + int n; + + memcpy(&rfd, &_s_fdsets[0], sizeof(fd_set)); + memcpy(&wfd, &_s_fdsets[1], sizeof(fd_set)); + memcpy(&efd, &_s_fdsets[2], sizeof(fd_set)); + + if ( _s_timers != NULL ) { + SysTime now = sys_time_ms(); + SysTimer first = _s_timers; + + timeout = &timeout_tv; + if (first->when <= now) { + timeout->tv_sec = 0; + timeout->tv_usec = 0; + } else { + SysTime diff = first->when - now; + timeout->tv_sec = diff / 1000; + timeout->tv_usec = (diff - timeout->tv_sec*1000) * 1000; + } + } + + n = select( _s_maxfd+1, &rfd, &wfd, &efd, timeout); + if(n < 0) { + if(errno == EINTR) return; + perror("select"); + return; + } + + /* enqueue pending channels */ + { + int i; + + sys_queue_reset( _s_pending_channels ); + for(i = 0; (i <= _s_maxfd) && (n > 0); i++) + { + int events = 0; + + if(FD_ISSET(i, &rfd)) events |= SYS_EVENT_READ; + if(FD_ISSET(i, &wfd)) events |= SYS_EVENT_WRITE; + if(FD_ISSET(i, &efd)) events |= SYS_EVENT_ERROR; + + if (events) { + SysChannel channel; + + n--; + for (channel = _s_channels; channel; channel = channel->next) + { + if (channel->fd != i) + continue; + + channel->ready = events; + channel->pending = 1; + sys_queue_add( _s_pending_channels, channel ); + break; + } + } + } + } + + /* enqueue pending timers */ + { + SysTimer timer = _s_timers; + SysTime now = sys_time_ms(); + + sys_queue_reset( _s_pending_timers ); + while (timer != NULL) + { + if (timer->when > now) + break; + + sys_queue_add( _s_pending_timers, timer ); + _s_timers = timer = timer->next; + } + } +} + +void sys_main_init( void ) +{ + sys_init_channels(); + sys_init_timers(); +} + + +int sys_main_loop( void ) +{ + for (;;) { + SysTimer timer; + SysChannel channel; + + /* exit if we have nothing to do */ + if (_s_channels == NULL && _s_timers == NULL) + break; + + sys_single_loop(); + + while ((timer = sys_queue_get( _s_pending_timers )) != NULL) { + timer->callback( timer->opaque ); + } + + while ((channel = sys_queue_get( _s_pending_channels )) != NULL) { + int events; + + channel->pending = 0; + if (channel->closed) { + /* the channel was closed by a previous callback */ + sys_channel_close(channel); + } + events = channel->ready; + channel->ready = 0; + channel->callback( channel->opaque, events ); + } + } + return 0; +} + + + + +SysChannel +sys_channel_create_tcp_server( int port ) +{ + SysChannel channel; + int on = 1; + const int BACKLOG = 4; + + channel = sys_channel_alloc(); + if (-1==(channel->fd=socket(AF_INET, SOCK_STREAM, 0))) { + perror("socket"); + sys_channel_free( channel ); + return NULL; + } + + /* Enable address re-use for server mode */ + if ( -1==setsockopt( channel->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) )) { + perror("setsockopt(SO_REUSEADDR)"); + } + + { + struct sockaddr_in servname; + long in_addr = INADDR_ANY; + + servname.sin_family = AF_INET; + servname.sin_port = htons(port); + + servname.sin_addr.s_addr=in_addr; + + if (-1==bind(channel->fd, (struct sockaddr*)&servname, sizeof(servname))) { + perror("bind"); + sys_channel_close(channel); + return NULL; + } + + /* Listen but don't accept */ + if ( listen(channel->fd, BACKLOG) < 0 ) { + perror("listen"); + sys_channel_close(channel); + return NULL; + } + } + return channel; +} + + +SysChannel +sys_channel_create_tcp_handler( SysChannel server_channel ) +{ + int on = 1; + SysChannel channel = sys_channel_alloc(); + + channel->fd = accept( server_channel->fd, NULL, 0 ); + if (channel->fd < 0) { + perror( "accept" ); + sys_channel_free( channel ); + return NULL; + } + + /* set to non-blocking and disable TCP Nagle algorithm */ + fcntl(channel->fd, F_SETFL, O_NONBLOCK); + setsockopt(channel->fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + return channel; +} + + +SysChannel +sys_channel_create_tcp_client( const char* hostname, int port ) +{ + struct hostent* hp; + struct sockaddr_in addr; + SysChannel channel = sys_channel_alloc(); + int on = 1; + + hp = gethostbyname(hostname); + if(hp == 0) { + fprintf(stderr, "unknown host: %s\n", hostname); + sys_channel_free(channel); + return NULL; + }; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = hp->h_addrtype; + addr.sin_port = htons(port); + memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); + + channel->fd = socket(hp->h_addrtype, SOCK_STREAM, 0); + if(channel->fd < 0) { + sys_channel_free(channel); + return NULL; + } + + if(connect( channel->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror( "connect" ); + sys_channel_free(channel); + return NULL; + } + + /* set to non-blocking and disable Nagle algorithm */ + fcntl(channel->fd, F_SETFL, O_NONBLOCK); + setsockopt( channel->fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) ); + return channel; +} + diff --git a/telephony/sysdeps_qemu.c b/telephony/sysdeps_qemu.c new file mode 100644 index 0000000..e1107aa --- /dev/null +++ b/telephony/sysdeps_qemu.c @@ -0,0 +1,376 @@ +/* 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 "sockets.h" +#include "sysdeps.h" +#include "vl.h" +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#define DEBUG 1 + +#define D_ACTIVE DEBUG + +#if DEBUG +#define D(...) do { if (D_ACTIVE) fprintf(stderr, __VA_ARGS__); } while (0) +#else +#define D(...) ((void)0) +#endif + +/** TIME + **/ + +SysTime +sys_time_ms( void ) +{ + return qemu_get_clock( rt_clock ); +} + +/** TIMERS + **/ + +typedef struct SysTimerRec_ { + QEMUTimer* timer; + QEMUTimerCB* callback; + void* opaque; + SysTimer next; +} SysTimerRec; + +#define MAX_TIMERS 32 + +static SysTimerRec _s_timers0[ MAX_TIMERS ]; +static SysTimer _s_free_timers; + +static void +sys_init_timers( void ) +{ + int nn; + for (nn = 0; nn < MAX_TIMERS-1; nn++) + _s_timers0[nn].next = _s_timers0 + (nn+1); + + _s_free_timers = _s_timers0; +} + +static SysTimer +sys_timer_alloc( void ) +{ + SysTimer timer = _s_free_timers; + + if (timer != NULL) { + _s_free_timers = timer->next; + timer->next = NULL; + timer->timer = NULL; + } + return timer; +} + + +static void +sys_timer_free( SysTimer timer ) +{ + if (timer->timer) { + qemu_del_timer( timer->timer ); + qemu_free_timer( timer->timer ); + timer->timer = NULL; + } + timer->next = _s_free_timers; + _s_free_timers = timer; +} + + +SysTimer sys_timer_create( void ) +{ + SysTimer timer = sys_timer_alloc(); + return timer; +} + +void +sys_timer_set( SysTimer timer, SysTime when, SysCallback _callback, void* opaque ) +{ + QEMUTimerCB* callback = (QEMUTimerCB*)_callback; + + if (callback == NULL) { /* unsetting the timer */ + if (timer->timer) { + qemu_del_timer( timer->timer ); + qemu_free_timer( timer->timer ); + timer->timer = NULL; + } + timer->callback = callback; + timer->opaque = NULL; + return; + } + + if ( timer->timer ) { + if ( timer->callback == callback && timer->opaque == opaque ) + goto ReuseTimer; + + /* need to replace the timer */ + qemu_free_timer( timer->timer ); + } + + timer->timer = qemu_new_timer( rt_clock, callback, opaque ); + timer->callback = callback; + timer->opaque = opaque; + +ReuseTimer: + qemu_mod_timer( timer->timer, when ); +} + +void +sys_timer_unset( SysTimer timer ) +{ + if (timer->timer) { + qemu_del_timer( timer->timer ); + } +} + +void +sys_timer_destroy( SysTimer timer ) +{ + sys_timer_free( timer ); +} + + +/** CHANNELS + **/ + +typedef struct SysChannelRec_ { + int fd; + SysChannelCallback callback; + void* opaque; + SysChannel next; +} SysChannelRec; + +#define MAX_CHANNELS 16 + +static SysChannelRec _s_channels0[ MAX_CHANNELS ]; +static SysChannel _s_free_channels; + +static void +sys_init_channels( void ) +{ + int nn; + + for ( nn = 0; nn < MAX_CHANNELS-1; nn++ ) { + _s_channels0[nn].next = _s_channels0 + (nn+1); + } + _s_free_channels = _s_channels0; +} + +static SysChannel +sys_channel_alloc( ) +{ + SysChannel channel = _s_free_channels; + if (channel != NULL) { + _s_free_channels = channel->next; + channel->next = NULL; + channel->fd = -1; + channel->callback = NULL; + channel->opaque = NULL; + } + return channel; +} + +static void +sys_channel_free( SysChannel channel ) +{ + if (channel->fd >= 0) { + socket_close( channel->fd ); + channel->fd = -1; + } + channel->next = _s_free_channels; + _s_free_channels = channel; +} + + +static void +sys_channel_read_handler( void* _channel ) +{ + SysChannel channel = _channel; + D( "%s: read event for channel %p:%d\n", __FUNCTION__, + channel, channel->fd ); + channel->callback( channel->opaque, SYS_EVENT_READ ); +} + +static void +sys_channel_write_handler( void* _channel ) +{ + SysChannel channel = _channel; + D( "%s: write event for channel %p:%d\n", __FUNCTION__, channel, channel->fd ); + channel->callback( channel->opaque, SYS_EVENT_WRITE ); +} + +void +sys_channel_on( SysChannel channel, + int events, + SysChannelCallback event_callback, + void* event_opaque ) +{ + IOHandler* read_handler = NULL; + IOHandler* write_handler = NULL; + + if (events & SYS_EVENT_READ) { + read_handler = sys_channel_read_handler; + } + if (events & SYS_EVENT_WRITE) { + write_handler = sys_channel_write_handler; + } + channel->callback = event_callback; + channel->opaque = event_opaque; + qemu_set_fd_handler( channel->fd, read_handler, write_handler, channel ); +} + +int +sys_channel_read( SysChannel channel, void* buffer, int size ) +{ + int len = size; + char* buf = (char*) buffer; + + while (len > 0) { + int ret = recv(channel->fd, buf, len, 0); + if (ret < 0) { + if (socket_errno == EINTR) + continue; + if (socket_errno == EWOULDBLOCK) + break; + D( "%s: after reading %d bytes, recv() returned error %d: %s\n", + __FUNCTION__, size - len, socket_errno, socket_errstr()); + return -1; + } else if (ret == 0) { + break; + } else { + buf += ret; + len -= ret; + } + } + return size - len; +} + + +int +sys_channel_write( SysChannel channel, const void* buffer, int size ) +{ + int len = size; + const char* buf = (const char*) buffer; + + while (len > 0) { + int ret = send(channel->fd, buf, len, 0); + if (ret < 0) { + if (socket_errno == EINTR) + continue; + if (socket_errno == EWOULDBLOCK) + break; + D( "%s: send() returned error %d: %s\n", + __FUNCTION__, socket_errno, socket_errstr()); + return -1; + } else if (ret == 0) { + break; + } else { + buf += ret; + len -= ret; + } + } + return size - len; +} + +void sys_channel_close( SysChannel channel ) +{ + qemu_set_fd_handler( channel->fd, NULL, NULL, NULL ); + sys_channel_free( channel ); +} + +void sys_main_init( void ) +{ + sys_init_channels(); + sys_init_timers(); +} + + +int sys_main_loop( void ) +{ + /* no looping, qemu has its own event loop */ + return 0; +} + + + + +SysChannel +sys_channel_create_tcp_server( int port ) +{ + SysChannel channel = sys_channel_alloc(); + const int BACKLOG = 4; + + channel->fd = socket_anyaddr_server( port, SOCK_STREAM ); + if (channel->fd < 0) { + D( "%s: failed to created network socket on TCP:%d\n", port ); + sys_channel_free( channel ); + return NULL; + } + + D( "%s: server channel %p:%d now listening on port %d\n", + __FUNCTION__, channel, channel->fd, port ); + + return channel; +} + + +SysChannel +sys_channel_create_tcp_handler( SysChannel server_channel ) +{ + SysChannel channel = sys_channel_alloc(); + + D( "%s: creating handler from server channel %p:%d\n", __FUNCTION__, + server_channel, server_channel->fd ); + + channel->fd = socket_accept_any( server_channel->fd ); + if (channel->fd < 0) { + perror( "accept" ); + sys_channel_free( channel ); + return NULL; + } + + /* disable Nagle algorithm */ + socket_set_lowlatency( channel->fd ); + + D( "%s: handler %p:%d created from server %p:%d\n", __FUNCTION__, + server_channel, server_channel->fd, channel, channel->fd ); + + return channel; +} + + +SysChannel +sys_channel_create_tcp_client( const char* hostname, int port ) +{ + SysChannel channel = sys_channel_alloc(); + + channel->fd = socket_network_client( hostname, port, SOCK_STREAM ); + if (channel->fd < 0) { + sys_channel_free(channel); + return NULL; + }; + + /* set to non-blocking and disable Nagle algorithm */ + socket_set_nonblock( channel->fd ); + socket_set_lowlatency( channel->fd ); + + return channel; +} + diff --git a/telephony/test1.c b/telephony/test1.c new file mode 100644 index 0000000..52701b9 --- /dev/null +++ b/telephony/test1.c @@ -0,0 +1,49 @@ +/* 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 "sysdeps.h" +#include + +#define MAX_COUNTER 10 + +static int counter = 0; + +static void +timer_func( void* _timer ) +{ + SysTimer timer = _timer; + SysTime now = sys_time_ms(); + + ++counter; + printf( "tick %d/%d a %.2fs\n", counter, MAX_COUNTER, now/1000. ); + if (counter < MAX_COUNTER) + sys_timer_set( timer, now + 2000, timer_func, timer ); + else + sys_timer_destroy( timer ); +} + + +int main( void ) +{ + SysTimer timer; + + /* initialize event subsystem */ + sys_main_init(); + + /* create timer and register it */ + timer = sys_timer_create(); + sys_timer_set( timer, sys_time_ms() + 1000, timer_func, timer ); + + printf("entering event loop\n"); + sys_main_loop(); + printf("exiting event loop\n" ); + return 0; +} diff --git a/telephony/test2.c b/telephony/test2.c new file mode 100644 index 0000000..a0cd66f --- /dev/null +++ b/telephony/test2.c @@ -0,0 +1,215 @@ +/* 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 "sysdeps.h" +#include +#include +#include +#include + +#define PORT 8000 +#define MAX_COUNTER 30 +#define INITIAL_DELAY 1000 +#define DELAY 5000 + +static int counter = 0; + +static void +timer_func( void* _timer ) +{ + SysTimer timer = _timer; + SysTime now = sys_time_ms(); + + ++counter; + printf( "tick %d/%d a %.2fs\n", counter, MAX_COUNTER, now/1000. ); + if (counter < MAX_COUNTER) + sys_timer_set( timer, now + DELAY, timer_func, timer ); + else + sys_timer_destroy( timer ); +} + +typedef struct { + SysChannel channel; + char in_buff[ 128 ]; + int in_pos; + + char out_buff[ 128 ]; + int out_pos; + int out_size; +} ClientRec, *Client; + +static Client +client_alloc( SysChannel channel ) +{ + Client client = calloc( sizeof(*client), 1 ); + + client->channel = channel; + return client; +} + +static void +client_free( Client client ) +{ + sys_channel_close( client->channel ); + client->channel = NULL; + free( client ); +} + +static void +client_append( Client client, const char* str, int len ); + +static void +client_handle_line( Client client, const char* cmd ) +{ + char temp[256]; + int nn, mm = 0; + + for (nn = 0; cmd[nn] != 0; nn++) { + int c = cmd[nn]; + if (c >= 32 && c <= 127) + temp[mm++] = c; + else if (c == '\n') { + strcat( temp+mm, "" ); + mm += 4; + } + else if (c == '\r') { + strcat( temp+mm, "" ); + mm += 4; + } + else { + sprintf( temp+mm, "\\x%02x", c ); + mm += strlen( temp+mm ); + } + } + temp[mm] = 0; + printf( "%p: << %s\n", client, temp ); + + if ( !strcmp( cmd, "quit" ) ) { + printf( "client %p quitting\n", client ); + client_free( client ); + return; + } + client_append( client, "type 'quit' to quit\n", -1 ); +} + +static void +client_handler( void* _client, int events ) +{ + Client client = _client; + + if (events & SYS_EVENT_READ) { + int ret; + /* read into buffer, one character at a time */ + ret = sys_channel_read( client->channel, client->in_buff + client->in_pos, 1 ); + if (ret != 1) { + fprintf(stderr, "client %p could not read byte, result = %d, error: %s\n", + client, ret, strerror(errno) ); + goto ExitClient; + } + if (client->in_buff[client->in_pos] == '\r' || + client->in_buff[client->in_pos] == '\n' ) { + const char* cmd = client->in_buff; + client->in_buff[client->in_pos] = 0; + + /* eat leading cr and lf, maybe left-overs from previous line */ + while (*cmd == '\r' || *cmd =='\n') + cmd++; + + client_handle_line( client, cmd ); + client->in_pos = 0; + } else + client->in_pos += 1; + } + + if (events & SYS_EVENT_WRITE) { + int ret; + /* write from output buffer, one char at a time */ + ret = sys_channel_write( client->channel, client->out_buff + client->out_pos, 1 ); + if (ret != 1) { + fprintf(stderr, "client %p could not write byte, result = %d, error: %s\n", + client, ret, strerror(errno) ); + goto ExitClient; + } + client->out_pos += 1; + if (client->out_pos == client->out_size) { + client->out_size = 0; + client->out_pos = 0; + /* we don't need to write */ + sys_channel_on( client->channel, SYS_EVENT_READ, client_handler, client ); + } + } + return; + +ExitClient: + printf( "client %p exiting\n", client ); + client_free( client ); +} + +static void +client_append( Client client, const char* str, int len ) +{ + int avail; + + if (len < 0) + len = strlen(str); + + avail = sizeof(client->out_buff) - client->out_size; + if (len > avail) + len = avail; + + memcpy( client->out_buff + client->out_size, str, len ); + if (client->out_size == 0) { + sys_channel_on( client->channel, SYS_EVENT_READ | SYS_EVENT_WRITE, client_handler, client ); + } + client->out_size += len; +} + + +static void +accept_func( void* _server, int events ) +{ + SysChannel server = _server; + SysChannel handler; + Client client; + + printf( "connection accepted for server channel, getting handler socket\n" ); + handler = sys_channel_create_tcp_handler( server ); + printf( "got one. creating client\n" ); + client = client_alloc( handler ); + + events=events; + sys_channel_on( handler, SYS_EVENT_READ, client_handler, client ); + client_append( client, "Welcome !\n", -1 ); +} + + +int main( void ) +{ + SysTimer timer; + SysChannel server_channel; + + /* initialize event subsystem */ + sys_main_init(); + + /* create timer and register it */ + timer = sys_timer_create(); + sys_timer_set( timer, sys_time_ms() + INITIAL_DELAY, timer_func, timer ); + + server_channel = sys_channel_create_tcp_server( PORT ); + printf( "listening on port %d with %p\n", PORT, server_channel ); + + sys_channel_on( server_channel, SYS_EVENT_READ, accept_func, server_channel ); + + printf("entering event loop\n"); + sys_main_loop(); + printf("exiting event loop\n" ); + return 0; +} -- cgit v1.1