Document ID: SAS-cpp-LIN-001
Revision: 0.1
Date: 2026-06-19
Author: Matt Jones
Standard: ISO 26262:2018 Part 6 §7.4, IEC 61508:2010 Part 3 §7.4
ASIL: ASIL-B
This SAS defines the decomposition of cpp-LIN into software modules, their responsibilities, invariants, interfaces, and inter-module dependencies. It serves as:
- The primary evidence for ISO 26262-6 §7.4.1 (software architectural design)
- The basis for the FMEA (see
fmea.json) - The design reference for the safety manual (see
SAFETY_MANUAL.md)
| Module | Namespace | Files | ASIL | Role |
|---|---|---|---|---|
| M-01 Core | lin:: |
src/lin.cpp, include/lin/lin.hpp |
ASIL-B | Frame validation, PID, checksum, message conversion |
| M-02 Virtual Bus | lin::virt:: |
src/virtual/bus.cpp, include/lin/virtual/bus.hpp |
ASIL-B | Thread-safe in-memory LIN transport |
| M-03 Safety | lin::safety:: |
src/safety.cpp, include/lin/safety.hpp |
ASIL-B | E2E protection (CRC-16, sequence counter) |
| M-04 Master | lin::master:: |
src/master.cpp, include/lin/master.hpp |
ASIL-B | Schedule-driven frame exchange |
| M-05 Slave | lin::slave:: |
src/slave.cpp, include/lin/slave.hpp |
ASIL-B | Response registration and subscription |
| M-06 LDF | lin::ldf:: |
src/ldf.cpp, include/lin/ldf.hpp |
ASIL-A | LDF file parser |
| M-07 RELAY Adapter | lin:: |
src/lin.cpp (adapt()) |
ASIL-B | Wraps IBus as relay::INode |
| M-08 RELAY Types | relay:: |
src/relay.cpp, include/lin/relay.hpp |
ASIL-B | RELAY spec types, error codes, subscriber options |
| M-09 Channel | lin:: |
include/lin/channel.hpp |
ASIL-B | Type-safe bounded SPSC/MPSC channel |
M-04 (Master) ──► M-01 (Core) ──► M-08 (RELAY Types)
M-05 (Slave) ──► M-01 (Core)
M-02 (Virt) ──► M-01 (Core)
M-07 (Adapt) ──► M-01 (Core) ──► M-08 (RELAY Types)
M-07 (Adapt) ──► M-09 (Channel)
M-03 (Safety) ──► (none — standalone, no LIN dependency)
M-06 (LDF) ──► (none — parse-only, no bus dependency)
No circular dependencies. M-03 and M-06 are independently usable.
Responsibilities:
- Frame validation (
validate_frame) - PID computation and verification (
protect_id,verify_pid) - Checksum computation (
calc_checksum,ChecksumType) - Message conversion (
to_message,from_message) - IBus abstract interface definition
- Error code definition (
Errc,ErrInvalidFrame,ErrNoResponse)
Invariants:
validate_frame(f)never modifiesfprotect_id(id)is a pure function: same input → same output, no side effectsprotect_id(protect_id(id) & 0x3F)is idempotent for any valid idcalc_checksumnever throws; result is always in [0x00, 0xFF]
Public Interface:
// Boundary validation
void validate_frame(const Frame& f); // throws ErrInvalidFrame
// PID
uint8_t protect_id(uint8_t id) noexcept;
uint8_t verify_pid(uint8_t pid); // throws ErrInvalidFrame
// Checksum
uint8_t calc_checksum(uint8_t pid, const std::vector<uint8_t>& data,
ChecksumType ct) noexcept;
// Message conversion
relay::Message to_message(const Frame& f);
Frame from_message(const relay::Message& m); // throws ErrInvalidFrame
// Bus abstraction
class IBus { ... };
std::unique_ptr<relay::INode> adapt(std::shared_ptr<IBus> bus);Requirements: REQ-LIN-001..021, REQ-ADAPT-001..005
Responsibilities:
- Thread-safe in-memory simulation of a LIN bus
- Response registration and send_header frame exchange
- Subscriber fan-out with backpressure (drop-full-channel)
- Health reporting and metrics collection
- Close/drain lifecycle
Invariants:
virt::Busis safe for concurrent access from any number of threadsclose()is idempotent — calling it twice has no effect- After
close(), all subscriber channels are closed; blockingChan::recv()returnsnullopt publish(id, data)stores a defensive copy — mutations to caller's data do not affect stored response
Internal Design:
std::shared_mutexfor read/write access to response table and subscriber list- Subscriber channels:
std::shared_ptr<Chan<Frame>>with bounded capacity (default 64) - Health/Metrics: atomic counters, no lock required for reading
Public Interface:
static std::shared_ptr<Bus> create();
std::error_code publish(uint8_t id, std::vector<uint8_t> data);
std::pair<Frame, std::error_code> send_header(uint8_t id);
std::pair<std::shared_ptr<Chan<Frame>>, std::error_code>
subscribe(std::vector<Filter> filters, std::vector<SubscriberOption> opts);
std::error_code close();
Health health() const;
Metrics metrics() const;
std::error_code close_with_drain(std::chrono::milliseconds timeout);Requirements: REQ-VIRT-001..019
Responsibilities:
- E2E protection: CRC-16/CCITT-FALSE over 10-byte header + payload
- Sequence counter (monotonically increasing, atomic)
- Header embedding: DataID (bytes 0-1), SourceID (bytes 2-3), counter (bytes 4-7), CRC (bytes 8-9)
- E2E verification: CRC check, sequence gap detection
Invariants:
Protector::protect(payload)output length is exactly10 + payload.size()Protector::protect()is thread-safe (usesstd::atomic<uint32_t>for counter)Receiver::unwrap()never returns partial results — either full payload or throwsE2EErrorE2EError::kind()is always one of:ErrHeaderTooShort,ErrCRCMismatch,ErrSequenceGap
CRC Algorithm:
- Polynomial: 0x1021 (CRC-16/CCITT-FALSE)
- Initial value: 0xFFFF
- Input reflection: No; Output reflection: No; XOR out: 0x0000
- CRC computed over
[header with CRC bytes zeroed] || payload
Public Interface:
struct Config { uint16_t data_id; uint16_t source_id; };
class Protector {
explicit Protector(Config cfg);
std::vector<uint8_t> protect(const std::vector<uint8_t>& payload);
};
class E2EError : public std::exception { ... };
class Receiver {
explicit Receiver(Config cfg);
std::vector<uint8_t> unwrap(const std::vector<uint8_t>& protected_data);
};Requirements: REQ-SAFETY-001..015
Responsibilities:
- Schedule-driven LIN frame exchange: iterate table in order, loop
- Per-slot timing:
std::this_thread::sleep_for(DelayMs) - Callback dispatch:
OnFrameon success,OnErroron error - Graceful shutdown via
std::atomic<bool>& stop
Invariants:
run()processes slots strictly in the order they appear in the schedule tablerun()returns without stopping the schedule on per-slotErrNoResponse(REQ-MASTER-013)run()only returns whenstop.load()is true or the bus is closedset_schedule()stores a defensive copy
Public Interface:
class Node {
explicit Node(std::shared_ptr<IBus> bus);
std::error_code set_schedule(std::vector<ScheduleEntry> entries);
std::error_code run(const std::atomic<bool>& stop);
void on_frame(std::function<void(Frame)> cb);
void on_error(std::function<void(std::error_code)> cb);
std::pair<Frame, std::error_code> send_header(uint8_t id);
};Requirements: REQ-MASTER-001..013
Responsibilities:
- Response registration via
IBus::publish() - Tracking registered IDs for
RegisteredIDs()diagnostic - Subscription delegation to
IBus::subscribe()
Invariants:
set_response(id, nullptr)removesidfrom the registered setRegisteredIDs()returns an empty vector (not null) when no responses registeredset_responsevalidates id ≤ 0x3F before callingbus->publish()
Requirements: REQ-SLAVE-001..008
Responsibilities:
- Parse LIN Description File (LDF) text
- Populate
DBstruct with: protocol version, baud rate, node names, frame descriptors, signal definitions, schedule tables - Never panic on arbitrary input (REQ-LDF-014)
Invariants:
ldf::parse()always returns a non-nullDB; empty fields for unparseable sectionsDB::Frame(id)returns null for unknown IDs (not a crash)DB::Signal(name)returns null for unknown names (not a crash)DB::Frames()returns a defensive copy
Requirements: REQ-LDF-001..015
Responsibilities:
- Wraps an
IBusas arelay::INode send(): decodes frame ID fromrelay::Message::id, publishes to bussubscribe(): spawns a background thread that convertsChan<Frame>toChan<relay::Message>close(): delegates toIBus::close()
Invariants:
protocol()always returnsrelay::Protocol::LINsend()rejects non-numeric or out-of-range ID strings without publishing- Background subscriber threads exit when the underlying
Chan<Frame>is closed seqcounter in relay::Messages is strictly monotonically increasing perLinAdapterinstance
Requirements: REQ-ADAPT-001..005
Responsibilities:
- Protocol enum (CAN=1, DDS=2, LIN=3, MQTT=4, RCP=5, SOMEIP=6)
Messageenvelope structErrcerror codes andstd::error_categoryBackPressurePolicyenumSubscriberOption/SubscriberConfig/apply_options()INode,ICallerabstract interfaces- Optional capability interfaces:
IHealthProvider,IMetricsProvider,IDrainer - kSpecVersion constant "1.11"
Requirements: REQ-RELAY-001..029, REQ-RELAY-051, REQ-RELAY-056, REQ-RELAY-059
Responsibilities:
- Bounded, thread-safe, multi-producer / multi-consumer channel
- Blocking
send()/recv()and non-blockingtry_send()/try_recv() - Drop-oldest policy:
send_drop_oldest() close(): unblocks all waiters; subsequentrecv()drains remaining items then returnsnullopt
Invariants:
Chanis constructed with a fixed capacity; it never grows dynamically- After
close(),send()returns immediately (channel closed) - Items pushed before
close()are still retrievable byrecv()until drained
validate_frame(), protect_id(), verify_pid(), and calc_checksum() are
free functions with no virtual dispatch. This ensures deterministic execution
time and avoids virtual table corruption hazards.
E2E protection (M-03) is applied above the bus transport (M-02). This ensures protection even when using the virtual bus for test, and is consistent with ISO 26262-6 E2E profile placement.
Chan<T> is designed for single-writer (producer) / single-reader (consumer)
use per channel instance. Multi-subscriber fan-out is implemented by the bus
layer (M-02), which holds one channel per subscriber.
cpp-LIN deliberately provides no physical layer implementation. IBus is the
boundary. All hardware I/O is the integrating system's responsibility (ASM-09).
This enforces a clean separation that enables independent ASIL qualification.
Sensor ECU (slave)
│
▼ lin::safety::Protector::protect(raw_payload)
│ → header{DataID, SourceID, counter, CRC} + raw_payload
│
▼ lin::virt::Bus::publish(frame_id, protected_payload)
│ → stored in response table
│
▼ lin::master::Node::run()
│ → calls bus::send_header(frame_id)
│ → receives Frame{id, data=protected_payload, pid, checksum}
│
▼ lin::safety::Receiver::unwrap(frame.data)
│ → verifies CRC, sequence counter
│ → returns raw_payload or throws E2EError
│
▼ Actuator ECU (integrating system)
| Risk | Mitigation |
|---|---|
| Buffer overflow in frame parsing | validate_frame() checks payload length; no raw pointer arithmetic |
| Use-after-free on bus close | shared_ptr<Bus> keeps bus alive while subscribers hold references |
| Stack overflow in recursive code | No recursive functions in cpp-LIN core |
| Heap exhaustion | Bounded Chan<T> capacity; integrator monitors heap (DSR-01) |
| Data race | shared_mutex in virt::Bus; atomic<uint32_t> for safety counter |
CI verification: ASan+UBSan (sanitizers job), ThreadSanitizer (tsan job).
HARA.md— Hazard AnalysisTARA.md— Threat Analysisfmea.json— Failure Mode and Effect AnalysisSAFETY_MANUAL.md— Integration guidanceSEOOC.md— SEooC assumptions- ISO 26262:2018 Part 6 §7.4 — Software architectural design
- IEC 61508:2010 Part 3 §7.4 — Software architecture