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