use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::{
error::Result,
pstream::{self, PObject},
};
pub const VERSION: u8 = 0x46; pub const MAGIC: u32 = 0x2552_1814;
pub const SCMD_REQUEST: u8 = 0x01;
pub const SCMD_CONNECT: u8 = 0x0E;
pub const SCMD_AUTH: u8 = 0x12;
pub const SCMD_SSL_UPGRADE: u8 = 0x16;
pub const SCMD_SERVER_INFO: u8 = 0x19;
pub const SCMD_LIST: u8 = 0x1a;
pub const CLIENT_VERSION: &str = "4.0.0-17889";
pub const CLIENT_BUILD: u64 = 17889;
pub async fn send_message<W: AsyncWrite + Unpin>(
writer: &mut W,
scmd: u8,
obj: &PObject,
) -> Result<()> {
let mut buf = Vec::new();
buf.extend_from_slice(&MAGIC.to_be_bytes());
buf.push(VERSION);
buf.push(scmd);
buf.extend_from_slice(&0u16.to_be_bytes()); pstream::encode(obj, &mut buf)?;
writer.write_all(&buf).await?;
writer.flush().await?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use tokio::io::{AsyncRead, AsyncReadExt};
use super::*;
use crate::error::Error;
async fn read_header<R: AsyncRead + Unpin>(reader: &mut R) -> Result<(u8, u8)> {
let magic = reader.read_u32().await?;
if magic != MAGIC {
return Err(Error::BadMagic);
}
let version = reader.read_u8().await?;
if !(70..80).contains(&version) {
return Err(Error::VersionMismatch { got: version });
}
let scmd = reader.read_u8().await?;
reader.read_u16().await?;
Ok((version, scmd))
}
async fn recv_message<R: AsyncRead + Unpin + Send>(reader: &mut R) -> Result<(u8, PObject)> {
let (_version, scmd) = read_header(reader).await?;
let obj = pstream::decode_from(reader, None).await?;
Ok((scmd, obj))
}
#[tokio::test]
async fn header_byte_layout() {
let mut buf = Vec::new();
send_message(&mut buf, 0x12, &pmap! {}).await.unwrap();
assert_eq!(&buf[..8], &[0x25, 0x52, 0x18, 0x14, 0x46, 0x12, 0x00, 0x00]);
}
#[tokio::test]
async fn roundtrip_via_duplex() {
let (mut client, mut server) = tokio::io::duplex(4096);
let obj = pmap! {
"action" => "test",
"value" => 42u64,
};
let obj_clone = obj.clone();
let write_handle = tokio::spawn(async move {
send_message(&mut client, 0x01, &obj_clone).await.unwrap();
});
let (scmd, decoded) = recv_message(&mut server).await.unwrap();
write_handle.await.unwrap();
assert_eq!(scmd, 0x01);
assert_eq!(decoded, obj);
}
#[tokio::test]
async fn bad_magic() {
let bad = [0xFF, 0xFF, 0xFF, 0xFF, 0x46, 0x01, 0x00, 0x00];
let mut cursor = Cursor::new(bad);
let err = read_header(&mut cursor).await.unwrap_err();
assert!(matches!(err, Error::BadMagic));
}
#[tokio::test]
async fn version_boundaries() {
let mut buf = Vec::new();
buf.extend_from_slice(&MAGIC.to_be_bytes());
buf.push(69);
buf.push(0x01);
buf.extend_from_slice(&0u16.to_be_bytes());
let err = read_header(&mut Cursor::new(&buf)).await.unwrap_err();
assert!(matches!(err, Error::VersionMismatch { got: 69 }));
buf[4] = 70;
let (version, _) = read_header(&mut Cursor::new(&buf)).await.unwrap();
assert_eq!(version, 70);
buf[4] = 79;
let (version, _) = read_header(&mut Cursor::new(&buf)).await.unwrap();
assert_eq!(version, 79);
buf[4] = 80;
let err = read_header(&mut Cursor::new(&buf)).await.unwrap_err();
assert!(matches!(err, Error::VersionMismatch { got: 80 }));
}
#[tokio::test]
async fn recv_response_reads_raw_pobject() {
let obj = pmap! { "alive" => 300u64, "type" => "response" };
let mut buf = Vec::new();
pstream::encode(&obj, &mut buf).unwrap();
let mut cursor = Cursor::new(buf);
let decoded = pstream::decode_from(&mut cursor, None).await.unwrap();
assert_eq!(decoded["alive"].as_int().unwrap(), 300);
assert_eq!(decoded["type"].as_str().unwrap(), "response");
}
#[tokio::test]
async fn send_message_all_scmd_types() {
for scmd in [0x01u8, 0x0E, 0x12, 0x16, 0x18, 0x19, 0x1a] {
let obj = pmap! { "_action" => "test" };
let mut buf = Vec::new();
send_message(&mut buf, scmd, &obj).await.unwrap();
assert_eq!(&buf[..4], &MAGIC.to_be_bytes());
assert_eq!(buf[4], VERSION);
assert_eq!(buf[5], scmd);
assert_eq!(&buf[6..8], &[0x00, 0x00]);
}
}
}