1use std::fmt::{Display, Formatter};
2use std::{
3 ffi::c_int,
4 io,
5 os::fd::{AsFd, AsRawFd},
6 time::Duration,
7};
8
9use ipmi_rs_core::connection::NetFn;
10use nix::errno::Errno;
11use nix::poll::{PollFd, PollFlags};
12
13use crate::connection::{
14 Address, IpmiConnection, Message, Request, RequestTargetAddress, Response,
15};
16
17#[repr(C)]
18#[derive(Debug)]
19pub struct IpmiMessage {
20 netfn: u8,
21 cmd: u8,
22 data_len: u16,
23 data: *mut u8,
24}
25
26impl IpmiMessage {
27 fn data(&self) -> &[u8] {
28 unsafe { core::slice::from_raw_parts(self.data, self.data_len as usize) }
29 }
30}
31
32impl IpmiMessage {
33 fn log(&self, level: log::Level) {
34 log::log!(level, " NetFn = 0x{:02X}", self.netfn);
35 log::log!(level, " Command = 0x{:02X}", self.cmd);
36 log::log!(level, " Data len = {}", self.data_len);
37 if self.data_len > 0 {
38 log::log!(level, " Data = {:02X?}", self.data());
39 }
40 }
41}
42
43#[repr(C)]
44#[derive(Debug)]
45pub struct IpmiRequest {
46 addr: *mut u8,
47 addr_len: u32,
48 msg_id: i64,
49 message: IpmiMessage,
50}
51
52impl IpmiRequest {
53 pub fn log(&self, level: log::Level) {
54 log::log!(level, " Message ID = 0x{:02X}", self.msg_id);
55 self.message.log(level);
56 }
57}
58
59#[repr(C)]
60#[derive(Debug)]
61pub struct IpmiRecv {
62 recv_type: i32,
63 addr: *mut u8,
64 addr_len: u32,
65 msg_id: i64,
66 message: IpmiMessage,
67}
68
69impl IpmiRecv {
70 fn log(&self, level: log::Level) {
71 log::log!(level, " Type = 0x{:02X}", self.recv_type);
72 log::log!(level, " Message ID = 0x{:02X}", self.msg_id);
73 self.message.log(level);
74 }
75}
76
77#[derive(Clone, Copy, Debug)]
78pub enum CreateResponseError {
79 NotAResponse,
80 NotEnoughData,
81 InvalidCmd,
82}
83
84impl TryFrom<IpmiRecv> for Response {
85 type Error = CreateResponseError;
86
87 fn try_from(value: IpmiRecv) -> Result<Self, Self::Error> {
88 let (netfn, cmd) = (value.message.netfn, value.message.cmd);
89
90 let netfn_parsed = NetFn::from(netfn);
91
92 if netfn_parsed.response_value() == netfn {
93 let message = Message::new_raw(netfn, cmd, value.message.data().to_vec());
94 let response =
95 Response::new(message, value.msg_id).ok_or(CreateResponseError::NotEnoughData)?;
96 Ok(response)
97 } else {
98 Err(CreateResponseError::NotAResponse)
99 }
100 }
101}
102
103mod ioctl {
104 const IPMI_IOC_MAGIC: u8 = b'i';
105
106 use nix::{ioctl_read, ioctl_readwrite};
107
108 use super::{IpmiRecv, IpmiRequest};
109
110 ioctl_readwrite!(ipmi_recv_msg_trunc, IPMI_IOC_MAGIC, 11, IpmiRecv);
111 ioctl_read!(ipmi_send_request, IPMI_IOC_MAGIC, 13, IpmiRequest);
112 ioctl_read!(ipmi_get_my_address, IPMI_IOC_MAGIC, 18, u32);
113}
114
115#[repr(C)]
116enum IpmiAddr {
117 SysIface(IpmiSysIfaceAddr),
118 Ipmb(IpmiIpmbAddr),
119}
120
121impl IpmiAddr {
122 fn ptr(&mut self) -> *mut u8 {
123 match self {
124 IpmiAddr::SysIface(ref mut bmc_addr) => std::ptr::addr_of_mut!(*bmc_addr) as *mut u8,
125 IpmiAddr::Ipmb(ref mut ipmb_addr) => std::ptr::addr_of_mut!(*ipmb_addr) as *mut u8,
126 }
127 }
128 fn size(&self) -> u32 {
129 match self {
130 IpmiAddr::SysIface(_) => core::mem::size_of::<IpmiSysIfaceAddr>() as u32,
131 IpmiAddr::Ipmb(_) => core::mem::size_of::<IpmiIpmbAddr>() as u32,
132 }
133 }
134}
135
136impl From<RequestTargetAddress> for IpmiAddr {
137 fn from(value: RequestTargetAddress) -> Self {
138 match value {
139 RequestTargetAddress::Bmc(lun) => {
140 IpmiAddr::SysIface(IpmiSysIfaceAddr::bmc(lun.value()))
141 }
142 RequestTargetAddress::BmcOrIpmb(addr, channel, lun) => IpmiAddr::Ipmb(
143 IpmiIpmbAddr::new(channel.value() as i16, addr.0, lun.value()),
144 ),
145 }
146 }
147}
148
149impl Display for IpmiAddr {
150 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151 match self {
152 IpmiAddr::SysIface(addr) => {
153 write!(f, "System interface (LUN: {})", addr.lun)
154 }
155 IpmiAddr::Ipmb(addr) => {
156 write!(
157 f,
158 "IPMB target (Channel: {}, Target: {}, LUN: {})",
159 addr.channel, addr.target_addr, addr.lun
160 )
161 }
162 }
163 }
164}
165
166#[repr(C)]
167#[derive(Clone, Debug, PartialEq)]
168pub struct IpmiSysIfaceAddr {
169 ty: i32,
170 channel: i16,
171 lun: u8,
172}
173
174#[repr(C)]
175#[derive(Clone, Debug, PartialEq)]
176pub struct IpmiIpmbAddr {
177 ty: i32,
178 channel: i16,
179 target_addr: u8,
180 lun: u8,
181}
182
183impl IpmiIpmbAddr {
184 const IPMI_IPMB_ADDR_TYPE: i32 = 0x01;
185
186 pub const fn new(channel: i16, target_addr: u8, lun: u8) -> Self {
187 Self {
188 ty: Self::IPMI_IPMB_ADDR_TYPE,
189 channel,
190 target_addr,
191 lun,
192 }
193 }
194}
195
196impl IpmiSysIfaceAddr {
197 const IPMI_SYSTEM_INTERFACE_ADDR_TYPE: i32 = 0x0c;
198 const IPMI_BMC_CHANNEL: i16 = 0xf;
199
200 pub const fn bmc(lun: u8) -> Self {
201 Self {
202 ty: Self::IPMI_SYSTEM_INTERFACE_ADDR_TYPE,
203 channel: Self::IPMI_BMC_CHANNEL,
204 lun,
205 }
206 }
207}
208
209pub struct File {
210 inner: std::fs::File,
211 recv_timeout: Duration,
212 seq: i64,
213 my_addr: Address,
214}
215
216impl File {
217 fn fd(&mut self) -> c_int {
218 self.inner.as_raw_fd()
219 }
220
221 pub fn new(path: impl AsRef<std::path::Path>, recv_timeout: Duration) -> io::Result<Self> {
222 let mut inner = std::fs::File::open(path)?;
223
224 let my_addr = match Self::load_my_address_from_file(&mut inner) {
225 Ok(addr) => addr,
226 Err(e) => {
227 log::warn!("Failed to get local address, defaulting to 0x20: {:?}", e);
228 Address(0x20)
229 }
230 };
231
232 Ok(Self {
233 inner,
234 recv_timeout,
235 seq: -1,
236 my_addr,
237 })
238 }
239
240 fn load_my_address_from_file(file: &mut std::fs::File) -> io::Result<Address> {
241 let mut my_addr: u32 = 8;
242 unsafe { ioctl::ipmi_get_my_address(file.as_raw_fd(), std::ptr::addr_of_mut!(my_addr))? };
243 if let Ok(addr) = u8::try_from(my_addr) {
244 Ok(Address(addr))
245 } else {
246 Err(io::Error::other(format!(
247 "ipmi_get_my_address returned non-u8 address: {my_addr}"
248 )))
249 }
250 }
251}
252
253impl IpmiConnection for File {
254 type SendError = io::Error;
255 type RecvError = io::Error;
256 type Error = io::Error;
257
258 fn send(&mut self, request: &mut Request) -> io::Result<()> {
259 let mut addr: IpmiAddr = match request.target() {
260 RequestTargetAddress::BmcOrIpmb(a, _, lun) if a == self.my_addr => {
261 RequestTargetAddress::Bmc(lun)
262 }
263 x => x,
264 }
265 .into();
266
267 self.seq += 1;
268
269 let netfn = request.netfn_raw();
270 let cmd = request.cmd();
271 let seq = self.seq;
272 let data = request.data_mut();
273
274 let data_len = data.len() as u16;
275 let ptr = data.as_mut_ptr();
276
277 log::debug!("Sending request (netfn: 0x{netfn:02X}, cmd: 0x{cmd:02X}) to {addr}");
278 let ipmi_message = IpmiMessage {
279 netfn,
280 cmd,
281 data_len,
282 data: ptr,
283 };
284 let mut request = IpmiRequest {
285 addr: addr.ptr(),
286 addr_len: addr.size(),
287 msg_id: seq,
288 message: ipmi_message,
289 };
290
291 request.log(log::Level::Trace);
292
293 unsafe {
296 ioctl::ipmi_send_request(self.fd(), std::ptr::addr_of_mut!(request))?;
297 }
298
299 #[allow(clippy::drop_non_drop)]
301 drop(request);
302 #[allow(clippy::drop_non_drop)]
303 drop(addr);
304
305 Ok(())
306 }
307
308 fn recv(&mut self) -> io::Result<Response> {
309 let start = std::time::Instant::now();
310
311 let mut bmc_addr = IpmiSysIfaceAddr::bmc(0);
312
313 let mut response_data = [0u8; 1024];
314
315 let response_data_len = response_data.len() as u16;
316 let response_data_ptr = response_data.as_mut_ptr();
317
318 let mut recv = IpmiRecv {
319 addr: std::ptr::addr_of_mut!(bmc_addr) as *mut u8,
320 addr_len: core::mem::size_of::<IpmiSysIfaceAddr>() as u32,
321 msg_id: 0,
322 recv_type: 0,
323 message: IpmiMessage {
324 netfn: 0,
325 cmd: 0,
326 data_len: response_data_len,
327 data: response_data_ptr,
328 },
329 };
330
331 let mut polls = [PollFd::new(self.inner.as_fd(), PollFlags::POLLIN)];
339 let poll = nix::poll::poll(
340 polls.as_mut_slice(),
341 self.recv_timeout.as_millis().try_into().unwrap_or(u16::MAX),
342 )?;
343
344 if poll != 1 {
345 log::warn!(
346 "Failed to receive message after waiting for {} ms.",
347 start.elapsed().as_millis(),
348 );
349
350 return Err(Errno::EAGAIN.into());
351 }
352
353 let ipmi_result =
356 unsafe { ioctl::ipmi_recv_msg_trunc(self.fd(), std::ptr::addr_of_mut!(recv)) };
357
358 let ipmi_result = match ipmi_result {
359 Ok(_) => recv,
360 Err(e) => {
361 log::error!("Error occurred while reading from IPMI: {e:?}");
362 return Err(e.into());
363 }
364 };
365
366 #[allow(dropping_copy_types)]
369 drop(response_data);
370 #[allow(clippy::drop_non_drop)]
371 drop(bmc_addr);
372
373 log::debug!("Received response after {} ms", start.elapsed().as_millis());
374 ipmi_result.log(log::Level::Trace);
375
376 match Response::try_from(ipmi_result) {
377 Ok(response) => {
378 if response.seq() == self.seq {
379 Ok(response)
380 } else {
381 Err(io::Error::other(format!(
382 "Invalid sequence number on response. Expected {}, got {}",
383 self.seq,
384 response.seq()
385 )))
386 }
387 }
388 Err(e) => Err(io::Error::other(format!(
389 "Error while creating response. {e:?}"
390 ))),
391 }
392 }
393
394 fn send_recv(&mut self, request: &mut Request) -> io::Result<Response> {
395 self.send(request)?;
396
397 self.recv()
398 }
399}