Files
songbird/src/events/context/data/disconnect.rs
Erk 500d679ae5 Fix: Move WS error handling (#174)
Moves all WS handling of unexpected payloads into the stream to prevent code duplication.

This also prevents non-{Hello,Resumed,Ready} messages from causing a handshake failure, as it seems Discord do not prevent such messages from appearing.

---------

Co-authored-by: Kyle Simpson <kyleandrew.simpson@gmail.com>
2023-11-20 00:02:56 +00:00

115 lines
4.2 KiB
Rust

use crate::{
error::ConnectionError,
id::*,
model::{CloseCode as VoiceCloseCode, FromPrimitive},
ws::Error as WsError,
};
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
/// Voice connection details gathered at termination or failure.
///
/// In the event of a failure, this event data is gathered after
/// a reconnection strategy has exhausted all of its attempts.
#[derive(Debug)]
#[non_exhaustive]
pub struct DisconnectData<'a> {
/// The location that a voice connection was terminated.
pub kind: DisconnectKind,
/// The cause of any connection failure.
///
/// If `None`, then this disconnect was requested by the user in some way
/// (i.e., leaving or changing voice channels).
pub reason: Option<DisconnectReason>,
/// ID of the voice channel being joined, if it is known.
///
/// If this is available, then this can be used to reconnect/renew
/// a voice session via thew gateway.
pub channel_id: Option<ChannelId>,
/// ID of the target voice channel's parent guild.
pub guild_id: GuildId,
/// Unique string describing this session for validation/authentication purposes.
pub session_id: &'a str,
}
/// The location that a voice connection was terminated.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum DisconnectKind {
/// The voice driver failed to connect to the server.
///
/// This requires explicit handling at the gateway level
/// to either reconnect or fully disconnect.
Connect,
/// The voice driver failed to reconnect to the server.
///
/// This requires explicit handling at the gateway level
/// to either reconnect or fully disconnect.
Reconnect,
/// The voice connection was terminated mid-session by either
/// the user or Discord.
///
/// If `reason == None`, then this disconnection is either
/// a full disconnect or a user-requested channel change.
/// Otherwise, this is likely a session expiry (requiring user
/// handling to fully disconnect/reconnect).
Runtime,
}
/// The reason that a voice connection failed.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DisconnectReason {
/// This (re)connection attempt was dropped due to another request.
AttemptDiscarded,
/// Songbird had an internal error.
///
/// This should never happen; if this is ever seen, raise an issue with logs.
Internal,
/// A host-specific I/O error caused the fault; this is likely transient, and
/// should be retried some time later.
Io,
/// Songbird and Discord disagreed on the protocol used to establish a
/// voice connection.
///
/// This should never happen; if this is ever seen, raise an issue with logs.
ProtocolViolation,
/// A voice connection was not established in the specified time.
TimedOut,
/// The Websocket connection was closed by Discord.
///
/// This typically indicates that the voice session has expired,
/// and a new one needs to be requested via the gateway.
WsClosed(Option<VoiceCloseCode>),
}
impl From<&ConnectionError> for DisconnectReason {
fn from(e: &ConnectionError) -> Self {
match e {
ConnectionError::AttemptDiscarded => Self::AttemptDiscarded,
ConnectionError::CryptoInvalidLength
| ConnectionError::CryptoModeInvalid
| ConnectionError::CryptoModeUnavailable
| ConnectionError::EndpointUrl
| ConnectionError::IllegalDiscoveryResponse
| ConnectionError::IllegalIp
| ConnectionError::Json(_) => Self::ProtocolViolation,
ConnectionError::Io(_) => Self::Io,
ConnectionError::Crypto(_) | ConnectionError::InterconnectFailure(_) => Self::Internal,
ConnectionError::Ws(ws) => ws.into(),
ConnectionError::TimedOut => Self::TimedOut,
}
}
}
impl From<&WsError> for DisconnectReason {
fn from(e: &WsError) -> Self {
Self::WsClosed(match e {
WsError::WsClosed(Some(frame)) => match frame.code {
CloseCode::Library(l) => VoiceCloseCode::from_u16(l),
_ => None,
},
_ => None,
})
}
}