ipmi_rs/
lib.rs

1pub mod app;
2
3pub mod connection;
4
5mod error;
6pub use error::IpmiError;
7
8pub mod storage;
9pub use storage::sdr::record::WithSensorRecordCommon;
10
11pub mod sensor_event;
12
13#[macro_use]
14mod fmt;
15#[cfg(test)]
16mod tests;
17
18pub use fmt::{LogOutput, Loggable, Logger};
19
20use connection::{
21    IpmiCommand, LogicalUnit, NetFn, ParseResponseError, Request, RequestTargetAddress,
22};
23use storage::sdr::{self, record::Record as SdrRecord};
24
25pub struct Ipmi<CON> {
26    inner: CON,
27}
28
29impl<CON> Ipmi<CON> {
30    pub fn release(self) -> CON {
31        self.inner
32    }
33}
34
35impl<CON> From<CON> for Ipmi<CON>
36where
37    CON: connection::IpmiConnection,
38{
39    fn from(value: CON) -> Self {
40        Self::new(value)
41    }
42}
43
44pub type IpmiCommandError<T, E> = IpmiError<T, ParseResponseError<E>>;
45
46impl<CON> Ipmi<CON>
47where
48    CON: connection::IpmiConnection,
49{
50    pub fn inner_mut(&mut self) -> &mut CON {
51        &mut self.inner
52    }
53
54    pub fn new(inner: CON) -> Self {
55        Self { inner }
56    }
57
58    pub fn sdrs(&mut self) -> SdrIter<CON> {
59        SdrIter {
60            ipmi: self,
61            next_id: Some(sdr::RecordId::FIRST),
62        }
63    }
64
65    pub fn send_recv<CMD>(
66        &mut self,
67        request: CMD,
68    ) -> Result<CMD::Output, IpmiCommandError<CON::Error, CMD::Error>>
69    where
70        CMD: IpmiCommand,
71    {
72        let target_address = match request.target() {
73            Some((a, c)) => RequestTargetAddress::BmcOrIpmb(a, c, LogicalUnit::Zero),
74            None => RequestTargetAddress::Bmc(LogicalUnit::Zero),
75        };
76
77        let message = request.into();
78        let (message_netfn, message_cmd) = (message.netfn(), message.cmd());
79        let mut request = Request::new(message, target_address);
80
81        let response = self.inner.send_recv(&mut request)?;
82
83        if response.netfn() != message_netfn || response.cmd() != message_cmd {
84            return Err(IpmiError::UnexpectedResponse {
85                netfn_sent: message_netfn,
86                netfn_recvd: response.netfn(),
87                cmd_sent: message_cmd,
88                cmd_recvd: response.cmd(),
89            });
90        }
91
92        CMD::parse_response(response.cc().into(), response.data()).map_err(|error| {
93            IpmiError::ParsingFailed {
94                error,
95                netfn: response.netfn(),
96                completion_code: response.cc(),
97                cmd: response.cmd(),
98                data: response.data().to_vec(),
99            }
100        })
101    }
102}
103
104pub struct SdrIter<'ipmi, CON> {
105    ipmi: &'ipmi mut Ipmi<CON>,
106    next_id: Option<sdr::RecordId>,
107}
108
109impl<T> Iterator for SdrIter<'_, T>
110where
111    T: connection::IpmiConnection,
112{
113    type Item = SdrRecord;
114
115    fn next(&mut self) -> Option<Self::Item> {
116        while let Some(current_id) = self.next_id.take() {
117            if current_id.is_last() {
118                return None;
119            }
120
121            let next_record = self
122                .ipmi
123                .send_recv(sdr::GetDeviceSdr::new(None, current_id));
124
125            match next_record {
126                Ok(record) => {
127                    let next_record_id = record.next_entry;
128
129                    if next_record_id == current_id {
130                        log::error!("Got duplicate SDR record IDs! Stopping iteration.");
131                        return None;
132                    }
133
134                    self.next_id = Some(next_record_id);
135                    return Some(record.record);
136                }
137                Err(IpmiError::ParsingFailed {
138                    error: ParseResponseError::Parse((e, next_record_id)),
139                    ..
140                }) => {
141                    log::warn!(
142                        "Recoverable error while parsing SDR record 0x{:04X}: {e:?}. Skipping to next.",
143                        current_id.value()
144                    );
145                    self.next_id = Some(next_record_id);
146                    continue; // skip the current one
147                }
148                Err(e) => {
149                    log::error!(
150                        "Unrecoverable error while parsing SDR record 0x{:04X}: {e:?}",
151                        current_id.value()
152                    );
153                    return None;
154                }
155            }
156        }
157        None
158    }
159}