327df0c
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<u8>),
}
#[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<String> {
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::<Vec<_>>()
.join(SEPARATOR_TEXT)
}
pub(crate) fn encode_frames(packets: &[Packet]) -> Vec<Frame> {
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<Packet> {
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<Vec<Packet>> {
if text.is_empty() {
return None;
}
text.split(SEPARATOR).map(decode_packet).collect()
}
pub(crate) fn binary(data: Vec<u8>) -> 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)
}