transportPresentation function
TransportPresentation
transportPresentation(
- WalletLocalizations l10n, {
- required TorState tor,
- WalletHostTransport? host,
Map the SDK's TorState — or the HOST's own transport claim, which WINS
when provided (the host knows about tunnels the SDK cannot see) — to the
shared presentation. PRIVACY RULE (spec §3.3): every arm that cannot
POSITIVELY verify protection reads as not protected; only a verified Tor
runtime or an explicit host protection: true earns the protected tone.
Implementation
TransportPresentation transportPresentation(
WalletLocalizations l10n, {
required TorState tor,
WalletHostTransport? host,
}) {
if (host != null) {
return TransportPresentation(
label: host.label,
tone: host.protection ? TransportTone.protected : TransportTone.neutral,
detail:
host.detail ??
(host.protection
? l10n.walletTransportExplainHostProxy
: l10n.walletTransportExplainDirect),
);
}
switch (tor) {
case TorState_Off():
return TransportPresentation(
label: l10n.walletTorOff,
tone: TransportTone.neutral,
detail: l10n.walletTransportExplainDirect,
);
// FR-30 (a): the failing arms name the host's transport the way the two
// `Active` arms do, or name none at all — never the hard-coded noun "Tor"
// (a host that registered Shadowsocks read "Tor starting…" until C1).
case TorState_Bootstrapping(:final transport):
final named = _declaredTransport(transport);
return TransportPresentation(
label: named == null
? l10n.walletTorBootstrapping
: l10n.walletTorBootstrappingNamed(named),
tone: TransportTone.progress,
detail: named == null
? l10n.walletTransportExplainBootstrapping
: l10n.walletTransportExplainBootstrappingNamed(named),
);
case TorState_Active(:final runtime):
return switch (runtime) {
// The protection claim is only as trustworthy as the runtime making
// it: an UNKNOWN runtime (a forward-compat arm this binding can't
// attribute) must NOT inherit the confident green "Tor active" (§3.3
// privacy rule).
TorRuntimeKind_Unknown() => TransportPresentation(
label: l10n.walletTorActiveUnverified,
tone: TransportTone.caution,
detail: l10n.walletTransportExplainUnverified,
),
// FR-29 / ADR-0547: the dialer the host's native library registered —
// render WHAT THE HOST DECLARED and nothing more (spec §3.4): the
// host's OWN name for its path, whether the wallet's connections can
// be linked on it, and whether it hides the device's address. An
// exposed path is not private, whatever the policy said.
TorRuntimeKind_HostDialer(
:final name,
:final isolation,
:final exposure,
) =>
hostDialerPresentation(
l10n,
name: name,
isolation: isolation,
exposure: exposure,
sentences: HostDialerSentences.active,
),
// Two runtimes, not three: the SDK-owned `builtIn` was removed at
// FR-5 C1 (ADR-0548 D3) — a host without a transport of its own adds
// the `zec_wallet_tor` plugin, which arrives as a host dialer above.
//
// `ExternalSocks5` is the ONE arm that may still say "Tor": the
// config variant is "host-side Tor speaking SOCKS5 (Orbot, system
// Tor, a host sidecar)" (config.rs `TorRuntime`), so the host named
// Tor by choosing it — the SDK is repeating a declaration, not making
// one. It is refused at the config door today (T0-6, ADR-0543), so
// nothing reaches this arm in a shipping configuration.
TorRuntimeKind_ExternalSocks5() => TransportPresentation(
label: l10n.walletTorActive,
tone: TransportTone.protected,
detail: l10n.walletTransportExplainTor,
),
// FR-32 (a): `Dialer` is an arbitrary byte-stream dialer a Rust host
// injected, which `net/dialer.rs` says in as many words the SDK
// "never knows or names" — it declared NO name, NO isolation and NO
// exposure. It read "Tor active" in the PROTECTED tone until stage S1
// `copy`, which asserted onion routing, in the confident colour, for
// a path the SDK cannot attest — the §2.3 trust boundary the
// `HostDialer` arm above was built to respect, and the same class as
// FR-30 (a). It now says only what is true (wallet traffic is riding
// the path the app supplied) and refuses the privacy claim. A Rust
// host that KNOWS what it injected says so through
// `WalletHostTransport` at the top of this function, which wins over
// any SDK `TorState` — that is the "asks the host to say so" half of
// FR-32 (a)'s product call, and it already exists.
TorRuntimeKind_Dialer() => TransportPresentation(
label: l10n.walletTorActiveUnattested,
tone: TransportTone.caution,
detail: l10n.walletTransportExplainUnverified,
),
};
case TorState_FellBack():
return TransportPresentation(
label: l10n.walletTorFellBack,
tone: TransportTone.caution,
detail: l10n.walletTransportExplainFellBack,
);
case TorState_Unavailable(:final transport):
final named = _declaredTransport(transport);
return TransportPresentation(
label: named == null
? l10n.walletTorUnavailable
: l10n.walletTorUnavailableNamed(named),
tone: TransportTone.danger,
detail: named == null
? l10n.walletTransportExplainUnavailable
: l10n.walletTransportExplainUnavailableNamed(named),
);
// Stage S1 `truth` (FR-36): the path took the connection and nothing has
// come back over it for a minute — the path or the wallet server, and the
// SDK does not guess which. Not `danger`: nothing is known to have leaked
// and the state is not the genuinely-down one (that is `Unavailable`
// above, whose chip says "not connected"). Not protected either, since
// nothing is carrying.
//
// Stage S1 `copy` split the chip from the sheet — a label states the two
// attested facts, the sheet's detail carries the either/or AND the two
// next steps, one per cause (the `walletStallBirthdayInFuture`
// discipline: when the wallet cannot tell which of two things is wrong,
// it names both steps rather than picking one).
//
// FR-44: the payload is `Active`'s ENTIRE payload — name, isolation and
// exposure — so a `HostDialer` goes through the same function `Active`
// does, with this family's sentences. It read only the NAME until then,
// and a path the host declared exposed therefore read the hidden
// family's sentence: the privacy loss the `Active` chip discloses
// vanished the moment the path went quiet, and the host's own Network
// tab (graded per-exposure) disagreed with the wallet tab about one
// connection. One place decides whether a path may be called private;
// two places is how these two arms diverged.
case TorState_Unanswered(:final runtime):
return switch (runtime) {
TorRuntimeKind_HostDialer(
:final name,
:final isolation,
:final exposure,
) =>
hostDialerPresentation(
l10n,
name: name,
isolation: isolation,
exposure: exposure,
sentences: HostDialerSentences.unanswered,
),
// `ExternalSocks5` reads the transport-neutral pair: the host named Tor
// by choosing that config variant, so the unqualified sentence repeats
// a declaration rather than making one.
//
// Exhaustive and wildcard-free like the sibling switches: a runtime
// added to the bridge is a compile error HERE. (Not at the bridge — see
// the note on `TorRuntimeKind_Unknown` below.)
TorRuntimeKind_ExternalSocks5() => TransportPresentation(
label: l10n.walletTorUnanswered,
tone: TransportTone.caution,
detail: l10n.walletTransportExplainUnanswered,
),
// `Dialer` and `Unknown` KEEP ACTIVE'S REFUSAL. They sat in the
// arm above until the review, on the reasoning that they carry no
// exposure and so have nothing for FR-44's switch to read. That is true
// and it is beside the point: what `Active` refuses for these two
// runtimes is not an exposure claim but an ATTESTATION claim. `Dialer`
// is an arbitrary byte-stream dialer a Rust host injected, which
// `net/dialer.rs` says the SDK "never knows or names", and `Active`
// says so in its label AND its detail ("treat it as not private").
//
// Dropping that on the way into `unanswered` made the wallet's claim
// STRONGER at the moment it knew less: "Private path in use (privacy
// not verified)" while the path carried, plain "Private path connected"
// once it went quiet. Same class as FR-44, same stage, different
// runtime — and the tone cannot carry the difference, because both arms
// are `caution`. `Unknown` is unreachable until the core enum gains a
// variant, and is here so that the day it becomes reachable it is
// already honest.
TorRuntimeKind_Dialer() ||
TorRuntimeKind_Unknown() => TransportPresentation(
label: l10n.walletTorUnansweredUnattested,
tone: TransportTone.caution,
detail: l10n.walletTransportExplainUnansweredUnverified,
),
};
// PRIVACY RULE (spec §3.3): an uninterpretable Tor state is treated as
// NOT protected — never inherit a benign framing.
case TorState_Unknown():
return TransportPresentation(
label: l10n.walletTorUnknown,
tone: TransportTone.caution,
detail: l10n.walletTransportExplainUnverified,
);
}
}