use crate::command_types::Command;
use crate::error::{Error, Result};
use crate::transport::transaction::{FrameFilter, Request};
use moteus_protocol::diagnostic as proto;
use moteus_protocol::{CanFdFrame, SERVER_TO_CLIENT, SERVER_TO_CLIENT_FLOW};
pub const DEFAULT_CHANNEL: u8 = 1;
pub const MAX_DIAGNOSTIC_WRITE: usize = 61;
pub const MAX_DIAGNOSTIC_READ: usize = 61;
#[derive(Debug, Clone)]
pub struct DiagnosticResponse {
pub id: u8,
pub data: Vec<u8>,
}
pub fn make_diagnostic_write_frame(
dest_id: u8,
source_id: u8,
channel: u8,
data: &[u8],
) -> CanFdFrame {
assert!(data.len() <= MAX_DIAGNOSTIC_WRITE);
let mut frame = CanFdFrame::new();
frame.arbitration_id =
moteus_protocol::calculate_arbitration_id(source_id as i8, dest_id as i8, 0, false);
frame.size = proto::write_client_to_server(channel, data, &mut frame.data);
frame
}
pub fn make_diagnostic_read_frame(
dest_id: u8,
source_id: u8,
channel: u8,
max_length: u8,
) -> CanFdFrame {
let mut frame = CanFdFrame::new();
frame.arbitration_id =
moteus_protocol::calculate_arbitration_id(source_id as i8, dest_id as i8, 0, true);
frame.size = proto::write_client_poll(channel, max_length, &mut frame.data);
frame
}
pub fn make_diagnostic_read_flow_frame(
dest_id: u8,
source_id: u8,
channel: u8,
packet_number: u8,
max_length: u8,
) -> CanFdFrame {
let mut frame = CanFdFrame::new();
frame.arbitration_id =
moteus_protocol::calculate_arbitration_id(source_id as i8, dest_id as i8, 0, true);
frame.size = proto::write_client_poll_flow(channel, packet_number, max_length, &mut frame.data);
frame
}
pub fn parse_diagnostic_response(frame: &CanFdFrame, channel: u8) -> Option<DiagnosticResponse> {
let data = proto::parse_server_to_client(frame.payload(), channel)?;
Some(DiagnosticResponse {
id: ((frame.arbitration_id >> 8) & 0x7F) as u8,
data: data.to_vec(),
})
}
#[derive(Debug, Clone)]
pub struct DiagnosticFlowResponse {
pub id: u8,
pub packet_number: u8,
pub data: Vec<u8>,
}
pub fn parse_diagnostic_flow_response(
frame: &CanFdFrame,
channel: u8,
) -> Option<DiagnosticFlowResponse> {
let flow = proto::parse_server_to_client_flow(frame.payload(), channel)?;
Some(DiagnosticFlowResponse {
id: ((frame.arbitration_id >> 8) & 0x7F) as u8,
packet_number: flow.packet_number,
data: flow.data.to_vec(),
})
}
pub const DEFAULT_FLOW_RETRIES: u32 = 3;
fn plain_reply_filter(id: u8) -> FrameFilter {
FrameFilter::custom(move |f| {
let frame_source = ((f.arbitration_id >> 8) & 0x7F) as u8;
frame_source == id && Command::diagnostic_reply_filter().matches(f)
})
}
fn flow_reply_filter(id: u8) -> FrameFilter {
FrameFilter::custom(move |f| {
let frame_source = ((f.arbitration_id >> 8) & 0x7F) as u8;
frame_source == id && f.size >= 4 && f.data[0] == SERVER_TO_CLIENT_FLOW
})
}
fn probe_reply_filter(id: u8) -> FrameFilter {
FrameFilter::custom(move |f| {
let frame_source = ((f.arbitration_id >> 8) & 0x7F) as u8;
frame_source == id
&& f.size >= 3
&& (f.data[0] == SERVER_TO_CLIENT || f.data[0] == SERVER_TO_CLIENT_FLOW)
})
}
use crate::blocking_controller::BlockingController;
use crate::transport::Transport;
pub struct DiagnosticStream<
'a,
T: Transport = std::sync::Arc<std::sync::Mutex<crate::transport::Router>>,
> {
controller: &'a mut BlockingController<T>,
channel: u8,
read_buffer: Vec<u8>,
use_flow_control: Option<bool>,
last_ack_packet: u8,
seen_first_packet: bool,
flow_retries: u32,
}
impl<'a, T: Transport> std::fmt::Debug for DiagnosticStream<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DiagnosticStream")
.field("device_id", &self.controller.controller.id)
.field("channel", &self.channel)
.field("read_buffer_len", &self.read_buffer.len())
.field("use_flow_control", &self.use_flow_control)
.field("last_ack_packet", &self.last_ack_packet)
.finish()
}
}
impl<'a, T: Transport> DiagnosticStream<'a, T> {
pub fn new(controller: &'a mut BlockingController<T>) -> Self {
Self::with_channel(controller, DEFAULT_CHANNEL)
}
pub fn with_channel(controller: &'a mut BlockingController<T>, channel: u8) -> Self {
Self {
controller,
channel,
read_buffer: Vec::new(),
use_flow_control: None,
last_ack_packet: 0,
seen_first_packet: false,
flow_retries: DEFAULT_FLOW_RETRIES,
}
}
fn accept_flow_packet<'d>(&mut self, flow: &'d DiagnosticFlowResponse) -> &'d [u8] {
let duplicate = self.seen_first_packet && flow.packet_number == self.last_ack_packet;
self.last_ack_packet = flow.packet_number;
self.seen_first_packet = true;
if duplicate {
&[]
} else {
&flow.data
}
}
#[must_use]
pub fn use_flow_control(mut self, enabled: bool) -> Self {
self.use_flow_control = Some(enabled);
self
}
fn id(&self) -> u8 {
self.controller.controller.id
}
fn source_id(&self) -> u8 {
self.controller.controller.source_id
}
pub fn write(&mut self, data: &[u8]) -> Result<()> {
for chunk in data.chunks(MAX_DIAGNOSTIC_WRITE) {
let frame =
make_diagnostic_write_frame(self.id(), self.source_id(), self.channel, chunk);
let mut requests = [Request::new(frame).with_expected_replies(0)];
self.controller.transport.cycle(&mut requests)?;
}
Ok(())
}
pub fn write_message(&mut self, data: &[u8]) -> Result<()> {
let mut msg = data.to_vec();
msg.push(b'\n');
self.write(&msg)
}
pub fn read(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
match self.use_flow_control {
Some(true) => self.read_flow(max_bytes),
Some(false) => self.read_plain(max_bytes),
None => self.probe_flow_control(max_bytes),
}
}
fn read_plain(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
let read_size = std::cmp::min(max_bytes, MAX_DIAGNOSTIC_READ) as u8;
let frame =
make_diagnostic_read_frame(self.id(), self.source_id(), self.channel, read_size);
let mut requests = [Request::new(frame)
.with_filter(plain_reply_filter(self.id()))
.with_expected_replies(1)];
self.controller.transport.cycle(&mut requests)?;
let mut result = Vec::new();
for response in requests[0].responses.take() {
if let Some(diag) = parse_diagnostic_response(&response, self.channel) {
result.extend(diag.data);
}
}
Ok(result)
}
fn read_flow(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
let read_size = std::cmp::min(max_bytes, MAX_DIAGNOSTIC_READ) as u8;
for _attempt in 0..=self.flow_retries {
let frame = make_diagnostic_read_flow_frame(
self.id(),
self.source_id(),
self.channel,
self.last_ack_packet,
read_size,
);
let mut requests = [Request::new(frame)
.with_filter(flow_reply_filter(self.id()))
.with_expected_replies(1)];
self.controller.transport.cycle(&mut requests)?;
let mut result = Vec::new();
let mut any_response = false;
for response in requests[0].responses.take() {
if let Some(flow) = parse_diagnostic_flow_response(&response, self.channel) {
result.extend(self.accept_flow_packet(&flow));
any_response = true;
}
}
if any_response {
return Ok(result);
}
}
Err(Error::Timeout)
}
fn probe_flow_control(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
let read_size = std::cmp::min(max_bytes, MAX_DIAGNOSTIC_READ) as u8;
let frame = make_diagnostic_read_flow_frame(
self.id(),
self.source_id(),
self.channel,
0,
read_size,
);
let mut requests = [Request::new(frame)
.with_filter(probe_reply_filter(self.id()))
.with_expected_replies(1)];
self.controller.transport.cycle(&mut requests)?;
let mut result = Vec::new();
let mut got_flow = false;
let mut got_plain = false;
for response in requests[0].responses.take() {
if let Some(flow) = parse_diagnostic_flow_response(&response, self.channel) {
result.extend(self.accept_flow_packet(&flow));
got_flow = true;
} else if let Some(diag) = parse_diagnostic_response(&response, self.channel) {
result.extend(diag.data);
got_plain = true;
}
}
if got_flow {
self.use_flow_control = Some(true);
} else if !got_plain {
self.use_flow_control = Some(false);
}
Ok(result)
}
pub fn flush_read(&mut self) -> Result<()> {
self.read_buffer.clear();
let start = std::time::Instant::now();
let timeout = std::time::Duration::from_millis(200);
while start.elapsed() < timeout {
let data = self.read_plain(MAX_DIAGNOSTIC_READ)?;
if data.is_empty() {
std::thread::sleep(std::time::Duration::from_millis(10));
}
}
self.read_buffer.clear();
Ok(())
}
pub fn readline(&mut self) -> Result<Vec<u8>> {
loop {
if let Some(pos) = self
.read_buffer
.iter()
.position(|&b| b == b'\n' || b == b'\r')
{
let line: Vec<u8> = self.read_buffer.drain(..=pos).collect();
let line: Vec<u8> = line
.into_iter()
.filter(|&b| b != b'\n' && b != b'\r')
.collect();
if !line.is_empty() {
return Ok(line);
}
continue;
}
let data = self.read(MAX_DIAGNOSTIC_READ)?;
if data.is_empty() {
std::thread::sleep(std::time::Duration::from_millis(10));
}
self.read_buffer.extend(data);
}
}
fn read_until_ok(&mut self) -> Result<Vec<u8>> {
let mut result = Vec::new();
loop {
let line = self.readline()?;
if line.starts_with(b"OK") {
return Ok(result);
}
if line.starts_with(b"ERR") {
return Err(Error::Protocol(String::from_utf8_lossy(&line).to_string()));
}
result.extend(&line);
result.push(b'\n');
}
}
pub fn command(&mut self, data: &[u8]) -> Result<Vec<u8>> {
self.write_message(data)?;
self.read_until_ok()
}
pub fn command_oneline(&mut self, data: &[u8]) -> Result<Vec<u8>> {
self.write_message(data)?;
self.readline()
}
}
#[cfg(feature = "tokio")]
use crate::async_controller::AsyncController;
#[cfg(feature = "tokio")]
use crate::transport::async_transport::AsyncTransport;
#[cfg(feature = "tokio")]
pub struct AsyncDiagnosticStream<
'a,
T: AsyncTransport = std::sync::Arc<
tokio::sync::Mutex<crate::transport::async_transport::AsyncRouter>,
>,
> {
controller: &'a mut AsyncController<T>,
channel: u8,
read_buffer: Vec<u8>,
use_flow_control: Option<bool>,
last_ack_packet: u8,
seen_first_packet: bool,
flow_retries: u32,
}
#[cfg(feature = "tokio")]
impl<'a, T: AsyncTransport> std::fmt::Debug for AsyncDiagnosticStream<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncDiagnosticStream")
.field("device_id", &self.controller.controller.id)
.field("channel", &self.channel)
.field("read_buffer_len", &self.read_buffer.len())
.field("use_flow_control", &self.use_flow_control)
.field("last_ack_packet", &self.last_ack_packet)
.finish()
}
}
#[cfg(feature = "tokio")]
impl<'a, T: AsyncTransport> AsyncDiagnosticStream<'a, T> {
pub fn new(controller: &'a mut AsyncController<T>) -> Self {
Self::with_channel(controller, DEFAULT_CHANNEL)
}
pub fn with_channel(controller: &'a mut AsyncController<T>, channel: u8) -> Self {
Self {
controller,
channel,
read_buffer: Vec::new(),
use_flow_control: None,
last_ack_packet: 0,
seen_first_packet: false,
flow_retries: DEFAULT_FLOW_RETRIES,
}
}
fn accept_flow_packet<'d>(&mut self, flow: &'d DiagnosticFlowResponse) -> &'d [u8] {
let duplicate = self.seen_first_packet && flow.packet_number == self.last_ack_packet;
self.last_ack_packet = flow.packet_number;
self.seen_first_packet = true;
if duplicate {
&[]
} else {
&flow.data
}
}
#[must_use]
pub fn use_flow_control(mut self, enabled: bool) -> Self {
self.use_flow_control = Some(enabled);
self
}
fn id(&self) -> u8 {
self.controller.controller.id
}
fn source_id(&self) -> u8 {
self.controller.controller.source_id
}
pub async fn write(&mut self, data: &[u8]) -> Result<()> {
for chunk in data.chunks(MAX_DIAGNOSTIC_WRITE) {
let frame =
make_diagnostic_write_frame(self.id(), self.source_id(), self.channel, chunk);
let mut requests = [Request::new(frame).with_expected_replies(0)];
self.controller.transport.cycle(&mut requests).await?;
}
Ok(())
}
pub async fn write_message(&mut self, data: &[u8]) -> Result<()> {
let mut msg = data.to_vec();
msg.push(b'\n');
self.write(&msg).await
}
pub async fn read(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
match self.use_flow_control {
Some(true) => self.read_flow(max_bytes).await,
Some(false) => self.read_plain(max_bytes).await,
None => self.probe_flow_control(max_bytes).await,
}
}
async fn read_plain(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
let read_size = std::cmp::min(max_bytes, MAX_DIAGNOSTIC_READ) as u8;
let frame =
make_diagnostic_read_frame(self.id(), self.source_id(), self.channel, read_size);
let mut requests = [Request::new(frame)
.with_filter(plain_reply_filter(self.id()))
.with_expected_replies(1)];
self.controller.transport.cycle(&mut requests).await?;
let mut result = Vec::new();
for response in requests[0].responses.take() {
if let Some(diag) = parse_diagnostic_response(&response, self.channel) {
result.extend(diag.data);
}
}
Ok(result)
}
async fn read_flow(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
let read_size = std::cmp::min(max_bytes, MAX_DIAGNOSTIC_READ) as u8;
for _attempt in 0..=self.flow_retries {
let frame = make_diagnostic_read_flow_frame(
self.id(),
self.source_id(),
self.channel,
self.last_ack_packet,
read_size,
);
let mut requests = [Request::new(frame)
.with_filter(flow_reply_filter(self.id()))
.with_expected_replies(1)];
self.controller.transport.cycle(&mut requests).await?;
let mut result = Vec::new();
let mut any_response = false;
for response in requests[0].responses.take() {
if let Some(flow) = parse_diagnostic_flow_response(&response, self.channel) {
result.extend(self.accept_flow_packet(&flow));
any_response = true;
}
}
if any_response {
return Ok(result);
}
}
Err(Error::Timeout)
}
async fn probe_flow_control(&mut self, max_bytes: usize) -> Result<Vec<u8>> {
let read_size = std::cmp::min(max_bytes, MAX_DIAGNOSTIC_READ) as u8;
let frame = make_diagnostic_read_flow_frame(
self.id(),
self.source_id(),
self.channel,
0,
read_size,
);
let mut requests = [Request::new(frame)
.with_filter(probe_reply_filter(self.id()))
.with_expected_replies(1)];
self.controller.transport.cycle(&mut requests).await?;
let mut result = Vec::new();
let mut got_flow = false;
let mut got_plain = false;
for response in requests[0].responses.take() {
if let Some(flow) = parse_diagnostic_flow_response(&response, self.channel) {
result.extend(self.accept_flow_packet(&flow));
got_flow = true;
} else if let Some(diag) = parse_diagnostic_response(&response, self.channel) {
result.extend(diag.data);
got_plain = true;
}
}
if got_flow {
self.use_flow_control = Some(true);
} else if !got_plain {
self.use_flow_control = Some(false);
}
Ok(result)
}
pub async fn flush_read(&mut self) -> Result<()> {
self.read_buffer.clear();
let start = std::time::Instant::now();
let timeout = std::time::Duration::from_millis(200);
while start.elapsed() < timeout {
let data = self.read_plain(MAX_DIAGNOSTIC_READ).await?;
if data.is_empty() {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
}
self.read_buffer.clear();
Ok(())
}
pub async fn readline(&mut self) -> Result<Vec<u8>> {
loop {
if let Some(pos) = self
.read_buffer
.iter()
.position(|&b| b == b'\n' || b == b'\r')
{
let line: Vec<u8> = self.read_buffer.drain(..=pos).collect();
let line: Vec<u8> = line
.into_iter()
.filter(|&b| b != b'\n' && b != b'\r')
.collect();
if !line.is_empty() {
return Ok(line);
}
continue;
}
let data = self.read(MAX_DIAGNOSTIC_READ).await?;
if data.is_empty() {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
self.read_buffer.extend(data);
}
}
async fn read_until_ok(&mut self) -> Result<Vec<u8>> {
let mut result = Vec::new();
loop {
let line = self.readline().await?;
if line.starts_with(b"OK") {
return Ok(result);
}
if line.starts_with(b"ERR") {
return Err(Error::Protocol(String::from_utf8_lossy(&line).to_string()));
}
result.extend(&line);
result.push(b'\n');
}
}
pub async fn command(&mut self, data: &[u8]) -> Result<Vec<u8>> {
self.write_message(data).await?;
self.read_until_ok().await
}
pub async fn command_oneline(&mut self, data: &[u8]) -> Result<Vec<u8>> {
self.write_message(data).await?;
self.readline().await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::transaction::dispatch_frame;
use moteus_protocol::{CLIENT_POLL_SERVER, CLIENT_POLL_SERVER_FLOW, CLIENT_TO_SERVER};
use std::sync::{Arc, Mutex};
use std::time::Duration;
struct ScriptedTransport {
responder: Box<dyn FnMut(&CanFdFrame) -> Vec<CanFdFrame> + Send>,
sent: Arc<Mutex<Vec<CanFdFrame>>>,
timeout: Duration,
}
impl ScriptedTransport {
fn new(
responder: impl FnMut(&CanFdFrame) -> Vec<CanFdFrame> + Send + 'static,
) -> (Self, Arc<Mutex<Vec<CanFdFrame>>>) {
let sent = Arc::new(Mutex::new(Vec::new()));
(
Self {
responder: Box::new(responder),
sent: sent.clone(),
timeout: Duration::from_millis(100),
},
sent,
)
}
}
impl Transport for ScriptedTransport {
fn cycle(&mut self, requests: &mut [Request]) -> Result<()> {
for i in 0..requests.len() {
if let Some(frame) = requests[i].frame.clone() {
self.sent.lock().unwrap().push(frame.clone());
for reply in (self.responder)(&frame) {
dispatch_frame(&reply, requests);
}
}
}
Ok(())
}
fn write(&mut self, _frame: &CanFdFrame) -> Result<()> {
Ok(())
}
fn read(&mut self, _channel: Option<usize>) -> Result<Option<CanFdFrame>> {
Ok(None)
}
fn flush_read(&mut self, _channel: Option<usize>) -> Result<()> {
Ok(())
}
fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
fn timeout(&self) -> Duration {
self.timeout
}
}
fn flow_response(id: u8, packet_number: u8, data: &[u8]) -> CanFdFrame {
let mut frame = CanFdFrame::new();
frame.arbitration_id = moteus_protocol::calculate_arbitration_id(id as i8, 0, 0, false);
frame.data[0] = moteus_protocol::SERVER_TO_CLIENT_FLOW;
frame.data[1] = 1; frame.data[2] = packet_number;
frame.data[3] = data.len() as u8;
frame.data[4..4 + data.len()].copy_from_slice(data);
frame.size = (4 + data.len()) as u8;
frame
}
fn plain_response(id: u8, data: &[u8]) -> CanFdFrame {
let mut frame = CanFdFrame::new();
frame.arbitration_id = moteus_protocol::calculate_arbitration_id(id as i8, 0, 0, false);
frame.data[0] = SERVER_TO_CLIENT;
frame.data[1] = 1; frame.data[2] = data.len() as u8;
frame.data[3..3 + data.len()].copy_from_slice(data);
frame.size = (3 + data.len()) as u8;
frame
}
#[test]
fn test_flow_control_probe_enabled() {
let (transport, sent) = ScriptedTransport::new(|frame: &CanFdFrame| {
match frame.data[0] {
CLIENT_POLL_SERVER_FLOW => {
vec![flow_response(1, frame.data[2].wrapping_add(1), b"hi")]
}
_ => vec![],
}
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl);
assert_eq!(stream.read(48).unwrap(), b"hi");
assert_eq!(stream.use_flow_control, Some(true));
assert_eq!(stream.last_ack_packet, 1);
assert_eq!(stream.read(48).unwrap(), b"hi");
assert_eq!(stream.last_ack_packet, 2);
let sent = sent.lock().unwrap();
assert_eq!(sent.len(), 2);
assert_eq!(sent[1].data[0], CLIENT_POLL_SERVER_FLOW);
assert_eq!(sent[1].data[2], 1, "second poll acks packet 1");
}
#[test]
fn test_flow_control_probe_disabled_plain() {
let (transport, sent) = ScriptedTransport::new(|frame: &CanFdFrame| {
match frame.data[0] {
CLIENT_POLL_SERVER => vec![plain_response(1, b"plain")],
_ => vec![], }
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl);
assert_eq!(stream.read(48).unwrap(), b"");
assert_eq!(stream.use_flow_control, Some(false));
assert_eq!(stream.read(48).unwrap(), b"plain");
let sent = sent.lock().unwrap();
assert_eq!(sent[1].data[0], CLIENT_POLL_SERVER);
}
#[test]
fn test_flow_control_forced_off() {
let (transport, sent) = ScriptedTransport::new(|frame: &CanFdFrame| match frame.data[0] {
CLIENT_POLL_SERVER => vec![plain_response(1, b"data")],
_ => vec![],
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl).use_flow_control(false);
assert_eq!(stream.read(48).unwrap(), b"data");
assert_eq!(sent.lock().unwrap()[0].data[0], CLIENT_POLL_SERVER);
}
#[test]
fn test_flow_control_arbitrary_start_packet() {
let (transport, _sent) = ScriptedTransport::new(|frame: &CanFdFrame| match frame.data[0] {
CLIENT_POLL_SERVER_FLOW if frame.data[2] == 0 => {
vec![flow_response(1, 200, b"a")]
}
CLIENT_POLL_SERVER_FLOW if frame.data[2] == 200 => {
vec![flow_response(1, 201, b"b")]
}
_ => vec![],
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl);
assert_eq!(stream.read(48).unwrap(), b"a");
assert_eq!(stream.last_ack_packet, 200);
assert_eq!(stream.read(48).unwrap(), b"b");
assert_eq!(stream.last_ack_packet, 201);
}
#[test]
fn test_flow_control_packet_number_wraps() {
let (transport, _sent) = ScriptedTransport::new(|frame: &CanFdFrame| {
match frame.data[0] {
CLIENT_POLL_SERVER_FLOW => {
vec![flow_response(1, frame.data[2].wrapping_add(1), b"x")]
}
_ => vec![],
}
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl).use_flow_control(true);
stream.last_ack_packet = 255;
stream.seen_first_packet = true;
assert_eq!(stream.read(48).unwrap(), b"x");
assert_eq!(stream.last_ack_packet, 0, "packet number wraps 255 -> 0");
}
#[test]
fn test_flow_control_duplicate_packet_not_delivered_twice() {
let (transport, _sent) = ScriptedTransport::new(|frame: &CanFdFrame| {
match frame.data[0] {
CLIENT_POLL_SERVER_FLOW => {
vec![flow_response(1, 5, b"dup")]
}
_ => vec![],
}
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl).use_flow_control(true);
assert_eq!(stream.read(48).unwrap(), b"dup");
assert_eq!(stream.read(48).unwrap(), b"", "retransmission deduplicated");
assert_eq!(stream.last_ack_packet, 5);
}
#[test]
fn test_flow_control_first_packet_zero_is_delivered() {
let (transport, _sent) = ScriptedTransport::new(|frame: &CanFdFrame| match frame.data[0] {
CLIENT_POLL_SERVER_FLOW => vec![flow_response(1, 0, b"first")],
_ => vec![],
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl).use_flow_control(true);
assert_eq!(stream.read(48).unwrap(), b"first");
}
#[test]
fn test_flow_control_probe_not_latched_by_plain_response() {
let mut polls = 0;
let (transport, _sent) = ScriptedTransport::new(move |frame: &CanFdFrame| {
match frame.data[0] {
CLIENT_POLL_SERVER_FLOW => {
polls += 1;
if polls == 1 {
vec![plain_response(1, b"stale")]
} else {
vec![flow_response(1, 7, b"flow")]
}
}
_ => vec![],
}
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl);
assert_eq!(stream.read(48).unwrap(), b"stale");
assert_eq!(stream.use_flow_control, None);
assert_eq!(stream.read(48).unwrap(), b"flow");
assert_eq!(stream.use_flow_control, Some(true));
}
#[test]
fn test_flow_control_advances_ack_on_empty_data() {
let (transport, _sent) = ScriptedTransport::new(|frame: &CanFdFrame| match frame.data[0] {
CLIENT_POLL_SERVER_FLOW => {
vec![flow_response(1, frame.data[2].wrapping_add(1), b"")]
}
_ => vec![],
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl).use_flow_control(true);
assert_eq!(stream.read(48).unwrap(), b"");
assert_eq!(stream.last_ack_packet, 1, "ack advances even when empty");
}
#[test]
fn test_flow_control_read_retries() {
let mut polls = 0;
let (transport, sent) = ScriptedTransport::new(move |frame: &CanFdFrame| {
match frame.data[0] {
CLIENT_POLL_SERVER_FLOW => {
polls += 1;
if polls == 1 {
vec![] } else {
vec![flow_response(1, frame.data[2].wrapping_add(1), b"ok")]
}
}
_ => vec![],
}
});
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl).use_flow_control(true);
assert_eq!(stream.read(48).unwrap(), b"ok");
let sent = sent.lock().unwrap();
assert_eq!(sent.len(), 2);
assert_eq!(
sent[0].data[2], sent[1].data[2],
"retry re-acks the same packet"
);
}
#[test]
fn test_flow_control_retries_exhausted() {
let (transport, sent) = ScriptedTransport::new(|_: &CanFdFrame| vec![]);
let mut ctrl = BlockingController::with_transport(1, transport);
let mut stream = DiagnosticStream::new(&mut ctrl).use_flow_control(true);
assert!(matches!(stream.read(48), Err(Error::Timeout)));
assert_eq!(
sent.lock().unwrap().len(),
1 + DEFAULT_FLOW_RETRIES as usize
);
}
#[test]
fn test_make_diagnostic_read_flow_frame() {
let frame = make_diagnostic_read_flow_frame(1, 0, 1, 0xAB, 48);
assert_eq!(
frame.arbitration_id,
moteus_protocol::calculate_arbitration_id(0, 1, 0, true)
);
assert_eq!(frame.data[0], CLIENT_POLL_SERVER_FLOW);
assert_eq!(frame.data[1], 1); assert_eq!(frame.data[2], 0xAB); assert_eq!(frame.data[3], 48); assert_eq!(frame.size, 4);
}
#[test]
fn test_parse_diagnostic_flow_response() {
let frame = flow_response(1, 7, b"abc");
let parsed = parse_diagnostic_flow_response(&frame, 1).unwrap();
assert_eq!(parsed.id, 1);
assert_eq!(parsed.packet_number, 7);
assert_eq!(parsed.data, b"abc");
assert!(parse_diagnostic_flow_response(&frame, 2).is_none());
let frame = plain_response(1, b"abc");
assert!(parse_diagnostic_flow_response(&frame, 1).is_none());
}
#[test]
fn test_make_diagnostic_write_frame() {
let frame = make_diagnostic_write_frame(1, 0, 1, b"hello");
assert_eq!(
frame.arbitration_id,
moteus_protocol::calculate_arbitration_id(0, 1, 0, false)
);
assert_eq!(frame.data[0], CLIENT_TO_SERVER);
assert_eq!(frame.data[1], 1); assert_eq!(frame.data[2], 5); assert_eq!(&frame.data[3..8], b"hello");
assert_eq!(frame.size, 8);
}
#[test]
fn test_make_diagnostic_read_frame() {
let frame = make_diagnostic_read_frame(1, 0, 1, 48);
assert_eq!(
frame.arbitration_id,
moteus_protocol::calculate_arbitration_id(0, 1, 0, true)
);
assert_eq!(frame.data[0], CLIENT_POLL_SERVER);
assert_eq!(frame.data[1], 1); assert_eq!(frame.data[2], 48); assert_eq!(frame.size, 3);
}
#[test]
fn test_parse_diagnostic_response() {
let mut frame = CanFdFrame::new();
frame.arbitration_id = 0x8100; frame.data[0] = SERVER_TO_CLIENT;
frame.data[1] = 1; frame.data[2] = 5; frame.data[3..8].copy_from_slice(b"hello");
frame.size = 8;
let result = parse_diagnostic_response(&frame, 1).unwrap();
assert_eq!(result.id, 1);
assert_eq!(result.data, b"hello");
}
#[test]
fn test_parse_diagnostic_response_wrong_channel() {
let mut frame = CanFdFrame::new();
frame.data[0] = SERVER_TO_CLIENT;
frame.data[1] = 2; frame.data[2] = 5;
frame.size = 8;
let result = parse_diagnostic_response(&frame, 1);
assert!(result.is_none());
}
}