Skip to main content

mumcord_protocol/
error.rs

1//! Typed errors for connecting, sending commands and server denials.
2
3use crate::proto;
4use crate::tls::ServerCert;
5use crate::version::MumbleVersion;
6
7/// Malformed data on the control channel.
8#[derive(Debug, thiserror::Error)]
9pub enum ProtocolError {
10    #[error("frame of {0} bytes exceeds the size limit")]
11    FrameTooLarge(usize),
12    #[error("cannot decode {kind} message: {source}")]
13    Decode {
14        kind: &'static str,
15        #[source]
16        source: prost::DecodeError,
17    },
18}
19
20/// Why the server refused the login (`Reject` message).
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum RejectReason {
23    None,
24    WrongVersion,
25    InvalidUsername,
26    WrongUserPassword,
27    WrongServerPassword,
28    UsernameInUse,
29    ServerFull,
30    NoCertificate,
31    AuthenticatorFail,
32    Unknown(i32),
33}
34
35impl RejectReason {
36    pub(crate) fn from_wire(value: Option<i32>) -> Self {
37        use proto::reject::RejectType as T;
38        let Some(value) = value else {
39            return Self::None;
40        };
41        match T::try_from(value) {
42            Ok(T::None) => Self::None,
43            Ok(T::WrongVersion) => Self::WrongVersion,
44            Ok(T::InvalidUsername) => Self::InvalidUsername,
45            Ok(T::WrongUserPw) => Self::WrongUserPassword,
46            Ok(T::WrongServerPw) => Self::WrongServerPassword,
47            Ok(T::UsernameInUse) => Self::UsernameInUse,
48            Ok(T::ServerFull) => Self::ServerFull,
49            Ok(T::NoCertificate) => Self::NoCertificate,
50            Ok(T::AuthenticatorFail) => Self::AuthenticatorFail,
51            Err(_) => Self::Unknown(value),
52        }
53    }
54}
55
56/// Failure while establishing a connection.
57#[derive(Debug, thiserror::Error)]
58pub enum ConnectError {
59    /// The host name could not be resolved (no `#[from]`: every other I/O error is `Io`).
60    #[error("address lookup failed: {0}")]
61    Resolve(std::io::Error),
62    #[error("network error: {0}")]
63    Io(#[from] std::io::Error),
64    #[error("invalid server address: {0}")]
65    InvalidAddress(String),
66    /// The caller's certificate check said no. Carries the certificate so the caller can ask the
67    /// user and reconnect (trust on first use, or a changed certificate).
68    #[error("server certificate not trusted (SHA-1 {})", .0.sha1_hex)]
69    CertificateRejected(Box<ServerCert>),
70    #[error("TLS error: {0}")]
71    Tls(#[from] rustls::Error),
72    #[error("server rejected the login ({reason:?}): {message}")]
73    Rejected {
74        reason: RejectReason,
75        message: String,
76    },
77    #[error(transparent)]
78    Protocol(#[from] ProtocolError),
79    #[error("server closed the connection during login")]
80    Closed,
81    #[error("login timed out")]
82    Timeout,
83}
84
85/// A command could not be sent.
86#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
87pub enum CommandError {
88    #[error("not connected")]
89    NotConnected,
90    #[error("{feature} needs server {required} or newer (server is {server})")]
91    Unsupported {
92        feature: &'static str,
93        required: MumbleVersion,
94        server: MumbleVersion,
95    },
96}
97
98/// Kind of a server denial (`PermissionDenied` message).
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum DenyKind {
101    /// Free-text denial; see `reason`.
102    Text,
103    /// A missing ACL permission; see `permission` and `channel_id`.
104    Permission,
105    SuperUser,
106    ChannelName,
107    TextTooLong,
108    H9K,
109    TemporaryChannel,
110    MissingCertificate,
111    UserName,
112    ChannelFull,
113    NestingLimit,
114    ChannelCountLimit,
115    /// The channel already has the maximum number of listeners.
116    ChannelListenerLimit,
117    /// This user already listens to the maximum number of channels.
118    UserListenerLimit,
119    Unknown(i32),
120}
121
122impl DenyKind {
123    fn from_wire(value: Option<i32>) -> Self {
124        use proto::permission_denied::DenyType as T;
125        // The protocol's default for a missing type is Text.
126        let Some(value) = value else {
127            return Self::Text;
128        };
129        match T::try_from(value) {
130            Ok(T::Text) => Self::Text,
131            Ok(T::Permission) => Self::Permission,
132            Ok(T::SuperUser) => Self::SuperUser,
133            Ok(T::ChannelName) => Self::ChannelName,
134            Ok(T::TextTooLong) => Self::TextTooLong,
135            Ok(T::H9k) => Self::H9K,
136            Ok(T::TemporaryChannel) => Self::TemporaryChannel,
137            Ok(T::MissingCertificate) => Self::MissingCertificate,
138            Ok(T::UserName) => Self::UserName,
139            Ok(T::ChannelFull) => Self::ChannelFull,
140            Ok(T::NestingLimit) => Self::NestingLimit,
141            Ok(T::ChannelCountLimit) => Self::ChannelCountLimit,
142            Ok(T::ChannelListenerLimit) => Self::ChannelListenerLimit,
143            Ok(T::UserListenerLimit) => Self::UserListenerLimit,
144            Err(_) => Self::Unknown(value),
145        }
146    }
147}
148
149/// A request was denied by the server.
150#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
151#[error("denied by server: {kind:?}")]
152pub struct PermissionDenied {
153    pub kind: DenyKind,
154    /// The missing permission bit(s), see [`crate::permissions`].
155    pub permission: Option<u32>,
156    pub channel_id: Option<u32>,
157    pub session: Option<u32>,
158    /// Server-provided text (untrusted).
159    pub reason: Option<String>,
160    /// Offending name, for name-related denials (untrusted).
161    pub name: Option<String>,
162}
163
164impl From<proto::PermissionDenied> for PermissionDenied {
165    fn from(m: proto::PermissionDenied) -> Self {
166        Self {
167            kind: DenyKind::from_wire(m.r#type),
168            permission: m.permission,
169            channel_id: m.channel_id,
170            session: m.session,
171            reason: m.reason,
172            name: m.name,
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn deny_mapping() {
183        let m = proto::PermissionDenied {
184            r#type: Some(12),
185            channel_id: Some(2),
186            ..Default::default()
187        };
188        let d = PermissionDenied::from(m);
189        assert_eq!(d.kind, DenyKind::ChannelListenerLimit);
190        assert_eq!(d.channel_id, Some(2));
191        assert_eq!(DenyKind::from_wire(None), DenyKind::Text);
192        assert_eq!(DenyKind::from_wire(Some(77)), DenyKind::Unknown(77));
193    }
194
195    #[test]
196    fn reject_mapping() {
197        assert_eq!(
198            RejectReason::from_wire(Some(4)),
199            RejectReason::WrongServerPassword
200        );
201        assert_eq!(
202            RejectReason::from_wire(Some(5)),
203            RejectReason::UsernameInUse
204        );
205        assert_eq!(RejectReason::from_wire(Some(99)), RejectReason::Unknown(99));
206    }
207}