The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1 | /* Copyright (C) 2007-2008 The Android Open Source Project |
| 2 | ** |
| 3 | ** This software is licensed under the terms of the GNU General Public |
| 4 | ** License version 2, as published by the Free Software Foundation, and |
| 5 | ** may be copied, distributed, and modified under those terms. |
| 6 | ** |
| 7 | ** This program is distributed in the hope that it will be useful, |
| 8 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | ** GNU General Public License for more details. |
| 11 | */ |
| 12 | #include "sim_card.h" |
| 13 | #include <string.h> |
| 14 | #include <assert.h> |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 15 | #include <stdio.h> |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 16 | |
| 17 | /* set ENABLE_DYNAMIC_RECORDS to 1 to enable dynamic records |
| 18 | * for now, this is an experimental feature that needs more testing |
| 19 | */ |
| 20 | #define ENABLE_DYNAMIC_RECORDS 0 |
| 21 | |
| 22 | #define A_SIM_PIN_SIZE 4 |
| 23 | #define A_SIM_PUK_SIZE 8 |
| 24 | |
| 25 | typedef struct ASimCardRec_ { |
| 26 | ASimStatus status; |
| 27 | char pin[ A_SIM_PIN_SIZE+1 ]; |
| 28 | char puk[ A_SIM_PUK_SIZE+1 ]; |
| 29 | int pin_retries; |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 30 | int port; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 31 | |
| 32 | char out_buff[ 256 ]; |
| 33 | int out_size; |
| 34 | |
| 35 | } ASimCardRec; |
| 36 | |
| 37 | static ASimCardRec _s_card[1]; |
| 38 | |
| 39 | ASimCard |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 40 | asimcard_create(int port) |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 41 | { |
| 42 | ASimCard card = _s_card; |
| 43 | card->status = A_SIM_STATUS_READY; |
| 44 | card->pin_retries = 0; |
| 45 | strncpy( card->pin, "0000", sizeof(card->pin) ); |
| 46 | strncpy( card->puk, "12345678", sizeof(card->puk) ); |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 47 | card->port = port; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 48 | return card; |
| 49 | } |
| 50 | |
| 51 | void |
| 52 | asimcard_destroy( ASimCard card ) |
| 53 | { |
| 54 | /* nothing really */ |
| 55 | card=card; |
| 56 | } |
| 57 | |
| 58 | static __inline__ int |
| 59 | asimcard_ready( ASimCard card ) |
| 60 | { |
| 61 | return card->status == A_SIM_STATUS_READY; |
| 62 | } |
| 63 | |
| 64 | ASimStatus |
| 65 | asimcard_get_status( ASimCard sim ) |
| 66 | { |
| 67 | return sim->status; |
| 68 | } |
| 69 | |
| 70 | void |
| 71 | asimcard_set_status( ASimCard sim, ASimStatus status ) |
| 72 | { |
| 73 | sim->status = status; |
| 74 | } |
| 75 | |
| 76 | const char* |
| 77 | asimcard_get_pin( ASimCard sim ) |
| 78 | { |
| 79 | return sim->pin; |
| 80 | } |
| 81 | |
| 82 | const char* |
| 83 | asimcard_get_puk( ASimCard sim ) |
| 84 | { |
| 85 | return sim->puk; |
| 86 | } |
| 87 | |
| 88 | void |
| 89 | asimcard_set_pin( ASimCard sim, const char* pin ) |
| 90 | { |
| 91 | strncpy( sim->pin, pin, A_SIM_PIN_SIZE ); |
| 92 | sim->pin_retries = 0; |
| 93 | } |
| 94 | |
| 95 | void |
| 96 | asimcard_set_puk( ASimCard sim, const char* puk ) |
| 97 | { |
| 98 | strncpy( sim->puk, puk, A_SIM_PUK_SIZE ); |
| 99 | sim->pin_retries = 0; |
| 100 | } |
| 101 | |
| 102 | |
| 103 | int |
| 104 | asimcard_check_pin( ASimCard sim, const char* pin ) |
| 105 | { |
| 106 | if (sim->status != A_SIM_STATUS_PIN && |
| 107 | sim->status != A_SIM_STATUS_READY ) |
| 108 | return 0; |
| 109 | |
| 110 | if ( !strcmp( sim->pin, pin ) ) { |
| 111 | sim->status = A_SIM_STATUS_READY; |
| 112 | sim->pin_retries = 0; |
| 113 | return 1; |
| 114 | } |
| 115 | |
| 116 | if (sim->status != A_SIM_STATUS_READY) { |
| 117 | if (++sim->pin_retries == 3) |
| 118 | sim->status = A_SIM_STATUS_PUK; |
| 119 | } |
| 120 | return 0; |
| 121 | } |
| 122 | |
| 123 | |
| 124 | int |
| 125 | asimcard_check_puk( ASimCard sim, const char* puk, const char* pin ) |
| 126 | { |
| 127 | if (sim->status != A_SIM_STATUS_PUK) |
| 128 | return 0; |
| 129 | |
| 130 | if ( !strcmp( sim->puk, puk ) ) { |
| 131 | strncpy( sim->puk, puk, A_SIM_PUK_SIZE ); |
| 132 | strncpy( sim->pin, pin, A_SIM_PIN_SIZE ); |
| 133 | sim->status = A_SIM_STATUS_READY; |
| 134 | sim->pin_retries = 0; |
| 135 | return 1; |
| 136 | } |
| 137 | |
| 138 | if ( ++sim->pin_retries == 6 ) { |
| 139 | sim->status = A_SIM_STATUS_ABSENT; |
| 140 | } |
| 141 | return 0; |
| 142 | } |
| 143 | |
| 144 | typedef enum { |
| 145 | SIM_FILE_DM = 0, |
| 146 | SIM_FILE_DF, |
| 147 | SIM_FILE_EF_DEDICATED, |
| 148 | SIM_FILE_EF_LINEAR, |
| 149 | SIM_FILE_EF_CYCLIC |
| 150 | } SimFileType; |
| 151 | |
| 152 | typedef enum { |
| 153 | SIM_FILE_READ_ONLY = (1 << 0), |
| 154 | SIM_FILE_NEED_PIN = (1 << 1), |
| 155 | } SimFileFlags; |
| 156 | |
| 157 | /* descriptor for a known SIM File */ |
| 158 | #define SIM_FILE_HEAD \ |
| 159 | SimFileType type; \ |
| 160 | unsigned short id; \ |
| 161 | unsigned short flags; |
| 162 | |
| 163 | typedef struct { |
| 164 | SIM_FILE_HEAD |
| 165 | } SimFileAnyRec, *SimFileAny; |
| 166 | |
| 167 | typedef struct { |
| 168 | SIM_FILE_HEAD |
| 169 | cbytes_t data; |
| 170 | int length; |
| 171 | } SimFileEFDedicatedRec, *SimFileEFDedicated; |
| 172 | |
| 173 | typedef struct { |
| 174 | SIM_FILE_HEAD |
| 175 | byte_t rec_count; |
| 176 | byte_t rec_len; |
| 177 | cbytes_t records; |
| 178 | } SimFileEFLinearRec, *SimFileEFLinear; |
| 179 | |
| 180 | typedef SimFileEFLinearRec SimFileEFCyclicRec; |
| 181 | typedef SimFileEFCyclicRec* SimFileEFCyclic; |
| 182 | |
| 183 | typedef union { |
| 184 | SimFileAnyRec any; |
| 185 | SimFileEFDedicatedRec dedicated; |
| 186 | SimFileEFLinearRec linear; |
| 187 | SimFileEFCyclicRec cyclic; |
| 188 | } SimFileRec, *SimFile; |
| 189 | |
| 190 | |
| 191 | #if ENABLE_DYNAMIC_RECORDS |
| 192 | /* convert a SIM File descriptor into an ASCII string, |
| 193 | assumes 'dst' is NULL or properly sized. |
| 194 | return the number of chars, or -1 on error */ |
| 195 | static int |
| 196 | sim_file_to_hex( SimFile file, bytes_t dst ) |
| 197 | { |
| 198 | SimFileType type = file->any.type; |
| 199 | int result = 0; |
| 200 | |
| 201 | /* see 9.2.1 in TS 51.011 */ |
| 202 | switch (type) { |
| 203 | case SIM_FILE_EF_DEDICATED: |
| 204 | case SIM_FILE_EF_LINEAR: |
| 205 | case SIM_FILE_EF_CYCLIC: |
| 206 | { |
| 207 | if (dst) { |
| 208 | int file_size, perm; |
| 209 | |
| 210 | memcpy(dst, "0000", 4); /* bytes 1-2 are RFU */ |
| 211 | dst += 4; |
| 212 | |
| 213 | /* bytes 3-4 are the file size */ |
| 214 | if (type == SIM_FILE_EF_DEDICATED) |
| 215 | file_size = file->dedicated.length; |
| 216 | else |
| 217 | file_size = file->linear.rec_count * file->linear.rec_len; |
| 218 | |
| 219 | gsm_hex_from_short( dst, file_size ); |
| 220 | dst += 4; |
| 221 | |
| 222 | /* bytes 5-6 are the file id */ |
| 223 | gsm_hex_from_short( dst, file->any.id ); |
| 224 | dst += 4; |
| 225 | |
| 226 | /* byte 7 is the file type - always EF, i.e. 0x04 */ |
| 227 | dst[0] = '0'; |
| 228 | dst[1] = '4'; |
| 229 | dst += 2; |
| 230 | |
| 231 | /* byte 8 is RFU, except bit 7 for cyclic files, which indicates |
| 232 | that INCREASE is allowed. Since we don't support this yet... */ |
| 233 | dst[0] = '0'; |
| 234 | dst[1] = '0'; |
| 235 | dst += 2; |
| 236 | |
| 237 | /* byte 9-11 are access conditions */ |
| 238 | if (file->any.flags & SIM_FILE_READ_ONLY) { |
| 239 | if (file->any.flags & SIM_FILE_NEED_PIN) |
| 240 | perm = 0x1a; |
| 241 | else |
| 242 | perm = 0x0a; |
| 243 | } else { |
| 244 | if (file->any.flags & SIM_FILE_NEED_PIN) |
| 245 | perm = 0x11; |
| 246 | else |
| 247 | perm = 0x00; |
| 248 | } |
| 249 | gsm_hex_from_byte(dst, perm); |
| 250 | memcpy( dst+2, "a0aa", 4 ); |
| 251 | dst += 6; |
| 252 | |
| 253 | /* byte 12 is file status, we don't support invalidation */ |
| 254 | dst[0] = '0'; |
| 255 | dst[1] = '0'; |
| 256 | dst += 2; |
| 257 | |
| 258 | /* byte 13 is length of the following data, always 2 */ |
| 259 | dst[0] = '0'; |
| 260 | dst[1] = '2'; |
| 261 | dst += 2; |
| 262 | |
| 263 | /* byte 14 is struct of EF */ |
| 264 | dst[0] = '0'; |
| 265 | if (type == SIM_FILE_EF_DEDICATED) |
| 266 | dst[1] = '0'; |
| 267 | else if (type == SIM_FILE_EF_LINEAR) |
| 268 | dst[1] = '1'; |
| 269 | else |
| 270 | dst[1] = '3'; |
| 271 | |
| 272 | /* byte 15 is lenght of record, or 0 */ |
| 273 | if (type == SIM_FILE_EF_DEDICATED) { |
| 274 | dst[0] = '0'; |
| 275 | dst[1] = '0'; |
| 276 | } else |
| 277 | gsm_hex_from_byte( dst, file->linear.rec_len ); |
| 278 | } |
| 279 | result = 30; |
| 280 | } |
| 281 | break; |
| 282 | |
| 283 | default: |
| 284 | result = -1; |
| 285 | } |
| 286 | return result; |
| 287 | } |
| 288 | |
| 289 | |
| 290 | static const byte_t _const_spn_cphs[20] = { |
| 291 | 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0xff, 0xff, 0xff, |
| 292 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff |
| 293 | }; |
| 294 | |
| 295 | static const byte_t _const_voicemail_cphs[1] = { |
| 296 | 0x55 |
| 297 | }; |
| 298 | |
| 299 | static const byte_t _const_iccid[10] = { |
| 300 | 0x98, 0x10, 0x14, 0x30, 0x12, 0x11, 0x81, 0x15, 0x70, 0x02 |
| 301 | }; |
| 302 | |
| 303 | static const byte_t _const_cff_cphs[1] = { |
| 304 | 0x55 |
| 305 | }; |
| 306 | |
| 307 | static SimFileEFDedicatedRec _const_files_dedicated[] = |
| 308 | { |
| 309 | { SIM_FILE_EF_DEDICATED, 0x6f14, SIM_FILE_READ_ONLY | SIM_FILE_NEED_PIN, |
| 310 | _const_spn_cphs, sizeof(_const_spn_cphs) }, |
| 311 | |
| 312 | { SIM_FILE_EF_DEDICATED, 0x6f11, SIM_FILE_NEED_PIN, |
| 313 | _const_voicemail_cphs, sizeof(_const_voicemail_cphs) }, |
| 314 | |
| 315 | { SIM_FILE_EF_DEDICATED, 0x2fe2, SIM_FILE_READ_ONLY, |
| 316 | _const_iccid, sizeof(_const_iccid) }, |
| 317 | |
| 318 | { SIM_FILE_EF_DEDICATED, 0x6f13, SIM_FILE_NEED_PIN, |
| 319 | _const_cff_cphs, sizeof(_const_cff_cphs) }, |
| 320 | |
| 321 | { 0, 0, 0, NULL, 0 } /* end of list */ |
| 322 | }; |
| 323 | #endif /* ENABLE_DYNAMIC_RECORDS */ |
| 324 | |
| 325 | const char* |
| 326 | asimcard_io( ASimCard sim, const char* cmd ) |
| 327 | { |
| 328 | int nn; |
| 329 | #if ENABLE_DYNAMIC_RECORDS |
| 330 | int command, id, p1, p2, p3; |
| 331 | #endif |
| 332 | static const struct { const char* cmd; const char* answer; } answers[] = |
| 333 | { |
| 334 | { "+CRSM=192,28436,0,0,15", "+CRSM: 144,0,000000146f1404001aa0aa01020000" }, |
| 335 | { "+CRSM=176,28436,0,0,20", "+CRSM: 144,0,416e64726f6964ffffffffffffffffffffffffff" }, |
| 336 | |
| 337 | { "+CRSM=192,28433,0,0,15", "+CRSM: 144,0,000000016f11040011a0aa01020000" }, |
| 338 | { "+CRSM=176,28433,0,0,1", "+CRSM: 144,0,55" }, |
| 339 | |
| 340 | { "+CRSM=192,12258,0,0,15", "+CRSM: 144,0,0000000a2fe204000fa0aa01020000" }, |
| 341 | { "+CRSM=176,12258,0,0,10", "+CRSM: 144,0,98101430121181157002" }, |
| 342 | |
| 343 | { "+CRSM=192,28435,0,0,15", "+CRSM: 144,0,000000016f13040011a0aa01020000" }, |
| 344 | { "+CRSM=176,28435,0,0,1", "+CRSM: 144,0,55" }, |
| 345 | |
| 346 | { "+CRSM=192,28472,0,0,15", "+CRSM: 144,0,0000000f6f3804001aa0aa01020000" }, |
| 347 | { "+CRSM=176,28472,0,0,15", "+CRSM: 144,0,ff30ffff3c003c03000c0000f03f00" }, |
| 348 | |
| 349 | { "+CRSM=192,28617,0,0,15", "+CRSM: 144,0,000000086fc9040011a0aa01020104" }, |
| 350 | { "+CRSM=178,28617,1,4,4", "+CRSM: 144,0,01000000" }, |
| 351 | |
| 352 | { "+CRSM=192,28618,0,0,15", "+CRSM: 144,0,0000000a6fca040011a0aa01020105" }, |
| 353 | { "+CRSM=178,28618,1,4,5", "+CRSM: 144,0,0000000000" }, |
| 354 | |
| 355 | { "+CRSM=192,28589,0,0,15", "+CRSM: 144,0,000000046fad04000aa0aa01020000" }, |
| 356 | { "+CRSM=176,28589,0,0,4", "+CRSM: 144,0,00000003" }, |
| 357 | |
| 358 | { "+CRSM=192,28438,0,0,15", "+CRSM: 144,0,000000026f1604001aa0aa01020000" }, |
| 359 | { "+CRSM=176,28438,0,0,2", "+CRSM: 144,0,0233" }, |
| 360 | |
| 361 | { "+CRSM=192,28486,0,0,15", "+CRSM: 148,4" }, |
| 362 | { "+CRSM=192,28621,0,0,15", "+CRSM: 148,4" }, |
| 363 | |
| 364 | { "+CRSM=192,28613,0,0,15", "+CRSM: 144,0,000000f06fc504000aa0aa01020118" }, |
| 365 | { "+CRSM=178,28613,1,4,24", "+CRSM: 144,0,43058441aa890affffffffffffffffffffffffffffffffff" }, |
| 366 | |
| 367 | { "+CRSM=192,28480,0,0,15", "+CRSM: 144,0,000000806f40040011a0aa01020120" }, |
| 368 | { "+CRSM=178,28480,1,4,32", "+CRSM: 144,0,ffffffffffffffffffffffffffffffffffff07815155258131f5ffffffffffff" }, |
| 369 | |
| 370 | { "+CRSM=192,28615,0,0,15", "+CRSM: 144,0,000000406fc7040011a0aa01020120" }, |
| 371 | { "+CRSM=178,28615,1,4,32", "+CRSM: 144,0,566f6963656d61696cffffffffffffffffff07915155125740f9ffffffffffff" }, |
| 372 | |
| 373 | { NULL, NULL } |
| 374 | }; |
| 375 | |
| 376 | assert( memcmp( cmd, "+CRSM=", 6 ) == 0 ); |
| 377 | |
| 378 | #if ENABLE_DYNAMIC_RECORDS |
| 379 | if ( sscanf(cmd, "+CRSM=%d,%d,%d,%d,%d", &command, &id, &p1, &p2, &p3) == 5 ) { |
| 380 | switch (command) { |
| 381 | case A_SIM_CMD_GET_RESPONSE: |
| 382 | { |
| 383 | const SimFileEFDedicatedRec* file = _const_files_dedicated; |
| 384 | |
| 385 | assert(p1 == 0 && p2 == 0 && p3 == 15); |
| 386 | |
| 387 | for ( ; file->id != 0; file++ ) { |
| 388 | if (file->id == id) { |
| 389 | int count; |
| 390 | char* out = sim->out_buff; |
| 391 | strcpy( out, "+CRSM: 144,0," ); |
| 392 | out += strlen(out); |
| 393 | count = sim_file_to_hex( (SimFile) file, out ); |
| 394 | if (count < 0) |
| 395 | return "ERROR: INTERNAL SIM ERROR"; |
| 396 | out[count] = 0; |
| 397 | return sim->out_buff; |
| 398 | } |
| 399 | } |
| 400 | break; |
| 401 | } |
| 402 | |
| 403 | case A_SIM_CMD_READ_BINARY: |
| 404 | { |
| 405 | const SimFileEFDedicatedRec* file = _const_files_dedicated; |
| 406 | |
| 407 | assert(p1 == 0 && p2 == 0); |
| 408 | |
| 409 | for ( ; file->id != 0; file++ ) { |
| 410 | if (file->id == id) { |
| 411 | char* out = sim->out_buff; |
| 412 | |
| 413 | if (p3 > file->length) |
| 414 | return "ERROR: BINARY LENGTH IS TOO LONG"; |
| 415 | |
| 416 | strcpy( out, "+CRSM: 144,0," ); |
| 417 | out += strlen(out); |
| 418 | gsm_hex_from_bytes( out, file->data, p3 ); |
| 419 | out[p3*2] = 0; |
| 420 | return sim->out_buff; |
| 421 | } |
| 422 | } |
| 423 | break; |
| 424 | } |
| 425 | |
| 426 | case A_SIM_CMD_READ_RECORD: |
| 427 | break; |
| 428 | |
| 429 | default: |
| 430 | return "ERROR: UNSUPPORTED SIM COMMAND"; |
| 431 | } |
| 432 | } |
| 433 | #endif |
| 434 | |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 435 | if (!strcmp("+CRSM=178,28480,1,4,32", cmd)) { |
| 436 | 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); |
| 437 | return sim->out_buff; |
| 438 | } |
| 439 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 440 | for (nn = 0; answers[nn].cmd != NULL; nn++) { |
| 441 | if ( !strcmp( answers[nn].cmd, cmd ) ) { |
| 442 | return answers[nn].answer; |
| 443 | } |
| 444 | } |
| 445 | return "ERROR: BAD COMMAND"; |
| 446 | } |
| 447 | |