Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Architecture — rust-LIN v0.2.0
# Architecture — rust-LIN

## Overview

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-lin"
version = "0.1.0"
version = "0.4.1"
edition = "2021"
description = "rust-LIN: Rust library for LIN bus (Local Interconnect Network) — LIN 2.x, virtual bus, LDF parser, master/slave nodes, safety E2E"
license = "MPL-2.0"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ The `Bus` and `MasterBus` traits are stable. Implementations are swappable witho
| `virtual_bus` | In-process bus — zero OS dependencies, master+slave | All |
| `mock` | Mock bus for unit testing with frame injection | All |
| `master` | `MasterNode` — schedule table execution, callbacks | All |
| `slave` | `SlaveNode` — registers/removes slave response registrations | All |
| `ldf` | LIN Description File (LDF) parser — nodes, frames, signals, schedule tables | All |
| `safety` | ISO 26262 ASIL-B end-to-end (E2E) data protection — `Protector`/`Receiver` | All |
| `adapt` | RELAY v1.11 adapter — `adapt()`, `to_message()`, `from_message()` | All |

---
Expand Down
67 changes: 58 additions & 9 deletions src/adapt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use tokio::sync::mpsc;

use crate::bus::Bus;
use crate::error::Error;
use crate::frame::{ChecksumType, Frame, LIN_MAX_ID};
use crate::frame::{ChecksumType, Frame, LIN_MAX_DATA_LEN, LIN_MAX_ID};
use crate::relay::{BackPressurePolicy, Context, Message, Protocol, SubscriberOptions};

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -118,18 +118,24 @@ impl crate::relay::Node for LinAdapter {
}

/// Send a relay::Message by converting to a LIN publish call.
///
/// Enforces `LIN_MAX_DATA_LEN` (§15.3) directly on the payload so
/// `ErrPayloadTooLarge` (§5.1) is returned for real oversized payloads,
/// independent of whichever `Bus` implementation is behind this adapter.
/// Structural conversion failures (bad ID, wrong protocol) are mapped via
/// `Error::kind()` rather than being collapsed into `PayloadTooLarge`.
//fusa:req REQ-LIN-021
//fusa:req REQ-SEC-002
async fn send(&self, _ctx: Context, msg: Message) -> Result<(), crate::relay::Error> {
let frame = from_message(&msg).map_err(|_| crate::relay::Error::PayloadTooLarge)?;
if msg.payload.len() > LIN_MAX_DATA_LEN {
return Err(crate::relay::Error::PayloadTooLarge);
}
let frame =
from_message(&msg).map_err(|e| e.kind().unwrap_or(crate::relay::Error::Closed))?;
self.bus
.publish(frame.id, Some(frame.data))
.await
.map_err(|e| match e {
Error::Closed => crate::relay::Error::Closed,
Error::NotConnected => crate::relay::Error::NotConnected,
Error::Timeout => crate::relay::Error::Timeout,
Error::PayloadTooLarge => crate::relay::Error::PayloadTooLarge,
_ => crate::relay::Error::Closed,
})
.map_err(|e| e.kind().unwrap_or(crate::relay::Error::Closed))
}

/// Subscribe to the bus and forward frames as relay::Messages.
Expand Down Expand Up @@ -292,4 +298,47 @@ mod tests {
assert_eq!(published.len(), 1);
assert_eq!(published[0].0, 0x10);
}

//fusa:test REQ-LIN-021
//fusa:test REQ-SEC-002
#[tokio::test]
async fn send_rejects_oversized_payload_with_payload_too_large() {
use crate::mock::MockBus;
let mock = Arc::new(MockBus::new());
let node = adapt(mock.clone());

let big_payload = vec![0u8; 200]; // 25x over LIN_MAX_DATA_LEN (8)
let msg = Message::new(Protocol::Lin, "16", big_payload);

let result = node.send(Context::background(), msg).await;
assert_eq!(result, Err(crate::relay::Error::PayloadTooLarge));
// The oversized payload must never have reached the bus.
assert!(mock.published_responses().await.is_empty());
}

//fusa:test REQ-LIN-021
#[tokio::test]
async fn send_malformed_id_does_not_return_payload_too_large() {
use crate::mock::MockBus;
let mock = Arc::new(MockBus::new());
let node = adapt(mock.clone());

let msg = Message::new(Protocol::Lin, "not_a_number", vec![0x01]);
let result = node.send(Context::background(), msg).await;

assert!(result.is_err());
assert_ne!(result.unwrap_err(), crate::relay::Error::PayloadTooLarge);
}

//fusa:test REQ-LIN-021
#[tokio::test]
async fn send_bus_payload_too_large_propagates() {
use crate::mock::MockBus;
// A payload right at the LIN_MAX_DATA_LEN boundary passes the
// adapter's own check, but MockBus::publish must still enforce the
// limit for callers that bypass the adapter's fast-path.
let mock = Arc::new(MockBus::new());
let err = mock.publish(0x10, Some(vec![0u8; 9])).await.unwrap_err();
assert!(matches!(err, Error::PayloadTooLarge));
}
}
22 changes: 21 additions & 1 deletion src/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use tokio::sync::Mutex;
use crate::bus::{Bus, FrameReceiver, MasterBus, SubInner};
use crate::error::Error;
use crate::frame::{
calc_checksum, protect_id, ChecksumType, Filter, Frame, ScheduleEntry, LIN_MAX_ID,
calc_checksum, protect_id, ChecksumType, Filter, Frame, ScheduleEntry, LIN_MAX_DATA_LEN,
LIN_MAX_ID,
};
use crate::relay::{Context, SubscriberOptions};

Expand Down Expand Up @@ -93,6 +94,12 @@ impl Bus for MockBus {
if self.closed.load(Ordering::SeqCst) {
return Err(Error::Closed);
}
// §5.1/§15.3: reject payloads over LIN_MAX_DATA_LEN with PayloadTooLarge.
if let Some(ref d) = data {
if d.len() > LIN_MAX_DATA_LEN {
return Err(Error::PayloadTooLarge);
}
}
self.published.lock().await.push((id, data.clone()));
let mut responses = self.responses.lock().await;
match data {
Expand Down Expand Up @@ -249,4 +256,17 @@ mod tests {
let err = bus.publish(0x10, Some(vec![0x01])).await.unwrap_err();
assert!(matches!(err, Error::Closed));
}

#[tokio::test]
async fn publish_rejects_oversized_payload() {
let bus = MockBus::new();
let err = bus.publish(0x10, Some(vec![0u8; 9])).await.unwrap_err();
assert!(matches!(err, Error::PayloadTooLarge));
}

#[tokio::test]
async fn publish_accepts_max_data_len() {
let bus = MockBus::new();
bus.publish(0x10, Some(vec![0u8; 8])).await.unwrap();
}
}
25 changes: 24 additions & 1 deletion src/virtual_bus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::bus::{Bus, FrameReceiver, HealthProvider, MasterBus, MetricsProvider,
use crate::error::Error;
use crate::frame::{
calc_checksum, protect_id, validate_frame, ChecksumType, Filter, Frame, ScheduleEntry,
LIN_MAX_ID,
LIN_MAX_DATA_LEN, LIN_MAX_ID,
};
use crate::relay::{Context, Health, Metrics, SubscriberOptions};

Expand Down Expand Up @@ -165,6 +165,14 @@ impl VirtualBus {
id, LIN_MAX_ID
)));
}
// §5.1/§15.3: reject payloads over LIN_MAX_DATA_LEN with PayloadTooLarge
// (distinct from validate_frame's structural ErrInvalidFrame, per §5.3).
if let Some(ref d) = data {
if d.len() > LIN_MAX_DATA_LEN {
self.error_count.fetch_add(1, Ordering::Relaxed);
return Err(Error::PayloadTooLarge);
}
}
let mut guard = self.inner.lock().await;
match data {
Some(d) => {
Expand Down Expand Up @@ -440,6 +448,21 @@ mod tests {
assert!(matches!(err, Error::InvalidFrame { .. }));
}

//fusa:test REQ-VIRT-004
#[tokio::test]
async fn publish_rejects_oversized_payload() {
let bus = VirtualBus::new();
let err = bus.publish(0x10, Some(vec![0u8; 9])).await.unwrap_err();
assert!(matches!(err, Error::PayloadTooLarge));
}

//fusa:test REQ-VIRT-004
#[tokio::test]
async fn publish_accepts_max_data_len() {
let bus = VirtualBus::new();
bus.publish(0x10, Some(vec![0u8; 8])).await.unwrap();
}

//fusa:test REQ-VIRT-005
#[tokio::test]
async fn publish_after_close_returns_error() {
Expand Down
Loading