transportPresentation function

TransportPresentation transportPresentation(
  1. WalletLocalizations l10n, {
  2. required TorState tor,
  3. 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,
      );
  }
}