use std::io::{Read, Write};
use crate::Message;
#[cfg(target_family = "unix")]
mod unix;
#[cfg(target_family = "windows")]
mod windows;
mod sys {
#[cfg(target_family = "unix")]
pub use crate::io::unix::*;
#[cfg(target_family = "windows")]
pub use crate::io::windows::*;
}
pub use self::sys::{
CommsInRaw,
CommsOutRaw,
};
#[derive(Clone, Debug)]
pub struct CommsEnvError {
repr: CommsEnvErrorRepr,
}
#[derive(Clone, Debug)]
enum CommsEnvErrorRepr {
NotSpecified,
NotParsable(std::ffi::OsString),
}
impl std::fmt::Display for CommsEnvError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.repr {
CommsEnvErrorRepr::NotSpecified => {
write!(fmt, "communication channel not specified")
}
CommsEnvErrorRepr::NotParsable(value) => {
write!(fmt, "invalid communication channel value: {value:?}")
}
}
}
}
impl std::error::Error for CommsEnvError {
}
pub fn handshake<R, W>(input: &mut R, output: &mut W) -> std::io::Result<()>
where
R: Read,
W: Write,
{
write_magic(output)?;
output.flush()?;
read_magic(input)?;
Ok(())
}
pub fn write_heartbeat<W>(output: &mut W) -> std::io::Result<()>
where
W: Write,
{
let mut proto = fleetspeak_proto::common::Message::new();
proto.set_message_type(String::from("Heartbeat"));
proto.mut_destination().set_service_name(String::from("system"));
write_proto(output, proto)
}
pub fn write_startup<W>(output: &mut W, version: &str) -> std::io::Result<()>
where
W: Write,
{
let mut data = fleetspeak_proto::channel::StartupData::new();
data.set_pid(i64::from(std::process::id()));
data.set_version(String::from(version));
let mut proto = fleetspeak_proto::common::Message::new();
proto.set_message_type(String::from("StartupData"));
proto.mut_destination().set_service_name(String::from("system"));
*proto.mut_data() = protobuf::well_known_types::any::Any::pack(&data)?;
write_proto(output, proto)
}
pub fn write_message<W>(output: &mut W, message: Message) -> std::io::Result<()>
where
W: Write,
{
let mut proto = fleetspeak_proto::common::Message::new();
proto.set_message_type(message.kind.unwrap_or_else(String::new));
proto.mut_destination().set_service_name(message.service);
proto.mut_data().value = message.data;
write_proto(output, proto)
}
pub fn try_read_message<R>(input: &mut R) -> std::io::Result<Option<Message>>
where
R: Read,
{
let mut proto = match try_read_proto(input)? {
Some(proto) => proto,
None => return Ok(None),
};
let service = if proto.has_source() {
proto.take_source().take_service_name()
} else {
use std::io::ErrorKind::InvalidData;
return Err(std::io::Error::new(InvalidData, "missing source address"));
};
let data = if proto.has_data() {
proto.take_data()
} else {
log::warn!("empty message from '{}'", service);
Default::default()
};
Ok(Some(Message {
service: service,
kind: Some(proto.message_type),
data: data.value,
}))
}
fn write_proto<W>(output: &mut W, proto: fleetspeak_proto::common::Message) -> std::io::Result<()>
where
W: Write,
{
use protobuf::Message as _;
let size = u32::try_from(proto.compute_size())
.map_err(|error| {
std::io::Error::new(std::io::ErrorKind::InvalidData, error)
})?;
output.write_all(&size.to_le_bytes())?;
proto.write_to_writer(output)?;
write_magic(output)?;
output.flush()?;
Ok(())
}
fn try_read_proto<R>(input: &mut R) -> std::io::Result<Option<fleetspeak_proto::common::Message>>
where
R: Read,
{
let mut len_buf = [0u8; 4];
let len_buf_read = input.read(&mut len_buf[..])?;
if len_buf_read == 0 {
return Ok(None)
}
input.read_exact(&mut len_buf[len_buf_read..])?;
let len = u32::from_le_bytes(len_buf) as usize;
let mut buf = vec!(0; len);
input.read_exact(&mut buf[..])?;
read_magic(input)?;
Ok(Some(protobuf::Message::parse_from_bytes(&buf[..])?))
}
fn write_magic<W>(output: &mut W) -> std::io::Result<()>
where
W: Write,
{
output.write_all(&MAGIC.to_le_bytes())?;
Ok(())
}
fn read_magic<R>(input: &mut R) -> std::io::Result<()>
where
R: Read,
{
let mut magic_buf = [0u8; 4];
input.read_exact(&mut magic_buf)?;
let magic = u32::from_le_bytes(magic_buf);
if magic != MAGIC {
return Err(InvalidMagicError { magic }.into());
}
Ok(())
}
#[derive(Debug)]
struct InvalidMagicError {
magic: u32,
}
impl std::fmt::Display for InvalidMagicError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "invalid Fleetspeak magic: 0x{:08x}", self.magic)
}
}
impl std::error::Error for InvalidMagicError {
}
impl From<InvalidMagicError> for std::io::Error {
fn from(error: InvalidMagicError) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::InvalidData, error)
}
}
const MAGIC: u32 = 0xf1ee1001;
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn handshake_good_magic() {
let mut buf_in = [0; 1024];
let mut buf_out = [0; 1024];
let mut cur = Cursor::new(&mut buf_in[..]);
assert!(cur.write_all(&MAGIC.to_le_bytes()).is_ok());
let mut cur_in = Cursor::new(&mut buf_in[..]);
let mut cur_out = Cursor::new(&mut buf_out[..]);
assert!(handshake(&mut cur_in, &mut cur_out).is_ok());
let mut cur = Cursor::new(&mut buf_out[..]);
let mut magic_buf = [0u8; 4];
cur.read_exact(&mut magic_buf).unwrap();
let magic = u32::from_le_bytes(magic_buf);
assert_eq!(magic, MAGIC);
}
#[test]
fn handshake_bad_magic() {
let mut buf_in = [0; 1024];
let mut buf_out = [0; 1024];
let mut cur = Cursor::new(&mut buf_in[..]);
assert!(cur.write_all(&0xf1ee1337u32.to_le_bytes()).is_ok());
let mut cur_in = Cursor::new(&mut buf_in[..]);
let mut cur_out = Cursor::new(&mut buf_out[..]);
assert!(handshake(&mut cur_in, &mut cur_out).is_err());
}
}