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:
2026-09-17 08:17:28 +03:00
parent b02f28f6ce
commit 404fdf5a1c
13 changed files with 241 additions and 76 deletions
+49 -10
View File
@@ -1,9 +1,11 @@
"""HTTP OTA (SCP81 / GP RAM over HTTP) emulation.
Phase A: terminal-side BIP emulation (OPEN/SEND/RECEIVE/CLOSE CHANNEL) plus a
raw TCP capture listener. The card's BIP channel is always redirected to the
locally configured target (the future PSK TLS platform); the address the card
requested is only logged.
raw TCP capture listener. In the default redirect mode the card's BIP channel
is always redirected to the locally configured target (the future PSK TLS
platform) and the address the card requested is only logged; in passthru mode
the channel dials the destination the card requests in OPEN CHANNEL (TCP,
UICC in client mode, remote connection).
Reference behavior (TS 102 223 8.52-8.56, GP v2.2 Amendment B) is taken from
the captured real-terminal traces in samples/HTTP_OTA/traces:
@@ -154,6 +156,7 @@ class BipTerminal:
def __init__(self):
self.enabled = False
self.mode = 'redirect'
self.target = None
self.channels = {}
self.next_id = 1
@@ -213,10 +216,17 @@ class BipTerminal:
name='bip-monitor', daemon=True)
self._monitor.start()
def enable(self, host, port):
self.target = (host, int(port))
def enable(self, host=None, port=None, mode='redirect'):
"""Enable the BIP terminal.
'redirect' (default) pins one target: every channel goes there whatever
address the card requests. 'passthru' has no target at all: every
channel dials the destination the card requested in OPEN CHANNEL."""
self.mode = mode if mode in ('redirect', 'passthru') else 'redirect'
self.target = (host, int(port)) if host and port not in (None, '') else None
self.enabled = True
self.log('enabled', target='%s:%d' % self.target)
self.log('enabled', mode=self.mode,
target='%s:%d' % self.target if self.target else None)
self._start_monitor()
def disable(self):
@@ -271,12 +281,40 @@ class BipTerminal:
return cid
return None
def open(self, requested_host, requested_port, buffer_size):
"""Open a channel to the redirect target. Returns (channel_id, error)."""
if not self.enabled or not self.target:
def open(self, requested_host, requested_port, buffer_size, proto=None):
"""Open a channel.
Redirect modes connect to the pinned target; passthru dials the
destination the card sent in OPEN CHANNEL (Other address + Transport
level port). Returns (channel_id, error)."""
if not self.enabled:
return None, 'bip disabled'
target = self.target
requested = '%s:%s' % (requested_host, requested_port)
if self.mode == 'passthru':
# Use the card's request as-is: TCP, UICC in client mode, remote
# connection (TS 102 223 6.4.27.2 / 8.59). The specs define no
# default port, so an incomplete or non-TCP request fails.
host = (requested_host or '').strip()
try:
port = int(requested_port)
except (TypeError, ValueError):
port = 0
if proto != 0x02:
reason = 'card did not request TCP client transport (passthru)'
elif not host or host == '-':
reason = 'card did not request a destination address (passthru)'
elif not 0 < port <= 0xFFFF:
reason = 'card did not request a valid port (passthru)'
else:
reason = None
if reason:
self.log('open-fail', requested=requested, reason=reason)
return None, reason
target = (host, port)
else:
if not self.target:
return None, 'bip disabled'
target = self.target
cid = self._alloc_id()
if cid is None:
self.log('open-fail', requested=requested, reason='no free channel')
@@ -363,6 +401,7 @@ class BipTerminal:
})
return {
'enabled': self.enabled,
'mode': self.mode,
'target': '%s:%d' % self.target if self.target else None,
'channels': channels,
'seq': self.seq,