Files
punktfunk/crates/punktfunk-core/src/error.rs
T
enricobuehler 1fc9ef0050
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 52s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 44s
ci / bench (push) Successful in 5m56s
flatpak / build-publish (push) Successful in 6m41s
docker / deploy-docs (push) Successful in 27s
apple / swift (push) Successful in 4m40s
deb / build-publish (push) Successful in 12m7s
arch / build-publish (push) Successful in 14m10s
android / android (push) Successful in 17m16s
ci / rust (push) Successful in 17m57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m50s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m15s
windows-host / package (push) Successful in 14m33s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m55s
release / apple (push) Successful in 25m31s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m49s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m11s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m9s
apple / screenshots (push) Successful in 19m41s
feat(core,host,clients): typed pairing rejections — every client says WHY, not "not accepted"
A host's pairing-gate rejections (not armed / bound to another device /
rate-limited / identity required / denied / approval timeout / superseded /
wire-version mismatch) used to drop the connection with a bare code-0 close,
and every client collapsed that — plus plain unreachability — into one
"wrong PIN / not accepted" message. A dead network path, a disarmed host,
and an operator denial were indistinguishable, which is exactly the
misdiagnosis behind the recent Android pairing support thread.

- core: new ungated `reject` module — shared close-code block 0x60–0x67
  (+ 0x42 busy promoted from the host), `RejectReason`, and
  `PunktfunkError::Rejected`; `pair()`/`connect()` decode the host's
  ApplicationClosed code into `Rejected` instead of a generic Io error.
  C ABI v7: status block −20…−28 and `punktfunk_connect_ex8` (`status_out`
  reports the failure cause; NULL-return alone can't). Wire unchanged —
  old peers see exactly the old bare close.
- host: every gate rejection `conn.close()`s with its typed code (and the
  human reason as close bytes) before erroring out of the session task.
- pf-client-core: shared `pair_error_message`/`connect_reject_message`
  wording consumed by the Windows + Linux + console-UI + CLI surfaces; a
  connect failure now renders the host's stated reason.
- android: `nativeTakeLastError()` JNI token + `ConnectErrors.kt` — a
  network timeout is no longer reported as "wrong PIN, or the host isn't
  armed", and a typed rejection skips the wake-and-wait fallback (the host
  is demonstrably awake).
- apple: `HostRejection` + `.rejected`; the pair sheet and session alerts
  show the stated reason; connect moves to `ex8`.

Completes the cross-client half of the hunks that rode along in 12148243
(client.rs / trust.rs / punktfunk1.rs) — main did not build without this.

Validated: workspace clippy -D warnings + full test suite green on .21
(EXIT=0, 309 host / 148 core suites); macOS core 147+c_abi green; swift
build green; Android Kotlin + native crate green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:58:43 +02:00

97 lines
3.7 KiB
Rust

//! Error type and the stable C ABI status codes it maps to.
use thiserror::Error;
/// The core's internal error type. Crosses the C ABI as a [`PunktfunkStatus`] code.
#[derive(Debug, Error)]
pub enum PunktfunkError {
#[error("invalid argument: {0}")]
InvalidArg(&'static str),
#[error("fec error: {0}")]
Fec(#[from] crate::fec::FecError),
#[error("crypto seal/open failed")]
Crypto,
#[error("malformed packet")]
BadPacket,
#[error("no complete frame available yet")]
NoFrame,
#[error("unsupported: {0}")]
Unsupported(&'static str),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("timed out")]
Timeout,
#[error("session closed")]
Closed,
/// The host deliberately turned this connection away and said why (a typed QUIC application
/// close, [`crate::reject::RejectReason`]) — distinct from transport trouble ([`Self::Io`] /
/// [`Self::Timeout`]) and from a failed PIN proof ([`Self::Crypto`]) so UIs can render the
/// real cause instead of a generic "not accepted".
#[error("rejected by host: {0}")]
Rejected(crate::reject::RejectReason),
}
pub type Result<T> = core::result::Result<T, PunktfunkError>;
/// Stable C ABI status codes. `Ok` is 0; all errors are negative so callers can
/// test `rc < 0`. Do not renumber existing variants — only append.
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PunktfunkStatus {
Ok = 0,
InvalidArg = -1,
Fec = -2,
Crypto = -3,
BadPacket = -4,
NoFrame = -5,
Unsupported = -6,
Io = -7,
NullPointer = -8,
Timeout = -9,
Closed = -10,
// -11..-19 reserved for future generic errors. The -20 block mirrors
// `crate::reject::RejectReason` one-to-one so FFI callers (Swift, JNI) can
// render the host's actual rejection reason.
RejectedNotArmed = -20,
RejectedBoundOther = -21,
RejectedRateLimited = -22,
RejectedIdentityRequired = -23,
RejectedDenied = -24,
RejectedApprovalTimeout = -25,
RejectedSuperseded = -26,
RejectedWireVersion = -27,
RejectedBusy = -28,
Panic = -99,
}
impl PunktfunkError {
/// Map to the C ABI status code.
pub fn status(&self) -> PunktfunkStatus {
match self {
PunktfunkError::InvalidArg(_) => PunktfunkStatus::InvalidArg,
PunktfunkError::Fec(_) => PunktfunkStatus::Fec,
PunktfunkError::Crypto => PunktfunkStatus::Crypto,
PunktfunkError::BadPacket => PunktfunkStatus::BadPacket,
PunktfunkError::NoFrame => PunktfunkStatus::NoFrame,
PunktfunkError::Unsupported(_) => PunktfunkStatus::Unsupported,
PunktfunkError::Io(_) => PunktfunkStatus::Io,
PunktfunkError::Timeout => PunktfunkStatus::Timeout,
PunktfunkError::Closed => PunktfunkStatus::Closed,
PunktfunkError::Rejected(r) => {
use crate::reject::RejectReason as R;
match r {
R::PairingNotArmed => PunktfunkStatus::RejectedNotArmed,
R::PairingBoundToOtherDevice => PunktfunkStatus::RejectedBoundOther,
R::PairingRateLimited => PunktfunkStatus::RejectedRateLimited,
R::IdentityRequired => PunktfunkStatus::RejectedIdentityRequired,
R::Denied => PunktfunkStatus::RejectedDenied,
R::ApprovalTimeout => PunktfunkStatus::RejectedApprovalTimeout,
R::Superseded => PunktfunkStatus::RejectedSuperseded,
R::WireVersionMismatch => PunktfunkStatus::RejectedWireVersion,
R::Busy => PunktfunkStatus::RejectedBusy,
}
}
}
}
}