http ota: spec-correct retry timer, TLV and APDU edges (v2.2.12)
Audit against GP v2.2 Am.B 4.7 / TS 102 226 / GP Card Spec 11.11 found the retry waiting delay encoded as plain hex instead of the TP-SCTS semi-octet order required by TS 102 223 8.38 -> TS 23.040 9.1.2.3 (1 min must be 10, 20 s must be 02); the builder now encodes semi-octets, clamps 0-59 / 0-99 and pads the 2-byte counter. Odd-length hex is padded instead of producing fractional BER lengths; empty 83/84/89 sub-TLVs are omitted (lengths are 1-n per Tables 4-3/4-5/4-8..10) and an empty trigger becomes 81 00; a store payload above a short APDU is chained as P1.b8=0 STORE DATA blocks (P2 = block number). Connection presets fixed: device identities 82, alpha 05, command details 81, bearer 35/03; the A5 store tag and the 'B0,00=unlimited' counter hint are marked unverified (not in the pinned spec). Docs: UICC_SPECS.md 9.6.5 example annotated 2 s, findings 10-minute timer corrected to 1 minute; help updated. SW cache otaman-v177.
This commit is contained in:
@@ -21,7 +21,7 @@ function extractFunc(src, name) {
|
||||
return src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
const FNS = ['_hotaHex', '_hotaAsciiHex', '_hotaBerLen', '_hotaTlv',
|
||||
const FNS = ['_hotaHex', '_hotaAsciiHex', '_hotaBerLen', '_hotaPad', '_hotaTlv',
|
||||
'hotaTlv', 'hotaBuildConn', 'hotaBuildSec', 'hotaBuildRetry',
|
||||
'hotaBuildHttpPost', 'hotaBuildTrigger', 'hotaBuildStore', 'hotaBuild'];
|
||||
let code = '';
|
||||
@@ -68,7 +68,10 @@ test('hotaBuildConn produces a full 84 TLV', () => {
|
||||
]),
|
||||
'840B0103014001020281820500');
|
||||
assert.strictEqual(hotaBuildConn([{ tag: '02', value: '8182' }, { tag: '', value: 'FF' }]), '840402028182');
|
||||
assert.strictEqual(hotaBuildConn([]), '8400');
|
||||
// a half byte is padded, never turned into a fractional BER length
|
||||
assert.strictEqual(hotaBuildConn([{ tag: '35', value: '3' }]), '8403350103');
|
||||
// '84' is 1-n per Table 4-5: an empty container is left out entirely
|
||||
assert.strictEqual(hotaBuildConn([]), '');
|
||||
});
|
||||
|
||||
test('hotaBuildSec produces a full 85 TLV per Table 4-6', () => {
|
||||
@@ -81,21 +84,36 @@ test('hotaBuildSec produces a full 85 TLV per Table 4-6', () => {
|
||||
});
|
||||
|
||||
test('hotaBuildRetry uses TS 102 223 timer TLV (25 03) and wraps in 86', () => {
|
||||
// the delay fields use the TP-SCTS semi-octet order (TS 23.040 9.1.2.3):
|
||||
// within an octet the low nibble holds the most significant digit, so
|
||||
// 1 min = '10', 20 s = '02', 10 min = '01', 1 h 2 min 3 s = '10 20 30'
|
||||
assert.strictEqual(
|
||||
hotaBuildRetry({ counter: 'B000', delayH: 1, delayM: 2, delayS: 3, reportFailure: '' }),
|
||||
'8607B0002503010203');
|
||||
'8607B0002503102030');
|
||||
assert.strictEqual(
|
||||
hotaBuildRetry({ counter: 'b0 00', delayH: '1', delayM: '2', delayS: '3', reportFailure: '0A080102030405060708' }),
|
||||
'8611B00025030102030A080102030405060708');
|
||||
'8611B00025031020300A080102030405060708');
|
||||
// counter is 2 bytes mandatory: short input is left-padded, odd report dropped
|
||||
assert.strictEqual(
|
||||
hotaBuildRetry({ counter: '0000', delayH: 0, delayM: 0, delayS: 0, reportFailure: '0A0' }),
|
||||
'860700002503000000');
|
||||
// round-trip of the card's own 1-minute timer value 25 03 00 10 00
|
||||
assert.strictEqual(
|
||||
hotaBuildRetry({ counter: '0001', delayH: 0, delayM: 1, delayS: 0, reportFailure: '' }),
|
||||
'860700012503001000');
|
||||
assert.strictEqual(
|
||||
hotaBuildRetry({ counter: 'B0', delayH: 0, delayM: 0, delayS: 20, reportFailure: '' }),
|
||||
'860700B02503000002');
|
||||
assert.strictEqual(
|
||||
hotaBuildRetry({ counter: '0', delayH: 0, delayM: 10, delayS: 0, reportFailure: '' }),
|
||||
'860700002503000100');
|
||||
});
|
||||
|
||||
test('hotaBuildHttpPost wraps 8A/8B/8C in a full 89 TLV', () => {
|
||||
assert.strictEqual(
|
||||
hotaBuildHttpPost({ host: '', agent: '', uri: '' }),
|
||||
'89068A008B008C00');
|
||||
test('hotaBuildHttpPost wraps non-empty 8A/8B/8C in a full 89 TLV', () => {
|
||||
// '8A'..'8C' are 1-n per Tables 4-8/9/10: empty parameters are omitted
|
||||
assert.strictEqual(hotaBuildHttpPost({ host: '', agent: '', uri: '' }), '');
|
||||
assert.strictEqual(hotaBuildHttpPost({ host: 'megafon.ru' }), '890C8A0A6D656761666F6E2E7275');
|
||||
assert.strictEqual(hotaBuildHttpPost({ agent: 'v1.0' }), '89068B0476312E30');
|
||||
assert.strictEqual(
|
||||
hotaBuildHttpPost({ host: 'megafon.ru', agent: 'v1.0', uri: '/sd' }),
|
||||
'89178A0A6D656761666F6E2E72758B0476312E308C032F7364');
|
||||
@@ -104,11 +122,13 @@ test('hotaBuildHttpPost wraps 8A/8B/8C in a full 89 TLV', () => {
|
||||
test('hotaBuildTrigger wraps 81 > 83 > (84/85/86/89)', () => {
|
||||
const conn = '840402028182';
|
||||
const sec = '850400020101';
|
||||
const retry = '8607B0002503010203';
|
||||
const httpPost = '89068A008B008C00';
|
||||
const retry = '8607B0002503102030';
|
||||
const httpPost = '890C8A0A6D656761666F6E2E7275';
|
||||
assert.strictEqual(
|
||||
hotaBuildTrigger(conn, sec, retry, httpPost, false),
|
||||
'811F831D8404020281828504000201018607B000250301020389068A008B008C00');
|
||||
'812583238404020281828504000201018607B0002503102030890C8A0A6D656761666F6E2E7275');
|
||||
// '81' is mandatory but 0-n; without parameters it stays an empty container
|
||||
assert.strictEqual(hotaBuildTrigger('', '', '', '', false), '8100');
|
||||
});
|
||||
|
||||
test('hotaBuildTrigger expanded wraps the 81 command in Command Scripting template AA', () => {
|
||||
@@ -127,6 +147,19 @@ test('hotaBuildStore emits STORE DATA TLV-mode APDU (80 E2 90 00)', () => {
|
||||
assert.strictEqual(
|
||||
hotaBuildStore('840402028182', '', '', '', 'A5'),
|
||||
'80E2900008A506840402028182');
|
||||
// nothing to store -> no APDU
|
||||
assert.strictEqual(hotaBuildStore('', '', '', '', '85'), '');
|
||||
// parameters longer than a short APDU are chained: P1.b8=0 for all but
|
||||
// the last block, P2 = block number, BER-TLV coding throughout
|
||||
const big = '84' + _hotaBerLen(140) + 'AA'.repeat(140);
|
||||
const inner = '85' + _hotaBerLen(big.length / 2) + big;
|
||||
const blocks = [];
|
||||
for (let i = 0; i < inner.length; i += 127 * 2) blocks.push(inner.slice(i, i + 127 * 2));
|
||||
assert.strictEqual(blocks.length, 2);
|
||||
assert.strictEqual(
|
||||
hotaBuildStore(big, '', '', '', '85'),
|
||||
'80E210007F' + blocks[0] +
|
||||
'80E29001' + (blocks[1].length / 2).toString(16).padStart(2, '0').toUpperCase() + blocks[1]);
|
||||
});
|
||||
|
||||
test('hotaBuild dispatches on mode', () => {
|
||||
|
||||
Reference in New Issue
Block a user