forked from Keelrleek/SoulStream
219 lines
6.2 KiB
Rust
219 lines
6.2 KiB
Rust
//! Low-level message building and reading, mirroring Soulseek.NET's
|
|
//! `MessageBuilder` / `MessageReader`.
|
|
|
|
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
|
use flate2::read::ZlibDecoder;
|
|
use std::io::Read;
|
|
|
|
use crate::{ProtoError, Result};
|
|
|
|
/// Builds one outgoing message. `finish_*` prepends the length header.
|
|
pub struct Writer {
|
|
buf: BytesMut,
|
|
}
|
|
|
|
impl Default for Writer {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl Writer {
|
|
pub fn new() -> Self {
|
|
Writer {
|
|
buf: BytesMut::with_capacity(64),
|
|
}
|
|
}
|
|
|
|
pub fn u8(mut self, v: u8) -> Self {
|
|
self.buf.put_u8(v);
|
|
self
|
|
}
|
|
|
|
pub fn u32(mut self, v: u32) -> Self {
|
|
self.buf.put_u32_le(v);
|
|
self
|
|
}
|
|
|
|
pub fn u64(mut self, v: u64) -> Self {
|
|
self.buf.put_u64_le(v);
|
|
self
|
|
}
|
|
|
|
pub fn bool(self, v: bool) -> Self {
|
|
self.u8(v as u8)
|
|
}
|
|
|
|
pub fn string(mut self, s: &str) -> Self {
|
|
self.buf.put_u32_le(s.len() as u32);
|
|
self.buf.put_slice(s.as_bytes());
|
|
self
|
|
}
|
|
|
|
/// Frame with a u32 code: `[len][code:u32][payload]`.
|
|
pub fn finish_u32(self, code: u32) -> Vec<u8> {
|
|
let mut out = Vec::with_capacity(self.buf.len() + 8);
|
|
out.extend_from_slice(&((self.buf.len() as u32 + 4).to_le_bytes()));
|
|
out.extend_from_slice(&code.to_le_bytes());
|
|
out.extend_from_slice(&self.buf);
|
|
out
|
|
}
|
|
|
|
/// Frame with a u32 code and a zlib-compressed payload:
|
|
/// `[len][code:u32][zlib(payload)]` (search responses, browse responses).
|
|
pub fn finish_u32_compressed(self, code: u32) -> Vec<u8> {
|
|
use flate2::write::ZlibEncoder;
|
|
use flate2::Compression;
|
|
use std::io::Write;
|
|
|
|
let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
|
|
enc.write_all(&self.buf).expect("zlib write");
|
|
let compressed = enc.finish().expect("zlib finish");
|
|
|
|
let mut out = Vec::with_capacity(compressed.len() + 8);
|
|
out.extend_from_slice(&((compressed.len() as u32 + 4).to_le_bytes()));
|
|
out.extend_from_slice(&code.to_le_bytes());
|
|
out.extend_from_slice(&compressed);
|
|
out
|
|
}
|
|
|
|
/// Frame with a single-byte code: `[len][code:u8][payload]`
|
|
/// (PeerInit / PierceFirewall only).
|
|
pub fn finish_u8(self, code: u8) -> Vec<u8> {
|
|
let mut out = Vec::with_capacity(self.buf.len() + 5);
|
|
out.extend_from_slice(&((self.buf.len() as u32 + 1).to_le_bytes()));
|
|
out.push(code);
|
|
out.extend_from_slice(&self.buf);
|
|
out
|
|
}
|
|
}
|
|
|
|
/// Reads one message payload (the bytes after the code).
|
|
pub struct Reader {
|
|
buf: Bytes,
|
|
}
|
|
|
|
impl Reader {
|
|
pub fn new(payload: Bytes) -> Self {
|
|
Reader { buf: payload }
|
|
}
|
|
|
|
pub fn remaining(&self) -> usize {
|
|
self.buf.remaining()
|
|
}
|
|
|
|
pub fn has_more(&self) -> bool {
|
|
self.buf.has_remaining()
|
|
}
|
|
|
|
/// Take all remaining bytes.
|
|
pub fn rest(&mut self) -> Bytes {
|
|
let n = self.buf.remaining();
|
|
self.buf.copy_to_bytes(n)
|
|
}
|
|
|
|
pub fn u8(&mut self) -> Result<u8> {
|
|
if self.buf.remaining() < 1 {
|
|
return Err(ProtoError::UnexpectedEof);
|
|
}
|
|
Ok(self.buf.get_u8())
|
|
}
|
|
|
|
pub fn bool(&mut self) -> Result<bool> {
|
|
Ok(self.u8()? != 0)
|
|
}
|
|
|
|
pub fn u32(&mut self) -> Result<u32> {
|
|
if self.buf.remaining() < 4 {
|
|
return Err(ProtoError::UnexpectedEof);
|
|
}
|
|
Ok(self.buf.get_u32_le())
|
|
}
|
|
|
|
pub fn u64(&mut self) -> Result<u64> {
|
|
if self.buf.remaining() < 8 {
|
|
return Err(ProtoError::UnexpectedEof);
|
|
}
|
|
Ok(self.buf.get_u64_le())
|
|
}
|
|
|
|
/// IPv4 address; wire order is reversed relative to display order.
|
|
pub fn ipv4(&mut self) -> Result<std::net::Ipv4Addr> {
|
|
if self.buf.remaining() < 4 {
|
|
return Err(ProtoError::UnexpectedEof);
|
|
}
|
|
let mut b = [0u8; 4];
|
|
self.buf.copy_to_slice(&mut b);
|
|
Ok(std::net::Ipv4Addr::new(b[3], b[2], b[1], b[0]))
|
|
}
|
|
|
|
/// UTF-8 with Latin-1 fallback, matching what real-world peers send.
|
|
pub fn string(&mut self) -> Result<String> {
|
|
let len = self.u32()? as usize;
|
|
if self.buf.remaining() < len {
|
|
return Err(ProtoError::UnexpectedEof);
|
|
}
|
|
let bytes = self.buf.copy_to_bytes(len);
|
|
Ok(match std::str::from_utf8(&bytes) {
|
|
Ok(s) => s.to_owned(),
|
|
Err(_) => bytes.iter().map(|&b| b as char).collect(),
|
|
})
|
|
}
|
|
|
|
/// A length-prefixed raw byte string (a user picture, for one): the same
|
|
/// framing as `string`, without the UTF-8 interpretation.
|
|
pub fn bytes(&mut self) -> Result<Bytes> {
|
|
let len = self.u32()? as usize;
|
|
if self.buf.remaining() < len {
|
|
return Err(ProtoError::UnexpectedEof);
|
|
}
|
|
Ok(self.buf.copy_to_bytes(len))
|
|
}
|
|
|
|
/// Replace the remaining payload with its zlib-decompressed form.
|
|
pub fn decompress(&mut self) -> Result<()> {
|
|
let mut out = Vec::new();
|
|
ZlibDecoder::new(&self.buf[..]).read_to_end(&mut out)?;
|
|
if out.len() > crate::MAX_MESSAGE_SIZE {
|
|
return Err(ProtoError::TooLarge(out.len()));
|
|
}
|
|
self.buf = Bytes::from(out);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn roundtrip_primitives() {
|
|
let msg = Writer::new().u32(7).string("héllo").u64(9).finish_u32(26);
|
|
// len = 4 (code) + 4 + 4+6 + 8 = 26
|
|
assert_eq!(&msg[0..4], &26u32.to_le_bytes());
|
|
assert_eq!(&msg[4..8], &26u32.to_le_bytes());
|
|
|
|
let mut r = Reader::new(Bytes::copy_from_slice(&msg[8..]));
|
|
assert_eq!(r.u32().unwrap(), 7);
|
|
assert_eq!(r.string().unwrap(), "héllo");
|
|
assert_eq!(r.u64().unwrap(), 9);
|
|
assert!(!r.has_more());
|
|
}
|
|
|
|
#[test]
|
|
fn u8_code_frame() {
|
|
let msg = Writer::new().u32(123).finish_u8(0);
|
|
assert_eq!(&msg[0..4], &5u32.to_le_bytes());
|
|
assert_eq!(msg[4], 0);
|
|
}
|
|
|
|
#[test]
|
|
fn latin1_fallback() {
|
|
let mut raw = Vec::new();
|
|
raw.extend_from_slice(&2u32.to_le_bytes());
|
|
raw.extend_from_slice(&[0xE9, 0x21]); // "é!" in Latin-1, invalid UTF-8
|
|
let mut r = Reader::new(Bytes::from(raw));
|
|
assert_eq!(r.string().unwrap(), "é!");
|
|
}
|
|
}
|