//! # `chanora_protocol` //! //! TeamSpeak-compatible protocol adapter. Isolates `tsclientlib` //! behind a typed boundary so the rest of Chanora is decoupled from //! the upstream library's types (SAD-067, SysDes-011, SysDes-029). //! //! ## What this crate exposes //! //! * [`ConnectConfig`] — typed connection parameters. //! * [`ProtocolClient`] — async handle owning the connection task. //! * [`ServerSnapshot`], [`ChannelInfo`], [`ClientInfo`] — opaque //! DTOs containing only `String`s and primitives. //! * [`ProtocolError`] — typed error catalogue. //! //! ## What this crate does NOT expose //! //! * `tsclientlib::*` types. //! * `tsproto::*` types. //! * Any audio-related types — those live in `chanora_audio`. //! //! Promoted from `poc/tsclientlib-connect-spike` on 2026-05-14 //! as part of the Alpha build. //! //! ## Hostname resolution (A.1) //! //! Upstream `tsclientlib` uses `hickory-resolver` which reads //! `/etc/resolv.conf`. That file does not exist on Android or iOS, //! and the Beta UI surfaced the resulting cryptic "connection task //! exited before signalling ready" errors. We side-step the issue by //! resolving hostnames ourselves with `tokio::net::lookup_host`, //! which uses platform `getaddrinfo` (works correctly on every //! supported platform), and feeding the resulting `SocketAddr` //! directly to `tsclientlib::Connection::build`. A small in-process //! positive-result cache keeps reconnects fast. #![forbid(unsafe_code)] #![warn(missing_docs)] mod adapter; mod dto; mod resolver; pub use adapter::{ConnectConfig, InboundVoice, ProtocolClient}; pub use dto::{ChannelInfo, ClientInfo, ServerSnapshot}; // Re-export the upstream voice types so chanora_audio can build outbound // voice packets without taking a direct dependency on tsclientlib / // tsproto_packets. Per SAD-067 this is the *one* deliberate // re-export: the audio path is performance-sensitive and a parallel // type hierarchy would force copies for every 20 ms frame. pub use tsproto_packets::packets::{ AudioData, CodecType, Direction, InAudioBuf, OutAudio, OutPacket, }; use thiserror::Error; /// Errors surfaced by the protocol adapter. None of these expose /// `tsclientlib`-specific types; raw upstream errors are mapped here /// to typed arms. #[derive(Debug, Error)] pub enum ProtocolError { /// Configuration is invalid before any I/O is attempted (bad /// hostname, missing identity, etc.). #[error("invalid protocol configuration: {0}")] Invalid(String), /// Hostname resolution failed. Distinct from [`Self::Connect`] /// so the UI can show a meaningful "Server not found" message /// instead of a generic connection error. #[error("dns lookup failed for '{host}': {reason}")] DnsFailed { /// The hostname (or `host:port`) the caller submitted. host: String, /// Reason from the platform resolver. reason: String, }, /// Failed to dial / handshake with the server. #[error("connect failed: {0}")] Connect(String), /// The connection ended before becoming ready. #[error("disconnected before ready: {0}")] DisconnectedEarly(String), /// Connection lost after becoming ready. #[error("connection lost: {0}")] Lost(String), /// Identity parsing failed. #[error("identity error: {0}")] Identity(String), /// Operation timed out. #[error("protocol timeout")] Timeout, /// A backend error escaped the mapping. Production callers /// should never see this; if they do, it is a mapping bug here. #[error("protocol backend: {0}")] Backend(String), }