use std::{
ffi::c_int,
io,
os::fd::AsRawFd,
time::{Duration, Instant},
};
use crate::{
connection::{IpmiConnection, Message, Request, Response},
NetFn,
};
#[repr(C)]
#[derive(Debug)]
pub struct IpmiMessage {
netfn: u8,
cmd: u8,
data_len: u16,
data: *mut u8,
}
impl IpmiMessage {
fn data(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.data, self.data_len as usize) }
}
}
impl IpmiMessage {
fn log(&self, level: log::Level) {
log::log!(level, " NetFn = 0x{:02X}", self.netfn);
log::log!(level, " Command = 0x{:02X}", self.cmd);
log::log!(level, " Data len = {}", self.data_len);
if self.data_len > 0 {
log::log!(level, " Data = {:02X?}", self.data());
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IpmiRequest {
addr: *mut u8,
addr_len: u32,
msg_id: i64,
message: IpmiMessage,
}
impl IpmiRequest {
pub fn log(&self, level: log::Level) {
log::log!(level, " Message ID = 0x{:02X}", self.msg_id);
self.message.log(level)
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IpmiRecv {
recv_type: i32,
addr: *mut u8,
addr_len: u32,
msg_id: i64,
message: IpmiMessage,
}
impl IpmiRecv {
fn log(&self, level: log::Level) {
log::log!(level, " Type = 0x{:02X}", self.recv_type);
log::log!(level, " Message ID = 0x{:02X}", self.msg_id);
self.message.log(level)
}
}
#[derive(Clone, Copy, Debug)]
pub enum CreateResponseError {
NotAResponse,
NotEnoughData,
InvalidCmd,
}
impl TryFrom<IpmiRecv> for Response {
type Error = CreateResponseError;
fn try_from(value: IpmiRecv) -> Result<Self, Self::Error> {
let (netfn, cmd) = (value.message.netfn, value.message.cmd);
let netfn_parsed = NetFn::from(netfn);
if netfn_parsed.response_value() == netfn {
let message = Message::new_raw(netfn, cmd, value.message.data().to_vec());
let response =
Response::new(message, value.msg_id).ok_or(CreateResponseError::NotEnoughData)?;
Ok(response)
} else {
Err(CreateResponseError::NotAResponse)
}
}
}
mod ioctl {
const IPMI_IOC_MAGIC: u8 = b'i';
use nix::{ioctl_read, ioctl_readwrite};
use super::*;
ioctl_readwrite!(ipmi_recv_msg_trunc, IPMI_IOC_MAGIC, 11, IpmiRecv);
ioctl_read!(ipmi_send_request, IPMI_IOC_MAGIC, 13, IpmiRequest);
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub struct IpmiSysIfaceAddr {
ty: i32,
channel: i16,
lun: u8,
}
impl IpmiSysIfaceAddr {
const IPMI_SYSTEM_INTERFACE_ADDR_TYPE: i32 = 0x0c;
const IPMI_BMC_CHANNEL: i16 = 0xf;
pub const fn bmc(lun: u8) -> Self {
Self {
ty: Self::IPMI_SYSTEM_INTERFACE_ADDR_TYPE,
channel: Self::IPMI_BMC_CHANNEL,
lun,
}
}
}
pub struct File {
inner: std::fs::File,
recv_timeout: Duration,
seq: i64,
}
impl File {
fn fd(&mut self) -> c_int {
self.inner.as_raw_fd()
}
pub fn new(path: impl AsRef<std::path::Path>, recv_timeout: Duration) -> io::Result<Self> {
let me = Ok(Self {
inner: std::fs::File::open(path)?,
recv_timeout,
seq: 0,
});
me
}
}
impl IpmiConnection for File {
type SendError = io::Error;
type RecvError = io::Error;
type Error = io::Error;
fn send(&mut self, request: &mut Request) -> io::Result<()> {
let mut bmc_addr = IpmiSysIfaceAddr::bmc(request.lun().value());
let netfn = request.netfn_raw();
let cmd = request.cmd();
let seq = self.seq;
let data = request.data_mut();
let data_len = data.len() as u16;
let ptr = data.as_mut_ptr();
let mut request = IpmiRequest {
addr: std::ptr::addr_of_mut!(bmc_addr) as *mut u8,
addr_len: core::mem::size_of::<IpmiSysIfaceAddr>() as u32,
msg_id: seq,
message: IpmiMessage {
netfn,
cmd,
data_len,
data: ptr,
},
};
log::debug!("Sending request (netfn: 0x{netfn:02X}, cmd: 0x{cmd:02X})");
request.log(log::Level::Trace);
unsafe {
ioctl::ipmi_send_request(self.fd(), std::ptr::addr_of_mut!(request))?;
}
#[allow(clippy::drop_non_drop)]
drop(request);
#[allow(clippy::drop_non_drop)]
drop(bmc_addr);
Ok(())
}
fn recv(&mut self) -> io::Result<Response> {
let start = std::time::Instant::now();
let mut bmc_addr = IpmiSysIfaceAddr::bmc(0);
let mut response_data = [0u8; 1024];
let response_data_len = response_data.len() as u16;
let response_data_ptr = response_data.as_mut_ptr();
let mut recv = IpmiRecv {
addr: std::ptr::addr_of_mut!(bmc_addr) as *mut u8,
addr_len: core::mem::size_of::<IpmiSysIfaceAddr>() as u32,
msg_id: 0,
recv_type: 0,
message: IpmiMessage {
netfn: 0,
cmd: 0,
data_len: response_data_len,
data: response_data_ptr,
},
};
let start_time = Instant::now();
let ipmi_result = loop {
let ioctl_result =
unsafe { ioctl::ipmi_recv_msg_trunc(self.fd(), std::ptr::addr_of_mut!(recv)) };
match ioctl_result {
Ok(_) => break Ok(recv),
Err(e) => {
if Instant::now().duration_since(start_time) > self.recv_timeout {
break Err(e);
} else {
continue;
}
}
}
};
#[allow(clippy::drop_copy)]
#[allow(dropping_copy_types)]
drop(response_data);
#[allow(clippy::drop_non_drop)]
drop(bmc_addr);
let end = std::time::Instant::now();
let duration = (end - start).as_millis() as u32;
match ipmi_result {
Ok(recv) => {
log::debug!("Received response after {} ms", duration);
recv.log(log::Level::Trace);
recv.try_into().map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Error while creating response. {:?}", e),
)
})
}
Err(e) => {
log::warn!(
"Failed to receive message after waiting for {} ms. {:?}",
e,
duration
);
Err(e.into())
}
}
}
fn send_recv(&mut self, request: &mut Request) -> io::Result<Response> {
self.send(request)?;
self.recv()
}
}