ui: guided OPEN CHANNEL parameters for the BIP opening request (v3.5.0)

The 01 (BIP channel opening) variant was raw hex while 03 (TCP) had a detailed
form.  The asymmetry is real - 01 accepts any OPEN CHANNEL COMPREHENSION-TLVs
(TS 102 226 9.2.1) while 03's set is fixed by 9.2.3 - so 01 now offers a
Fields/Hex switch instead of a rigid form:

- Fields (default) composes the most-used OPEN CHANNEL parameters per
  SIMalliance Stepping Stones R7 18.6.1: bearer description (default 35 01 03),
  transport protocol (UDP 01 / TCP 02, UICC client, remote - the only values
  TS 102 223 6.6.27.4 allows when the transport level is present) with the
  destination port, destination address, NAA/APN, buffer size, alpha
  (transparent checkbox -> 05 00 = no user confirmation) and an extra-TLVs
  field for login/password and anything else.  The local address is never
  emitted (9.2.1 and the guide forbid it).
- Hex keeps the raw COMPREHENSION-TLVs field; pushOpenChannelTlvs() is a pure,
  vector-tested composer feeding the same encoder as before.

Help 2.7 EN/RU and the READMEs explain the two modes; sw cache ->
simple-v251; version stays 3.5.0.
This commit is contained in:
2026-09-23 22:58:22 +03:00
parent 052d90c03e
commit 82f45d488f
7 changed files with 144 additions and 17 deletions
+40 -8
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', 'pushSectionApdus'];
'chainApduList', 'chainBuildFcp', 'pushSectionApdus', 'pushOpenChannelTlvs'];
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']) {
@@ -229,8 +229,8 @@ test('CREATE FILE FCP template skeleton (TS 102 222 table 4)', () => {
test('pushSectionApdus builds the guided §9 channel/link requests', () => {
// BIP channel opening: OPEN CHANNEL TLVs are optional.
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', pushData: '350103' }), ['80EC010103350103']);
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', pushData: '' }), ['80EC0101']);
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '350103' }), ['80EC010103350103']);
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '' }), ['80EC0101']);
// CAT_TP: the destination port is mandatory (9.2.2).
assert.deepStrictEqual(pushSectionApdus('link', { request: '02', pushPort: '1F90' }), ['80EC0102053C03001F90']);
assert.deepStrictEqual(pushSectionApdus('link', { request: '02', pushPort: '' }), []);
@@ -250,19 +250,51 @@ test('pushSectionApdus builds the guided §9 channel/link requests', () => {
pushSectionApdus('link', { request: '03', pushPort: '0050', pushAddr: '210A000001', pushApn: 'internet', withIdp: true, idpIdent: '0102' }),
[tcp, '80EC01040436020102']);
// The follow-up only applies to the TCP request.
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', pushData: '', withIdp: true }), ['80EC0101']);
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '', withIdp: true }), ['80EC0101']);
// Stepping Stones R7 18.6: the BIP channel opening and the CAT_TP link
// request go in the same message; 01 carries the channel parameters, 02 the
// CAT_TP port (mandatory) plus optional max SDU / identification data.
assert.deepStrictEqual(
pushSectionApdus('link', { request: '01', pushData: '', withCat: true, pushPort: '1F90' }),
pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '', withCat: true, pushPort: '1F90' }),
['80EC0101', '80EC0102053C03001F90']);
assert.deepStrictEqual(
pushSectionApdus('link', { request: '01', pushData: '', withCat: true, pushPort: '1F90', pushSdu: '0200', pushIdent: 'ABCD' }),
pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '', withCat: true, pushPort: '1F90', pushSdu: '0200', pushIdent: 'ABCD' }),
['80EC0101', '80EC01020D3C03001F90390202003602ABCD']);
// The CAT_TP port is mandatory for the pair, and the option only applies to 01.
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', pushData: '', withCat: true, pushPort: '' }), []);
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', pushData: '', withCat: false, pushPort: '1F90' }), ['80EC0101']);
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '', withCat: true, pushPort: '' }), []);
assert.deepStrictEqual(pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '', withCat: false, pushPort: '1F90' }), ['80EC0101']);
});
test('pushOpenChannelTlvs composes the guided OPEN CHANNEL parameters', () => {
// The bearer defaults to the default bearer (35 01 03); the local address is
// never emitted (TS 102 226 9.2.1 / Stepping Stones 18.6.1).
assert.strictEqual(pushOpenChannelTlvs({}), '350103');
assert.strictEqual(pushOpenChannelTlvs({ ocBearer: '350104' }), '350104');
// Transport level (UDP or TCP client remote only, TS 102 223 6.6.27.4).
assert.strictEqual(pushOpenChannelTlvs({ ocProto: '01', ocPort: '1F90' }), '3501033C03011F90');
assert.strictEqual(pushOpenChannelTlvs({ ocProto: '02', ocPort: '0050' }), '3501033C03020050');
// Full set: transport level + destination address + NAA + buffer size +
// transparent alpha (05 00 = no user confirmation).
assert.strictEqual(
pushOpenChannelTlvs({ ocProto: '01', ocPort: '1F90', ocAddr: '210A000001', ocApn: 'internet',
ocBuffer: '0200', ocAlphaNull: true }),
'350103' + '3C03011F90' + '3E05210A000001' + '4708696E7465726E6574' + '39020200' + '0500');
// A custom alpha identifier and free-form extra TLVs (login/password, ...).
assert.strictEqual(pushOpenChannelTlvs({ ocAlpha: '00410042' }), '350103050400410042');
assert.strictEqual(pushOpenChannelTlvs({ ocExtra: '4708696E7465726E6574' }), '3501034708696E7465726E6574');
// Fields mode feeds the same encoder as the raw hex field.
assert.deepStrictEqual(
pushSectionApdus('link', { request: '01', ocMode: 'fields', ocProto: '01', ocPort: '1F90' }),
['80EC0101083501033C03011F90']);
assert.deepStrictEqual(
pushSectionApdus('link', { request: '01', ocMode: 'hex', pushData: '350103' }),
['80EC010103350103']);
// Fields mode plus the CAT_TP follow-up: the OPEN CHANNEL port is its own
// field, the CAT_TP port comes from the shared one.
assert.deepStrictEqual(
pushSectionApdus('link', { request: '01', ocMode: 'fields', ocProto: '01', ocPort: '1F90',
withCat: true, pushPort: '1F91' }),
['80EC0101083501033C03011F90', '80EC0102053C03001F91']);
});
test('chainApduList drops GET RESPONSE and splits multi-APDU rows', () => {