Files
punktfunk/crates/punktfunk-core/src/error.rs
T
enricobuehlerandClaude Fable 5 2562663fc6 fix(host/linux): survive and self-heal dead gamescope sessions on headless SteamOS boxes
Three fixes for the "most connects simply fail" trap on a SteamOS host
with no physical display (VM testbox, panel-less mini PC):

- restore path: skip the restore-to-physical-panel when no DRM connector
  reports a connected display — removing the headless drop-in and
  restarting gamescope-session.target on such a box just crash-loops
  gamescope and strands every later connect on "no usable compositor".
  Keep the headless session (and the takeover state) instead.
- connect path: when no live graphical session exists but the MANAGED
  gamescope infra is present (SteamOS gamescope-session / Bazzite
  session-plus), route to the gamescope takeover — which rebuilds the
  session at the client's mode — instead of failing the connect.
- error delivery: a session-setup failure used to just drop the QUIC
  connection, reaching the client as "control stream finished mid-frame"
  (indistinguishable from transport trouble). Close with a typed
  setup-failed code (0x68, RejectedSetupFailed = -29) carrying the error
  text, and give the client handshake/pairing error paths a short grace
  for the CONNECTION_CLOSE to beat the stream error it caused.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 09:50:30 +02:00

99 lines
3.8 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,
RejectedSetupFailed = -29,
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,
R::SetupFailed => PunktfunkStatus::RejectedSetupFailed,
}
}
}
}
}