diff --git a/.fusa-reqs.json b/.fusa-reqs.json index 4dd5e19..3cfa89d 100644 --- a/.fusa-reqs.json +++ b/.fusa-reqs.json @@ -800,6 +800,54 @@ "asil": "ASIL-B", "rationale": "go-LIN uses time.After for slot delays; clock accuracy below 1 ms will cause schedule timing jitter.", "tags": ["seooc", "assumption", "timing"] + }, + { + "id": "REQ-ADAPT-001", + "title": "Adapt returns a LIN relay.Node", + "description": "Adapt shall return a non-nil relay.Node whose Protocol method reports relay.LIN.", + "asil": "ASIL-B", + "rationale": "RELAY §10.3/§13.7: the protocol-agnostic application layer must observe the correct protocol tag to route LIN traffic.", + "tags": ["adapt", "relay"] + }, + { + "id": "REQ-ADAPT-002", + "title": "linNode.Send publishes payload for a valid frame ID", + "description": "linNode.Send shall publish msg.Payload as the slave response for the frame ID parsed from msg.ID when the ID is in range 0-63.", + "asil": "ASIL-B", + "rationale": "RELAY §10.3: the adapter egress path must faithfully forward application payloads onto the LIN bus.", + "tags": ["adapt", "relay"] + }, + { + "id": "REQ-ADAPT-003", + "title": "linNode.Send rejects an out-of-range frame ID", + "description": "linNode.Send shall return a non-nil error and shall not publish when msg.ID is not a decimal string in range 0-63.", + "asil": "ASIL-B", + "rationale": "SG-05: an invalid identifier supplied through the adapter must not be transmitted.", + "tags": ["adapt", "relay", "validation"] + }, + { + "id": "REQ-ADAPT-004", + "title": "linNode.Subscribe converts frames to relay.Message", + "description": "linNode.Subscribe shall return a channel that yields one relay.Message per received frame, each carrying the frame's canonical ToMessage() conversion.", + "asil": "ASIL-B", + "rationale": "RELAY §10.5: the adapter ingress path must deliver every received frame to the application as a canonical envelope.", + "tags": ["adapt", "relay"] + }, + { + "id": "REQ-ADAPT-005", + "title": "linNode.Close closes the underlying bus", + "description": "linNode.Close shall close the wrapped Bus and return its error unchanged.", + "asil": "ASIL-B", + "rationale": "RELAY §6 lifecycle: closing the node must release the underlying transport so no further frames are processed.", + "tags": ["adapt", "relay", "lifecycle"] + }, + { + "id": "REQ-MOCK-001", + "title": "mock.New returns a MasterBus-satisfying bus", + "description": "mock.New shall return a non-nil bus that satisfies lin.MasterBus (Publish, Subscribe, SendHeader, SetSchedule, Close).", + "asil": "ASIL-B", + "rationale": "RELAY §7 rule 4 / §13.7: a mandatory mock transport must implement the full bus contract so applications can be tested without hardware.", + "tags": ["mock", "relay"] } ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1553a85..80dc9c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,12 +88,60 @@ jobs: - name: FuzzProtectUnwrap (safety) run: go test -fuzz=^FuzzProtectUnwrap$ -fuzztime=200000x ./safety/... - # ── go-FuSa safety checks ───────────────────────────────────────────────── - # Pinned to v0.30.0; exits 1 on any ERROR-level finding. - # To upgrade: install the new version locally, run 'gofusa check ./...', - # fix all findings, then bump the @version pin below. + # ── RELAY conformance gate (spec §12 / §17 / §20.1.1) ───────────────────── + # Validates the built CLI's version/capabilities/status documents against the + # §12 JSON Schemas embedded in the relay binary. --strict fails on FAIL or WARN. + # Pin the relay version so spec bumps are an explicit upgrade. + relay-conform: + name: RELAY conform (--strict) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-go@v6 + with: + go-version: "1.25" + + - name: Install relay CLI + run: go install github.com/SoundMatt/RELAY/cmd/relay@v1.10.0 + + - name: Build CLI + run: go build -o /tmp/go-lin ./cmd/go-lin + + - name: relay conform --strict + run: relay conform --strict /tmp/go-lin + + # ── RELAY interop behavioural conformance (spec §11.2 / §20.2) ──────────── + # Drives the CLI's `convert` command with the LIN golden vectors and confirms + # its relay.Message output is EQUIVALENT to the reference implementation. + relay-interop: + name: RELAY interop (LIN) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-go@v6 + with: + go-version: "1.25" + + - name: Install relay CLI + run: go install github.com/SoundMatt/RELAY/cmd/relay@v1.10.0 + + - name: Build CLI + run: go build -o /tmp/go-lin ./cmd/go-lin + + - name: relay interop --protocol LIN + run: relay interop --protocol LIN /tmp/go-lin + + # ── go-FuSa full safety lifecycle (spec §20.1.2) ────────────────────────── + # Pinned to v0.30.0. §20 makes the full lifecycle normative: every change must + # pass check (ERROR gate), 100% requirement traceability, cybersecurity + # analysis, dependency vulnerability scan, and tool qualification. The + # remaining steps generate evidence artifacts (verify/fmea/release) and are + # non-gating. To upgrade: install the new version locally, run the gating + # commands, fix all findings, then bump the @version pin below. gofusa: - name: go-FuSa safety check (v0.30.0) + name: go-FuSa full lifecycle (v0.30.0) runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -105,5 +153,40 @@ jobs: - name: Install go-FuSa run: go install github.com/SoundMatt/go-FuSa/cmd/gofusa@v0.30.0 - - name: gofusa check + - name: gofusa check (gate on ERROR findings) run: gofusa check ./... + + - name: gofusa trace (gate on 100% requirement traceability) + run: gofusa trace -req-coverage 100 + + - name: gofusa cyber (cybersecurity analysis) + run: gofusa cyber + + - name: gofusa vuln (dependency vulnerability scan) + run: gofusa vuln + + - name: gofusa qualify (tool qualification suite) + run: gofusa qualify + + - name: gofusa verify (test evidence bundle) + run: gofusa verify || true + + - name: gofusa tara (threat analysis, ISO 21434) + run: gofusa tara || true + + - name: gofusa fmea (dFMEA) + run: gofusa fmea || true + + - name: gofusa release (SBOM + build provenance, SLSA) + run: gofusa release || true + + - name: gofusa audit-pack (bundle all evidence) + run: gofusa audit-pack --output gofusa-audit-pack.zip || true + + - name: Upload safety evidence + if: always() + uses: actions/upload-artifact@v6 + with: + name: gofusa-safety-evidence + path: gofusa-audit-pack.zip + if-no-files-found: ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffcba01..95a651a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,12 @@ jobs: - name: Regenerate dFMEA table run: gofusa fmea -cyber + - name: Regenerate TARA (threat analysis, ISO 21434) + run: gofusa tara + + - name: Generate test evidence bundle (closes safety-case verify gap) + run: gofusa verify || true + - name: Regenerate safety case run: gofusa safety-case @@ -46,6 +52,7 @@ jobs: run: | TAG="${{ github.ref_name }}" git add fmea.csv fmea.json \ + tara.json tara.md \ safety-case.json safety-case.md safety-case.mermaid \ sbom.json provenance.json artifact-manifest.json if git diff --cached --quiet; then diff --git a/.gitignore b/.gitignore index 47e19c2..fb5f616 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ *.so *.dylib dist/ +/go-lin +/lintool # Test output *.out @@ -21,8 +23,13 @@ go.work.sum *.swp *.swo -# go-FuSa generated (regenerated by gofusa release on tag) +# go-FuSa transient gate reports (regenerated by the CI lifecycle each run) check-report.json +cyber-report.json +qualify-report.json +vuln.json +gofusa-audit-pack.zip +.fusa-evidence.json # OS .DS_Store diff --git a/adapt.go b/adapt.go index 4f44520..9769dff 100644 --- a/adapt.go +++ b/adapt.go @@ -16,6 +16,8 @@ import ( // Adapt wraps bus as a relay.Node for use with protocol-agnostic applications. // Adapt does not block; it does not connect. +// +//fusa:req REQ-ADAPT-001 func Adapt(bus Bus) relay.Node { return &linNode{bus: bus} } @@ -24,12 +26,16 @@ type linNode struct { bus Bus } +//fusa:req REQ-ADAPT-001 func (n *linNode) Protocol() relay.Protocol { return relay.LIN } // Send publishes msg.Payload as a slave response for the frame ID in msg.ID. // msg.ID must be a decimal string in range 0–63. +// +//fusa:req REQ-ADAPT-002 +//fusa:req REQ-ADAPT-003 func (n *linNode) Send(ctx context.Context, msg relay.Message) error { id, err := strconv.ParseUint(msg.ID, 10, 8) if err != nil || id > LINMaxID { @@ -39,6 +45,8 @@ func (n *linNode) Send(ctx context.Context, msg relay.Message) error { } // Subscribe returns a channel of relay.Message envelopes for all received frames. +// +//fusa:req REQ-ADAPT-004 func (n *linNode) Subscribe(opts ...relay.SubscriberOption) (<-chan relay.Message, error) { cfg := relay.ApplySubscriberOpts(opts) ch := make(chan relay.Message, cfg.ChanDepth(64)) @@ -75,6 +83,7 @@ func (n *linNode) Subscribe(opts ...relay.SubscriberOption) (<-chan relay.Messag return ch, nil } +//fusa:req REQ-ADAPT-005 func (n *linNode) Close() error { return n.bus.Close() } diff --git a/adapt_test.go b/adapt_test.go new file mode 100644 index 0000000..b97d599 --- /dev/null +++ b/adapt_test.go @@ -0,0 +1,108 @@ +// 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/. + +package lin_test + +import ( + "context" + "testing" + "time" + + relay "github.com/SoundMatt/RELAY" + lin "github.com/SoundMatt/go-LIN" + "github.com/SoundMatt/go-LIN/virtual" +) + +// ── RELAY adapter (spec §10.3 / §13.7) ──────────────────────────────────────── + +//fusa:test REQ-ADAPT-001 +func TestAdapt_protocolIsLIN(t *testing.T) { + bus, err := virtual.New() + if err != nil { + t.Fatalf("virtual.New: %v", err) + } + defer bus.Close() + + node := lin.Adapt(bus) + if node == nil { + t.Fatal("Adapt returned nil") + } + if node.Protocol() != relay.LIN { + t.Errorf("Protocol() = %v, want relay.LIN", node.Protocol()) + } +} + +//fusa:test REQ-ADAPT-002 +//fusa:test REQ-ADAPT-004 +func TestAdapt_sendThenReceive(t *testing.T) { + bus, err := virtual.New() + if err != nil { + t.Fatalf("virtual.New: %v", err) + } + node := lin.Adapt(bus) + defer node.Close() + + ch, err := node.Subscribe() + if err != nil { + t.Fatalf("Subscribe: %v", err) + } + + payload := []byte{0xAA, 0xBB, 0xCC, 0xDD} + if err := node.Send(context.Background(), relay.Message{ID: "16", Payload: payload}); err != nil { + t.Fatalf("Send: %v", err) + } + // Send registers the slave response; trigger one exchange so the frame is + // delivered to subscribers. + if _, err := bus.SendHeader(context.Background(), 16); err != nil { + t.Fatalf("SendHeader: %v", err) + } + + select { + case msg := <-ch: + if msg.Protocol != relay.LIN { + t.Errorf("msg.Protocol = %v, want LIN", msg.Protocol) + } + if msg.ID != "16" { + t.Errorf("msg.ID = %q, want \"16\"", msg.ID) + } + if string(msg.Payload) != string(payload) { + t.Errorf("msg.Payload = %x, want %x", msg.Payload, payload) + } + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for adapted message") + } +} + +//fusa:test REQ-ADAPT-003 +func TestAdapt_sendRejectsBadID(t *testing.T) { + bus, err := virtual.New() + if err != nil { + t.Fatalf("virtual.New: %v", err) + } + node := lin.Adapt(bus) + defer node.Close() + + for _, id := range []string{"64", "not-a-number", "-1"} { + if err := node.Send(context.Background(), relay.Message{ID: id, Payload: []byte{1}}); err == nil { + t.Errorf("Send(ID=%q) = nil error, want rejection", id) + } + } +} + +//fusa:test REQ-ADAPT-005 +func TestAdapt_closeClosesBus(t *testing.T) { + bus, err := virtual.New() + if err != nil { + t.Fatalf("virtual.New: %v", err) + } + node := lin.Adapt(bus) + if err := node.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + // The underlying bus is closed: a further Publish must fail. + if err := bus.Publish(1, []byte{1}); err == nil { + t.Error("Publish after node.Close() = nil error, want closed error") + } +} diff --git a/artifact-manifest.json b/artifact-manifest.json index 85355fc..55927ec 100644 --- a/artifact-manifest.json +++ b/artifact-manifest.json @@ -4,16 +4,16 @@ "tool": "go-FuSa", "toolVersion": "0.30.0", "language": "go", - "generatedAt": "2026-06-17T14:31:08.909144783Z", + "generatedAt": "2026-06-19T19:27:27.299813Z", "format": "x-FuSa manifest v1", "artifacts": [ { - "path": "/home/runner/work/go-LIN/go-LIN/sbom.json", - "sha256": "70d4a8c18eb0c3a9503c88ca2b5badfe39ca097d7ce89e4d5c421808044a300f" + "path": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/sbom.json", + "sha256": "0f3af662af7a0b96cd87813d5704d73b9dc008af6829a6fc8cc1ca3fee3fccb0" }, { - "path": "/home/runner/work/go-LIN/go-LIN/provenance.json", - "sha256": "a9a45ab826bfcfee13e6e52f3cea0be22545d361ecb38bb454f531e2895d652e" + "path": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/provenance.json", + "sha256": "58f4cad2d2f2256a2296773386f410bb675565d5af2812239359ed88db1c56bb" } ] } diff --git a/cmd/go-lin/main.go b/cmd/go-lin/main.go index fbcd41a..2a40c86 100644 --- a/cmd/go-lin/main.go +++ b/cmd/go-lin/main.go @@ -14,16 +14,30 @@ // Protocol commands: // // send Publish a response for a frame ID and trigger one exchange. +// send --format json Streaming relay.Message NDJSON sink (crossbar spoke, §11.2). +// subscribe --format json Streaming relay.Message NDJSON source (crossbar spoke, §11.2). // dump Subscribe to all frames and print them to stdout. // pid Compute and display the Protected Identifier. // cs Compute and display the enhanced checksum. +// +// RELAY interop driver (§11.2): +// +// convert --protocol LIN [--format json] +// Read a lin.Frame as JSON on stdin, run it through Frame.ToMessage() (the +// §15.7.3 canonical conversion) and write the relay.Message as JSON on +// stdout. Exit 0 converted, 1 invalid input, 2 invalid args. package main import ( + "bufio" + "bytes" "context" "encoding/hex" "encoding/json" + "errors" + "flag" "fmt" + "io" "os" "os/signal" "runtime" @@ -31,7 +45,9 @@ import ( "strconv" "strings" "syscall" + "time" + relay "github.com/SoundMatt/RELAY" lin "github.com/SoundMatt/go-LIN" "github.com/SoundMatt/go-LIN/virtual" ) @@ -54,6 +70,10 @@ func main() { cmdStatus(os.Args[2:]) case "send": cmdSend(os.Args[2:]) + case "subscribe": + cmdSubscribe(os.Args[2:]) + case "convert": + os.Exit(cmdConvert(os.Args[2:], os.Stdin, os.Stdout, os.Stderr)) case "dump": cmdDump() case "pid": @@ -77,9 +97,14 @@ RELAY mandatory commands: Protocol commands: send publish response for and trigger one frame exchange + send --format json streaming relay.Message NDJSON sink (crossbar spoke) + subscribe --format json streaming relay.Message NDJSON source (crossbar spoke) dump print all frames on the virtual bus until SIGINT pid compute the Protected Identifier for a 6-bit frame ID cs compute the enhanced LIN checksum for a frame + +RELAY interop driver: + convert --protocol LIN [--format json] lin.Frame JSON (stdin) -> relay.Message JSON (stdout) `, toolName) } @@ -123,7 +148,7 @@ func cmdCapabilities() { "protocol_int": protocolInt, "version": ver, "spec_version": lin.SpecVersion, - "commands": []string{"version", "capabilities", "status", "send", "dump", "pid", "cs"}, + "commands": []string{"version", "capabilities", "status", "send", "subscribe", "convert", "dump", "pid", "cs"}, "transports": []string{"virtual"}, "features": []string{}, "interfaces": []string{"Bus", "MasterBus"}, @@ -164,8 +189,13 @@ func cmdStatus(args []string) { // ── Protocol commands ───────────────────────────────────────────────────────── func cmdSend(args []string) { + // `send --format json` is the streaming NDJSON sink / crossbar spoke (§11.2); + // `send ` is the ad-hoc single-exchange form. + if flagFormat(args) == "json" { + os.Exit(cmdSendStream(os.Stdin, os.Stdout, os.Stderr)) + } if len(args) != 2 { - fmt.Fprintf(os.Stderr, "usage: %s send \n", toolName) + fmt.Fprintf(os.Stderr, "usage: %s send | send --format json\n", toolName) os.Exit(1) } id := parseID(args[0]) @@ -188,6 +218,162 @@ func cmdSend(args []string) { printFrame(f) } +// cmdSendStream is the streaming JSON sink (RELAY §11.2 / crossbar spoke). It +// reads relay.Message values as NDJSON on stdin (one per line) and publishes +// each — via FromMessage → Bus.Publish — until EOF. It is the egress dual of +// `subscribe --format json`. Malformed or undeliverable lines are reported to +// stderr and skipped so a single bad message does not tear down the route; only +// a stdin read error is fatal. Exit: 0 clean EOF, 1 stdin read error. +func cmdSendStream(stdin io.Reader, w, errw io.Writer) int { + bus, err := virtual.New() + if err != nil { + fmt.Fprintf(errw, "%s: virtual.New: %v\n", toolName, err) + return 1 + } + defer bus.Close() //nolint:errcheck + + sc := bufio.NewScanner(stdin) + sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) // tolerate large messages + sent := 0 + for sc.Scan() { + line := bytes.TrimSpace(sc.Bytes()) + if len(line) == 0 { + continue + } + var msg relay.Message + if err := json.Unmarshal(line, &msg); err != nil { + fmt.Fprintf(errw, "send: skipping malformed message: %v\n", err) + continue + } + f, err := lin.FromMessage(msg) + if err != nil { + fmt.Fprintf(errw, "send: skipping message %q: %v\n", msg.ID, err) + continue + } + if err := bus.Publish(f.ID, f.Data); err != nil { + fmt.Fprintf(errw, "send: id %d: %v\n", f.ID, err) + continue + } + sent++ + } + if err := sc.Err(); err != nil { + fmt.Fprintf(errw, "send: read error: %v\n", err) + return 1 + } + fmt.Fprintf(w, "published %d message(s)\n", sent) + return 0 +} + +// cmdSubscribe is the streaming JSON source (RELAY §11.2 / crossbar spoke). With +// `--format json` it subscribes to every frame on the virtual bus and writes each +// as a one-line relay.Message (NDJSON) to stdout until SIGINT. It is the ingress +// dual of `send --format json`. +func cmdSubscribe(args []string) { + if flagFormat(args) != "json" { + fmt.Fprintf(os.Stderr, "usage: %s subscribe --format json\n", toolName) + os.Exit(1) + } + bus, err := virtual.New() + if err != nil { + fatal("virtual.New: %v", err) + } + defer bus.Close() + + ch, err := bus.Subscribe([]lin.Filter{{All: true}}) + if err != nil { + fatal("Subscribe: %v", err) + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + enc := json.NewEncoder(os.Stdout) + var seq uint64 + for { + select { + case <-ctx.Done(): + return + case f, ok := <-ch: + if !ok { + return + } + msg := f.ToMessage() + msg.Timestamp = time.Now().UTC() + msg.Seq = seq + seq++ + _ = enc.Encode(msg) // one compact JSON object per line (NDJSON) + } + } +} + +// ── RELAY interop driver (spec §11.2) ───────────────────────────────────────── + +// cmdConvert implements `convert --protocol LIN [--format json]` (spec §11.2). +// It reads one lin.Frame as JSON on stdin, validates it with ValidateFrame, and +// converts it via Frame.ToMessage() — the same §15.7.3 code path used at runtime +// — writing the relay.Message as JSON on stdout. The timestamp is left zero so +// interop comparisons are deterministic. On invalid input it writes the RELAY +// §5 sentinel name to stderr. Exit: 0 converted, 1 invalid input, 2 invalid args. +func cmdConvert(args []string, stdin io.Reader, stdout, stderr io.Writer) int { + fs := flag.NewFlagSet("convert", flag.ContinueOnError) + fs.SetOutput(stderr) + protocol := fs.String("protocol", "", "canonical protocol (must be LIN)") + format := fs.String("format", "json", "output format (json)") + if err := fs.Parse(args); err != nil { + return 2 + } + if *protocol != "LIN" { + fmt.Fprintln(stderr, "convert: --protocol LIN is required") + return 2 + } + if *format != "json" { + fmt.Fprintln(stderr, "convert: only --format json is supported") + return 2 + } + + raw, err := io.ReadAll(stdin) + if err != nil { + fmt.Fprintln(stderr, lin.ErrInvalidFrame.Error()) + return 1 + } + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + var f lin.Frame + if err := dec.Decode(&f); err != nil { + fmt.Fprintln(stderr, "ErrInvalidFrame") // §5 sentinel name + return 1 + } + if err := lin.ValidateFrame(f); err != nil { + if errors.Is(err, lin.ErrInvalidFrame) { + fmt.Fprintln(stderr, "ErrInvalidFrame") + } else { + fmt.Fprintln(stderr, err.Error()) + } + return 1 + } + msg := f.ToMessage() // deterministic: Timestamp left zero + out, err := json.Marshal(msg) + if err != nil { + fmt.Fprintln(stderr, "ErrInvalidFrame") + return 1 + } + fmt.Fprintln(stdout, string(out)) + return 0 +} + +// flagFormat returns the value of a --format flag (e.g. "json"), or "" if absent. +func flagFormat(args []string) string { + for i, a := range args { + switch { + case a == "--format" && i+1 < len(args): + return args[i+1] + case strings.HasPrefix(a, "--format="): + return strings.TrimPrefix(a, "--format=") + } + } + return "" +} + func cmdDump() { bus, err := virtual.New() if err != nil { diff --git a/cmd/go-lin/main_test.go b/cmd/go-lin/main_test.go new file mode 100644 index 0000000..c24eb6e --- /dev/null +++ b/cmd/go-lin/main_test.go @@ -0,0 +1,82 @@ +// 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/. + +package main + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + relay "github.com/SoundMatt/RELAY" +) + +// TestConvert_goldenVector verifies the §11.2 interop driver produces the +// canonical relay.Message for the published LIN golden vector value, with a +// zeroed timestamp so cross-implementation comparison is deterministic. +func TestConvert_goldenVector(t *testing.T) { + in := `{"id":16,"data":"qrs=","checksum":73,"checksum_type":1}` + var out, errb bytes.Buffer + code := cmdConvert([]string{"--protocol", "LIN", "--format", "json"}, + strings.NewReader(in), &out, &errb) + if code != 0 { + t.Fatalf("convert exit = %d, stderr = %q", code, errb.String()) + } + var msg relay.Message + if err := json.Unmarshal(out.Bytes(), &msg); err != nil { + t.Fatalf("output is not valid relay.Message JSON: %v", err) + } + if msg.Protocol != relay.LIN { + t.Errorf("Protocol = %v, want LIN", msg.Protocol) + } + if msg.ID != "16" { + t.Errorf("ID = %q, want \"16\"", msg.ID) + } + if !msg.Timestamp.IsZero() { + t.Errorf("Timestamp = %v, want zero (deterministic interop output)", msg.Timestamp) + } + if msg.Meta["lin.checksum_type"] != "enhanced" { + t.Errorf("meta checksum_type = %q, want enhanced", msg.Meta["lin.checksum_type"]) + } +} + +// TestConvert_invalidInput rejects a structurally invalid frame with exit 1 and +// the RELAY §5 sentinel name on stderr. +func TestConvert_invalidInput(t *testing.T) { + in := `{"id":255,"data":"qrs=","checksum":0,"checksum_type":0}` // ID > 0x3F + var out, errb bytes.Buffer + code := cmdConvert([]string{"--protocol", "LIN"}, strings.NewReader(in), &out, &errb) + if code != 1 { + t.Fatalf("convert exit = %d, want 1", code) + } + if got := strings.TrimSpace(errb.String()); got != "ErrInvalidFrame" { + t.Errorf("stderr = %q, want ErrInvalidFrame", got) + } +} + +// TestConvert_wrongProtocol returns exit 2 (invalid args) for a non-LIN protocol. +func TestConvert_wrongProtocol(t *testing.T) { + var out, errb bytes.Buffer + code := cmdConvert([]string{"--protocol", "CAN"}, strings.NewReader(`{}`), &out, &errb) + if code != 2 { + t.Errorf("convert exit = %d, want 2", code) + } +} + +// TestSendStream_publishesNDJSON feeds two relay.Message lines (one valid, one +// malformed) and confirms the valid one is published and the malformed one +// skipped without aborting the stream. +func TestSendStream_publishesNDJSON(t *testing.T) { + in := "{\"protocol\":3,\"id\":\"16\",\"payload\":\"qrs=\",\"meta\":{\"lin.checksum\":\"73\",\"lin.checksum_type\":\"enhanced\"}}\nnot-json\n" + var out, errb bytes.Buffer + code := cmdSendStream(strings.NewReader(in), &out, &errb) + if code != 0 { + t.Fatalf("send stream exit = %d, stderr = %q", code, errb.String()) + } + if !strings.Contains(out.String(), "published 1 message") { + t.Errorf("stdout = %q, want \"published 1 message\"", out.String()) + } +} diff --git a/fmea.csv b/fmea.csv index 6b5f5c0..e1b6974 100644 --- a/fmea.csv +++ b/fmea.csv @@ -1,13 +1,13 @@ Component,Function,FailureModes,Effects,Severity,DetectionControl,RequirementIDs,CyberRisks -go-LIN (lin),Adapt,Incorrect output,Incorrect system behavior,low,unit tests,,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range +go-LIN (lin),Adapt,Incorrect output,Incorrect system behavior,high,requirement testing + unit tests,REQ-ADAPT-001,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range go-LIN (lin),CalcChecksum,Incorrect output,Incorrect system behavior,high,requirement testing + unit tests,REQ-LIN-008; REQ-LIN-009; REQ-LIN-010,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range -go-LIN (lin),Close,Returns unexpected error,Silent failure propagated to caller,medium,unit tests,,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range +go-LIN (lin),Close,Returns unexpected error,Silent failure propagated to caller,high,requirement testing + unit tests,REQ-ADAPT-005,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range go-LIN (lin),FromMessage,Returns unexpected error,Silent failure propagated to caller,medium,unit tests,,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range go-LIN (lin),Matches,Incorrect output,Incorrect system behavior,low,unit tests,,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range go-LIN (lin),ProtectID,Incorrect output,Incorrect system behavior,high,requirement testing + unit tests,REQ-LIN-004; REQ-LIN-005; REQ-LIN-018,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range -go-LIN (lin),Protocol,Incorrect output,Incorrect system behavior,low,unit tests,,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range -go-LIN (lin),Send,Returns unexpected error,Silent failure propagated to caller,medium,unit tests,,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range -go-LIN (lin),Subscribe,Returns unexpected error; Goroutine not terminated,Silent failure propagated to caller; Memory leak or deadlock,medium,unit tests,,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range +go-LIN (lin),Protocol,Incorrect output,Incorrect system behavior,high,requirement testing + unit tests,REQ-ADAPT-001,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range +go-LIN (lin),Send,Returns unexpected error,Silent failure propagated to caller,high,requirement testing + unit tests,REQ-ADAPT-002; REQ-ADAPT-003,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range +go-LIN (lin),Subscribe,Returns unexpected error; Goroutine not terminated,Silent failure propagated to caller; Memory leak or deadlock,high,requirement testing + unit tests,REQ-ADAPT-004,CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range go-LIN (lin),ToMessage,Incorrect output,Incorrect system behavior,low,unit tests,,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range go-LIN (lin),ValidateFrame,Returns unexpected error,Silent failure propagated to caller,high,requirement testing + unit tests,REQ-LIN-001; REQ-LIN-002; REQ-LIN-003; REQ-LIN-015; REQ-LIN-016; REQ-LIN-017,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range go-LIN (lin),VerifyPID,Returns unexpected error,Silent failure propagated to caller,high,requirement testing + unit tests,REQ-LIN-006; REQ-LIN-007,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range; CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range @@ -24,7 +24,7 @@ master,OnFrame,Incorrect output,Incorrect system behavior,high,requirement testi master,Run,Returns unexpected error; Uncontrolled execution,Silent failure propagated to caller; Resource exhaustion,high,requirement testing + unit tests,REQ-MASTER-003; REQ-MASTER-004; REQ-MASTER-005; REQ-MASTER-006; REQ-MASTER-007; REQ-MASTER-008; REQ-MASTER-009; REQ-MASTER-013, master,SendHeader,Returns unexpected error,Silent failure propagated to caller,high,requirement testing + unit tests,REQ-MASTER-002, master,SetSchedule,Returns unexpected error,Silent failure propagated to caller,high,requirement testing + unit tests,REQ-MASTER-010; REQ-MASTER-011; REQ-MASTER-012, -mock,New,Returns unexpected error,Silent failure propagated to caller,medium,code review,, +mock,New,Returns unexpected error,Silent failure propagated to caller,high,requirement testing + unit tests,REQ-MOCK-001, safety,Error,Incorrect output,Incorrect system behavior,low,unit tests,,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range safety,NewProtector,Incorrect output,Incorrect system behavior,high,requirement testing + unit tests,REQ-SAFETY-003,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range safety,NewReceiver,Incorrect output,Incorrect system behavior,high,requirement testing + unit tests,REQ-SAFETY-013,CYBER009: uint16(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range diff --git a/fmea.json b/fmea.json index 7ba7a29..fc20289 100644 --- a/fmea.json +++ b/fmea.json @@ -1,20 +1,23 @@ { "format": "go-FuSa dFMEA v1", - "generated_at": "2026-06-17T14:31:08.739861457Z", + "generated_at": "2026-06-19T19:27:25.872187Z", "module": "github.com/SoundMatt/go-LIN", "entries": [ { "component": "go-LIN (lin)", "function": "Adapt", - "file": "/home/runner/work/go-LIN/go-LIN/adapt.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/adapt.go", "failure_modes": [ "Incorrect output" ], "effects": [ "Incorrect system behavior" ], - "severity": "low", - "detection_control": "unit tests", + "severity": "high", + "detection_control": "requirement testing + unit tests", + "requirement_ids": [ + "REQ-ADAPT-001" + ], "cyber_risks": [ "CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range" ] @@ -22,7 +25,7 @@ { "component": "go-LIN (lin)", "function": "CalcChecksum", - "file": "/home/runner/work/go-LIN/go-LIN/lin.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", "failure_modes": [ "Incorrect output" ], @@ -48,15 +51,18 @@ { "component": "go-LIN (lin)", "function": "Close", - "file": "/home/runner/work/go-LIN/go-LIN/adapt.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/adapt.go", "failure_modes": [ "Returns unexpected error" ], "effects": [ "Silent failure propagated to caller" ], - "severity": "medium", - "detection_control": "unit tests", + "severity": "high", + "detection_control": "requirement testing + unit tests", + "requirement_ids": [ + "REQ-ADAPT-005" + ], "cyber_risks": [ "CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range" ] @@ -64,7 +70,7 @@ { "component": "go-LIN (lin)", "function": "FromMessage", - "file": "/home/runner/work/go-LIN/go-LIN/lin.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", "failure_modes": [ "Returns unexpected error" ], @@ -85,7 +91,7 @@ { "component": "go-LIN (lin)", "function": "Matches", - "file": "/home/runner/work/go-LIN/go-LIN/lin.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", "failure_modes": [ "Incorrect output" ], @@ -106,7 +112,7 @@ { "component": "go-LIN (lin)", "function": "ProtectID", - "file": "/home/runner/work/go-LIN/go-LIN/lin.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", "failure_modes": [ "Incorrect output" ], @@ -132,15 +138,18 @@ { "component": "go-LIN (lin)", "function": "Protocol", - "file": "/home/runner/work/go-LIN/go-LIN/adapt.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/adapt.go", "failure_modes": [ "Incorrect output" ], "effects": [ "Incorrect system behavior" ], - "severity": "low", - "detection_control": "unit tests", + "severity": "high", + "detection_control": "requirement testing + unit tests", + "requirement_ids": [ + "REQ-ADAPT-001" + ], "cyber_risks": [ "CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range" ] @@ -148,15 +157,19 @@ { "component": "go-LIN (lin)", "function": "Send", - "file": "/home/runner/work/go-LIN/go-LIN/adapt.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/adapt.go", "failure_modes": [ "Returns unexpected error" ], "effects": [ "Silent failure propagated to caller" ], - "severity": "medium", - "detection_control": "unit tests", + "severity": "high", + "detection_control": "requirement testing + unit tests", + "requirement_ids": [ + "REQ-ADAPT-002", + "REQ-ADAPT-003" + ], "cyber_risks": [ "CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range" ] @@ -164,7 +177,7 @@ { "component": "go-LIN (lin)", "function": "Subscribe", - "file": "/home/runner/work/go-LIN/go-LIN/adapt.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/adapt.go", "failure_modes": [ "Returns unexpected error", "Goroutine not terminated" @@ -173,8 +186,11 @@ "Silent failure propagated to caller", "Memory leak or deadlock" ], - "severity": "medium", - "detection_control": "unit tests", + "severity": "high", + "detection_control": "requirement testing + unit tests", + "requirement_ids": [ + "REQ-ADAPT-004" + ], "cyber_risks": [ "CYBER009: uint8(x) — explicit narrowing conversion may silently truncate bits if x exceeds the target type's range" ] @@ -182,7 +198,7 @@ { "component": "go-LIN (lin)", "function": "ToMessage", - "file": "/home/runner/work/go-LIN/go-LIN/lin.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", "failure_modes": [ "Incorrect output" ], @@ -203,7 +219,7 @@ { "component": "go-LIN (lin)", "function": "ValidateFrame", - "file": "/home/runner/work/go-LIN/go-LIN/lin.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", "failure_modes": [ "Returns unexpected error" ], @@ -232,7 +248,7 @@ { "component": "go-LIN (lin)", "function": "VerifyPID", - "file": "/home/runner/work/go-LIN/go-LIN/lin.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", "failure_modes": [ "Returns unexpected error" ], @@ -257,7 +273,7 @@ { "component": "ldf", "function": "Decode", - "file": "/home/runner/work/go-LIN/go-LIN/ldf/parser.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", "failure_modes": [ "Incorrect output" ], @@ -278,7 +294,7 @@ { "component": "ldf", "function": "Frame", - "file": "/home/runner/work/go-LIN/go-LIN/ldf/parser.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", "failure_modes": [ "Incorrect output" ], @@ -299,7 +315,7 @@ { "component": "ldf", "function": "Frames", - "file": "/home/runner/work/go-LIN/go-LIN/ldf/parser.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", "failure_modes": [ "Incorrect output" ], @@ -320,7 +336,7 @@ { "component": "ldf", "function": "Parse", - "file": "/home/runner/work/go-LIN/go-LIN/ldf/parser.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", "failure_modes": [ "Returns unexpected error" ], @@ -344,7 +360,7 @@ { "component": "ldf", "function": "Schedule", - "file": "/home/runner/work/go-LIN/go-LIN/ldf/parser.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", "failure_modes": [ "Incorrect output" ], @@ -364,7 +380,7 @@ { "component": "ldf", "function": "Signal", - "file": "/home/runner/work/go-LIN/go-LIN/ldf/parser.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", "failure_modes": [ "Incorrect output" ], @@ -385,7 +401,7 @@ { "component": "ldf", "function": "Signals", - "file": "/home/runner/work/go-LIN/go-LIN/ldf/parser.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", "failure_modes": [ "Incorrect output" ], @@ -406,7 +422,7 @@ { "component": "master", "function": "New", - "file": "/home/runner/work/go-LIN/go-LIN/master/master.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/master/master.go", "failure_modes": [ "Incorrect output" ], @@ -422,7 +438,7 @@ { "component": "master", "function": "OnError", - "file": "/home/runner/work/go-LIN/go-LIN/master/master.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/master/master.go", "failure_modes": [ "Incorrect output" ], @@ -438,7 +454,7 @@ { "component": "master", "function": "OnFrame", - "file": "/home/runner/work/go-LIN/go-LIN/master/master.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/master/master.go", "failure_modes": [ "Incorrect output" ], @@ -454,7 +470,7 @@ { "component": "master", "function": "Run", - "file": "/home/runner/work/go-LIN/go-LIN/master/master.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/master/master.go", "failure_modes": [ "Returns unexpected error", "Uncontrolled execution" @@ -479,7 +495,7 @@ { "component": "master", "function": "SendHeader", - "file": "/home/runner/work/go-LIN/go-LIN/master/master.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/master/master.go", "failure_modes": [ "Returns unexpected error" ], @@ -495,7 +511,7 @@ { "component": "master", "function": "SetSchedule", - "file": "/home/runner/work/go-LIN/go-LIN/master/master.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/master/master.go", "failure_modes": [ "Returns unexpected error" ], @@ -513,20 +529,23 @@ { "component": "mock", "function": "New", - "file": "/home/runner/work/go-LIN/go-LIN/mock/mock.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/mock/mock.go", "failure_modes": [ "Returns unexpected error" ], "effects": [ "Silent failure propagated to caller" ], - "severity": "medium", - "detection_control": "code review" + "severity": "high", + "detection_control": "requirement testing + unit tests", + "requirement_ids": [ + "REQ-MOCK-001" + ] }, { "component": "safety", "function": "Error", - "file": "/home/runner/work/go-LIN/go-LIN/safety/e2e.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e.go", "failure_modes": [ "Incorrect output" ], @@ -542,7 +561,7 @@ { "component": "safety", "function": "NewProtector", - "file": "/home/runner/work/go-LIN/go-LIN/safety/e2e.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e.go", "failure_modes": [ "Incorrect output" ], @@ -561,7 +580,7 @@ { "component": "safety", "function": "NewReceiver", - "file": "/home/runner/work/go-LIN/go-LIN/safety/e2e.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e.go", "failure_modes": [ "Incorrect output" ], @@ -580,7 +599,7 @@ { "component": "safety", "function": "Protect", - "file": "/home/runner/work/go-LIN/go-LIN/safety/e2e.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e.go", "failure_modes": [ "Incorrect output" ], @@ -606,7 +625,7 @@ { "component": "safety", "function": "Unwrap", - "file": "/home/runner/work/go-LIN/go-LIN/safety/e2e.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e.go", "failure_modes": [ "Returns unexpected error" ], @@ -631,7 +650,7 @@ { "component": "slave", "function": "New", - "file": "/home/runner/work/go-LIN/go-LIN/slave/slave.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/slave/slave.go", "failure_modes": [ "Incorrect output" ], @@ -647,7 +666,7 @@ { "component": "slave", "function": "RegisteredIDs", - "file": "/home/runner/work/go-LIN/go-LIN/slave/slave.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/slave/slave.go", "failure_modes": [ "Incorrect output" ], @@ -664,7 +683,7 @@ { "component": "slave", "function": "SetResponse", - "file": "/home/runner/work/go-LIN/go-LIN/slave/slave.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/slave/slave.go", "failure_modes": [ "Returns unexpected error" ], @@ -683,7 +702,7 @@ { "component": "slave", "function": "Subscribe", - "file": "/home/runner/work/go-LIN/go-LIN/slave/slave.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/slave/slave.go", "failure_modes": [ "Returns unexpected error" ], @@ -699,7 +718,7 @@ { "component": "virtual", "function": "Close", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], @@ -716,7 +735,7 @@ { "component": "virtual", "function": "CloseWithDrain", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], @@ -729,7 +748,7 @@ { "component": "virtual", "function": "Health", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Incorrect output" ], @@ -742,7 +761,7 @@ { "component": "virtual", "function": "Metrics", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Incorrect output" ], @@ -755,7 +774,7 @@ { "component": "virtual", "function": "New", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], @@ -771,7 +790,7 @@ { "component": "virtual", "function": "Publish", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], @@ -791,7 +810,7 @@ { "component": "virtual", "function": "PublishClassic", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], @@ -804,7 +823,7 @@ { "component": "virtual", "function": "SendHeader", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], @@ -826,7 +845,7 @@ { "component": "virtual", "function": "SetSchedule", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], @@ -839,7 +858,7 @@ { "component": "virtual", "function": "Subscribe", - "file": "/home/runner/work/go-LIN/go-LIN/virtual/bus.go", + "file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/virtual/bus.go", "failure_modes": [ "Returns unexpected error" ], diff --git a/go.mod b/go.mod index f3352a2..05814d7 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/SoundMatt/go-LIN go 1.25.0 -require github.com/SoundMatt/RELAY v1.0.0 +require github.com/SoundMatt/RELAY v1.10.0 diff --git a/go.sum b/go.sum index 9b77f24..0516fd5 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/SoundMatt/RELAY v1.0.0 h1:eb4PvxHrJd0jsR97zBHOeTNJZXB69S3MWFpaknJho6c= github.com/SoundMatt/RELAY v1.0.0/go.mod h1:8HXkxErdk4pHaM1de9eopm2S8o1aE7tKYkCBBsHxxcg= +github.com/SoundMatt/RELAY v1.10.0 h1:P+UrbaMCwb44KKPdotBSPfjPtqnC6GkrCZg8lNSgtKE= +github.com/SoundMatt/RELAY v1.10.0/go.mod h1:8HXkxErdk4pHaM1de9eopm2S8o1aE7tKYkCBBsHxxcg= diff --git a/mock/mock.go b/mock/mock.go index 9e8486a..90b703b 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -16,6 +16,8 @@ import "github.com/SoundMatt/go-LIN/virtual" // New returns a fully functional in-process LIN bus for testing. // It implements lin.MasterBus (Publish, Subscribe, SendHeader, SetSchedule, Close). +// +//fusa:req REQ-MOCK-001 func New() (*virtual.Bus, error) { return virtual.New() } diff --git a/mock/mock_test.go b/mock/mock_test.go new file mode 100644 index 0000000..c6f6802 --- /dev/null +++ b/mock/mock_test.go @@ -0,0 +1,29 @@ +// 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/. + +package mock_test + +import ( + "testing" + + lin "github.com/SoundMatt/go-LIN" + "github.com/SoundMatt/go-LIN/mock" +) + +//fusa:test REQ-MOCK-001 +func TestNew_satisfiesMasterBus(t *testing.T) { + bus, err := mock.New() + if err != nil { + t.Fatalf("mock.New: %v", err) + } + if bus == nil { + t.Fatal("mock.New returned nil bus") + } + defer bus.Close() + + // Compile-time + runtime assertion that the mock satisfies the full + // lin.MasterBus contract (RELAY §7 rule 4). + var _ lin.MasterBus = bus +} diff --git a/provenance.json b/provenance.json index 21c328c..a27824b 100644 --- a/provenance.json +++ b/provenance.json @@ -4,12 +4,12 @@ "tool": "go-FuSa", "toolVersion": "0.30.0", "language": "go", - "generatedAt": "2026-06-17T14:31:08.908940596Z", + "generatedAt": "2026-06-19T19:27:27.299587Z", "format": "x-FuSa provenance v1", "module": "github.com/SoundMatt/go-LIN", - "goVersion": "go1.25.11", - "goos": "linux", - "goarch": "amd64", - "vcsRevision": "10d44d284a73b688cb8ce8c0925f0b3bf7dd722f", + "goVersion": "go1.26.3", + "goos": "darwin", + "goarch": "arm64", + "vcsRevision": "1f4c96910577e196ec7be31d72cdaff605466954", "vcsModified": true } diff --git a/safety-case.json b/safety-case.json index 6bf6be6..5d79b3d 100644 --- a/safety-case.json +++ b/safety-case.json @@ -1,6 +1,6 @@ { "format": "go-FuSa Safety Case v1", - "generatedAt": "2026-06-17T14:31:08.887346033Z", + "generatedAt": "2026-06-19T19:27:27.262353Z", "module": "github.com/SoundMatt/go-LIN", "standard": "generic", "evidence": [ @@ -8,29 +8,29 @@ "id": "check", "description": "Coding standard and static analysis checks", "file": "check-report.json", - "status": "absent", - "detail": "run 'gofusa check --output check-report.json' to generate" + "status": "present", + "detail": "291 findings (0 errors, 216 warnings)" }, { "id": "trace", "description": "Requirements traceability matrix", "file": ".fusa-reqs.json", "status": "present", - "detail": "100 requirements" + "detail": "106 requirements" }, { "id": "verify", "description": "Test evidence bundle", "file": ".fusa-evidence.json", - "status": "absent", - "detail": "run 'gofusa verify' to generate" + "status": "present", + "detail": "137/137 tests passed" }, { "id": "qualify", "description": "Tool qualification report", "file": "qualify-report.json", - "status": "absent", - "detail": "run 'gofusa qualify' to generate" + "status": "present", + "detail": "44/44 cases passed" }, { "id": "sbom", @@ -88,9 +88,5 @@ ] } ], - "gaps": [ - "check", - "verify", - "qualify" - ] + "gaps": null } diff --git a/safety-case.md b/safety-case.md index 9e6dc2e..2b46acc 100644 --- a/safety-case.md +++ b/safety-case.md @@ -1,6 +1,6 @@ # Safety Case: github.com/SoundMatt/go-LIN -Generated: 2026-06-17T14:31:08Z +Generated: 2026-06-19T19:27:27Z Standard: generic ## Top Claim @@ -12,10 +12,10 @@ argued by demonstrating compliance with the safety development lifecycle. | ID | Description | Status | Detail | |---|---|---|---| -| Sn1 | Coding standard and static analysis checks | ⚠ absent | run 'gofusa check --output check-report.json' to generate | -| Sn2 | Requirements traceability matrix | ✅ present | 100 requirements | -| Sn3 | Test evidence bundle | ⚠ absent | run 'gofusa verify' to generate | -| Sn4 | Tool qualification report | ⚠ absent | run 'gofusa qualify' to generate | +| Sn1 | Coding standard and static analysis checks | ✅ present | 291 findings (0 errors, 216 warnings) | +| Sn2 | Requirements traceability matrix | ✅ present | 106 requirements | +| Sn3 | Test evidence bundle | ✅ present | 137/137 tests passed | +| Sn4 | Tool qualification report | ✅ present | 44/44 cases passed | | Sn5 | SBOM (SPDX 3.0.1) | ✅ present | | | Sn6 | Build provenance | ✅ present | | @@ -31,8 +31,4 @@ argued by demonstrating compliance with the safety development lifecycle. ## Gaps -The following evidence items are absent: - -- `check` -- `verify` -- `qualify` +None — all evidence present. diff --git a/safety-case.mermaid b/safety-case.mermaid index d6bb873..b4aa285 100644 --- a/safety-case.mermaid +++ b/safety-case.mermaid @@ -1,13 +1,13 @@ flowchart TD G1["**G1** github.com/SoundMatt/go-LIN is acceptably safe\nfor use in generic context"] S1{{"**S1** Argued over safety lifecycle\nprocess compliance"}} - G2["**G2** Coding standard and static analysis checks\n⚠ run 'gofusa check --output check-report.json' to generate"] + G2["**G2** Coding standard and static analysis checks\n✅ 291 findings (0 errors, 216 warnings)"] Sn1(["**Sn1** check-report.json"]) - G3["**G3** Requirements traceability matrix\n✅ 100 requirements"] + G3["**G3** Requirements traceability matrix\n✅ 106 requirements"] Sn2(["**Sn2** .fusa-reqs.json"]) - G4["**G4** Test evidence bundle\n⚠ run 'gofusa verify' to generate"] + G4["**G4** Test evidence bundle\n✅ 137/137 tests passed"] Sn3(["**Sn3** .fusa-evidence.json"]) - G5["**G5** Tool qualification report\n⚠ run 'gofusa qualify' to generate"] + G5["**G5** Tool qualification report\n✅ 44/44 cases passed"] Sn4(["**Sn4** qualify-report.json"]) G6["**G6** SBOM (SPDX 3.0.1)\n✅ sbom.json"] Sn5(["**Sn5** sbom.json"]) @@ -26,9 +26,3 @@ flowchart TD G5 --> Sn4 G6 --> Sn5 G7 --> Sn6 - style G2 fill:#fee2e2,stroke:#ef4444 - style Sn1 fill:#fee2e2,stroke:#ef4444 - style G4 fill:#fee2e2,stroke:#ef4444 - style Sn3 fill:#fee2e2,stroke:#ef4444 - style G5 fill:#fee2e2,stroke:#ef4444 - style Sn4 fill:#fee2e2,stroke:#ef4444 diff --git a/sbom.json b/sbom.json index f6fc6c4..826376e 100644 --- a/sbom.json +++ b/sbom.json @@ -4,15 +4,15 @@ "tool": "go-FuSa", "toolVersion": "0.30.0", "language": "go", - "generatedAt": "2026-06-17T14:31:08.903084284Z", + "generatedAt": "2026-06-19T19:27:27.272357Z", "format": "x-FuSa SBOM v1", "module": "github.com/SoundMatt/go-LIN", "goVersion": "1.25.0", "components": [ { "name": "github.com/SoundMatt/RELAY", - "version": "v1.0.0", - "hash": "sha256:79be0fbf11eb25dd23b11f7bcc11ce79334965707af52dcc585a5a927261a3a7" + "version": "v1.10.0", + "hash": "sha256:3fe52b6da302c1be3828a3dda2d0523df8cfb6a9c2e8692b09983c94d4a0b4a1" } ] } diff --git a/tara.json b/tara.json new file mode 100644 index 0000000..1725c84 --- /dev/null +++ b/tara.json @@ -0,0 +1,527 @@ +{ + "format": "go-FuSa TARA v1", + "generated_at": "2026-06-19T19:27:25.98967Z", + "module": "github.com/SoundMatt/go-LIN", + "entries": [ + { + "id": "TARA-001", + "asset": "adapt.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/adapt.go", + "source_line": 44 + }, + { + "id": "TARA-002", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/cmd/go-lin/main.go", + "source_line": 447 + }, + { + "id": "TARA-003", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/cmd/lintool/main.go", + "source_line": 155 + }, + { + "id": "TARA-004", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/examples/quickstart/main.go", + "source_line": 53 + }, + { + "id": "TARA-005", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/examples/quickstart/main.go", + "source_line": 54 + }, + { + "id": "TARA-006", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/examples/quickstart/main.go", + "source_line": 65 + }, + { + "id": "TARA-007", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/examples/quickstart/main.go", + "source_line": 66 + }, + { + "id": "TARA-008", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/examples/quickstart/main.go", + "source_line": 93 + }, + { + "id": "TARA-009", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/examples/quickstart/main.go", + "source_line": 99 + }, + { + "id": "TARA-010", + "asset": "main.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/examples/quickstart/main.go", + "source_line": 100 + }, + { + "id": "TARA-011", + "asset": "parser.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", + "source_line": 441 + }, + { + "id": "TARA-012", + "asset": "parser.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/ldf/parser.go", + "source_line": 487 + }, + { + "id": "TARA-013", + "asset": "lin.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", + "source_line": 255 + }, + { + "id": "TARA-014", + "asset": "lin.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", + "source_line": 258 + }, + { + "id": "TARA-015", + "asset": "lin.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", + "source_line": 263 + }, + { + "id": "TARA-016", + "asset": "lin.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", + "source_line": 263 + }, + { + "id": "TARA-017", + "asset": "lin.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", + "source_line": 327 + }, + { + "id": "TARA-018", + "asset": "lin.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin.go", + "source_line": 329 + }, + { + "id": "TARA-019", + "asset": "lin_test.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin_test.go", + "source_line": 93 + }, + { + "id": "TARA-020", + "asset": "lin_test.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin_test.go", + "source_line": 95 + }, + { + "id": "TARA-021", + "asset": "lin_test.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin_test.go", + "source_line": 100 + }, + { + "id": "TARA-022", + "asset": "lin_test.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin_test.go", + "source_line": 134 + }, + { + "id": "TARA-023", + "asset": "lin_test.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/lin_test.go", + "source_line": 139 + }, + { + "id": "TARA-024", + "asset": "e2e.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e.go", + "source_line": 231 + }, + { + "id": "TARA-025", + "asset": "e2e_test.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e_test.go", + "source_line": 61 + }, + { + "id": "TARA-026", + "asset": "e2e_test.go", + "threat": "Integer narrowing conversion causes silent data truncation", + "stride": [ + "T", + "D" + ], + "cwe": "CWE-190", + "standard": "MISRA Rule 10.3", + "attack_vector": "Local", + "likelihood": "Low", + "impact": "Medium", + "security_level": 1, + "current_control": "Add range check before conversion", + "residual_risk": "Low after remediation", + "cyber_rule_id": "CYBER009", + "source_file": "/Users/matt/Documents/Coding/SoundMatt/go-LIN/safety/e2e_test.go", + "source_line": 229 + } + ] +} diff --git a/tara.md b/tara.md new file mode 100644 index 0000000..3f59ddb --- /dev/null +++ b/tara.md @@ -0,0 +1,34 @@ +# Threat Analysis and Risk Assessment (TARA) + +**Module:** github.com/SoundMatt/go-LIN +**Generated:** 2026-06-19T19:27:25Z +**Standard:** ISO 21434 Chapter 9 + +| ID | Asset | Threat | STRIDE | CWE | Vector | Likelihood | Impact | SL | Control | Residual Risk | +|---|---|---|---|---|---|---|---|---|---|---| +| TARA-001 | adapt.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-002 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-003 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-004 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-005 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-006 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-007 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-008 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-009 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-010 | main.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-011 | parser.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-012 | parser.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-013 | lin.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-014 | lin.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-015 | lin.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-016 | lin.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-017 | lin.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-018 | lin.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-019 | lin_test.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-020 | lin_test.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-021 | lin_test.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-022 | lin_test.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-023 | lin_test.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-024 | e2e.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-025 | e2e_test.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation | +| TARA-026 | e2e_test.go | Integer narrowing conversion causes silent data truncation | T/D | CWE-190 | Local | Low | Medium | 1 | Add range check before conversion | Low after remediation |