Read the original article:Stage The Summary of Learnning HCE
Problem Description
Developers implementing HCE (Host Card Emulation) on smart devices face challenges in handling foreground and background modes, processing long APDU instructions, and managing response timeouts. Key problems include:
- Difficulty in understanding HCE service lifecycle in foreground vs. background modes.
- Handling overlong APDU commands exceeding NFC transmit limits.
- Managing response timeouts when NFC I/O operations fail.
The goal is to establish reliable NFC communication between the HCE service on the device and external NFC readers while maintaining correct session handling and command processing.
Background Knowledge
- HCE Modes
| Mode | Foreground | Background |
|---|---|---|
| Work Mode | Only works when the app is in the foreground | Works in foreground or background |
| Config (module.json5) | actions: HOST_APDU_SERVICE; permissions: NFC_CARD_EMULATION | actions: HOST_APDU_SERVICE; permissions: NFC_CARD_EMULATION; metadata: aid |
| onCreate | - | Listen for HCE commands |
| onDestroy | Stop HCE service | - |
| onForeground | Bind AIDs; Start HCE service; Listen for HCE commands | - |
| Foreground stop | Stop HCE service | - |
- Interaction Logic
- Step 1: HCE service binds AIDs on the device.
-
Step 2: NFC reader sends
SELECT AIDcommand. Device chooses the correct HCE service; session established. - Step 3: NFC reader sends APDU command; HCE service responds.
- Select AID Instruction Example
0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06
-
0x00, 0xA4, 0x04, 0x00→ APDU select command -
0x07→ length of AID -
0xA0 0x01 0x02 0x03 0x04 0x05 0x06→ AID
Troubleshooting Process
-
Response Timeout
- Error Code:
3100204→ NFC tag I/O operation failed. - Check logs for
transceiveTimeout. - Solution:
- Use
getTimeout()to obtain timeout period. - Reset timeout using
setTimeout()if exceeded. - Ensure HCE service properly receives and responds.
- Use
- Error Code:
-
Overlong Instruction Handling
- Use
getMaxTransmitSize()to obtain maximum transmit size. - Split instructions exceeding maximum length into packets.
- Send packets sequentially; receiver assembles the full command.
- Custom packet structure example:
Packet ID + Total packets + Sequence number + Instruction fragment - Use
- HCE cannot actively send commands; responses must be queried by the card reader.
Example: Sending long command from card reader
async dealLongTransmitSend(command: number[], tag: tag.TagSession) {
let usefulLen: number = this.transmitMaxLen - PART_SEND.length - 2;
let totalPacks: number = Math.floor((command.length - 1) / usefulLen) + 1;
for (let i = 0; i < totalPacks; i++) {
let curCommand: number[] = [];
appendArr(curCommand, PART_SEND);
curCommand.push(totalPacks);
curCommand.push(i);
let end = Math.min((i + 1) * usefulLen, command.length);
appendArr(curCommand, command.slice(i * usefulLen, end));
await tag.transmit(curCommand);
}
}
HCE side parses long command
async dealCommandWithLongTransmit(command: number[]): Promise<number[]> {
if (isStartWith(PART_SEND, command)) {
removeHead(command, PART_SEND.length);
let total = command.shift();
let sortNo = command.shift();
let length = command.shift();
if (total === undefined || sortNo === undefined || length === undefined || command.length < length) {
log.error("Cannot parse command");
return DEFAULT_RESP;
}
if (sortNo == 0) { this.tempArr = []; appendArr(this.tempArr, command); }
else { removeHead(command, length); appendArr(this.tempArr, command); }
if (sortNo == total - 1) { this.dealCommand(this.tempArr); }
return DEFAULT_RESP;
} else if (isStartWith(PART_RECEIVE, command)) {
removeHead(command, PART_RECEIVE.length);
let sortNo = command.shift();
if (sortNo === undefined || sortNo < 0) { log.error("PART_RECEIVE error"); return DEFAULT_RESP; }
let begin = Math.min(sortNo * this.transmitMaxLen, this.tempArr.length);
let end = Math.min((sortNo + 1) * this.transmitMaxLen, this.tempArr.length);
let resp: number[] = []; appendArr(resp, this.tempArr.slice(begin, end));
return resp;
} else {
return this.dealCommand(command);
}
}
Analysis Conclusion
- HCE communication requires strict packetized instruction handling.
- Timeouts and maximum transmit sizes must be carefully managed to avoid NFC errors.
- Foreground and background HCE modes behave differently; developers must bind AIDs and start services correctly.
Solution
-
HCE Mode Management
- Foreground mode: Bind AIDs, start service, and listen for commands.
- Background mode: Service continues listening without onForeground binding.
-
Response Timeout Handling
- Use
getTimeout()andsetTimeout()to manage NFC I/O timeout.
- Use
-
Long Command Transmission
- Split commands into packets.
- Use sequential transmission and assembly on HCE side.
- Use custom packet structure to ensure proper reconstruction.
-
APDU Command Processing
- Handle
SELECT AIDcommands according to APDU protocol. - Process commands sequentially to maintain NFC session consistency.
- Handle
-
Best Practices
- Log errors and packet assembly issues.
- Ensure card reader queries each segment of long instructions.
- Avoid concurrent commands to maintain session integrity.

Top comments (0)