273bd1b
use serde_json::Value;
const MAX_ATTACHMENTS: usize = 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kind {
Connect,
Disconnect,
Event,
Ack,
ConnectError,
BinaryEvent,
BinaryAck,
}
impl Kind {
fn from_char(value: char) -> Option<Self> {
match value {
'0' => Some(Self::Connect),
'1' => Some(Self::Disconnect),
'2' => Some(Self::Event),
'3' => Some(Self::Ack),
'4' => Some(Self::ConnectError),
'5' => Some(Self::BinaryEvent),
'6' => Some(Self::BinaryAck),
_ => None,
}
}
fn code(self) -> char {
match self {
Self::Connect => '0',
Self::Disconnect => '1',
Self::Event => '2',
Self::Ack => '3',
Self::ConnectError => '4',
Self::BinaryEvent => '5',
Self::BinaryAck => '6',
}
}
fn is_binary(self) -> bool {
matches!(self, Self::BinaryEvent | Self::BinaryAck)
}
}
#[derive(Debug, Clone)]
pub(crate) struct Packet {
pub kind: Kind,
pub namespace: String,
pub id: Option<u64>,
pub data: Value,
pub attachments: usize,
}
pub(crate) fn decode(text: &str) -> Option<Packet> {
let mut chars = text.chars();
let kind = Kind::from_char(chars.next()?)?;
let mut rest = chars.as_str();
let mut attachments = 0;
if kind.is_binary() {
let (count, tail) = rest.split_once('-')?;
attachments = count.parse().ok()?;
if !(1..=MAX_ATTACHMENTS).contains(&attachments) {
return None;
}
rest = tail;
}
let mut namespace = "/".to_string();
if let Some(tail) = rest.strip_prefix('/') {
let (name, tail) = tail.split_once(',').unwrap_or((tail, ""));
namespace = format!("/{name}");
rest = tail;
}
let digits = rest
.bytes()
.take_while(|byte| byte.is_ascii_digit())
.count();
let (id, payload) = rest.split_at(digits);
let id = if id.is_empty() {
None
} else {
Some(id.parse().ok()?)
};
let data = if payload.is_empty() {
Value::Null
} else {
serde_json::from_str(payload).ok()?
};
let valid = match kind {
Kind::Connect => data.is_null() || data.is_object(),
Kind::Disconnect => data.is_null(),
Kind::Event | Kind::Ack | Kind::BinaryEvent | Kind::BinaryAck => data.is_array(),
Kind::ConnectError => data.is_object(),
};
valid.then_some(Packet {
kind,
namespace,
id,
data,
attachments,
})
}
pub(crate) fn encode_connect(namespace: &str, sid: &str, device_id: &str) -> String {
format!(
"{}{}",
prefix(Kind::Connect, namespace, None, 0),
serde_json::json!({ "sid": sid, "device_id": device_id })
)
}
pub(crate) fn encode_connect_error(namespace: &str, message: &str) -> String {
format!(
"{}{}",
prefix(Kind::ConnectError, namespace, None, 0),
serde_json::json!({ "message": message })
)
}
pub(crate) fn encode_event(namespace: &str, id: Option<u64>, data: &[Value]) -> String {
format!(
"{}{}",
prefix(Kind::Event, namespace, id, 0),
Value::Array(data.to_vec())
)
}
pub(crate) fn encode_ack(namespace: &str, id: u64, data: &[Value]) -> String {
format!(
"{}{}",
prefix(Kind::Ack, namespace, Some(id), 0),
Value::Array(data.to_vec())
)
}
pub(crate) fn encode_binary_event(
namespace: &str,
id: Option<u64>,
data: &[Value],
attachments: usize,
) -> String {
format!(
"{}{}",
prefix(Kind::BinaryEvent, namespace, id, attachments),
Value::Array(data.to_vec())
)
}
pub(crate) fn encode_binary_ack(
namespace: &str,
id: u64,
data: &[Value],
attachments: usize,
) -> String {
format!(
"{}{}",
prefix(Kind::BinaryAck, namespace, Some(id), attachments),
Value::Array(data.to_vec())
)
}
fn prefix(kind: Kind, namespace: &str, id: Option<u64>, attachments: usize) -> String {
let mut text = String::new();
text.push(kind.code());
if kind.is_binary() {
text.push_str(&attachments.to_string());
text.push('-');
}
if namespace != "/" {
text.push_str(namespace);
text.push(',');
}
if let Some(id) = id {
text.push_str(&id.to_string());
}
text
}