scp81: rename passthru to redirect, add a true passthru mode (v2.2.13)
The former 'passthru' mode is now 'redirect': it pins the configured target and every BIP channel is connected there (the card's requested address is only logged; host/port required). The name 'passthru' now means the new mode: no listener and no target - each channel dials the destination the card requests in OPEN CHANNEL (Other address + Transport level port, TCP client, remote, 0x02 only). The specs define no default port (TS 102 223 8.59), so an incomplete or non-TCP request fails the channel with result 3A and an open-fail log reason. BipTerminal gains a mode (enable(host, port, mode=...), open(..., proto=...)), reports it in status(), and the control API/status expose redirect (target) and passthru (per-channel targets). The PWA mode selector shows four modes with per-mode notes and disables Host/Port in passthru; the status line shows each channel's actual target. Docs, help (EN/RU), READMEs and the AGENTS notes updated; SW cache otaman-v179. Breaking API change: mode:'passthru' has the new semantics - use mode:'redirect' for the previous behavior (no alias).
This commit is contained in:
@@ -323,7 +323,8 @@
|
||||
<p class="mb-2">Кнопки <strong>Start</strong> / <strong>Остановить</strong> управляют целью; <strong>Перезапустить скрипт</strong> ставит выбранный скрипт в очередь заново с <code class="font-mono text-sm">force</code>, начиная прогон с первого APDU. Режимы:</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>PSK TLS сервер</strong> (по умолчанию) — PSK TLS-слушатель на <strong>Хост</strong>:<strong>Порт</strong>, отвечающий PSK-наборами TLS 1.2 из спецификации и ведущий HTTP-диалог административной сессии GP (заголовки <code class="font-mono text-sm">X-Admin-*</code>, <code class="font-mono text-sm">200</code> со строкой команд или <code class="font-mono text-sm">204 No Content</code>). PSK-ключи берутся из предустановок карт (вкладка <strong>«Карты»</strong>): ключ выбирается по идентификатору, который карта присылает в рукопожатии; если ни у одной предустановки нет пары, <strong>Start</strong> не запускается. Ключ не сохраняется и не записывается в журнал; незнакомый идентификатор помечается в журнале как <code class="font-mono text-sm">tls-psk-unknown</code>.</li>
|
||||
<li><strong>Проброс (внешний сервер)</strong> — без локального слушателя: каждый BIP-канал подключается к указанной внешней платформе (нужны <strong>Хост</strong> и <strong>Порт</strong>), которая завершает TLS и ведёт административный диалог; запрошенный картой адрес только журналируется.</li>
|
||||
<li><strong>Перенаправление на внешний сервер</strong> — без локального слушателя: каждый BIP-канал подключается к указанной цели (нужны <strong>Хост</strong> и <strong>Порт</strong>), которая завершает TLS и ведёт административный диалог; запрошенный картой адрес только журналируется.</li>
|
||||
<li><strong>Проброс (адрес карты)</strong> — без слушателя и без цели: терминал подключает каждый BIP-канал к адресу, который карта запрашивает в OPEN CHANNEL (Other address + порт Transport level, только TCP-клиент, удалённое соединение; спецификации не задают порт по умолчанию, поэтому неполный запрос отклоняет канал). TLS завершается на той платформе, и используется сеть сервера (только для лаборатории).</li>
|
||||
<li><strong>Захват (dump)</strong> — принимает TCP-канал карты и записывает всё, что она отправляет (например, TLS ClientHello), не отвечая.</li>
|
||||
</ul>
|
||||
<p class="mb-2"><strong>Сценарий</strong> выбирает список команд, отдаваемый в сессии — <strong>Нет</strong> (оставить настроенный на сервере скрипт) или один из скриптов подвкладки <strong>«Скрипты»</strong>; выбранный список передаётся серверу при старте слушателя.</p>
|
||||
|
||||
+2
-1
@@ -323,7 +323,8 @@
|
||||
<p class="mb-2"><strong>Start</strong> / <strong>Stop</strong> control the target; <strong>Restart script</strong> queues the selected script again with <code class="font-mono text-sm">force</code>, restarting its run from the first APDU. Modes:</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>PSK TLS server</strong> (default) — a PSK TLS listener on <strong>Host</strong>:<strong>Port</strong> that answers with the TLS 1.2 PSK cipher suites of the spec and speaks the GP HTTP administration dialog (<code class="font-mono text-sm">X-Admin-*</code> headers, <code class="font-mono text-sm">200</code> with a command string or <code class="font-mono text-sm">204 No Content</code>). PSK keys come from the card presets (<strong>Cards</strong> tab): the listener picks the key by the identity the card sends in the handshake, and <strong>Start</strong> is refused when no preset has both parts. The key is never stored or logged; <code class="font-mono text-sm">tls-psk-unknown</code> marks an unrecognised identity.</li>
|
||||
<li><strong>Pass-through (external server)</strong> — no local listener: every BIP channel is connected to the configured external platform (<strong>Host</strong> and <strong>Port</strong> required), which terminates TLS and runs the administration dialog; the address the card requests is only logged.</li>
|
||||
<li><strong>Redirect to external server</strong> — no local listener: every BIP channel is connected to the configured target (<strong>Host</strong> and <strong>Port</strong> required), which terminates TLS and runs the administration dialog; the address the card requests is only logged.</li>
|
||||
<li><strong>Pass-through (card destination)</strong> — no listener and no target: the terminal connects each BIP channel to the destination the card requests in OPEN CHANNEL (Other address + Transport level port, TCP client remote only; the specs define no default port, so an incomplete request fails the channel). TLS is terminated by that platform, and the server's network is used (lab only).</li>
|
||||
<li><strong>Capture (dump)</strong> — accepts the card's TCP channel and logs whatever it sends (e.g. the TLS ClientHello) without answering.</li>
|
||||
</ul>
|
||||
<p class="mb-2"><strong>Script</strong> selects the command list served over the session — <strong>None</strong> (leave the server's configured script) or one of the scripts from the <strong>Scripts</strong> pill; the chosen list is sent to the server when the listener starts.</p>
|
||||
|
||||
+32
-18
@@ -18,7 +18,7 @@
|
||||
<div class="max-w-7xl mx-auto px-6 py-2">
|
||||
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.2.12</span></h1>
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.2.13</span></h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
||||
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
||||
@@ -969,7 +969,8 @@
|
||||
<label class="block mb-1 text-xs font-medium text-gray-600 dark:text-slate-400" data-l10n="Mode">Mode</label>
|
||||
<select id="scp81-mode" onchange="scp81ModeChanged()" class="border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800">
|
||||
<option value="tls" data-l10n="PSK TLS server" selected>PSK TLS server</option>
|
||||
<option value="passthru" data-l10n="Pass-through (external server)">Pass-through (external server)</option>
|
||||
<option value="redirect" data-l10n="Redirect to external server">Redirect to external server</option>
|
||||
<option value="passthru" data-l10n="Pass-through (card destination)">Pass-through (card destination)</option>
|
||||
<option value="dump" data-l10n="Capture (dump)">Capture (dump)</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -991,7 +992,8 @@
|
||||
</div>
|
||||
<div id="scp81-msg" class="text-xs mt-2 hidden"></div>
|
||||
<div id="scp81-psk-note" class="mt-2 text-xs text-gray-500 dark:text-slate-400" data-l10n="PSK keys come from the card presets (Cards tab): the listener picks the key by the identity the card sends in the TLS handshake.">PSK keys come from the card presets (Cards tab): the listener picks the key by the identity the card sends in the TLS handshake.</div>
|
||||
<div id="scp81-passthru-note" class="hidden mt-2 text-xs text-gray-500 dark:text-slate-400" data-l10n="Pass-through: every BIP channel the card opens is connected to this Host:Port (the external HTTP OTA platform); TLS is terminated there, the card's requested address is only logged.">Pass-through: every BIP channel the card opens is connected to this Host:Port (the external HTTP OTA platform); TLS is terminated there, the card's requested address is only logged.</div>
|
||||
<div id="scp81-redirect-note" class="hidden mt-2 text-xs text-gray-500 dark:text-slate-400" data-l10n="Redirect: every BIP channel the card opens is connected to this Host:Port (the external HTTP OTA platform); TLS is terminated there, and the address the card requests is only logged.">Redirect: every BIP channel the card opens is connected to this Host:Port (the external HTTP OTA platform); TLS is terminated there, and the address the card requests is only logged.</div>
|
||||
<div id="scp81-passthru-note" class="hidden mt-2 text-xs text-gray-500 dark:text-slate-400" data-l10n="Pass-through: the terminal connects each BIP channel to the destination the card requests in OPEN CHANNEL (Other address + transport port, TCP client only); Host and Port above are not used, TLS is terminated by that platform, and the server's network is used (lab only).">Pass-through: the terminal connects each BIP channel to the destination the card requests in OPEN CHANNEL (Other address + transport port, TCP client only); Host and Port above are not used, TLS is terminated by that platform, and the server's network is used (lab only).</div>
|
||||
</div>
|
||||
<div class="border border-gray-200 dark:border-slate-700 rounded p-3 mb-3">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
@@ -7764,7 +7766,7 @@ async function pysimProactiveLogRender() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== SCP81: HTTP OTA listener (dump capture / PSK TLS server) =====
|
||||
// ===== SCP81: HTTP OTA listener (PSK TLS / redirect / passthru / capture) =====
|
||||
let _scp81Timer = null;
|
||||
let _scp81LastStatus = null;
|
||||
|
||||
@@ -7774,8 +7776,18 @@ function scp81ModeChanged() {
|
||||
if (row) row.style.display = (mode === 'tls') ? '' : 'none';
|
||||
const pskNote = document.getElementById('scp81-psk-note');
|
||||
if (pskNote) pskNote.classList.toggle('hidden', mode !== 'tls');
|
||||
const directNote = document.getElementById('scp81-passthru-note');
|
||||
if (directNote) directNote.classList.toggle('hidden', mode !== 'passthru');
|
||||
const redirectNote = document.getElementById('scp81-redirect-note');
|
||||
if (redirectNote) redirectNote.classList.toggle('hidden', mode !== 'redirect');
|
||||
const passthruNote = document.getElementById('scp81-passthru-note');
|
||||
if (passthruNote) passthruNote.classList.toggle('hidden', mode !== 'passthru');
|
||||
// passthru dials the destination from the card's OPEN CHANNEL: the
|
||||
// configured target fields do not apply.
|
||||
for (const id of ['scp81-host', 'scp81-port']) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) continue;
|
||||
el.disabled = (mode === 'passthru');
|
||||
el.classList.toggle('opacity-40', mode === 'passthru');
|
||||
}
|
||||
}
|
||||
|
||||
function scp81SwitchSubtab(name) {
|
||||
@@ -8109,7 +8121,7 @@ async function scp81StatusRefresh() {
|
||||
el.textContent = t('not running');
|
||||
return st;
|
||||
}
|
||||
let s = l.mode + ' ' + l.host + ':' + l.port;
|
||||
let s = l.mode + (l.host && l.port ? ' ' + l.host + ':' + l.port : '');
|
||||
if (l.mode === 'tls') {
|
||||
const ids = l.psk_identities || [];
|
||||
s += ' | PSK: ' + ids.length;
|
||||
@@ -8121,7 +8133,7 @@ async function scp81StatusRefresh() {
|
||||
: t('unknown identity')) + ']';
|
||||
}
|
||||
}
|
||||
const ch = (bip.channels || []).map(c => 'ch' + c.id + ' in:' + c.bytes_in + ' out:' + c.bytes_out).join(', ');
|
||||
const ch = (bip.channels || []).map(c => 'ch' + c.id + (c.target ? ' → ' + c.target : '') + ' in:' + c.bytes_in + ' out:' + c.bytes_out).join(', ');
|
||||
if (ch) s += ' | ' + ch;
|
||||
el.textContent = s;
|
||||
return st;
|
||||
@@ -8152,15 +8164,15 @@ async function scp81Start() {
|
||||
const mode = document.getElementById('scp81-mode').value;
|
||||
const hostVal = document.getElementById('scp81-host').value.trim();
|
||||
const portVal = document.getElementById('scp81-port').value.trim();
|
||||
if (mode === 'passthru' && (!hostVal || !portVal)) {
|
||||
scp81Msg(t('Pass-through requires the target host and port'), 'text-red-500');
|
||||
if (mode === 'redirect' && (!hostVal || !portVal)) {
|
||||
scp81Msg(t('Redirect requires the target host and port'), 'text-red-500');
|
||||
return;
|
||||
}
|
||||
const body = {
|
||||
action: 'start', mode: mode,
|
||||
host: hostVal || '127.0.0.1',
|
||||
port: parseInt(portVal || '8443', 10),
|
||||
};
|
||||
const body = { action: 'start', mode: mode };
|
||||
if (mode !== 'passthru') {
|
||||
body.host = hostVal || '127.0.0.1';
|
||||
body.port = parseInt(portVal || '8443', 10);
|
||||
}
|
||||
if (mode === 'tls') {
|
||||
const map = cardsPskMap();
|
||||
if (!map.length) {
|
||||
@@ -11433,9 +11445,11 @@ const LANG_RU = {
|
||||
'LOAD blocks': 'Блоки LOAD',
|
||||
'clamped from': 'ограничено с',
|
||||
'auto-fit': 'авто',
|
||||
'Pass-through (external server)': 'Проброс (внешний сервер)',
|
||||
'Pass-through: every BIP channel the card opens is connected to this Host:Port (the external HTTP OTA platform); TLS is terminated there, the card\'s requested address is only logged.': 'Проброс: каждый открываемый картой BIP-канал подключается к этому Host:Port (внешняя платформа HTTP OTA); TLS завершается там, запрошенный картой адрес только журналируется.',
|
||||
'Pass-through requires the target host and port': 'Для режима проброса нужны host и port внешнего сервера',
|
||||
'Redirect to external server': 'Перенаправление на внешний сервер',
|
||||
'Pass-through (card destination)': 'Проброс (адрес карты)',
|
||||
'Redirect: every BIP channel the card opens is connected to this Host:Port (the external HTTP OTA platform); TLS is terminated there, and the address the card requests is only logged.': 'Перенаправление: каждый открываемый картой BIP-канал подключается к этому Host:Port (внешняя платформа HTTP OTA); TLS завершается там, а запрошенный картой адрес только журналируется.',
|
||||
'Pass-through: the terminal connects each BIP channel to the destination the card requests in OPEN CHANNEL (Other address + transport port, TCP client only); Host and Port above are not used, TLS is terminated by that platform, and the server\'s network is used (lab only).': 'Проброс: терминал подключает каждый BIP-канал к адресу, который карта запрашивает в OPEN CHANNEL (Other address + порт Transport level, только TCP-клиент); поля Host и Port выше не используются, TLS завершается на той платформе, и используется сеть сервера (только для лаборатории).',
|
||||
'Redirect requires the target host and port': 'Для перенаправления нужны host и port внешнего сервера',
|
||||
'ADM verified': 'ADM подтверждён',
|
||||
'ADM not verified': 'ADM не подтверждён',
|
||||
'TERMINAL PROFILE': 'TERMINAL PROFILE',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v178';
|
||||
const CACHE = 'otaman-v179';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -168,3 +168,14 @@ test('every help anchor used by the UI exists in help.html', () => {
|
||||
assert.ok(html.includes("'scp81-listener' : 'scp81-scripts'"));
|
||||
assert.ok(html.includes("? 'scp81-scripts' : 'scp81-listener'"));
|
||||
});
|
||||
|
||||
test('SCP81 listener exposes its four modes with the matching notes', () => {
|
||||
for (const v of ['tls', 'redirect', 'passthru', 'dump']) {
|
||||
assert.ok(html.includes('value="' + v + '"'), v);
|
||||
}
|
||||
assert.ok(html.includes('id="scp81-redirect-note"'));
|
||||
assert.ok(html.includes('id="scp81-passthru-note"'));
|
||||
// redirect needs the configured target; passthru uses the card's one
|
||||
assert.ok(html.includes("mode === 'redirect' && (!hostVal || !portVal)"));
|
||||
assert.ok(html.includes("el.disabled = (mode === 'passthru')"));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user