1 package com.android.bluetooth.tests; 2 3 import java.io.IOException; 4 import java.io.PipedInputStream; 5 import java.io.PipedOutputStream; 6 import java.util.ArrayList; 7 import java.util.Arrays; 8 9 import android.annotation.TargetApi; 10 import android.content.Context; 11 import android.content.Intent; 12 import android.os.Build; 13 import android.os.Handler; 14 import android.os.Handler.Callback; 15 import android.os.HandlerThread; 16 import android.os.Looper; 17 import android.os.Message; 18 import android.test.AndroidTestCase; 19 import android.util.Log; 20 21 import com.android.bluetooth.sap.SapMessage; 22 import com.android.bluetooth.sap.SapServer; 23 24 /** 25 * Test either using the reference ril without a modem, or using a RIL implementing the 26 * BT SAP API, by providing the rild-bt socket as well as the extended API functions for SAP. 27 * 28 */ 29 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 30 public class SapServerTest extends AndroidTestCase { 31 protected static String TAG = "SapServerTest"; 32 protected static final boolean D = true; 33 // Set the RIL driver in test mode, where request stubs are used instead 34 // of forwarding to the Modem/SIM. 35 private static final boolean rilTestModeEnabled = false; 36 private Context mContext = null; 37 38 public SapServerTest() { 39 super(); 40 } 41 42 private void buildDefaultInitSeq(SapSequencer sequencer) throws IOException { 43 SapMessage connectReq = new SapMessage(SapMessage.ID_CONNECT_REQ); 44 connectReq.setMaxMsgSize(276); 45 46 SapMessage connectResp = new SapMessage(SapMessage.ID_CONNECT_RESP); 47 connectResp.setConnectionStatus(SapMessage.CON_STATUS_OK); 48 connectResp.setMaxMsgSize(0); /* shall be connection status (0) on success */ 49 50 SapMessage statusInd = new SapMessage(SapMessage.ID_STATUS_IND); 51 statusInd.setStatusChange(SapMessage.STATUS_CARD_RESET); 52 53 int index = sequencer.addStep(connectReq, connectResp); 54 sequencer.addSubStep(index, null, statusInd); 55 56 } 57 /** 58 * Test that the SapServer is capable of handling a connect request with no call ongoing. 59 */ 60 public void testSapServerConnectSimple() { 61 mContext = this.getContext(); 62 63 try { 64 65 SapSequencer sequencer = new SapSequencer(); 66 if(rilTestModeEnabled) { 67 sequencer.testModeEnable(true); 68 } 69 /* Build a default init sequence */ 70 buildDefaultInitSeq(sequencer); 71 SapMessage disconnectReq = new SapMessage(SapMessage.ID_DISCONNECT_REQ); 72 SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP); 73 74 SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP); 75 resetResp.setResultCode(SapMessage.RESULT_OK); 76 77 int index = sequencer.addStep(disconnectReq, disconnectResp); 78 79 assertTrue(sequencer.run()); 80 } catch (IOException e) { 81 Log.e(TAG, "IOException", e); 82 } 83 } 84 85 public void testSapServerApiComplete() { 86 mContext = this.getContext(); 87 byte[] dummyBytes = {1, 2, 3, 4}; 88 89 /* Select file '2FE2' - observed selected in modem init sequence. 90 * According to spec file '2FE2' is EF_ICCID (Elementary file 91 * ICC identification). 92 */ 93 94 byte[] selectFileApdu = {(byte)0xa0, (byte)0xa4, (byte)0x00, (byte)0x00, 95 (byte)0x02, (byte)0x2f, (byte)0xe2}; 96 97 /* Command succesfull '9F', length '0F' of response data */ 98 byte[] selectFileApduResp = {(byte)0x9f, (byte)0x0f}; 99 100 try { 101 102 SapSequencer sequencer = new SapSequencer(); 103 if(rilTestModeEnabled) { 104 sequencer.testModeEnable(true); 105 } 106 107 /* Build a default init sequence */ 108 buildDefaultInitSeq(sequencer); 109 110 SapMessage powerOffReq = new SapMessage(SapMessage.ID_POWER_SIM_OFF_REQ); 111 112 SapMessage powerOffResp = new SapMessage(SapMessage.ID_POWER_SIM_OFF_RESP); 113 powerOffResp.setResultCode(SapMessage.RESULT_OK); 114 sequencer.addStep(powerOffReq, powerOffResp); 115 116 117 SapMessage powerOnReq = new SapMessage(SapMessage.ID_POWER_SIM_ON_REQ); 118 119 SapMessage powerOnResp = new SapMessage(SapMessage.ID_POWER_SIM_ON_RESP); 120 powerOnResp.setResultCode(SapMessage.RESULT_OK); 121 sequencer.addStep(powerOnReq, powerOnResp); 122 123 SapMessage resetReq = new SapMessage(SapMessage.ID_RESET_SIM_REQ); 124 125 SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP); 126 resetResp.setResultCode(SapMessage.RESULT_OK); 127 int index = sequencer.addStep(resetReq, resetResp); 128 129 /* SapMessage statusInd = new SapMessage(SapMessage.ID_STATUS_IND); */ 130 /* statusInd.setStatusChange(SapMessage.STATUS_CARD_RESET); */ 131 /* sequencer.addSubStep(index, null, statusInd); */ 132 133 SapMessage atrReq = new SapMessage(SapMessage.ID_TRANSFER_ATR_REQ); 134 135 SapMessage atrResp = new SapMessage(SapMessage.ID_TRANSFER_ATR_RESP); 136 atrResp.setResultCode(SapMessage.RESULT_OK); 137 if(rilTestModeEnabled) { 138 /* Use the hard coded return array, must match the test array in RIL */ 139 byte[] atr = {1, 2, 3, 4}; 140 atrResp.setAtr(atr); 141 } else { 142 atrResp.setAtr(null); 143 } 144 sequencer.addStep(atrReq, atrResp); 145 146 147 SapMessage apduReq = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ); 148 if(rilTestModeEnabled) { 149 apduReq.setApdu(dummyBytes); 150 } else { 151 apduReq.setApdu(selectFileApdu); 152 } 153 154 SapMessage apduResp = new SapMessage(SapMessage.ID_TRANSFER_APDU_RESP); 155 apduResp.setResultCode(SapMessage.RESULT_OK); 156 if(rilTestModeEnabled) { 157 apduResp.setApduResp(dummyBytes); 158 } else { 159 apduResp.setApduResp(selectFileApduResp); 160 } 161 sequencer.addStep(apduReq, apduResp); 162 163 164 SapMessage apdu7816Req = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ); 165 if(rilTestModeEnabled) { 166 apdu7816Req.setApdu7816(dummyBytes); 167 } else { 168 apdu7816Req.setApdu7816(selectFileApdu); 169 } 170 171 SapMessage apdu7816Resp = new SapMessage(SapMessage.ID_TRANSFER_APDU_RESP); 172 apdu7816Resp.setResultCode(SapMessage.RESULT_OK); 173 if(rilTestModeEnabled) { 174 apdu7816Resp.setApduResp(dummyBytes); 175 } else { 176 apdu7816Resp.setApduResp(selectFileApduResp); 177 } 178 sequencer.addStep(apdu7816Req, apdu7816Resp); 179 180 SapMessage transferCardReaderStatusReq = 181 new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_REQ); 182 183 SapMessage transferCardReaderStatusResp = 184 new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP); 185 transferCardReaderStatusResp.setResultCode(SapMessage.RESULT_OK); 186 sequencer.addStep(transferCardReaderStatusReq, transferCardReaderStatusResp); 187 188 SapMessage setTransportProtocolReq = 189 new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ); 190 setTransportProtocolReq.setTransportProtocol(0x01); // T=1 191 192 SapMessage setTransportProtocolResp = 193 new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP); 194 setTransportProtocolResp.setResultCode(SapMessage.RESULT_OK); 195 sequencer.addStep(setTransportProtocolReq, setTransportProtocolResp); 196 197 SapMessage disconnectReq = new SapMessage(SapMessage.ID_DISCONNECT_REQ); 198 199 SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP); 200 sequencer.addStep(disconnectReq, disconnectResp); 201 202 assertTrue(sequencer.run()); 203 } catch (IOException e) { 204 Log.e(TAG, "IOException", e); 205 } 206 } 207 208 /** 209 * This test fails if the apdu request generates a response before the reset request is handled. 210 * This happens if the reference ril is used in test mode. 211 */ 212 public void testSapServerResetWhileWritingApdu() { 213 mContext = this.getContext(); 214 byte[] dummyBytes = {1, 2, 3, 4}; 215 int index; 216 217 try { 218 219 SapSequencer sequencer = new SapSequencer(); 220 if(rilTestModeEnabled) { 221 sequencer.testModeEnable(true); 222 } 223 224 /* Build a default init sequence */ 225 buildDefaultInitSeq(sequencer); 226 227 SapMessage apduReq = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ); 228 apduReq.setApdu(dummyBytes); 229 230 // 231 // Expect no response as we send a SIM_RESET before the write APDU 232 // completes. 233 // TODO: Consider adding a real response, and add an optional flag. 234 // 235 SapMessage apduResp = null; 236 index = sequencer.addStep(apduReq, apduResp); 237 238 SapMessage resetReq = new SapMessage(SapMessage.ID_RESET_SIM_REQ); 239 SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP); 240 resetResp.setResultCode(SapMessage.RESULT_OK); 241 sequencer.addSubStep(index, resetReq, resetResp); 242 243 SapMessage statusInd = new SapMessage(SapMessage.ID_STATUS_IND); 244 statusInd.setStatusChange(SapMessage.STATUS_CARD_RESET); 245 sequencer.addSubStep(index, null, statusInd); 246 247 assertTrue(sequencer.run()); 248 } catch (IOException e) { 249 Log.e(TAG, "IOException", e); 250 } 251 } 252 253 /** 254 * Test that SapServer can disconnect based on a disconnect intent. 255 * TODO: This test could validate the timeout values. 256 * TODO: We need to add a IAction and add an action to a step, to be able to send 257 * the disconnect intent at the right time. 258 */ 259 public void testSapServerTimeouts() { 260 Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION); 261 sapDisconnectIntent.putExtra( 262 SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_IMMEDIATE); 263 mContext = this.getContext(); 264 265 try { 266 267 SapSequencer sequencer = new SapSequencer(); 268 if(rilTestModeEnabled) { 269 sequencer.testModeEnable(true); 270 } 271 /* Build a default init sequence */ 272 buildDefaultInitSeq(sequencer); 273 274 SapMessage disconnectReq = new SapMessage(SapMessage.ID_DISCONNECT_REQ); 275 SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP); 276 277 SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP); 278 resetResp.setResultCode(SapMessage.RESULT_OK); 279 280 int index = sequencer.addStep(disconnectReq, disconnectResp); 281 282 assertTrue(sequencer.run()); 283 284 mContext.sendBroadcast(sapDisconnectIntent); 285 286 } catch (IOException e) { 287 Log.e(TAG, "IOException", e); 288 } 289 } 290 public void testSapServerTimeoutsActionDiscIntent() { 291 292 } 293 294 public class SapSequencer implements Callback { 295 296 private final static int MSG_ID_TIMEOUT = 0x01; 297 private final static int TIMEOUT_VALUE = 100*2000; // ms 298 private ArrayList<SeqStep> sequence = null; 299 private HandlerThread handlerThread = null; 300 private Handler messageHandler = null; 301 302 private SapServer sapServer = null; 303 304 private PipedInputStream inStream = null; // Used to write requests to SapServer 305 private PipedOutputStream outStream = null; // Used to read commands from the SapServer 306 307 308 public class SeqStep { 309 public ArrayList<SapMessage> requests = null; 310 public ArrayList<SapMessage> responses = null; 311 public int index = 0; // Requests with same index are executed in 312 // parallel without waiting for a response 313 public SeqStep(SapMessage request, SapMessage response) { 314 requests = new ArrayList<SapMessage>(); 315 responses = new ArrayList<SapMessage>(); 316 this.requests.add(request); 317 this.responses.add(response); 318 } 319 320 public void add(SapMessage request, SapMessage response) { 321 this.requests.add(request); 322 this.responses.add(response); 323 } 324 325 /** 326 * Examine if the step has any expected response. 327 * @return true if one or more of the responses are != null. False otherwise. 328 */ 329 public boolean hasResponse() { 330 if(responses == null) 331 return false; 332 for(SapMessage response : responses) { 333 if(response != null) 334 return true; 335 } 336 return false; 337 } 338 } 339 340 public SapSequencer() throws IOException { 341 /* Setup the looper thread to handle messages */ 342 handlerThread = new HandlerThread("SapTestTimeoutHandler", 343 android.os.Process.THREAD_PRIORITY_BACKGROUND); 344 handlerThread.start(); 345 Looper testLooper = handlerThread.getLooper(); 346 messageHandler = new Handler(testLooper, this); 347 348 /* Initialize members */ 349 sequence = new ArrayList<SeqStep>(); 350 351 /* Create a SapServer. Fake the BtSocket using piped input/output streams*/ 352 inStream = new PipedInputStream(8092); 353 outStream = new PipedOutputStream(); 354 sapServer = new SapServer(null, mContext, new PipedInputStream(outStream, 8092), 355 new PipedOutputStream(inStream)); 356 sapServer.start(); 357 } 358 359 /* TODO: 360 * - Add support for actions ? 361 * */ 362 363 /** 364 * Enable/Disable test mode during the next connect request. 365 * @param enable 366 */ 367 public void testModeEnable(boolean enable) { 368 if(enable) 369 sapServer.setTestMode(SapMessage.TEST_MODE_ENABLE); 370 else 371 sapServer.setTestMode(SapMessage.TEST_MODE_DISABLE); 372 } 373 374 /** 375 * Add a test step to the sequencer 376 * @param request The request to send to the SAP server 377 * @param response The response to EXPECT from the SAP server 378 * @return The created step index, which can be used when adding events or actions. 379 */ 380 public int addStep(SapMessage request, SapMessage response) { 381 // TODO: should we add a step trigger? (in stead of just executing in sequence) 382 SeqStep newStep = new SeqStep(request, response); 383 sequence.add(newStep); 384 return sequence.indexOf(newStep); 385 } 386 387 /** 388 * Add a sub-step to a sequencer step. All requests added to the same index will be send to 389 * the SapServer in the order added before listening for the response. 390 * The response order is not validated - hence for each response received the entire list of 391 * responses in the step will be searched for a match. 392 * @param index the index returned from addStep() to which the sub-step is to be added. 393 * @param request The request to send to the SAP server 394 * @param response The response to EXPECT from the SAP server 395 */ 396 public void addSubStep(int index, SapMessage request, SapMessage response) { 397 SeqStep step = sequence.get(index); 398 step.add(request, response); 399 } 400 401 402 /** 403 * Run the sequence, by writing a request and wait for a response. Validate the response 404 * is either the expected response or one of the expected events. 405 * @return 406 */ 407 public boolean run() throws IOException { 408 SapMessage inMsg = null; 409 boolean done; 410 for(SeqStep step : sequence) { 411 412 /* Write all requests - if any */ 413 if(step.requests != null) { 414 for(SapMessage request : step.requests) { 415 if(request != null) { 416 Log.i(TAG, "Writing request: " + 417 SapMessage.getMsgTypeName(request.getMsgType())); 418 writeSapMessage(request, false); // write the message without flushing 419 } 420 } 421 writeSapMessage(null, true); /* flush the pipe */ 422 } 423 424 /* Handle and validate all responses - if any */ 425 if(step.hasResponse() == true) { 426 done = false; 427 boolean foundMatch = false; 428 SapMessage responseMatch; 429 while(!done) { 430 for(SapMessage response : step.responses) { 431 if(response != null) 432 Log.i(TAG, "Waiting for the response: " + 433 SapMessage.getMsgTypeName(response.getMsgType())); 434 } 435 inMsg = readSapMessage(); 436 if(inMsg != null) 437 Log.i(TAG, "Read message: " + 438 SapMessage.getMsgTypeName(inMsg.getMsgType())); 439 else 440 assertTrue("Failed to read message.", false); 441 442 responseMatch = null; 443 for(SapMessage response : step.responses) { 444 if(response != null 445 && inMsg.getMsgType() == response.getMsgType() 446 && compareSapMessages(inMsg, response) == true) { 447 foundMatch = true; 448 responseMatch = response; 449 break; 450 } 451 } 452 453 if(responseMatch != null) 454 step.responses.remove(responseMatch); 455 456 /* If we are expecting no more responses for this step, continue. */ 457 if(step.hasResponse() != true) { 458 done = true; 459 } 460 /* Ensure what we received was expected */ 461 assertTrue("wrong message received.", foundMatch); 462 } 463 } 464 } 465 return true; 466 } 467 468 private void startTimer() { 469 Message timeoutMessage = messageHandler.obtainMessage(MSG_ID_TIMEOUT); 470 messageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE); 471 } 472 473 private void stopTimer() { 474 messageHandler.removeMessages(MSG_ID_TIMEOUT); 475 } 476 477 /** 478 * Compare two messages by comparing each member variable 479 * @param received message 480 * @param expected message 481 * @return true if equal, else false 482 */ 483 private boolean compareSapMessages(SapMessage received, SapMessage expected) { 484 boolean retVal = true; 485 if(expected.getCardReaderStatus() != -1 && 486 received.getCardReaderStatus() != expected.getCardReaderStatus()) { 487 Log.i(TAG, "received.getCardReaderStatus() != expected.getCardReaderStatus() " 488 + received.getCardReaderStatus() + " != " + expected.getCardReaderStatus()); 489 retVal = false; 490 } 491 if(received.getConnectionStatus() != expected.getConnectionStatus()) { 492 Log.i(TAG, "received.getConnectionStatus() != expected.getConnectionStatus() " 493 + received.getConnectionStatus() + " != " + expected.getConnectionStatus()); 494 retVal = false; 495 } 496 if(received.getDisconnectionType() != expected.getDisconnectionType()) { 497 Log.i(TAG, "received.getDisconnectionType() != expected.getDisconnectionType() " 498 + received.getDisconnectionType() + " != " 499 + expected.getDisconnectionType()); 500 retVal = false; 501 } 502 if(received.getMaxMsgSize() != expected.getMaxMsgSize()) { 503 Log.i(TAG, "received.getMaxMsgSize() != expected.getMaxMsgSize() " 504 + received.getMaxMsgSize() +" != " + expected.getMaxMsgSize()); 505 retVal = false; 506 } 507 if(received.getMsgType() != expected.getMsgType()) { 508 Log.i(TAG, "received.getMsgType() != expected.getMsgType() " 509 + received.getMsgType() +" != " + expected.getMsgType()); 510 retVal = false; 511 } 512 if(received.getResultCode() != expected.getResultCode()) { 513 Log.i(TAG, "received.getResultCode() != expected.getResultCode() " 514 + received.getResultCode() + " != " + expected.getResultCode()); 515 retVal = false; 516 } 517 if(received.getStatusChange() != expected.getStatusChange()) { 518 Log.i(TAG, "received.getStatusChange() != expected.getStatusChange() " 519 + received.getStatusChange() + " != " + expected.getStatusChange()); 520 retVal = false; 521 } 522 if(received.getTransportProtocol() != expected.getTransportProtocol()) { 523 Log.i(TAG, "received.getTransportProtocol() != expected.getTransportProtocol() " 524 + received.getTransportProtocol() + " != " 525 + expected.getTransportProtocol()); 526 retVal = false; 527 } 528 if(!Arrays.equals(received.getApdu(), expected.getApdu())) { 529 Log.i(TAG, "received.getApdu() != expected.getApdu() " 530 + Arrays.toString(received.getApdu()) + " != " 531 + Arrays.toString(expected.getApdu())); 532 retVal = false; 533 } 534 if(!Arrays.equals(received.getApdu7816(), expected.getApdu7816())) { 535 Log.i(TAG, "received.getApdu7816() != expected.getApdu7816() " 536 + Arrays.toString(received.getApdu7816()) + " != " 537 + Arrays.toString(expected.getApdu7816())); 538 retVal = false; 539 } 540 if(expected.getApduResp() != null && !Arrays.equals(received.getApduResp(), 541 expected.getApduResp())) { 542 Log.i(TAG, "received.getApduResp() != expected.getApduResp() " 543 + Arrays.toString(received.getApduResp()) + " != " 544 + Arrays.toString(expected.getApduResp())); 545 retVal = false; 546 } 547 if(expected.getAtr() != null && !Arrays.equals(received.getAtr(), expected.getAtr())) { 548 Log.i(TAG, "received.getAtr() != expected.getAtr() " 549 + Arrays.toString(received.getAtr()) + " != " 550 + Arrays.toString(expected.getAtr())); 551 retVal = false; 552 } 553 return retVal; 554 } 555 556 private SapMessage readSapMessage() throws IOException { 557 startTimer(); 558 int requestType = inStream.read(); 559 Log.i(TAG,"Received message with type: " + SapMessage.getMsgTypeName(requestType)); 560 SapMessage msg = SapMessage.readMessage(requestType, inStream); 561 stopTimer(); 562 if(requestType != -1) { 563 return msg; 564 } else 565 { 566 assertTrue("Reached EOF too early...", false); 567 } 568 return msg; 569 } 570 571 private void writeSapMessage(SapMessage message, boolean flush) throws IOException { 572 startTimer(); 573 if(message != null) 574 message.write(outStream); 575 if(flush == true) 576 outStream.flush(); 577 stopTimer(); 578 } 579 580 @Override 581 public boolean handleMessage(Message msg) { 582 583 Log.i(TAG,"Handling message ID: " + msg.what); 584 585 switch(msg.what) { 586 case MSG_ID_TIMEOUT: 587 Log.w(TAG, "Timeout occured!"); 588 try { 589 inStream.close(); 590 } catch (IOException e) { 591 Log.e(TAG, "failed to close inStream", e); 592 } 593 try { 594 outStream.close(); 595 } catch (IOException e) { 596 Log.e(TAG, "failed to close outStream", e); 597 } 598 break; 599 default: 600 /* Message not handled */ 601 return false; 602 } 603 return true; // Message handles 604 } 605 606 } 607 608 } 609