1use std::{net::UdpSocket, num::NonZeroU32};
2
3use crate::{
4 app::auth::{
5 ActivateSession, AuthError, AuthType, ChannelAuthenticationCapabilities,
6 GetSessionChallenge, PrivilegeLevel,
7 },
8 connection::{IpmiConnection, Request, Response},
9 Ipmi, IpmiError,
10};
11
12use super::{
13 internal::{validate_ipmb_checksums, IpmbState},
14 socket::RmcpIpmiSocket,
15 RmcpIpmiError, RmcpIpmiReceiveError, RmcpIpmiSendError,
16};
17
18pub use message::Message;
19
20mod auth;
21mod md2;
22#[cfg(feature = "md5")]
23mod md5;
24mod message;
25
26#[derive(Debug)]
27pub enum ActivationError {
28 Io(std::io::Error),
29 PasswordTooLong,
30 UsernameTooLong,
31 GetSessionChallenge(IpmiError<RmcpIpmiError, AuthError>),
32 NoSupportedAuthenticationType,
33 ActivateSession(IpmiError<RmcpIpmiError, AuthError>),
34}
35
36#[derive(Debug)]
37pub enum WriteError {
38 Io(std::io::Error),
39 MissingPassword,
42 PayloadTooLarge(usize),
45 UnsupportedAuthType(AuthType),
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
50pub enum ReadError {
51 NotEnoughData,
53 UnsupportedAuthType(u8),
55 IncorrectPayloadLen,
58 AuthcodeError,
60}
61
62pub struct State {
63 socket: RmcpIpmiSocket,
64 ipmb_state: IpmbState,
65 session_id: Option<NonZeroU32>,
66 auth_type: crate::app::auth::AuthType,
67 password: Option<[u8; 16]>,
68 session_sequence: u32,
69}
70
71impl core::fmt::Debug for State {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 f.debug_struct("State")
74 .field("socket", &self.socket)
75 .field("ipmb_state", &self.ipmb_state)
76 .field("session_id", &self.session_id)
77 .field("auth_type", &self.auth_type)
78 .field("password", &"<redacted>")
79 .field("session_sequence", &self.session_sequence)
80 .finish()
81 }
82}
83
84impl State {
85 pub fn new(socket: UdpSocket) -> Self {
86 Self {
87 socket: RmcpIpmiSocket::new(socket),
88 ipmb_state: IpmbState::default(),
89 auth_type: AuthType::None,
90 password: None,
91 session_id: None,
92 session_sequence: 0,
93 }
94 }
95
96 pub fn release_socket(self) -> UdpSocket {
97 self.socket.release()
98 }
99
100 pub fn activate(
101 mut self,
102 authentication_caps: &ChannelAuthenticationCapabilities,
103 privilege_level: PrivilegeLevel,
104 username: Option<&str>,
105 password: Option<&[u8]>,
106 ) -> Result<Self, ActivationError> {
107 let password = if let Some(password) = password {
108 if password.len() > 16 {
109 return Err(ActivationError::PasswordTooLong);
110 }
111 let mut padded = [0u8; 16];
112 padded[..password.len()].copy_from_slice(password);
113 Some(padded)
114 } else {
115 None
116 };
117
118 self.password = password;
119
120 let mut ipmi = Ipmi::new(self);
121
122 log::debug!("Requesting challenge");
123
124 let challenge_command = match GetSessionChallenge::new(AuthType::None, username) {
125 Some(v) => v,
126 None => return Err(ActivationError::UsernameTooLong),
127 };
128
129 let challenge = match ipmi.send_recv(challenge_command) {
130 Ok(v) => v,
131 Err(e) => return Err(ActivationError::GetSessionChallenge(e)),
132 };
133
134 let activation_auth_type = authentication_caps
135 .best_auth()
136 .ok_or(ActivationError::NoSupportedAuthenticationType)?;
137
138 let activate_session: ActivateSession = ActivateSession {
139 auth_type: activation_auth_type,
140 maximum_privilege_level: privilege_level,
141 challenge_string: challenge.challenge_string,
142 initial_sequence_number: 0xDEAD_BEEF,
143 };
144
145 ipmi.inner_mut().session_id = Some(challenge.temporary_session_id);
146 ipmi.inner_mut().auth_type = activation_auth_type;
147
148 log::debug!("Activating session");
149
150 let activation_info = match ipmi.send_recv(activate_session.clone()) {
151 Ok(v) => v,
152 Err(e) => return Err(ActivationError::ActivateSession(e)),
153 };
154
155 log::debug!("Successfully started a session ({:?})", activation_info);
156
157 self = ipmi.release();
158
159 self.session_sequence = activation_info.initial_sequence_number;
160 self.session_id = Some(activation_info.session_id);
161
162 assert_eq!(activate_session.auth_type, activation_auth_type);
163
164 Ok(self)
165 }
166}
167
168impl IpmiConnection for State {
169 type SendError = RmcpIpmiSendError;
170
171 type RecvError = RmcpIpmiReceiveError;
172
173 type Error = RmcpIpmiError;
174
175 fn send(&mut self, request: &mut Request) -> Result<(), RmcpIpmiSendError> {
176 log::trace!("Sending message with auth type {:?}", self.auth_type);
177
178 let request_sequence = &mut self.session_sequence;
179
180 if self.session_id.is_some() {
183 *request_sequence = request_sequence.wrapping_add(1);
184 }
185
186 let final_data = super::internal::next_ipmb_message(request, &mut self.ipmb_state);
187
188 let message = Message {
189 auth_type: self.auth_type,
190 session_sequence_number: self.session_sequence,
191 session_id: self.session_id.map_or(0, std::num::NonZero::get),
192 payload: final_data,
193 };
194
195 enum Send {
196 Ipmi(WriteError),
197 Io(std::io::Error),
198 }
199
200 impl From<std::io::Error> for Send {
201 fn from(value: std::io::Error) -> Self {
202 Self::Io(value)
203 }
204 }
205
206 match self.socket.send(|buffer| {
207 message
208 .write_data(self.password.as_ref(), buffer)
209 .map_err(Send::Ipmi)
210 }) {
211 Ok(()) => Ok(()),
212 Err(Send::Ipmi(ipmi)) => Err(RmcpIpmiSendError::V1_5(ipmi)),
213 Err(Send::Io(io)) => Err(RmcpIpmiSendError::V1_5(WriteError::Io(io))),
214 }
215 }
216
217 fn recv(&mut self) -> Result<Response, RmcpIpmiReceiveError> {
218 let data = self.socket.recv()?;
219
220 let data = Message::from_data(self.password.as_ref(), data)
221 .map_err(|e| RmcpIpmiReceiveError::Session(super::UnwrapSessionError::V1_5(e)))?
222 .payload;
223
224 if data.len() < 7 {
225 return Err(RmcpIpmiReceiveError::NotEnoughData);
226 }
227
228 let _req_addr = data[0];
229 let netfn = data[1] >> 2;
230 let _checksum1 = data[2];
231 let _rs_addr = data[3];
232 let _rqseq = data[4];
233 let cmd = data[5];
234 let response_data: Vec<_> = data[6..data.len() - 1].to_vec();
235 let _checksum2 = data[data.len() - 1];
236
237 if !validate_ipmb_checksums(&data) {
238 return Err(RmcpIpmiReceiveError::IpmbChecksumFailed);
239 }
240
241 if let Some(resp) = Response::new(
244 crate::connection::Message::new_raw(netfn, cmd, response_data),
245 0,
246 ) {
247 Ok(resp)
248 } else {
249 Err(RmcpIpmiReceiveError::EmptyMessage)
251 }
252 }
253
254 fn send_recv(&mut self, request: &mut Request) -> Result<Response, Self::Error> {
255 self.send(request)?;
256 let response = self.recv()?;
257 Ok(response)
258 }
259}