ipmi_rs/
lib.rs

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