• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.nfc.emulatorapp
18 
19 import android.nfc.cardemulation.HostApduService
20 import android.nfc.cardemulation.PollingFrame
21 import android.os.Bundle
22 import androidx.lifecycle.MutableLiveData
23 import java.math.BigInteger
24 
25 /**
26  * Implementation of HostApduService that receives APDU commands from the reader and sends back
27  * responses.
28  */
29 class EmulatorHostApduService : HostApduService() {
30 
processPollingFramesnull31   override fun processPollingFrames(frames: List<PollingFrame>) {
32     for (frame in frames) {
33       viewModel.addLog("Received polling frame ${frame.toString()}")
34     }
35   }
36 
37   /**
38    * Processes the APDU command received from the reader and sends back a response. If the command
39    * was not found in the original snoop log, a failure response is sent instead.
40    */
processCommandApdunull41   override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray {
42     val command = commandApdu?.toHexString()
43     val responseList = apdus.value?.get(command)
44     if (responseList != null && responseList.isNotEmpty()) {
45       val response = responseList.removeAt(0)
46       viewModel.addLog(createApduLog(command, response))
47       return if (response.isEmpty()) ByteArray(0) else BigInteger(response, 16).toByteArray()
48     } else {
49       viewModel.addLog(createApduLog(command, FAILURE_RESPONSE.toHexString()))
50       return FAILURE_RESPONSE
51     }
52   }
53 
onDeactivatednull54   override fun onDeactivated(reason: Int) {
55     if (reason == DEACTIVATION_LINK_LOSS) {
56       viewModel.addLog("Service has been deactivated due to NFC link loss")
57     } else { // DEACTIVATION_DESELECTED
58       viewModel.addLog("Service has been deactivated due to a different AID being selected")
59     }
60   }
61 
toHexStringnull62   private fun ByteArray.toHexString(): String {
63     return this.joinToString("") { java.lang.String.format("%02x", it) }
64   }
65 
createApduLognull66   private fun createApduLog(command: String?, response: String): String {
67     if (command == null) {
68       return "Received null command"
69     }
70     if (responseDecoder.containsKey(response.lowercase())) {
71       return "Received command: $command\n\n     Sent response: ${responseDecoder[response]}"
72     } else {
73       return "Received command: $command\n\n     Sent response: $response"
74     }
75   }
76 
77   companion object {
78     private const val TAG = "EmulatorHostApduServiceLog"
79     private val FAILURE_RESPONSE = BigInteger("6f00", 16).toByteArray()
80     val apdus = MutableLiveData<Map<String, MutableList<String>>>()
81     val responseDecoder = mapOf("6f00" to "Failure", "6a82" to "AID not found")
82     lateinit var viewModel: EmulatorViewModel
83   }
84 }