aboutsummaryrefslogtreecommitdiffstats
path: root/telephony
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit55f4e4a5ec657a017e3bf75299ad71fd1c968dd3 (patch)
tree550ce922ea0e125ac6a9738210ce2939bf2fe901 /telephony
parent413f05aaf54fa08c0ae7e997327a4f4a473c0a8d (diff)
downloadexternal_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.zip
external_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.tar.gz
external_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.tar.bz2
Initial Contribution
Diffstat (limited to 'telephony')
-rw-r--r--telephony/Jamfile13
-rw-r--r--telephony/android_modem.c1869
-rw-r--r--telephony/android_modem.h137
-rw-r--r--telephony/gsm.c1217
-rw-r--r--telephony/gsm.h196
-rw-r--r--telephony/modem_driver.c147
-rw-r--r--telephony/modem_driver.h29
-rw-r--r--telephony/remote_call.c429
-rw-r--r--telephony/remote_call.h55
-rw-r--r--telephony/sim_card.c432
-rw-r--r--telephony/sim_card.h54
-rw-r--r--telephony/simulator.c195
-rw-r--r--telephony/sms.c1655
-rw-r--r--telephony/sms.h117
-rw-r--r--telephony/sysdeps.h80
-rw-r--r--telephony/sysdeps_posix.c645
-rw-r--r--telephony/sysdeps_qemu.c376
-rw-r--r--telephony/test1.c49
-rw-r--r--telephony/test2.c215
19 files changed, 7910 insertions, 0 deletions
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 <memory.h>
+#include <stdarg.h>
+#include <time.h>
+#include <assert.h>
+#include <stdio.h>
+#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, "<CR>", 4 );
+ p += 4;
+ }
+ else if (c == '\n') {
+ memcpy( p, "<LF>", 4 );strcat( p, "<LF>" );
+ p += 4;
+ }
+ else {
+ p[0] = '\\';
+ p[1] = 'x';
+ p[2] = hexdigits[ (c) >> 4 ];
+ p[3] = hexdigits[ (c) & 15 ];
+ p += 4;
+ }
+ }
+ *p = 0;
+ return temp;
+}
+#endif
+
+extern 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=<id>,"<type>","<apn>",,0,0 */
+ int id = cmd[0] - '1';
+ ADataType type;
+ char apn[32];
+ ADataContext data;
+
+ if ((unsigned)id > 3)
+ goto BadCommand;
+
+ if ( !memcmp( cmd+1, ",\"IP\",\"", 7 ) ) {
+ type = A_DATA_IP;
+ cmd += 8;
+ } else if ( !memcmp( cmd+1, ",\"PPP\",\"", 8 ) ) {
+ type = A_DATA_PPP;
+ cmd += 9;
+ } else
+ goto BadCommand;
+
+ {
+ const char* p = strchr( cmd, '"' );
+ int len;
+ if (p == NULL)
+ goto BadCommand;
+ len = (int)( p - cmd );
+ if (len > sizeof(apn)-1 )
+ len = sizeof(apn)-1;
+ memcpy( apn, cmd, len );
+ apn[len] = 0;
+ }
+
+ data = modem->data_contexts + id;
+
+ data->id = id + 1;
+ data->active = 1;
+ data->type = type;
+ memcpy( data->apn, apn, sizeof(data->apn) );
+ }
+ return NULL;
+BadCommand:
+ return "ERROR: BAD COMMAND";
+}
+
+
+static const char*
+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 <stdlib.h>
+#include <string.h>
+
+/** 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 <stdio.h>
+# 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( "<LF>" );
+ else if (c == '\r')
+ D( "<CR>" );
+ 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 <stdlib.h>
+
+#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 <stdio.h>
+# 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 <string.h>
+#include <assert.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#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( "<CR>" );
+ else if (c == '\n')
+ printf( "<LF>" );
+ 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 <memory.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#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, &timestamp ) < 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, "<unknown>" );
+ 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 <time.h>
+
+/** 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 <assert.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <memory.h>
+#include <stdio.h>
+#ifndef HAVE_WINSOCK
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#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 <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#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 <stdio.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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, "<LF>" );
+ mm += 4;
+ }
+ else if (c == '\r') {
+ strcat( temp+mm, "<CR>" );
+ 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;
+}