#![allow(unused)]
use std::{io::ErrorKind, time::Duration};
use clap::{Args, Parser};
use ipmi_rs::{
connection::IpmiCommand,
rmcp::{
Rmcp, RmcpIpmiError, RmcpIpmiReceiveError, RmcpIpmiSendError, V1_5WriteError,
V2_0WriteError,
},
storage::sdr,
File, Ipmi, IpmiError, SdrIter,
};
#[allow(unused)]
fn main() {}
pub enum IpmiConnectionEnum {
Rmcp(Ipmi<Rmcp>),
File(Ipmi<File>),
}
enum SdrIterInner<'a> {
Rmcp(SdrIter<'a, Rmcp>),
File(SdrIter<'a, File>),
}
impl Iterator for SdrIterInner<'_> {
type Item = sdr::Record;
fn next(&mut self) -> Option<Self::Item> {
match self {
SdrIterInner::Rmcp(rmcp) => rmcp.next(),
SdrIterInner::File(file) => file.next(),
}
}
}
impl IpmiConnectionEnum {
pub fn send_recv<CMD>(
&mut self,
request: CMD,
) -> Result<CMD::Output, IpmiError<std::io::Error, CMD::Error>>
where
CMD: IpmiCommand,
{
match self {
IpmiConnectionEnum::Rmcp(rmcp) => match rmcp.send_recv(request) {
Ok(v) => Ok(v),
Err(e) => {
let mapped = e.map(|e| match e {
RmcpIpmiError::Receive(RmcpIpmiReceiveError::Io(io))
| RmcpIpmiError::Send(RmcpIpmiSendError::V1_5(V1_5WriteError::Io(io)))
| RmcpIpmiError::Send(RmcpIpmiSendError::V2_0(V2_0WriteError::Io(io))) => {
io
}
e => {
log::error!("RMCP command failed: {e:?}");
std::io::Error::new(ErrorKind::Other, format!("{e:?}"))
}
});
Err(mapped)
}
},
IpmiConnectionEnum::File(file) => file.send_recv(request),
}
}
pub fn sdrs(&mut self) -> impl Iterator<Item = sdr::Record> + '_ {
match self {
IpmiConnectionEnum::Rmcp(rmcp) => SdrIterInner::Rmcp(rmcp.sdrs()),
IpmiConnectionEnum::File(file) => SdrIterInner::File(file.sdrs()),
}
}
}
#[derive(Parser)]
pub struct CliOpts {
#[clap(flatten)]
pub common: CommonOpts,
}
impl CliOpts {
pub fn get_connection(&self) -> std::io::Result<IpmiConnectionEnum> {
self.common.get_connection()
}
}
#[derive(Args)]
pub struct CommonOpts {
#[clap(default_value = "file:///dev/ipmi0", long, short)]
connection_uri: String,
#[clap(default_value = "2000", long)]
timeout_ms: u64,
}
fn error<T>(val: T) -> std::io::Error
where
T: Into<Box<dyn std::error::Error + Send + Sync>>,
{
std::io::Error::new(ErrorKind::Other, val)
}
impl CommonOpts {
pub fn get_connection(&self) -> std::io::Result<IpmiConnectionEnum> {
let timeout = Duration::from_millis(self.timeout_ms);
if self.connection_uri.starts_with("file://") {
let (_, path) = self.connection_uri.split_once("file://").unwrap();
log::debug!("Opening file {path}");
let file = File::new(path, timeout)?;
let ipmi = Ipmi::new(file);
Ok(IpmiConnectionEnum::File(ipmi))
} else if self.connection_uri.starts_with("rmcp://") {
let (_, data) = self.connection_uri.split_once("rmcp://").unwrap();
let err = || {
error("Invalid connection URI. Format: `rmcp://[username]:[password]@[address]:[port]")
};
let (username, rest) = data.split_once(':').ok_or(err())?;
let (password, address) = rest.split_once('@').ok_or(err())?;
log::debug!("Opening connection to {address}");
let mut rmcp = Rmcp::new(address, timeout)?;
rmcp.activate(true, Some(username), Some(password.as_bytes()))
.map_err(|e| error(format!("RMCP activation error: {:?}", e)))?;
let ipmi = Ipmi::new(rmcp);
Ok(IpmiConnectionEnum::Rmcp(ipmi))
} else {
Err(error(format!(
"Invalid connection URI {}",
self.connection_uri
)))
}
}
}