use std::time::Duration; use base64::Engine as _; use base64::engine::general_purpose::STANDARD; use super::Frame; pub(crate) const SEPARATOR: char = '\u{1e}'; const SEPARATOR_TEXT: &str = "\u{1e}"; pub(crate) const OPEN: char = '0'; pub(crate) const CLOSE: char = '1'; pub(crate) const PING: char = '2'; pub(crate) const PONG: char = '3'; pub(crate) const MESSAGE: char = '4'; pub(crate) const UPGRADE: char = '5'; pub(crate) const NOOP: char = '6'; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum Data { Text(String), Binary(Vec), } #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum Packet { Open(String), Close, Ping(String), Pong(String), Message(Data), Upgrade(String), Noop, } pub(crate) fn open( sid: &str, ping_interval: Duration, ping_timeout: Duration, max_payload: usize, upgrades: &[&str], ) -> Packet { let payload = serde_json::json!({ "sid": sid, "upgrades": upgrades, "pingInterval": millis(ping_interval), "pingTimeout": millis(ping_timeout), "maxPayload": max_payload, }); Packet::Open(payload.to_string()) } pub(crate) fn encode_text(packet: &Packet) -> Option { match packet { Packet::Open(payload) => Some(format!("{OPEN}{payload}")), Packet::Close => Some(CLOSE.to_string()), Packet::Ping(payload) => Some(format!("{PING}{payload}")), Packet::Pong(payload) => Some(format!("{PONG}{payload}")), Packet::Message(Data::Text(payload)) => Some(format!("{MESSAGE}{payload}")), Packet::Message(Data::Binary(_)) => None, Packet::Upgrade(payload) => Some(format!("{UPGRADE}{payload}")), Packet::Noop => Some(NOOP.to_string()), } } pub(crate) fn encode_payload(packets: &[Packet]) -> String { packets .iter() .filter_map(|packet| match packet { Packet::Message(Data::Binary(data)) => Some(format!("b{}", STANDARD.encode(data))), other => encode_text(other), }) .collect::>() .join(SEPARATOR_TEXT) } pub(crate) fn encode_frames(packets: &[Packet]) -> Vec { let mut frames = Vec::with_capacity(packets.len()); for packet in packets { match packet { Packet::Message(Data::Binary(data)) => frames.push(Frame::Binary(data.clone())), other => { if let Some(text) = encode_text(other) { frames.push(Frame::Text(text)); } } } } frames } pub(crate) fn decode_packet(text: &str) -> Option { if let Some(encoded) = text.strip_prefix('b') { let data = STANDARD.decode(encoded).ok()?; return Some(Packet::Message(Data::Binary(data))); } let mut chars = text.chars(); let kind = chars.next()?; let payload = chars.as_str(); match kind { OPEN => Some(Packet::Open(payload.to_string())), CLOSE if payload.is_empty() => Some(Packet::Close), PING => Some(Packet::Ping(payload.to_string())), PONG => Some(Packet::Pong(payload.to_string())), MESSAGE => Some(Packet::Message(Data::Text(payload.to_string()))), UPGRADE => Some(Packet::Upgrade(payload.to_string())), NOOP if payload.is_empty() => Some(Packet::Noop), _ => None, } } pub(crate) fn decode_payload(text: &str) -> Option> { if text.is_empty() { return None; } text.split(SEPARATOR).map(decode_packet).collect() } pub(crate) fn binary(data: Vec) -> Packet { Packet::Message(Data::Binary(data)) } pub(crate) fn text(data: String) -> Packet { Packet::Message(Data::Text(data)) } fn millis(duration: Duration) -> u64 { u64::try_from(duration.as_millis()).unwrap_or(u64::MAX) }