///|
/// UDS application session states.
pub enum UdsSessionState {
DefaultSession
ProgrammingSession
ExtendedDiagnosticSession
SafetySystemDiagnosticSession
LockedSession
} derive(Debug)
///|
/// Errors raised by the session state machine.
pub suberror UdsSessionError {
InvalidTransition
SecurityRequired
ResponseTimeout
UnexpectedResponse
} derive(Debug)
///|
/// A small deterministic UDS session tracker.
pub struct UdsSession {
mut state : UdsSessionState
mut unlocked : Bool
mut last_activity_us : UInt64
p2_timeout_us : UInt64
mut pending_service : UdsService?
}
///|
pub fn new_uds_session(p2_timeout_us? : UInt64 = 50_000) -> UdsSession {
{
state: DefaultSession,
unlocked: false,
last_activity_us: 0,
p2_timeout_us,
pending_service: None,
}
}
///|
/// Create a session that requires security access for protected services.
pub fn locked_uds_session(p2_timeout_us? : UInt64 = 50_000) -> UdsSession {
let session = new_uds_session(p2_timeout_us~)
session.state = LockedSession
session
}
///|
pub fn UdsSession::state(self : UdsSession) -> UdsSessionState {
self.state
}
///|
pub fn UdsSession::is_unlocked(self : UdsSession) -> Bool {
self.unlocked
}
///|
pub fn UdsSession::last_activity(self : UdsSession) -> UInt64 {
self.last_activity_us
}
///|
pub fn UdsSession::pending_service(self : UdsSession) -> UdsService? {
self.pending_service
}
///|
/// Mark the current activity time.
pub fn UdsSession::touch(self : UdsSession, timestamp_us : UInt64) -> Unit {
self.last_activity_us = timestamp_us
}
///|
/// Check whether the response timer has expired.
pub fn UdsSession::timed_out(self : UdsSession, timestamp_us : UInt64) -> Bool {
timestamp_us >= self.last_activity_us + self.p2_timeout_us
}
///|
/// Begin a request and remember the expected service.
pub fn UdsSession::begin(
self : UdsSession,
request : DiagnosticRequest,
timestamp_us : UInt64,
) -> DiagnosticRequest raise UdsSessionError {
if request.service() is SecurityAccess && self.state is LockedSession {
self.pending_service = Some(request.service())
self.touch(timestamp_us)
request
} else if request.service() is WriteMemoryByAddress ||
request.service() is RequestDownload {
if !self.unlocked {
raise SecurityRequired
}
self.pending_service = Some(request.service())
self.touch(timestamp_us)
request
} else {
self.pending_service = Some(request.service())
self.touch(timestamp_us)
request
}
}
///|
/// Apply a diagnostic response and advance session state.
pub fn UdsSession::accept(
self : UdsSession,
request : DiagnosticRequest,
payload : Array[Byte],
timestamp_us : UInt64,
) -> UdsResponseInfo raise UdsSessionError {
if self.timed_out(timestamp_us) && timestamp_us != 0 {
raise ResponseTimeout
}
let response = parse_uds_response(request, payload) catch {
_ => raise UnexpectedResponse
}
self.touch(timestamp_us)
if response.is_positive() {
match request.service() {
DiagnosticSessionControl => self.apply_session(payload)
SecurityAccess =>
if payload.length() >= 2 && (payload[1].to_int() & 1) == 0 {
self.unlocked = true
}
_ => ()
}
}
self.pending_service = None
response
}
///|
/// Force a session change after an externally validated response.
pub fn UdsSession::change_state(
self : UdsSession,
target : UdsSessionState,
) -> Unit raise UdsSessionError {
if self.state is LockedSession && !(target is DefaultSession) {
raise InvalidTransition
}
self.state = target
}
///|
/// Return whether a service is safe in the current state.
pub fn UdsSession::allows(self : UdsSession, service : UdsService) -> Bool {
if service is TesterPresent ||
service is DiagnosticSessionControl ||
service is ReadDataByIdentifier ||
service is SecurityAccess {
true
} else {
!(self.state is LockedSession) && self.unlocked
}
}
///|
fn UdsSession::apply_session(self : UdsSession, payload : Array[Byte]) -> Unit {
if payload.length() < 2 {
return
}
self.state = match payload[1] {
1 => DefaultSession
2 => ProgrammingSession
3 => ExtendedDiagnosticSession
4 => SafetySystemDiagnosticSession
_ => self.state
}
}
///|
/// Return a stable state label.
pub fn uds_session_name(state : UdsSessionState) -> String {
match state {
DefaultSession => "default"
ProgrammingSession => "programming"
ExtendedDiagnosticSession => "extended"
SafetySystemDiagnosticSession => "safety"
LockedSession => "locked"
}
}