use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use core::fmt;
use crate::app_protocol::SupportedAppProtocolRes;
use crate::message::Message;
use crate::session::{
Connection, ConnectionError, Flow, FlowError, Instant, Millis, SequenceError, SessionId, Timer,
Timers,
};
use crate::trace::{trace_close, trace_event};
use crate::{MAX_EXI_PAYLOAD_LEN, Protocol};
#[derive(Debug, Clone)]
pub struct SeccConfig {
pub protocols: &'static [Protocol],
pub session_id: SessionId,
pub max_payload_len: usize,
pub sequence_timeout: Millis,
pub setup_timeout: Millis,
}
impl Default for SeccConfig {
fn default() -> Self {
Self {
protocols: &[Protocol::Iso20, Protocol::Iso2],
session_id: SessionId::NONE,
max_payload_len: MAX_EXI_PAYLOAD_LEN,
sequence_timeout: crate::session::timers::iso2::SECC_SEQUENCE_TIMEOUT,
setup_timeout: crate::session::timers::iso2::EVCC_COMMUNICATION_SETUP_TIMEOUT,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Event {
ProtocolAgreed(Protocol),
Request(Box<Message>),
Refused {
message: Box<Message>,
response_code: u8,
reason: Refusal,
},
Closed(Close),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Refusal {
Sequence(SequenceError),
UnknownSession,
}
impl fmt::Display for Refusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Sequence(e) => write!(f, "{e}"),
Self::UnknownSession => f.write_str("unknown session id"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Close {
Normal,
Paused,
Timeout(Timer),
Refused,
NoCommonProtocol,
}
impl fmt::Display for Close {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Normal => f.write_str("stopped by the vehicle"),
Self::Paused => f.write_str("paused by the vehicle"),
Self::Timeout(t) => write!(f, "{} timer expired", t.name()),
Self::Refused => f.write_str("the vehicle broke the message ordering rules"),
Self::NoCommonProtocol => f.write_str("no protocol in common"),
}
}
}
#[derive(Debug)]
pub struct Secc {
config: SeccConfig,
conn: Connection,
timers: Timers,
flow: Option<Flow>,
events: VecDeque<Event>,
established: bool,
outstanding: Option<Outstanding>,
phase: Option<&'static str>,
closed: bool,
}
impl Secc {
#[must_use]
pub fn new(config: SeccConfig) -> Self {
let conn = Connection::with_limit(config.max_payload_len);
Self {
config,
conn,
timers: Timers::new(),
flow: None,
events: VecDeque::new(),
established: false,
outstanding: None,
phase: None,
closed: false,
}
}
pub fn opened(&mut self, now: Instant) {
self.timers.arm(Timer::CommunicationSetup, now, self.config.setup_timeout);
}
#[must_use]
pub const fn protocol(&self) -> Option<Protocol> {
self.conn.protocol()
}
#[must_use]
pub const fn flow(&self) -> Option<&Flow> {
self.flow.as_ref()
}
#[must_use]
pub const fn is_closed(&self) -> bool {
self.closed
}
pub fn handle_input(&mut self, now: Instant, data: &[u8]) -> Result<(), SeccError> {
if self.closed {
return Ok(());
}
self.conn.receive(data)?;
self.pump(now)
}
fn pump(&mut self, now: Instant) -> Result<(), SeccError> {
while !self.closed && self.outstanding.is_none() {
let Some(message) = self.conn.next_message()? else { break };
self.dispatch(now, message)?;
}
Ok(())
}
fn dispatch(&mut self, now: Instant, message: Message) -> Result<(), SeccError> {
self.timers.arm(Timer::Sequence, now, self.config.sequence_timeout);
if let Message::AppProtocolReq(req) = &message {
self.timers.disarm(Timer::CommunicationSetup);
let agreed = req
.negotiate(self.config.protocols)
.and_then(|a| Flow::new(a.protocol).map(|flow| (a, flow)));
let Some((agreed, flow)) = agreed else {
self.conn
.send(&Message::AppProtocolRes(Box::new(SupportedAppProtocolRes::reject())))?;
self.close(Close::NoCommonProtocol);
return Ok(());
};
self.conn.send(&Message::AppProtocolRes(Box::new(SupportedAppProtocolRes::accept(
agreed,
))))?;
self.conn.set_protocol(agreed.protocol);
self.flow = Some(flow);
trace_event!(protocol = ?agreed.protocol, "protocol agreed");
self.events.push_back(Event::ProtocolAgreed(agreed.protocol));
return Ok(());
}
if !message.is_request() {
return Err(SeccError::NotARequest(message.name()));
}
let Some(flow) = self.flow.as_mut() else {
return Err(SeccError::BeforeHandshake(message.name()));
};
if self.established && message.session_id() != Some(self.config.session_id) {
let response_code = unknown_session_code(self.conn.protocol());
self.outstanding = Some(Outstanding { name: message.name(), refused: true });
self.events.push_back(Event::Refused {
message: Box::new(message),
response_code,
reason: Refusal::UnknownSession,
});
self.close_after_refusal();
return Ok(());
}
match flow.accept(&message) {
Ok(()) => {
self.established |= message.name() == "SessionSetupReq";
trace_event!(
message = message.name(),
phase = self.flow.as_ref().map(Flow::phase_name),
"request"
);
self.enter_phase(now);
self.outstanding = Some(Outstanding { name: message.name(), refused: false });
self.events.push_back(Event::Request(Box::new(message)));
}
Err(FlowError::Sequence(e)) => {
trace_close!(message = e.got, phase = e.phase, "out of sequence");
self.outstanding = Some(Outstanding { name: message.name(), refused: true });
self.events.push_back(Event::Refused {
message: Box::new(message),
response_code: e.response_code,
reason: Refusal::Sequence(e),
});
self.close_after_refusal();
}
Err(FlowError::NotARequest) => return Err(SeccError::NotARequest(message.name())),
}
Ok(())
}
#[must_use]
pub const fn session_id(&self) -> SessionId {
self.config.session_id
}
pub const fn join_session(&mut self, session_id: SessionId) {
self.config.session_id = session_id;
}
#[cfg(feature = "iso20-common")]
#[cfg_attr(docsrs, doc(cfg(feature = "iso20-common")))]
pub fn resume(&mut self, session_id: SessionId, service: crate::session::iso20::Service) {
self.join_session(session_id);
if let Some(flow) = self.flow.as_mut() {
flow.resume(service);
}
}
pub fn respond(&mut self, now: Instant, mut response: Message) -> Result<(), SeccError> {
if response.is_request() {
return Err(SeccError::NotAResponse(response.name()));
}
let Some(outstanding) = self.outstanding else {
return Err(SeccError::NothingToAnswer(response.name()));
};
if !outstanding.refused && !response.answers(outstanding.name) {
return Err(SeccError::WrongResponse {
expected: outstanding.name,
got: response.name(),
});
}
response.set_session_id(self.config.session_id);
if response.outcome().is_some_and(crate::message::Outcome::is_failure)
&& let Some(flow) = self.flow.as_mut()
{
trace_close!(message = response.name(), "failure response; awaiting SessionStopReq");
flow.failed();
}
self.conn.send(&response)?;
self.outstanding = None;
match self.flow.as_ref() {
Some(flow) if flow.is_paused() => self.close(Close::Paused),
Some(flow) if flow.is_finished() => self.close(Close::Normal),
_ if self.closed => {}
_ => {
self.timers.arm(Timer::Sequence, now, self.config.sequence_timeout);
}
}
self.pump(now)
}
#[must_use]
pub const fn awaiting_response(&self) -> bool {
self.outstanding.is_some()
}
#[must_use]
pub const fn outstanding(&self) -> Option<&'static str> {
match self.outstanding {
Some(Outstanding { name, .. }) => Some(name),
None => None,
}
}
fn enter_phase(&mut self, now: Instant) {
let Some(flow) = self.flow.as_ref() else { return };
let phase = flow.phase_name();
if self.phase == Some(phase) {
return;
}
self.phase = Some(phase);
match flow.loop_timeout() {
Some(budget) => self.timers.arm(Timer::Ongoing, now, budget),
None => self.timers.disarm(Timer::Ongoing),
}
}
pub fn poll_event(&mut self) -> Option<Event> {
self.events.pop_front()
}
#[must_use]
pub fn poll_timeout(&self) -> Option<Instant> {
self.timers.next_deadline()
}
pub fn handle_timeout(&mut self, now: Instant) {
while let Some(timer) = self.timers.expired(now) {
self.close(Close::Timeout(timer));
}
}
#[must_use]
pub fn take_transmit(&mut self) -> Vec<u8> {
self.conn.take_transmit()
}
#[must_use]
pub fn transmit_is_empty(&self) -> bool {
self.conn.transmit_is_empty()
}
fn close(&mut self, why: Close) {
if self.closed {
return;
}
self.closed = true;
self.timers.disarm_all();
trace_close!(reason = %why, "session closed");
self.events.push_back(Event::Closed(why));
}
fn close_after_refusal(&mut self) {
if self.closed {
return;
}
self.closed = true;
self.timers.disarm_all();
self.events.push_back(Event::Closed(Close::Refused));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Outstanding {
name: &'static str,
refused: bool,
}
const fn unknown_session_code(protocol: Option<Protocol>) -> u8 {
match protocol {
#[cfg(feature = "iso2")]
Some(Protocol::Iso2) => crate::iso2::ResponseCode::FAILEDUnknownSession as u8,
#[cfg(feature = "iso20-common")]
Some(Protocol::Iso20) => crate::iso20::common::ResponseCode::FAILEDUnknownSession as u8,
_ => 0,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SeccError {
Connection(ConnectionError),
NotARequest(&'static str),
NotAResponse(&'static str),
BeforeHandshake(&'static str),
NothingToAnswer(&'static str),
WrongResponse {
expected: &'static str,
got: &'static str,
},
}
impl From<ConnectionError> for SeccError {
fn from(e: ConnectionError) -> Self {
Self::Connection(e)
}
}
impl fmt::Display for SeccError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Connection(e) => write!(f, "{e}"),
Self::NotARequest(name) => write!(f, "{name} is a response; the SECC expects requests"),
Self::NotAResponse(name) => write!(f, "{name} is a request, not a response"),
Self::BeforeHandshake(name) => {
write!(f, "{name} arrived before supportedAppProtocol")
}
Self::NothingToAnswer(name) => {
write!(f, "{name} was sent with no request outstanding")
}
Self::WrongResponse { expected, got } => {
write!(f, "{got} does not answer {expected}")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SeccError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Connection(e) => Some(e),
_ => None,
}
}
}