From 3fe1b97c96d5b7d7dddcc3a08f7ec20a468eeec2 Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:56:35 -0700 Subject: [PATCH 1/7] fix(master): route diagnostic frames through classic-checksum publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node.Diagnostics built a correct classic-checksum Frame for 0x3C/0x3D via req.ToFrame() but then discarded it, re-registering the payload through lin.Bus.Publish(id, data). virtual.Bus.Publish always applies the enhanced checksum, so every diagnostic request/response emitted by Diagnostics carried an invalid (enhanced) checksum on the wire — a frame the library's own ValidateFrame would reject. Add a checksum-type-aware PublishFrame(f Frame) to the Bus interface and virtual.Bus, and have master.Node.Diagnostics register the fully-formed classic-checksum Frame instead of just its ID/data. Strengthen TestDiagnostics_requestResponseRoundTrip to assert ChecksumType and Checksum on both the 0x3C request and the 0x3D response frames actually placed on the bus, closing the test gap that let this pass silently (verified by reverting the fix: the strengthened test fails as expected). Addresses go-LIN-01 (Critical) and go-LIN-02 (High). Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- lin.go | 11 +++++++++++ master/master.go | 14 +++++++------- master/master_test.go | 29 ++++++++++++++++++++++++++--- virtual/bus.go | 14 ++++++++++++++ 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/lin.go b/lin.go index 89f485c..7b6f360 100644 --- a/lin.go +++ b/lin.go @@ -174,6 +174,17 @@ type Bus interface { //fusa:req REQ-LIN-019 Publish(id uint8, data []byte) error + // PublishFrame registers a fully-formed response Frame for f.ID, + // preserving f.ChecksumType. Unlike Publish (which always uses the + // enhanced checksum), PublishFrame lets callers select the checksum + // type — required for diagnostic frames 0x3C/0x3D, which ISO 17987 / + // LIN 2.x §4.2.3 mandate carry the classic checksum. Passing a Frame + // with nil Data removes a previously registered response. + // + //fusa:req REQ-LIN-011 + //fusa:req REQ-LIN-019 + PublishFrame(f Frame) error + // Subscribe returns a channel that delivers frames matching any of the // supplied filters. Pass nil to receive all frames. // opts configures channel delivery (depth, back-pressure per relay §14). diff --git a/master/master.go b/master/master.go index 8e824c5..5cf9e9b 100644 --- a/master/master.go +++ b/master/master.go @@ -129,7 +129,7 @@ func (n *Node) Diagnostics(ctx context.Context, req lin.MasterRequestFrame) (lin if err != nil { return lin.SlaveResponseFrame{}, fmt.Errorf("master: diagnostics: %w", err) } - if err := n.bus.Publish(f.ID, f.Data); err != nil { + if err := n.bus.PublishFrame(f); err != nil { return lin.SlaveResponseFrame{}, fmt.Errorf("master: diagnostics: publish request: %w", err) } if _, err := n.bus.SendHeader(ctx, lin.LINDiagRequestID); err != nil { @@ -161,12 +161,12 @@ func (n *Node) Diagnostics(ctx context.Context, req lin.MasterRequestFrame) (lin // //fusa:req REQ-MASTER-016 func (n *Node) SetSporadicGroup(slotID uint8, candidates []uint8) error { - if slotID > lin.MaxID { - return fmt.Errorf("master: sporadic slot ID 0x%02X exceeds maximum 0x%02X", slotID, lin.MaxID) + if slotID > lin.LINMaxID { + return fmt.Errorf("master: sporadic slot ID 0x%02X exceeds maximum 0x%02X", slotID, lin.LINMaxID) } for i, id := range candidates { - if id > lin.MaxID { - return fmt.Errorf("master: sporadic candidate %d: ID 0x%02X exceeds maximum 0x%02X", i, id, lin.MaxID) + if id > lin.LINMaxID { + return fmt.Errorf("master: sporadic candidate %d: ID 0x%02X exceeds maximum 0x%02X", i, id, lin.LINMaxID) } } @@ -283,8 +283,8 @@ func (n *Node) Run(ctx context.Context) error { //fusa:req REQ-MASTER-011 func validateSchedule(entries []lin.ScheduleEntry) error { for i, e := range entries { - if e.ID > lin.MaxID { - return fmt.Errorf("master: schedule entry %d: ID 0x%02X exceeds maximum 0x%02X", i, e.ID, lin.MaxID) + if e.ID > lin.LINMaxID { + return fmt.Errorf("master: schedule entry %d: ID 0x%02X exceeds maximum 0x%02X", i, e.ID, lin.LINMaxID) } } return nil diff --git a/master/master_test.go b/master/master_test.go index 8b25870..6a18140 100644 --- a/master/master_test.go +++ b/master/master_test.go @@ -347,11 +347,12 @@ func TestDiagnostics_requestResponseRoundTrip(t *testing.T) { if err != nil { t.Fatalf("resp.ToFrame: %v", err) } - if err := bus.Publish(lin.LINDiagResponseID, respFrame.Data); err != nil { - t.Fatalf("Publish(0x3D): %v", err) + if err := bus.PublishFrame(respFrame); err != nil { + t.Fatalf("PublishFrame(0x3D): %v", err) } ch, _ := bus.Subscribe([]lin.Filter{{ID: lin.LINDiagRequestID}}) + respCh, _ := bus.Subscribe([]lin.Filter{{ID: lin.LINDiagResponseID}}) n := master.New(bus) req := lin.MasterRequestFrame{NAD: 0x01, SID: 0xB2, Data: []byte{0x01, 0x02}} @@ -363,16 +364,38 @@ func TestDiagnostics_requestResponseRoundTrip(t *testing.T) { t.Errorf("Diagnostics response = %+v, want %+v", got, resp) } - // The request itself must actually have been transmitted on 0x3C. + // The request itself must actually have been transmitted on 0x3C, and + // diagnostic frames must carry the classic checksum (ISO 17987 §4.2.3). select { case sent := <-ch: reqFrame, _ := req.ToFrame() if string(sent.Data) != string(reqFrame.Data) { t.Errorf("transmitted request data = % X, want % X", sent.Data, reqFrame.Data) } + if sent.ChecksumType != lin.ClassicChecksum { + t.Errorf("0x3C request ChecksumType = %v, want ClassicChecksum", sent.ChecksumType) + } + wantCS := lin.CalcChecksum(lin.ProtectID(lin.LINDiagRequestID), sent.Data, lin.ClassicChecksum) + if sent.Checksum != wantCS { + t.Errorf("0x3C request Checksum = 0x%02X, want 0x%02X", sent.Checksum, wantCS) + } case <-time.After(time.Second): t.Fatal("timed out waiting for the request frame to be broadcast") } + + // The 0x3D response frame must likewise carry the classic checksum. + select { + case sent := <-respCh: + if sent.ChecksumType != lin.ClassicChecksum { + t.Errorf("0x3D response ChecksumType = %v, want ClassicChecksum", sent.ChecksumType) + } + wantCS := lin.CalcChecksum(lin.ProtectID(lin.LINDiagResponseID), sent.Data, lin.ClassicChecksum) + if sent.Checksum != wantCS { + t.Errorf("0x3D response Checksum = 0x%02X, want 0x%02X", sent.Checksum, wantCS) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for the response frame to be broadcast") + } } func TestDiagnostics_noResponseRegistered(t *testing.T) { diff --git a/virtual/bus.go b/virtual/bus.go index 9dcc1d0..b9f8264 100644 --- a/virtual/bus.go +++ b/virtual/bus.go @@ -100,6 +100,9 @@ func (b *Bus) Publish(id uint8, data []byte) error { if id > lin.LINMaxID { return fmt.Errorf("lin/virtual: frame ID 0x%02X exceeds maximum 0x%02X", id, lin.LINMaxID) } + if len(data) > lin.LINMaxDataLen { + return fmt.Errorf("lin/virtual: payload length %d exceeds maximum %d: %w", len(data), lin.LINMaxDataLen, lin.ErrPayloadTooLarge) + } b.mu.Lock() defer b.mu.Unlock() if b.closed { @@ -137,6 +140,17 @@ func (b *Bus) PublishClassic(id uint8, data []byte) error { return nil } +// PublishFrame registers f.Data for f.ID, preserving f.ChecksumType so that +// callers can select the classic checksum required for diagnostic frames +// 0x3C/0x3D (ISO 17987 / LIN 2.x §4.2.3). A Frame with nil Data removes a +// previously registered response. +func (b *Bus) PublishFrame(f lin.Frame) error { + if f.ChecksumType == lin.ClassicChecksum { + return b.PublishClassic(f.ID, f.Data) + } + return b.Publish(f.ID, f.Data) +} + // SendHeader drives a frame exchange for the given ID. // It looks up any registered slave response, synthesises the Frame with the // correct PID and checksum, broadcasts it to all matching subscribers, and From ec08458c424e873de0b0f0261df3bb3574a881fc Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:56:40 -0700 Subject: [PATCH 2/7] chore: replace deprecated MaxID/MaxDataLen with LINMaxID/LINMaxDataLen staticcheck ./... reports SA1019 deprecation warnings across production packages (cmd/lintool, ldf, master, slave) and tests that still consume the deprecated lin.MaxID/lin.MaxDataLen aliases instead of LINMaxID/LINMaxDataLen. go vet is clean so CI's vet gate doesn't catch this class of issue. Addresses go-LIN-03. Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- cmd/lintool/main.go | 6 +++--- ldf/parser.go | 2 +- lin_test.go | 6 +++--- seooc_test.go | 2 +- slave/slave.go | 4 ++-- virtual/bus_test.go | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/lintool/main.go b/cmd/lintool/main.go index 5233aae..065d024 100644 --- a/cmd/lintool/main.go +++ b/cmd/lintool/main.go @@ -149,7 +149,7 @@ func parseID(s string) uint8 { } else { v, err = strconv.ParseUint(s, 10, 8) } - if err != nil || v > lin.MaxID { + if err != nil || v > lin.LINMaxID { fatal("invalid LIN frame ID %q (must be 0x00–0x3F)", s) } return uint8(v) @@ -161,8 +161,8 @@ func parseHex(s string) []byte { if err != nil { fatal("invalid hex data %q: %v", s, err) } - if len(b) == 0 || len(b) > lin.MaxDataLen { - fatal("data length %d is not in range 1–%d", len(b), lin.MaxDataLen) + if len(b) == 0 || len(b) > lin.LINMaxDataLen { + fatal("data length %d is not in range 1–%d", len(b), lin.LINMaxDataLen) } return b } diff --git a/ldf/parser.go b/ldf/parser.go index bc2f86e..711b03a 100644 --- a/ldf/parser.go +++ b/ldf/parser.go @@ -533,7 +533,7 @@ func (p *ldfParser) parseScheduleTables(db *DB) error { delay, _ := parseUint(delayStr) // resolve frame name → ID id := frameIDByName(db, parts[0]) - if id > lin.MaxID { + if id > lin.LINMaxID { continue } entries = append(entries, lin.ScheduleEntry{ID: id, DelayMs: uint32(delay)}) diff --git a/lin_test.go b/lin_test.go index dde2a3a..027074b 100644 --- a/lin_test.go +++ b/lin_test.go @@ -37,7 +37,7 @@ func TestProtectID_P0(t *testing.T) { } func TestProtectID_allIDs(t *testing.T) { - for id := uint8(0); id <= lin.MaxID; id++ { + for id := uint8(0); id <= lin.LINMaxID; id++ { pid := lin.ProtectID(id) if pid&0x3F != id { t.Errorf("ProtectID(0x%02X): lower 6 bits 0x%02X != id", id, pid&0x3F) @@ -51,7 +51,7 @@ func TestProtectID_allIDs(t *testing.T) { //fusa:test REQ-LIN-007 func TestVerifyPID_valid(t *testing.T) { - for id := uint8(0); id <= lin.MaxID; id++ { + for id := uint8(0); id <= lin.LINMaxID; id++ { pid := lin.ProtectID(id) got, err := lin.VerifyPID(pid) if err != nil { @@ -319,7 +319,7 @@ func TestFilterMatches_exact(t *testing.T) { func TestFilterMatches_all(t *testing.T) { flt := lin.Filter{All: true} - for id := uint8(0); id <= lin.MaxID; id++ { + for id := uint8(0); id <= lin.LINMaxID; id++ { if !flt.Matches(lin.Frame{ID: id, Data: []byte{1}}) { t.Errorf("all-filter should match ID 0x%02X", id) } diff --git a/seooc_test.go b/seooc_test.go index 6651157..5ad6520 100644 --- a/seooc_test.go +++ b/seooc_test.go @@ -135,7 +135,7 @@ func TestSEOOC_LDFScheduleIDsValid(t *testing.T) { frames := db.Frames() for i, entry := range sched { - if entry.ID > lin.MaxID { + if entry.ID > lin.LINMaxID { t.Errorf("schedule entry %d: ID 0x%02X exceeds MaxID", i, entry.ID) } if frames[entry.ID] == nil { diff --git a/slave/slave.go b/slave/slave.go index 9802d84..7ba0439 100644 --- a/slave/slave.go +++ b/slave/slave.go @@ -60,8 +60,8 @@ func New(bus lin.Bus) *Node { //fusa:req REQ-SLAVE-004 //fusa:req REQ-SLAVE-008 func (n *Node) SetResponse(id uint8, data []byte) error { - if id > lin.MaxID { - return fmt.Errorf("slave: frame ID 0x%02X exceeds maximum 0x%02X", id, lin.MaxID) + if id > lin.LINMaxID { + return fmt.Errorf("slave: frame ID 0x%02X exceeds maximum 0x%02X", id, lin.LINMaxID) } if err := n.bus.Publish(id, data); err != nil { return fmt.Errorf("slave: Publish: %w", err) diff --git a/virtual/bus_test.go b/virtual/bus_test.go index 67a9a68..6ded7b0 100644 --- a/virtual/bus_test.go +++ b/virtual/bus_test.go @@ -395,7 +395,7 @@ func FuzzSendHeader(f *testing.F) { t.Fatal(err) } defer b.Close() - if id > lin.MaxID || len(data) == 0 || len(data) > lin.MaxDataLen { + if id > lin.LINMaxID || len(data) == 0 || len(data) > lin.LINMaxDataLen { return } _ = b.Publish(id, data) From 7062a5dfdac92b0275962e9cfded6ff24e3d25fd Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:56:47 -0700 Subject: [PATCH 3/7] =?UTF-8?q?docs:=20fix=20sas.md=20=E2=80=94=20SVP/SCMP?= =?UTF-8?q?/SQAP=20are=20present,=20not=20absent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Software Accomplishment Summary marked SVP.md, SCMP.md, and SQAP.md as "not found" and asserted 6 gaps / 14/20 evidence items, though all three files exist at the repo root. Correct the presence column, gap count/list, and completeness assertion to match actual repo contents (17/20, 3 remaining gaps). Addresses go-LIN-04. Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- sas.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sas.md b/sas.md index a08ff96..aced12b 100644 --- a/sas.md +++ b/sas.md @@ -14,14 +14,14 @@ ## Evidence Summary -**14 / 20** lifecycle data items present. +**17 / 20** lifecycle data items present. | Item | File | Present | |---|---|:---:| | Software Development Plan | `SAFETY_PLAN.md` | ✓ | -| Software Verification Plan | `SVP.md` | ✗ | -| Software Configuration Management Plan | `SCMP.md` | ✗ | -| Software Quality Assurance Plan | `SQAP.md` | ✗ | +| Software Verification Plan | `SVP.md` | ✓ | +| Software Configuration Management Plan | `SCMP.md` | ✓ | +| Software Quality Assurance Plan | `SQAP.md` | ✓ | | Requirements Manifest | `.fusa-reqs.json` | ✓ | | Traceability Matrix | `.fusa-reqs.json` | ✓ | | Test Evidence Bundle | `.fusa-evidence.json` | ✓ | @@ -39,18 +39,15 @@ | Problem Reports | `.fusa-problems.json` | ✓ | | Audit Pack | `audit-pack.zip` | ✗ | -## Gaps (6) +## Gaps (3) -- Software Verification Plan (SVP.md) — not found -- Software Configuration Management Plan (SCMP.md) — not found -- Software Quality Assurance Plan (SQAP.md) — not found - Tool Qualification Report (qualify-report.json) — not found - DO-178C Gap Report (do178-gap-report.json) — not found - Audit Pack (audit-pack.zip) — not found ## Assertion -Software Accomplishment Summary INCOMPLETE — 6 lifecycle data item(s) are absent. See gaps list. Address all gaps before submitting for DER review. +Software Accomplishment Summary INCOMPLETE — 3 lifecycle data item(s) are absent. See gaps list. Address all gaps before submitting for DER review. --- _Generated by go-FuSa v0.18.0 — DO-178C §11.20_ From 63ffa465522a78b349bdeebaa55edca900bd3771 Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:56:47 -0700 Subject: [PATCH 4/7] docs: remove nonexistent transport/ row from CONTRIBUTING.md The project-structure table listed a transport/ directory (physical- layer serial/UART abstraction) that does not exist anywhere in the repo. Addresses go-LIN-05. Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fac27ae..bbbb55f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,7 +58,6 @@ with Matt Jones. The DCO sign-off transfers no copyright — it only affirms you | `ldf/` | LIN Description File (LDF) parser | | `master/` | LIN master node — schedule execution and header transmission | | `slave/` | LIN slave node — response publisher | -| `transport/` | Physical-layer abstraction (serial/UART) | | `safety/` | E2E protection header (CRC, sequence counter) | | `cmd/lintool/` | CLI tool — `send`, `dump`, `ldf` subcommands | | `examples/quickstart/` | Docker quickstart | From 50b10900536c06b0fdbdb94e372a85f7770af929 Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:56:51 -0700 Subject: [PATCH 5/7] fix: reject oversize payloads; use ErrInvalidFrame for malformed IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linNode.Send validated only the frame ID and forwarded straight to bus.Publish, which never checked len(data) against LINMaxDataLen — RELAY spec section 10.1 requires ErrPayloadTooLarge here, but the already-defined sentinel was unreachable on this path. Add the length check to linNode.Send. Also: an out-of-range/unparseable msg.ID was wrapped as ErrNotConnected, a connection-state sentinel, for what is a structural violation. FromMessage already returns ErrInvalidFrame for the same condition; make Send consistent with it. Addresses go-LIN-06 and go-LIN-09. Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- adapt.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adapt.go b/adapt.go index 9769dff..1e23143 100644 --- a/adapt.go +++ b/adapt.go @@ -39,7 +39,10 @@ func (n *linNode) Protocol() relay.Protocol { func (n *linNode) Send(ctx context.Context, msg relay.Message) error { id, err := strconv.ParseUint(msg.ID, 10, 8) if err != nil || id > LINMaxID { - return fmt.Errorf("lin: invalid frame ID %q: %w", msg.ID, ErrNotConnected) + return fmt.Errorf("lin: invalid frame ID %q: %w", msg.ID, ErrInvalidFrame) + } + if len(msg.Payload) > LINMaxDataLen { + return fmt.Errorf("lin: payload length %d exceeds maximum %d: %w", len(msg.Payload), LINMaxDataLen, ErrPayloadTooLarge) } return n.bus.Publish(uint8(id), msg.Payload) } From 1664feb1e1d08b6501ee763a7b673c29c47eb6aa Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:57:51 -0700 Subject: [PATCH 6/7] docs: correct SG-02 traceability from REQ-LIN-004 to REQ-LIN-008/009/010 SG-02 ("detect frame payload corruption using the LIN checksum") was allocated to REQ-LIN-004, which is the PID-parity requirement (ProtectID carries //fusa:req REQ-LIN-004). The checksum logic (CalcChecksum) is REQ-LIN-008/009/010; SG-02 traced to the wrong requirement and to no checksum requirement at all. Addresses go-LIN-07. Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- HARA.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HARA.md b/HARA.md index 8c21e39..657e493 100644 --- a/HARA.md +++ b/HARA.md @@ -46,7 +46,7 @@ SEOOC (Safety Element Out Of Context) in automotive and industrial LIN bus syste | SG | Requirement IDs | |---|---| | SG-01 | REQ-LIN-003, REQ-LIN-004 | -| SG-02 | REQ-LIN-004 | +| SG-02 | REQ-LIN-008, REQ-LIN-009, REQ-LIN-010 | | SG-03 | REQ-LDF-002, REQ-LDF-003 | | SG-04 | REQ-SAFETY-004, REQ-SAFETY-005 | | SG-05 | REQ-LIN-001 | From 3ea54632ffc7f566606994db00badbee48f4423a Mon Sep 17 00:00:00 2001 From: Matt Jones <47545907+SoundMatt@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:57:51 -0700 Subject: [PATCH 7/7] fix(safety): recompute HARA ASIL ratings from S/E/C determination Every hazard's declared asil was one band above what its own severity/exposure/controllability fields yield under the standard ISO 26262 Part 3 ASIL determination table: H-01..H-04 (S2/E3/C2) yield ASIL-A, not the declared ASIL-B; H-05 (S1/E3/C2) and H-06 (S2/E2/C2) yield QM, not the declared ASIL-A. SG-01/02/05 inherited the inflated ASIL-B from H-01/03 and H-02/04. Recomputed independently against the standard S/E/C -> ASIL lookup (not the repo's apparent sum-based shortcut) and confirmed the same corrected values, so this is a genuine miscalculation, not a deliberate conservative up-rating. Note: HARA.md's own SG ASIL summary table (lines 36-40) still shows the old ASIL-B/ASIL-A values and is now inconsistent with this file; that document-level reconciliation is tracked separately as an open item (go-LIN-08) and is out of scope here, which touches only the machine-readable .fusa-hara.json S/E/C -> ASIL computation. Addresses go-LIN-N2-01. Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com> --- .fusa-hara.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.fusa-hara.json b/.fusa-hara.json index 14a015c..7636bef 100644 --- a/.fusa-hara.json +++ b/.fusa-hara.json @@ -24,42 +24,42 @@ "id": "H-01", "description": "Master transmits a header for the wrong frame ID — an unintended slave actuates the wrong actuator.", "situations": ["OS-001", "OS-004"], - "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-B"}, + "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-A"}, "safetyGoals": ["SG-01", "SG-05"] }, { "id": "H-02", "description": "A corrupted LIN frame payload is received without error detection and an incorrect command is delivered to an actuator.", "situations": ["OS-001", "OS-004"], - "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-B"}, + "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-A"}, "safetyGoals": ["SG-02"] }, { "id": "H-03", "description": "Incorrect PID parity allows a wrong frame ID to be accepted as valid.", "situations": ["OS-001", "OS-002"], - "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-B"}, + "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-A"}, "safetyGoals": ["SG-01"] }, { "id": "H-04", "description": "A checksum error is not detected and corrupted data is passed to the application.", "situations": ["OS-001", "OS-004"], - "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-B"}, + "risk": {"severity": "S2", "exposure": "E3", "controllability": "C2", "asil": "ASIL-A"}, "safetyGoals": ["SG-02"] }, { "id": "H-05", "description": "An LDF signal is decoded with wrong bit offsets and an actuator is set to an out-of-range value.", "situations": ["OS-001", "OS-004"], - "risk": {"severity": "S1", "exposure": "E3", "controllability": "C2", "asil": "ASIL-A"}, + "risk": {"severity": "S1", "exposure": "E3", "controllability": "C2", "asil": "QM"}, "safetyGoals": ["SG-03"] }, { "id": "H-06", "description": "An E2E sequence-counter gap is not detected — a replayed or lost safety frame goes unnoticed.", "situations": ["OS-001", "OS-003", "OS-004"], - "risk": {"severity": "S2", "exposure": "E2", "controllability": "C2", "asil": "ASIL-A"}, + "risk": {"severity": "S2", "exposure": "E2", "controllability": "C2", "asil": "QM"}, "safetyGoals": ["SG-04"] } ], @@ -68,35 +68,35 @@ "id": "SG-01", "description": "go-LIN shall correctly identify frame IDs using PID parity computation and verification.", "hazards": ["H-01", "H-03"], - "asil": "ASIL-B", + "asil": "ASIL-A", "safeState": "Frame with an unverifiable or mismatched PID is rejected and not delivered to the application." }, { "id": "SG-02", "description": "go-LIN shall detect frame payload corruption using the LIN checksum algorithm.", "hazards": ["H-02", "H-04"], - "asil": "ASIL-B", + "asil": "ASIL-A", "safeState": "Frame failing checksum verification is rejected and reported as an error rather than delivered." }, { "id": "SG-03", "description": "go-LIN shall correctly parse LDF signal definitions and decode frame payloads without offset errors.", "hazards": ["H-05"], - "asil": "ASIL-A", + "asil": "QM", "safeState": "Malformed LDF input is rejected at parse time with a descriptive error; no decode occurs." }, { "id": "SG-04", "description": "go-LIN shall detect E2E sequence gaps and CRC mismatches.", "hazards": ["H-06"], - "asil": "ASIL-A", + "asil": "QM", "safeState": "E2E-protected frame with a sequence gap or CRC mismatch is surfaced as an E2EError and not treated as valid." }, { "id": "SG-05", "description": "go-LIN shall validate all frame IDs and data lengths at API boundaries.", "hazards": ["H-01"], - "asil": "ASIL-B", + "asil": "ASIL-A", "safeState": "Out-of-range frame ID or data length is rejected by ValidateFrame before transmission or processing." } ]