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 .fusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"configVersion": "1.0",
"project": {
"name": "cpp-LIN",
"version": "0.1.0"
"version": "0.4.0"
},
"sourceDirs": [
"src",
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ jobs:

- name: cpfusa init
working-directory: cpp-LIN
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.1.0 --force || true
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 --force || true

- name: cpfusa check
working-directory: cpp-LIN
Expand Down Expand Up @@ -448,7 +448,7 @@ jobs:

- name: cpfusa init
working-directory: cpp-LIN
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.1.0 --force || true
run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 --force || true

- name: Generate SARIF report
working-directory: cpp-LIN
Expand Down
13 changes: 12 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.21)

project(cpplin
VERSION 0.1.0
VERSION 0.4.0
DESCRIPTION "C++ LIN bus library — RELAY-conformant, ISO 26262 ASIL-B"
HOMEPAGE_URL "https://github.com/SoundMatt/cpp-LIN"
LANGUAGES CXX
Expand Down Expand Up @@ -34,6 +34,17 @@ endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(cmake/FetchDeps.cmake)

# ── Generated headers ─────────────────────────────────────────────────────────
# cli/version.hpp is generated from PROJECT_VERSION so the CLI's self-reported
# version (§12.1) can't drift from the project's own version a second time
# (see the v0.1.0-stale-for-two-releases fix).
set(CPPLIN_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/cli/version.hpp.in
${CPPLIN_GENERATED_DIR}/cli/version.hpp
@ONLY
)

# ── Library ───────────────────────────────────────────────────────────────────

add_library(cpplin_lib STATIC
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ of [go-LIN](https://github.com/SoundMatt/go-LIN) and is patterned after
### Features

- **Virtual bus** — in-process, thread-safe LIN bus for testing and simulation
- **Mock module** — RELAY spec §13.7.1 canonical `mock` namespace (`lin::mock::Bus`)
- **LDF parser** — parse LIN Description Files (`.ldf`), decode signals
- **Master node** — schedule table runner with configurable slot timing
- **Slave node** — publish response payloads for specific LIN frame IDs
Expand Down Expand Up @@ -52,6 +53,7 @@ include/lin/
relay.hpp — RELAY v1.11 types (Protocol, Message, INode, …)
lin.hpp — IBus, IMasterBus, Frame, Filter, free functions
virtual/bus.hpp — In-process virtual LIN bus
mock/mock.hpp — RELAY spec §13.7.1 canonical `mock` module (alias of virtual::Bus)
safety/e2e.hpp — E2E Protector / Receiver
ldf/parser.hpp — LDF file parser / signal decoder
master/node.hpp — Master schedule runner
Expand Down
48 changes: 42 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,76 @@
# Roadmap — cpp-LIN

## v0.1.0 — Initial Release (current)
## v0.1.0 — Initial Release

- [x] Virtual bus with Classic and Enhanced checksum support
- [x] Protected Identifier (PID) computation and verification
- [x] LDF parser (nodes, signals, frames, schedule tables, signal decode)
- [x] Master node with schedule table runner (`std::stop_token`)
- [x] Slave node with per-ID response registration
- [x] E2E safety: Protector and Receiver (CRC-16/CCITT-FALSE, 10-byte header)
- [x] RELAY v1.11 adapter (`relay::INode`)
- [x] RELAY v1.10 adapter (`relay::INode`)
- [x] CLI binary (`cpp-lin-cli`) for relay conform/interop
- [x] ISO 26262 ASIL-B requirements traceability
- [x] CI pipeline: build, test, coverage, sanitizers, RELAY conform, cpfusa
- [x] Docker multi-stage image

## v0.2.0 — Enhanced Bus Features
## v0.2.0 — Full Safety & Cyber Pack

- [x] TARA (ISO/SAE 21434, IEC 62443-4-1 SL-2): threats, controls, `tara.json`
- [x] FMEA (ISO 26262-9) linked to hazards, `fmea.json`
- [x] SAS (ISO 26262-6 §7.4): 9-module architecture specification
- [x] Boundary diagram (ISO 26262-6 §7.4.1): `boundary.mermaid`
- [x] Safety Manual (ISO 26262-6 §7, Part 10 §9): `SAFETY_MANUAL.md`
- [x] `SECURITY.md` + `INCIDENT-RESPONSE.md`: coordinated disclosure, ISN process
- [x] IEC 62443: expanded `.fusa-iec62443.json` security requirements
- [x] Full requirements traceability closure in `.fusa-reqs.json`
- [x] CI: `cpfusa lint`/`cyber` hard gates; ThreadSanitizer gate added

## v0.3.0 — RELAY spec v1.11 conformance

- [x] `kSpecVersion` bumped 1.10 → 1.11 (`relay.hpp`, `lin.hpp`)
- [x] CLI version/capabilities JSON report `spec_version` 1.11
- [x] `RELAY_BUILD_CLI` CMake alias (§17.7, since removed — see v0.4.0)
- [x] Test vectors updated to spec_version 1.11

## v0.4.0 — RELAY ecosystem audit fixes (current)

- [x] LIN-specific error category (`lin::Errc::invalid_frame`) distinct from
`ErrPayloadTooLarge` for out-of-range frame IDs (spec §5.3)
- [x] `Adapt()` subscription thread no longer captures a raw `this` — fixes a
use-after-free if the adapter is dropped before the bus is closed
- [x] `convert` writes the correct `ErrInvalidFrame` sentinel name to stderr
- [x] `capabilities` reports an actual transport backend (`virtual`), not the
protocol name
- [x] `version`/`capabilities`/`status` self-report the real release version
(driven by `PROJECT_VERSION`, not a second hard-coded literal)
- [x] `version`/`status` support `--format text|json` with exit `2` on an
unrecognized format or unknown argument (spec §11.1)
- [x] `tool` field lowercased to `cpp-lin` (spec §13.2 CLI-binary-name
convention), matching peers like `go-can`
- [x] `lin::mock` canonical module (spec §13.7.1), aliasing `lin::virt::Bus`

## v0.5.0 — Enhanced Bus Features

- [ ] Sleep/wake frame support (LIN 2.x go-to-sleep command)
- [ ] Break field and sync field simulation in virtual bus
- [ ] Configurable baud rate metadata on virtual bus
- [ ] `IMasterBus::set_schedule` hot-swap without restarting `run()`
- [ ] `IBus::unsubscribe` to cancel an active subscription

## v0.3.0 — Diagnostics
## v0.6.0 — Diagnostics

- [ ] ISO 15765-3 / LIN Transport Layer (segmented diagnostic frames)
- [ ] UDS (ISO 14229) over LIN TP adapter
- [ ] Diagnostic session management in master node

## v0.4.0 — Hardware Abstraction
## v0.7.0 — Hardware Abstraction

- [ ] Serial/UART hardware bus implementation (Linux `termios`)
- [ ] SocketLIN adapter (if Linux kernel support is available)
- [ ] PEAK LIN hardware adapter

## v0.5.0 — AUTOSAR-Lite
## v0.8.0 — AUTOSAR-Lite

- [ ] LIN Interface (LINIf) / LIN Driver (LINDrv) API-compatible shim
- [ ] COM-stack signal packing / unpacking with endianness support
Expand Down
5 changes: 4 additions & 1 deletion cli/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
add_executable(cpp-lin-cli main.cpp)
target_link_libraries(cpp-lin-cli PRIVATE cpplin_lib)
target_include_directories(cpp-lin-cli PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(cpp-lin-cli PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CPPLIN_GENERATED_DIR}/cli
)
49 changes: 40 additions & 9 deletions cli/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#pragma once

#include "version.hpp"
#include <lin/lin.hpp>
#include <lin/relay.hpp>
#include <algorithm>
Expand Down Expand Up @@ -132,7 +133,11 @@ inline lin::Frame parse_frame_json(const std::string& json) {
lin::Frame f{};
uint32_t id_val = 0;
if (!detail::extract_u32(json, "id", id_val))
throw std::runtime_error("ErrInvalidInput: missing or invalid 'id'");
// Not a spec §5 sentinel: JSON that doesn't even parse into a
// lin.Frame never reaches lin::validate_frame(), so there is no
// ErrInvalidFrame to name here — describe the parse failure plainly
// instead of fabricating a sentinel-looking name that doesn't exist.
throw std::runtime_error("invalid input JSON: missing or invalid 'id' field");
f.id = static_cast<uint8_t>(id_val);
detail::extract_bytes(json, "data", f.data);
uint32_t ct = 0;
Expand Down Expand Up @@ -173,28 +178,41 @@ inline std::string message_to_json(const relay::Message& m) {
// fusa:req REQ-CLI-001
inline std::string version_json() {
return "{"
"\"tool\":\"cpp-LIN\","
"\"tool\":\"cpp-lin\","
"\"protocol\":\"LIN\","
"\"protocol_int\":3,"
"\"version\":\"0.1.0\","
"\"version\":\"" + std::string(kToolVersion) + "\","
"\"spec_version\":\"1.11\","
"\"language\":\"cpp\","
"\"runtime\":\"c++17\""
"}";
}

// fusa:req REQ-CLI-001
// Human-readable rendering of version_json() for `version --format text` (spec §11.1).
inline std::string version_text() {
std::ostringstream o;
o << "tool: cpp-lin\n"
<< "protocol: LIN\n"
<< "version: " << kToolVersion << "\n"
<< "spec: 1.11\n"
<< "language: cpp\n"
<< "runtime: c++17\n";
return o.str();
}

// fusa:req REQ-CLI-002
inline std::string capabilities_json() {
return "{"
"\"kind\":\"capabilities\","
"\"tool\":\"cpp-LIN\","
"\"tool\":\"cpp-lin\","
"\"protocol\":\"LIN\","
"\"protocol_int\":3,"
"\"version\":\"0.1.0\","
"\"version\":\"" + std::string(kToolVersion) + "\","
"\"spec_version\":\"1.11\","
"\"commands\":[\"version\",\"capabilities\",\"status\",\"convert\"],"
"\"transports\":[\"LIN\"],"
"\"features\":[\"ldf\",\"e2e\",\"master\",\"slave\",\"virtual\"],"
"\"transports\":[\"virtual\"],"
"\"features\":[\"ldf\",\"e2e\",\"master\",\"slave\",\"virtual\",\"mock\"],"
"\"interfaces\":[\"IBus\",\"IMasterBus\",\"INode\"],"
"\"optional_interfaces\":[\"IHealthProvider\",\"IMetricsProvider\",\"IDrainer\"],"
"\"adapt\":true"
Expand All @@ -205,13 +223,26 @@ inline std::string capabilities_json() {
inline std::string status_json() {
return "{"
"\"protocol\":\"LIN\","
"\"tool\":\"cpp-LIN\","
"\"version\":\"0.1.0\","
"\"tool\":\"cpp-lin\","
"\"version\":\"" + std::string(kToolVersion) + "\","
"\"healthy\":true,"
"\"connected\":false,"
"\"endpoint\":\"\","
"\"details\":{}"
"}";
}

// fusa:req REQ-CLI-003
// Human-readable rendering of status_json() for `status --format text` (spec §11.1).
inline std::string status_text() {
std::ostringstream o;
o << "protocol: LIN\n"
<< "tool: cpp-lin\n"
<< "version: " << kToolVersion << "\n"
<< "healthy: true\n"
<< "connected: false\n"
<< "endpoint: (none)\n";
return o.str();
}

} // namespace cli
55 changes: 50 additions & 5 deletions cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,43 @@
#include <iostream>
#include <string>

static int cmd_version() { std::cout << cli::version_json() << "\n"; return 0; }
// Parses an optional "--format <value>" flag from argv[2..argc) for commands
// that accept it (spec §11.1: `version [--format text|json]`, `status
// [--format text|json]`). Defaults `fmt` to "json". Any other argument
// (including an unrecognized --format value) is an invalid-args error: the
// caller is responsible for validating `fmt` against the command's allowed
// set and exiting 2 per spec §11.1/§11.3.
static bool parse_format_flag(int argc, char* argv[], std::string& fmt) {
fmt = "json";
for (int i = 2; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--format" && i + 1 < argc) {
fmt = argv[++i];
} else {
std::cerr << "ErrInvalidArgs: unrecognized argument '" << arg << "'\n";
return false;
}
}
return true;
}

// fusa:req REQ-CLI-001
static int cmd_version(const std::string& fmt) {
if (fmt == "json") { std::cout << cli::version_json() << "\n"; return 0; }
if (fmt == "text") { std::cout << cli::version_text(); return 0; }
std::cerr << "ErrInvalidArgs: unsupported --format '" << fmt << "' (want text|json)\n";
return 2;
}

static int cmd_capabilities() { std::cout << cli::capabilities_json() << "\n"; return 0; }
static int cmd_status() { std::cout << cli::status_json() << "\n"; return 0; }

// fusa:req REQ-CLI-003
static int cmd_status(const std::string& fmt) {
if (fmt == "json") { std::cout << cli::status_json() << "\n"; return 0; }
if (fmt == "text") { std::cout << cli::status_text(); return 0; }
std::cerr << "ErrInvalidArgs: unsupported --format '" << fmt << "' (want text|json)\n";
return 2;
}

// fusa:req REQ-CLI-006
static int cmd_convert(const std::string& protocol) {
Expand All @@ -44,7 +78,10 @@ static int cmd_convert(const std::string& protocol) {
try {
lin::validate_frame(f);
} catch (const lin::ErrInvalidFrame& e) {
std::cerr << "ErrInvalidInput: " << e.what() << "\n";
// spec §11.2: convert MUST write the sentinel error name (§5) to
// stderr — the thrown type here is lin::ErrInvalidFrame, so that is
// the name relay interop/relay conform expect, not a made-up one.
std::cerr << "ErrInvalidFrame: " << e.what() << "\n";
return 1;
}

Expand All @@ -64,9 +101,17 @@ int main(int argc, char* argv[]) {

std::string cmd = argv[1];

if (cmd == "version") return cmd_version();
if (cmd == "version") {
std::string fmt;
if (!parse_format_flag(argc, argv, fmt)) return 2;
return cmd_version(fmt);
}
if (cmd == "capabilities") return cmd_capabilities();
if (cmd == "status") return cmd_status();
if (cmd == "status") {
std::string fmt;
if (!parse_format_flag(argc, argv, fmt)) return 2;
return cmd_status(fmt);
}

if (cmd == "convert") {
std::string protocol;
Expand Down
17 changes: 17 additions & 0 deletions cli/version.hpp.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2026 Matt Jones. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// cli/version.hpp.in — configure_file template for the CLI's self-reported
// version. Generated into ${CMAKE_CURRENT_BINARY_DIR}/version.hpp so the
// version string is driven by CMakeLists.txt's project(cpplin VERSION ...)
// rather than a second hard-coded literal (spec §12.1).

#pragma once

namespace cli {

inline constexpr const char* kToolVersion = "@PROJECT_VERSION@";

} // namespace cli
23 changes: 23 additions & 0 deletions include/lin/lin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,23 @@ inline std::error_code ErrNotConnected() noexcept { return relay::ErrNotConne
inline std::error_code ErrTimeout() noexcept { return relay::ErrTimeout(); }
inline std::error_code ErrPayloadTooLarge() noexcept { return relay::ErrPayloadTooLarge(); }

// ── LIN-specific error category (spec §5.2, §5.3, §5.4) ──────────────────────
//
// A structurally invalid frame (bad ID bits, out-of-range ID) is distinct from
// a too-large payload (§5.3): "ErrInvalidFrame and ErrPayloadTooLarge are
// distinct ... ValidateFrame MUST return ErrInvalidFrame; it MUST NOT return
// ErrPayloadTooLarge." IBus/IMasterBus (§8.3) are LIN's own protocol interface,
// not the relay.Node interface, so — like CAN's ErrInvalidFrame (§5.4, "not a
// relay sentinel") — this code is not mapped to any relay::Errc value.

// fusa:req REQ-LIN-015
enum class Errc : int {
invalid_frame = 1,
};

const std::error_category& error_category() noexcept;
std::error_code make_error_code(Errc e) noexcept;

// ── Free functions ────────────────────────────────────────────────────────────

// Computes the Protected Identifier (PID) for a 6-bit LIN frame ID.
Expand Down Expand Up @@ -205,3 +222,9 @@ using IDrainer = relay::IDrainer;
std::unique_ptr<relay::INode> adapt(std::shared_ptr<IBus> bus);

} // namespace lin

// fusa:req REQ-LIN-015 — allow implicit construction of std::error_code from lin::Errc.
namespace std {
template<>
struct is_error_code_enum<lin::Errc> : true_type {};
} // namespace std
Loading
Loading