feat: Push commands pill — GP administration trigger + TS 102 226 §9 pushes (v3.5.0)

The former HTTP OTA pill becomes "Push commands" (RU: Пуш/триггер) and groups
everything that makes the card dial out:

- Administrative session (GP Amd B §4.7): the existing Trigger/Store UI,
  unchanged, as the first section (most used).
- BIP / CAT_TP trigger (§9): request 01 BIP channel opening (optional OPEN
  CHANNEL COMPREHENSION-TLVs) or 02 CAT_TP link (destination port, optional
  max SDU / identification data).
- TCP trigger (§9): request 03 TCP connection (bearer, transport level with
  protocol type 02, destination address 21/57/F0, NAA/APN, extra TLVs) or
  04 identification packet (optional data, ICCID when absent — sent over an
  already open channel).

The guided sections reuse chainPushData (the RAM/GP chain PUSH encoder) and
preview the C-APDU; mandatory parameters (02 port, 03 port + address) are
enforced there while the chain row stays lenient.  Actions: Pack into Secured
packet (no auto-TAR — a §9 PUSH goes to the target application) and
→ Expanded Script (22 Command TLV import; berAppendApdu extracted from
loadExpandedScript).  Each §9 request has a note separating it from the
administration session.  Chain-builder PUSH command and the expanded-script
C-APDU picker are unchanged.

Tests: pushSectionApdu vectors in ts102226.test.js; help 2.7 EN/RU
restructured, READMEs, AGENTS; sw cache -> simple-v246.
This commit is contained in:
2026-09-23 08:57:59 +03:00
parent cab54a5504
commit f17e7cb8da
9 changed files with 250 additions and 18 deletions
+19 -1
View File
@@ -31,7 +31,7 @@ function extractFunc(src, name) {
const FNS = ['berLenStr', 'buildApdu', 'buildSelect', 'escHtml', 'esc', 'chainInit',
'chainKind', 'chainIsEmbedded', 'chainCommands', 'chainBuildRowHex',
'chainSimBuildRowHex', 'chainRamBuildRowHex', 'chainPushData', '_hotaAsciiHex',
'chainApduList', 'chainBuildFcp'];
'chainApduList', 'chainBuildFcp', 'pushSectionApdu'];
let code = '';
for (const f of FNS) code += extractFunc(html, f) + '\n';
for (const c of ['CHAIN_CMDS_SIM', 'CHAIN_CMDS_USIM', 'CHAIN_CMDS_RAM']) {
@@ -227,6 +227,24 @@ test('CREATE FILE FCP template skeleton (TS 102 222 table 4)', () => {
assert.strictEqual(r4.fields.fcp, '620883026F0780020010');
});
test('pushSectionApdu builds the guided §9 push commands (Remote APDU pill)', () => {
// BIP channel opening: OPEN CHANNEL TLVs are optional.
assert.strictEqual(pushSectionApdu('bip', { request: '01', pushData: '350103' }), '80EC010103350103');
assert.strictEqual(pushSectionApdu('bip', { request: '01', pushData: '' }), '80EC0101');
// CAT_TP: the destination port is mandatory (9.2.2).
assert.strictEqual(pushSectionApdu('bip', { request: '02', pushPort: '1F90' }), '80EC0102053C03001F90');
assert.strictEqual(pushSectionApdu('bip', { request: '02', pushPort: '' }), '');
// TCP: port and destination address are mandatory (9.2.3).
assert.strictEqual(
pushSectionApdu('tcp', { request: '03', pushPort: '0050', pushAddr: '210A000001', pushApn: 'internet' }),
'80EC010319' + '3501033C030200503E05210A0000014708696E7465726E6574');
assert.strictEqual(pushSectionApdu('tcp', { request: '03', pushPort: '0050' }), '');
assert.strictEqual(pushSectionApdu('tcp', { request: '03', pushAddr: '210A000001' }), '');
// Identification packet: optional data, ICCID is used when absent (9.2.4).
assert.strictEqual(pushSectionApdu('tcp', { request: '04', pushIdent: '0102' }), '80EC01040436020102');
assert.strictEqual(pushSectionApdu('tcp', { request: '04', pushIdent: '' }), '80EC0104');
});
test('chainApduList drops GET RESPONSE and splits multi-APDU rows', () => {
_chains['ber-usim-1'] = { rows: [
row('read-binary', { offset: '0000', le: '00' }),