/* 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> #include <stdio.h> /* set ENABLE_DYNAMIC_RECORDS to 1 to enable dynamic records * for now, this is an experimental feature that needs more testing */ #define ENABLE_DYNAMIC_RECORDS 0 #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; int port; char out_buff[ 256 ]; int out_size; } ASimCardRec; static ASimCardRec _s_card[1]; ASimCard asimcard_create(int port) { 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) ); card->port = port; 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; #if ENABLE_DYNAMIC_RECORDS /* 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, 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 */ }; #endif /* ENABLE_DYNAMIC_RECORDS */ const char* asimcard_io( ASimCard sim, const char* cmd ) { int nn; #if ENABLE_DYNAMIC_RECORDS int command, id, p1, p2, p3; #endif 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 ENABLE_DYNAMIC_RECORDS 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 if (!strcmp("+CRSM=178,28480,1,4,32", cmd)) { snprintf( sim->out_buff, sizeof(sim->out_buff), "+CRSM: 144,0,ffffffffffffffffffffffffffffffffffff0781515525%d1%d%df%dffffffffffff", (sim->port / 1000) % 10, (sim->port / 10) % 10, (sim->port / 100) % 10, sim->port % 10); return sim->out_buff; } for (nn = 0; answers[nn].cmd != NULL; nn++) { if ( !strcmp( answers[nn].cmd, cmd ) ) { return answers[nn].answer; } } return "ERROR: BAD COMMAND"; }