use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum HmsgType {
None = 0,
GetInterfaces = 3,
GetInterface = 6,
Subscribe = 9,
Notification = 10,
}
impl HmsgType {
pub(crate) fn matches(self, got: i32) -> bool {
self as i32 == got
}
}
pub(crate) const HMSG_MAX_SIZE: usize = 1 << 19;
const PTR_SIZE: usize = std::mem::size_of::<usize>();
pub(crate) fn header_len() -> usize {
align_up(4, PTR_SIZE) + PTR_SIZE
}
pub(crate) fn align_up(len: usize, align: usize) -> usize {
len + (align - len % align) % align
}
pub(crate) fn encode_header(ty: HmsgType, payload_len: usize) -> Vec<u8> {
let mut buf = vec![0u8; header_len()];
buf[0..4].copy_from_slice(&(ty as i32).to_ne_bytes());
let len_off = align_up(4, PTR_SIZE);
buf[len_off..len_off + PTR_SIZE]
.copy_from_slice(&(payload_len as u64).to_ne_bytes()[..PTR_SIZE]);
buf
}
pub(crate) fn decode_header(buf: &[u8]) -> (i32, usize) {
let ty = i32::from_ne_bytes(buf[0..4].try_into().unwrap());
let len_off = align_up(4, PTR_SIZE);
let mut len_bytes = [0u8; 8];
len_bytes[..PTR_SIZE].copy_from_slice(&buf[len_off..len_off + PTR_SIZE]);
(ty, u64::from_ne_bytes(len_bytes) as usize)
}
pub(crate) fn request(stream: &mut UnixStream, ty: HmsgType, payload: &[u8]) -> Result<Vec<u8>> {
let mut out = encode_header(ty, payload.len());
out.extend_from_slice(payload);
stream.write_all(&out)?;
read_message(stream, ty)
}
pub(crate) fn recv_notification(stream: &mut UnixStream) -> Result<Vec<u8>> {
read_message(stream, HmsgType::Notification)
}
fn read_message(stream: &mut UnixStream, expected: HmsgType) -> Result<Vec<u8>> {
let mut header = vec![0u8; header_len()];
stream.read_exact(&mut header)?;
let (got_ty, len) = decode_header(&header);
if len > HMSG_MAX_SIZE {
return Err(Error::Protocol(format!(
"reply announces a {len}-byte payload, larger than the {HMSG_MAX_SIZE}-byte cap"
)));
}
let mut payload = vec![0u8; len];
stream.read_exact(&mut payload)?;
if got_ty == HmsgType::None as i32 {
return Err(Error::RequestRejected);
}
if !expected.matches(got_ty) {
return Err(Error::UnexpectedMessageType {
expected: expected as i32 as u32,
got: got_ty as u32,
});
}
Ok(payload)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_len_is_two_pointers_wide() {
assert_eq!(header_len(), 2 * PTR_SIZE);
}
#[test]
fn header_roundtrips() {
let buf = encode_header(HmsgType::GetInterface, 42);
assert_eq!(decode_header(&buf), (HmsgType::GetInterface as i32, 42));
}
#[test]
fn align_up_examples() {
assert_eq!(align_up(0, 8), 0);
assert_eq!(align_up(1, 8), 8);
assert_eq!(align_up(8, 8), 8);
assert_eq!(align_up(9, 8), 16);
}
#[test]
fn request_over_socketpair_roundtrips() {
let (mut client, mut server) = UnixStream::pair().unwrap();
let handle = std::thread::spawn(move || {
let mut header = vec![0u8; header_len()];
server.read_exact(&mut header).unwrap();
let (ty, len) = decode_header(&header);
assert_eq!(ty, HmsgType::GetInterfaces as i32);
assert_eq!(len, 0);
let mut reply = encode_header(HmsgType::GetInterfaces, 3);
reply.extend_from_slice(b"abc");
server.write_all(&reply).unwrap();
});
let reply = request(&mut client, HmsgType::GetInterfaces, &[]).unwrap();
assert_eq!(reply, b"abc");
handle.join().unwrap();
}
}