use crate::{
header::PayloadType,
payload::{DiagnosticNackCode, RoutingActivationRequest, RoutingActivationResponse},
session::{ActivationAuthProvider, ActivationDenialReason, ActivationStateMachine},
};
use ace_core::FrameRead;
use ace_sim::clock::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub alive_check_timeout: Duration,
pub idle_timeout: Duration,
}
impl Default for ConnectionConfig {
fn default() -> Self {
Self {
alive_check_timeout: Duration::from_millis(500),
idle_timeout: Duration::from_millis(5_000),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionPhase {
Connected,
Active,
AliveCheckPending { sent_at: Instant },
Closing,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionEvent<const BUF: usize = 4096> {
SendActivationResponse(RoutingActivationResponse),
ForwardToEcu {
source_address: u16,
target_address: u16,
uds_data: heapless::Vec<u8, BUF>,
},
SendDiagnosticAck {
source_address: u16,
target_address: u16,
},
SendDiagnosticNack {
source_address: u16,
target_address: u16,
nack_code: DiagnosticNackCode,
},
SendAliveCheckRequest,
SendAliveCheckResponse,
Close,
}
#[derive(Debug)]
pub struct ConnectionState<A: ActivationAuthProvider, const BUF: usize = 4096> {
config: ConnectionConfig,
phase: ConnectionPhase,
activation: ActivationStateMachine<A>,
last_rx: Instant,
events: heapless::Vec<ConnectionEvent<BUF>, 8>,
}
impl<A: ActivationAuthProvider, const BUF: usize> ConnectionState<A, BUF> {
pub fn new(
config: ConnectionConfig,
activation: ActivationStateMachine<A>,
now: Instant,
) -> Self {
Self {
config,
phase: ConnectionPhase::Connected,
activation,
last_rx: now,
events: heapless::Vec::new(),
}
}
pub fn is_active(&self) -> bool {
self.phase == ConnectionPhase::Active
}
pub fn active_source_address(&self) -> Option<u16> {
self.activation.state.active_source_address()
}
pub fn handle(&mut self, payload_type: &PayloadType, payload_data: &[u8], now: Instant) {
self.last_rx = now;
match payload_type {
PayloadType::RoutingActivationRequest => self.on_routing_activation(payload_data),
PayloadType::DiagnosticMessage => self.on_diagnostic_message(payload_data),
PayloadType::AliveCheckRequest => self.on_alive_check_request(),
PayloadType::AliveCheckResponse => self.on_alive_check_response(),
_ => {}
}
}
pub fn tick(&mut self, now: Instant) {
match &self.phase {
ConnectionPhase::Active => {
let idle = now
.checked_duration_since(self.last_rx)
.unwrap_or(Duration::ZERO);
if idle > self.config.idle_timeout {
self.phase = ConnectionPhase::AliveCheckPending { sent_at: now };
let _ = self.events.push(ConnectionEvent::SendAliveCheckRequest);
}
}
ConnectionPhase::AliveCheckPending { sent_at } => {
let elapsed = now
.checked_duration_since(*sent_at)
.unwrap_or(Duration::ZERO);
if elapsed > self.config.alive_check_timeout {
self.phase = ConnectionPhase::Closing;
let _ = self.events.push(ConnectionEvent::Close);
}
}
_ => {}
}
}
pub fn drop_activation_line(&mut self, reason: ActivationDenialReason) {
self.activation.drop_line(reason);
self.phase = ConnectionPhase::Closing;
let _ = self.events.push(ConnectionEvent::Close);
}
pub fn drain_events(&mut self) -> impl Iterator<Item = ConnectionEvent<BUF>> + '_ {
self.events.drain(..)
}
fn on_routing_activation(&mut self, payload_data: &[u8]) {
let mut cursor = payload_data;
let req = match RoutingActivationRequest::decode(&mut cursor) {
Ok(r) => r,
Err(_) => {
self.phase = ConnectionPhase::Closing;
let _ = self.events.push(ConnectionEvent::Close);
return;
}
};
let resp = self.activation.process_request(&req);
let _ = self
.events
.push(ConnectionEvent::SendActivationResponse(resp));
if self.activation.state.is_active() {
self.phase = ConnectionPhase::Active;
} else {
self.phase = ConnectionPhase::Closing;
let _ = self.events.push(ConnectionEvent::Close);
}
}
fn on_diagnostic_message(&mut self, payload_data: &[u8]) {
let source = u16::from_be_bytes([
payload_data.get(0).copied().unwrap_or(0),
payload_data.get(1).copied().unwrap_or(0),
]);
let target = u16::from_be_bytes([
payload_data.get(2).copied().unwrap_or(0),
payload_data.get(3).copied().unwrap_or(0),
]);
if !self.is_active() {
let _ = self.events.push(ConnectionEvent::SendDiagnosticNack {
source_address: source,
target_address: target,
nack_code: DiagnosticNackCode::InvalidSourceAddress,
});
return;
}
if self.activation.state.active_source_address() != Some(source) {
let _ = self.events.push(ConnectionEvent::SendDiagnosticNack {
source_address: source,
target_address: target,
nack_code: DiagnosticNackCode::InvalidSourceAddress,
});
return;
}
let uds_bytes = payload_data.get(4..).unwrap_or(&[]);
if uds_bytes.is_empty() {
let _ = self.events.push(ConnectionEvent::SendDiagnosticNack {
source_address: source,
target_address: target,
nack_code: DiagnosticNackCode::TransportProtocolError,
});
return;
}
let mut uds_data: heapless::Vec<u8, BUF> = heapless::Vec::new();
if uds_data.extend_from_slice(uds_bytes).is_err() {
let _ = self.events.push(ConnectionEvent::SendDiagnosticNack {
source_address: source,
target_address: target,
nack_code: DiagnosticNackCode::OutOfMemory,
});
return;
}
let _ = self.events.push(ConnectionEvent::SendDiagnosticAck {
source_address: source,
target_address: target,
});
let _ = self.events.push(ConnectionEvent::ForwardToEcu {
source_address: source,
target_address: target,
uds_data,
});
}
fn on_alive_check_request(&mut self) {
let _ = self.events.push(ConnectionEvent::SendAliveCheckResponse);
}
fn on_alive_check_response(&mut self) {
if matches!(self.phase, ConnectionPhase::AliveCheckPending { .. }) {
self.phase = ConnectionPhase::Active;
}
}
}