1use crate::adapter::Adapter;
12use crate::error::Obd2Error;
13use crate::protocol::service::{ServiceRequest, Target, DiagSession, ActuatorCommand};
14use tokio::sync::watch;
15
16#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub enum SessionState {
19 #[default]
21 Default,
22 Extended {
24 unlocked_modules: Vec<String>,
26 },
27 Programming,
29}
30
31
32pub type KeyFunction = Box<dyn Fn(&[u8]) -> Vec<u8> + Send>;
35
36pub async fn enter_session<A: Adapter>(
40 adapter: &mut A,
41 session: DiagSession,
42 module: &str,
43) -> Result<SessionState, Obd2Error> {
44 let sub = match session {
45 DiagSession::Default => 0x01,
46 DiagSession::Programming => 0x02,
47 DiagSession::Extended => 0x03,
48 };
49
50 let req = ServiceRequest {
51 service_id: 0x10,
52 data: vec![sub],
53 target: Target::Module(module.to_string()),
54 };
55
56 adapter.request(&req).await?;
57
58 let state = match session {
59 DiagSession::Default => SessionState::Default,
60 DiagSession::Extended => SessionState::Extended { unlocked_modules: vec![] },
61 DiagSession::Programming => SessionState::Programming,
62 };
63
64 tracing::info!(session = ?session, module = module, "entered diagnostic session");
65 Ok(state)
66}
67
68pub async fn security_access<A: Adapter>(
73 adapter: &mut A,
74 module: &str,
75 key_fn: &KeyFunction,
76) -> Result<(), Obd2Error> {
77 let seed_req = ServiceRequest {
79 service_id: 0x27,
80 data: vec![0x01],
81 target: Target::Module(module.to_string()),
82 };
83 let seed = adapter.request(&seed_req).await?;
84
85 if seed.is_empty() {
86 return Err(Obd2Error::Adapter("empty seed from Mode 27".into()));
87 }
88
89 if seed.iter().all(|&b| b == 0) {
91 tracing::info!(module = module, "security already unlocked (zero seed)");
92 return Ok(());
93 }
94
95 let key = key_fn(&seed);
97
98 let key_req = ServiceRequest {
99 service_id: 0x27,
100 data: std::iter::once(0x02).chain(key.into_iter()).collect(),
101 target: Target::Module(module.to_string()),
102 };
103 adapter.request(&key_req).await?;
104
105 tracing::info!(module = module, "security access granted");
106 Ok(())
107}
108
109pub async fn actuator_control<A: Adapter>(
113 adapter: &mut A,
114 did: u16,
115 module: &str,
116 command: &ActuatorCommand,
117 state: &SessionState,
118) -> Result<(), Obd2Error> {
119 match state {
121 SessionState::Extended { unlocked_modules } => {
122 if !unlocked_modules.contains(&module.to_string()) {
123 return Err(Obd2Error::SecurityRequired);
124 }
125 }
126 _ => return Err(Obd2Error::SecurityRequired),
127 }
128
129 let did_bytes = [(did >> 8) as u8, (did & 0xFF) as u8];
130 let control_bytes = match command {
131 ActuatorCommand::ReturnToEcu => vec![0x00],
132 ActuatorCommand::Activate => vec![0x03],
133 ActuatorCommand::Adjust(data) => {
134 let mut v = vec![0x03];
135 v.extend(data);
136 v
137 }
138 };
139
140 let mut data = Vec::new();
141 data.extend_from_slice(&did_bytes);
142 data.extend(control_bytes);
143
144 let req = ServiceRequest {
145 service_id: 0x2F,
146 data,
147 target: Target::Module(module.to_string()),
148 };
149
150 tracing::warn!(did = format!("{:#06X}", did), module = module, "actuator control command");
151 adapter.request(&req).await?;
152 Ok(())
153}
154
155pub async fn actuator_release<A: Adapter>(
157 adapter: &mut A,
158 did: u16,
159 module: &str,
160) -> Result<(), Obd2Error> {
161 let did_bytes = [(did >> 8) as u8, (did & 0xFF) as u8];
162 let req = ServiceRequest {
163 service_id: 0x2F,
164 data: vec![did_bytes[0], did_bytes[1], 0x00],
165 target: Target::Module(module.to_string()),
166 };
167 adapter.request(&req).await?;
168 tracing::info!(did = format!("{:#06X}", did), module = module, "actuator released to ECU");
169 Ok(())
170}
171
172pub async fn end_session<A: Adapter>(
174 adapter: &mut A,
175 module: &str,
176) -> Result<(), Obd2Error> {
177 let req = ServiceRequest {
178 service_id: 0x10,
179 data: vec![0x01], target: Target::Module(module.to_string()),
181 };
182 adapter.request(&req).await?;
183 tracing::info!(module = module, "returned to default diagnostic session");
184 Ok(())
185}
186
187pub async fn tester_present<A: Adapter>(
189 adapter: &mut A,
190 module: &str,
191) -> Result<(), Obd2Error> {
192 let req = ServiceRequest {
193 service_id: 0x3E,
194 data: vec![],
195 target: Target::Module(module.to_string()),
196 };
197 adapter.request(&req).await?;
198 Ok(())
199}
200
201pub fn start_tester_present_keepalive() -> watch::Sender<bool> {
204 let (cancel_tx, _cancel_rx) = watch::channel(false);
205 cancel_tx
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::adapter::mock::MockAdapter;
214
215 #[tokio::test]
216 async fn test_enter_extended_session() {
217 let mut adapter = MockAdapter::new();
218 adapter.initialize().await.unwrap();
219 let state = enter_session(&mut adapter, DiagSession::Extended, "ecm").await.unwrap();
220 assert!(matches!(state, SessionState::Extended { .. }));
221 }
222
223 #[tokio::test]
224 async fn test_enter_default_session() {
225 let mut adapter = MockAdapter::new();
226 adapter.initialize().await.unwrap();
227 let state = enter_session(&mut adapter, DiagSession::Default, "ecm").await.unwrap();
228 assert_eq!(state, SessionState::Default);
229 }
230
231 #[tokio::test]
232 async fn test_end_session() {
233 let mut adapter = MockAdapter::new();
234 adapter.initialize().await.unwrap();
235 let result = end_session(&mut adapter, "ecm").await;
236 assert!(result.is_ok());
237 }
238
239 #[tokio::test]
240 async fn test_actuator_requires_security() {
241 let mut adapter = MockAdapter::new();
242 let state = SessionState::Default; let result = actuator_control(
244 &mut adapter, 0x1196, "ecm",
245 &ActuatorCommand::Activate, &state,
246 ).await;
247 assert!(matches!(result, Err(Obd2Error::SecurityRequired)));
248 }
249
250 #[tokio::test]
251 async fn test_actuator_requires_unlock() {
252 let mut adapter = MockAdapter::new();
253 let state = SessionState::Extended {
254 unlocked_modules: vec![], };
256 let result = actuator_control(
257 &mut adapter, 0x1196, "ecm",
258 &ActuatorCommand::Activate, &state,
259 ).await;
260 assert!(matches!(result, Err(Obd2Error::SecurityRequired)));
261 }
262
263 #[tokio::test]
264 async fn test_actuator_with_security() {
265 let mut adapter = MockAdapter::new();
266 adapter.initialize().await.unwrap();
267 let state = SessionState::Extended {
268 unlocked_modules: vec!["ecm".to_string()],
269 };
270 let result = actuator_control(
271 &mut adapter, 0x1196, "ecm",
272 &ActuatorCommand::Activate, &state,
273 ).await;
274 assert!(result.is_ok());
275 }
276
277 #[tokio::test]
278 async fn test_tester_present() {
279 let mut adapter = MockAdapter::new();
280 adapter.initialize().await.unwrap();
281 let result = tester_present(&mut adapter, "ecm").await;
282 assert!(result.is_ok());
283 }
284
285 #[test]
286 fn test_session_state_default() {
287 let state = SessionState::default();
288 assert_eq!(state, SessionState::Default);
289 }
290}