Languages: This README is in English. For Korean, see README.ko.md.
This project generates portable C99 parser modules (headers/sources) from a .dbc file using an F# code generator.
- SignalCandy.Core — Core F# library (parsing, config, codegen)
- SignalCandy — C#-friendly facade over the Core
Install:
dotnet add package SignalCandy.Core --version 0.2.1
dotnet add package SignalCandy --version 0.2.1- Check prerequisites
dotnet --version # 8.0+
gcc --version # C compiler (optional: for local validation)
make --version # GNU Make (optional: for local validation)- Generate C code from a sample DBC
dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen- (Optional) Generate a local test harness and build
# Create/upgrade an adaptive Makefile and copy a sample main.c
dotnet run --project src/Signal.CANdy.CLI -- -d examples/sample.dbc -o gen -t
make -C gen build
./gen/build/test_runner test_roundtripExpected: roundtrip test passes and generated headers appear under gen/include.
Note: Step 3 is optional but recommended for validation. The generated C files under gen/include and gen/src are ready for integration into your firmware project with any compatible C99 toolchain.
- Getting Started
- Usage
- Supported features
- Configuration (overview)
- How code is generated
- Large-scale testing and stress suite
- Multiplexed messages
- Value tables (VAL_)
- Output layout and naming
- Including generated files in firmware
- Build system examples
- Platforms, compilers, and test environments
- Runtime usage examples
- PhysType details and numeric precision
- Dispatch modes and registry
- Endianness and bit utilities
- Project Structure
- Performance benchmarks
- Troubleshooting
- Limitations
- License, third-party, and AI provenance
- .NET SDK 8.0 or later
- gcc or clang (optional: for validating generated C code; unrelated to your target firmware toolchain)
- make (optional: for validating generated C code; unrelated to your target firmware toolchain)
-
Generate C code from a DBC file:
dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen
This command will parse
examples/sample.dbcand generate C header and source files into thegen/directory. -
Build the generated C code:
make -C gen build
This command compiles the C code generated in the
gen/directory. -
Run tests for the generated C code:
make -C gen testThis command executes the tests for the generated C code.
- Endianness: Little-Endian (Intel-style) and Big-Endian (Motorola-style), supporting both Motorola MSB and LSB start-bit conventions
- Multiplexing: one switch (M) and branch signals (m) with valid bitmask and mux_active enum
- Value tables: parse VAL_ and generate per-signal enums and to_string helpers
- Configurable scaling math: phys_type float or fixed with phys_mode selection
- Range checking: optional min/max checks in encode/decode
- Dispatch modes: binary_search or direct_map registry
Optional file to control code generation behavior:
- phys_type: "float" | "fixed"
- float: physical-value math using floating point intermediates
- fixed: enable integer fast path when factor is a power of 10 (10^-n) and offset is integral
- phys_mode: "double" | "float" | "fixed_double" | "fixed_float"
- double: when phys_type=float, use double intermediates for math (default)
- float: when phys_type=float, use float intermediates for math
- fixed_double: when phys_type=fixed, use double as fallback when fast path not applicable (default)
- fixed_float: when phys_type=fixed, use float as fallback when fast path not applicable
- Defaults (backward compatible):
- If phys_type is omitted or "float" → phys_mode defaults to "double"
- If phys_type is "fixed" → phys_mode defaults to "fixed_double"
- range_check: true | false
- Enforce min/max bounds during encode/decode (rejects out-of-range)
- dispatch: "binary_search" | "direct_map"
- Registry dispatch strategy for id → decode function
- motorola_start_bit: "msb" | "lsb"
- Motorola big-endian start-bit convention used for codegen normalization
- msb: treat DBC start bit as MSB-based sawtooth (default, common in many tools)
- lsb: treat DBC start bit as LSB-based, generator converts to MSB sawtooth internally
- crc_counter_check: true | false
- Reserved for future; hooks to validate CRC/counter signals (deferred)
Examples:
# examples/config_range_check.yaml
phys_type: float
phys_mode: double # default when omitted
range_check: true
dispatch: direct_map
crc_counter_check: false# examples/config_fixed.yaml (snake_case preferred)
phys_type: fixed
phys_mode: fixed_double # default fallback; uses integer fast path when applicable
range_check: false
dispatch: direct_map
crc_counter_check: falseNote: Config keys use snake_case by default. For compatibility, PascalCase aliases are accepted for the same keys (e.g., PhysType ↔ phys_type). Matching is alias-based, not fully case-insensitive.
# Single-precision FPU MCU (minimize double ops in fallback)
phys_type: fixed
phys_mode: fixed_float
range_check: true
dispatch: binary_search
crc_counter_check: false# Prefix common generated files to avoid name collisions
# yields: gen/include/sc_registry.h, gen/src/sc_registry.c, etc.
file_prefix: sc_You can override some config fields from the command line:
--prefix <str>: overridesfile_prefixfor generated common files.--emit-main <true|false>: controls copyingexamples/main.ctogen/src/main.c.
Examples
# Use prefix foo_ and skip copying main.c
dotnet run --project src/Generator -- \
--dbc examples/sample.dbc \
--out gen \
--config examples/config.yaml \
--prefix foo_ \
--emit-main falseThe CLI provides short flags and an optional harness mode that creates/updates an adaptive Makefile and copies a sample main.c for quick local builds.
-d, --dbc <path>: input DBC (required)-o, --out <dir>: output directory (required)-c, --config <path>: optional YAML config-t, --harness: write/upgradegen/Makefileand ensuregen/src/main.cexists; idempotent-v, --version: print version-h, --help: usage
Examples
# Minimal codegen only
dotnet run --project src/Signal.CANdy.CLI -- -d examples/sample.dbc -o gen
# With harness (writes Makefile + copies main.c), then build
dotnet run --project src/Signal.CANdy.CLI -- -d examples/sample.dbc -o gen -t
make -C gen build# with config
dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen --config examples/config_range_check.yaml
make -C gen build
./gen/build/test_runner test_range_checkMotorola LSB start-bit example
# generate with LSB convention (Motorola BE)
dotnet run --project src/Generator -- \
--dbc examples/motorola_lsb_suite.dbc \
--out gen \
--config examples/config_motorola_lsb.yaml
make -C gen build
./gen/build/test_runner test_moto_lsb_basic
./gen/build/test_runner test_moto_lsb_roundtrip- Basic generation
# Binary search dispatch (default)
dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen- With a config file
# Choose phys type, dispatch strategy, range checks, etc.
dotnet run --project src/Generator -- --dbc <your>.dbc --out gen --config examples/config.yaml- Validate generated C builds
make -C gen buildNotes
- Codegen writes files under
gen/includeandgen/src. - A sample test runner
gen/src/main.cis provided when using the CLI with-t/--harness(it copiesexamples/main.c). Do not ship or compile this in firmware; it exists only for local testing.
To validate at scale, this repo includes a stress test harness that repeatedly decodes/encodes frames and optionally benchmarks registry dispatch:
-
One-off run after codegen/build:
make -C gen build ./gen/build/test_runner test_stress_suite
-
Bulk run across many DBC files (PowerShell):
pwsh ./scripts/bulk_stress.ps1 # Options # pwsh ./scripts/bulk_stress.ps1 -Config examples/config_directmap_fixed.yaml -OutDir gen
Guidance for expanding the corpus:
- Place permissively licensed public DBC files under
external_test/. - See
scripts/fetch_dbcs.mdfor tips and sources. Respect licenses; do not commit proprietary files. - The stress runner tolerates decode failures for random payloads and focuses on stability/throughput and decoder robustness.
Related docs
- Test overview and outcomes: see
TEST_SUMMARY.md - How to find public DBCs:
scripts/fetch_dbcs.md
The generator supports DBC multiplexing (one switch M, and branch signals tagged m<k>):
- Decode: the switch is decoded first; only signals in the active branch are decoded. The struct exposes:
uint32_t validbitmask macros per field (e.g.,MSG_VALID_SIGNAL) to know which signals were decoded<Msg>_mux_e mux_activeenum with known branch values (e.g.,MSG_MUX_0,MSG_MUX_1, ...)
- Encode: only signals belonging to the branch selected by the switch value are encoded.
Example
dotnet run --project src/Generator -- --dbc examples/multiplex_suite.dbc --out gen --config examples/config.yaml
make -C gen build
./gen/build/test_runner test_multiplex_roundtripNotes
- Branch selection uses the raw integer value of the switch signal (typical DBC semantics).
- Base (non-multiplexed) signals are always decoded/encoded.
- Valid bitmask width: current implementations use a 32-bit
validfield. Extremely large messages with >32 branch/base signals may require widening (e.g., to 64-bit) or an array. This is called out in Limitations; auto-widening is on the roadmap.
Using valid and mux_active
#include "mux_msg.h"
void handle_mux(const uint8_t data[8]) {
MUX_MSG_t m = {0};
if (MUX_MSG_decode(&m, data, 8)) {
if (m.mux_active == MUX_MSG_MUX_1) {
if (m.valid & MUX_MSG_VALID_SIG_M1) {
// use m.Sig_m1
}
} else if (m.mux_active == MUX_MSG_MUX_2) {
if (m.valid & MUX_MSG_VALID_SIG_M2) {
// use m.Sig_m2
}
}
if (m.valid & MUX_MSG_VALID_BASE_8) {
// base signal always available when decoded
}
}
}Signals with VAL_ mappings get C enums and to_string helpers:
- Header emits:
typedef enum { <MSG>_<SIG>_<NAME> = <value>, ... } <Msg>_<Sig>_e; - Source emits:
const char* <Msg>_<Sig>_to_string(int v);returning the label or"UNKNOWN"if not mapped.
Example
dotnet run --project src/Generator -- --dbc examples/value_table.dbc --out gen --config examples/config_directmap_fixed.yaml
make -C gen build
./gen/build/test_runner test_value_tableUsing enums and to_string
#include "vt_msg.h"
void log_mode(int v) {
const char* label = VT_MSG_Mode_to_string(v);
// prints OFF/ON/AUTO or UNKNOWN
}
void compare_state(int v) {
if (v == VT_MSG_STATE_RUN) {
// matched via generated enum value
}
}- gen/include/
- utils.h or utils.h, registry.h or registry.h (prefix configurable via config: file_prefix)
- .h per message (snake_case filename)
- gen/src/
- utils.c or utils.c, registry.c or registry.c (prefix configurable)
- .c per message (snake_case filename)
- main.c (test runner; exclude in firmware builds)
Message API naming convention
- Type:
<MessageName>_t(e.g.,MESSAGE_1_t,C2_MSG0280A1_BMS2VCU_Sts1_t) - Functions:
bool <MessageName>_decode(<MessageName>_t* out, const uint8_t data[8], uint8_t dlc);bool <MessageName>_encode(uint8_t data[8], uint8_t* out_dlc, const <MessageName>_t* in);
- Registry (dispatch):
bool decode_message(uint32_t can_id, const uint8_t data[8], uint8_t dlc, void* out_msg_struct);- When
can_idmatches a known message, this fills the struct of the corresponding type pointed to byout_msg_structand returns true.
- When
Type-safety note (important)
- The registry API takes a
void*. Passing the wrong struct type is undefined behavior. Prefer per-message calls when you know the type, or guard with a switch on ID:
switch (can_id) {
case MESSAGE_1_ID: {
MESSAGE_1_t m = {0};
if (MESSAGE_1_decode(&m, data, dlc)) { /* use m */ }
break;
}
/* other IDs */
}Behavior reference
- DLC semantics
- encode: sets
*out_dlcto the minimal required bytes for that message. - decode: returns false if
dlc< required; extra bytes are ignored. - Planned: a config knob to select encode DLC policy (e.g.,
encode_dlc_mode: minimal | fixed_8).
- encode: sets
- Range checks and atomicity
- When enabled, out-of-range causes
false. Operations are non-atomic by design: on failure, the output buffer/struct may be partially updated; callers must discard outputs whenfalse. - Planned: optional atomic mode (all-or-nothing) if demand is strong.
- When enabled, out-of-range causes
- Overflow/truncation policy
- Encode raw intermediate uses
int64_t; decode usesuint64_tplus sign-fix for signed. - Without
range_check, values exceeding bit width are masked/truncated to fit. - With
range_check, out-of-range returnsfalsebefore committing that signal. - Planned: a saturate-on-overflow option.
- Encode raw intermediate uses
You have two integration options: direct per-message calls or registry-based dispatch.
-
Direct per-message
#include "<message>.h"- Call
<Message>_decode(...)/<Message>_encode(...)when you already know the message type.
-
Registry-based dispatch
#include "sc_registry.h"(or your<prefix>registry.h)- Call
decode_message(can_id, data, dlc, &your_msg_struct)to route by CAN ID at runtime.
Minimal GCC/Make example
# Add include path
o_cflags += -I$(PROJECT_ROOT)/gen/include
# Compile all generated sources except the test runner
GEN_SRC := $(wildcard $(PROJECT_ROOT)/gen/src/*.c)
GEN_SRC := $(filter-out $(PROJECT_ROOT)/gen/src/main.c,$(GEN_SRC))
objgen := $(GEN_SRC:.c=.o)
$(objgen): CFLAGS += -I$(PROJECT_ROOT)/gen/include -std=c99 -Wall -Wextra
# Link your firmware with $(objgen)
firmware.elf: $(objgen) $(your_other_objects)
$(CC) $(LDFLAGS) -o $@ $^CMake example
file(GLOB GEN_SRC "${CMAKE_SOURCE_DIR}/gen/src/*.c")
list(REMOVE_ITEM GEN_SRC "${CMAKE_SOURCE_DIR}/gen/src/main.c")
add_library(dbccodegen STATIC ${GEN_SRC})
target_include_directories(dbccodegen PUBLIC ${CMAKE_SOURCE_DIR}/gen/include)
add_executable(app main.c)
target_link_libraries(app PRIVATE dbccodegen)Vendor IDEs
- Add
gen/includeto include paths. - Add all
gen/src/*.cexceptgen/src/main.cto your project.
Configurable prefix
- Common files use a prefix (default
sc_), yieldingsc_utils.{h,c}andsc_registry.{h,c}. - Change it via
file_prefixin your YAML config if needed.
All generated headers include extern "C" guards for seamless C++ integration:
// Your C++ firmware can include generated headers directly
#include "gen/include/sc_utils.h" // prefixed header
#include "gen/include/utils.h" // compatibility shim
#include "gen/include/MESSAGE_1.h" // message-specific header
extern "C" {
// C++ code can call generated C functions directly
MESSAGE_1_t msg = {0};
bool success = MESSAGE_1_decode(&msg, can_data, dlc);
}The generated code remains pure C99, ensuring compatibility with both C and C++ projects without requiring changes to build systems or toolchains.
When you run the CLI with -t/--harness, it creates or upgrades gen/Makefile:
- Discovers
src/*.cdynamically so it adapts to any generated filenames or prefixes. - Avoids duplicate common sources (drops legacy unprefixed
utils.c/registry.cwhen prefixed variants exist). - Supports optional
../examples/stress_test.cwith-DHAVE_STRESS. - Toolchain knobs:
CC ?= gcc,CFLAGS ?= -Wall -Wextra -std=c99,EXTRA_CFLAGS ?=,LDLIBS ?= -lm. - Idempotent and safe: if a non-harness Makefile exists, it is backed up to
Makefile.bakbefore upgrading.
Tested combos
- Local: macOS (Apple Silicon) + clang + GNU Make
- CI: Linux (GitHub Actions Ubuntu) + gcc + GNU Make
- Windows: not yet tested
Windows notes (early guidance)
- Prefer CMake to integrate generated C on Windows.
- Toolchains: MSVC, LLVM clang-cl, or MinGW-w64 gcc should work in principle.
- Set C standard and include path; exclude the test runner:
- CMake: add
target_compile_features(dbccodegen PUBLIC c_std_99)target_include_directories(dbccodegen PUBLIC ${CMAKE_SOURCE_DIR}/gen/include)- Remove
gen/src/main.cfrom sources.
- CMake: add
- Linking: MSVC doesn’t need
-lm; math functions (llround/llroundf) are in the CRT when including<math.h>. - If you hit MSVC-specific C99 quirks, consider LLVM clang-cl or MinGW as a fallback. Please open an issue with compiler/version details so we can add CI coverage.
MinGW quick tip
# Build with MinGW toolchain and extra flags via harness Makefile
make -C gen build CC=x86_64-w64-mingw32-gcc EXTRA_CFLAGS='-O2 -DNDEBUG'- Do NOT compile
gen/src/main.c(test runner only) - Add
gen/includeto include paths - Compile all
gen/src/*.cexceptmain.c - Include your prefix headers (e.g.,
#include "sc_registry.h") - Choose dispatch mode per product:
binary_search(sparse IDs) vsdirect_map(dense range) - If you need different common-file names, set
file_prefix(e.g.,file_prefix: fw_)
Decode known message
#include "message_1.h"
void on_frame(const uint8_t* data, size_t dlc) {
MESSAGE_1_t m = {0};
if (MESSAGE_1_decode(&m, data, dlc)) {
// use m.Signal_*
}
}Decode by ID (registry)
#include "sc_registry.h"
#include "message_1.h"
void on_can(uint32_t id, const uint8_t* data, size_t dlc) {
MESSAGE_1_t m = {0};
if (decode_message(id, data, dlc, &m)) {
// routed to MESSAGE_1 decoder if id matches
}
}Encode
#include "message_1.h"
void build_frame(uint8_t out[8], size_t* out_dlc) {
MESSAGE_1_t m = { .Signal_1 = 123.0, .Signal_2 = 45.6 };
if (MESSAGE_1_encode(out, out_dlc, &m)) {
// transmit out, *out_dlc
}
}phys_type determines how physical values are computed. Generated C struct fields remain 32-bit float for ABI stability; phys_mode controls the math precision/perf in encode/decode.
Summary
- float + phys_mode=double: use double intermediates (default)
- float + phys_mode=float: use float intermediates
- fixed + phys_mode=fixed_double: enable 10^-n fast path; fallback uses double (default)
- fixed + phys_mode=fixed_float: enable 10^-n fast path; fallback uses float
Details
-
phys_type: float
- Fields: float (32-bit)
- Decode
- double:
msg->sig = (float)((double)raw * factor + offset); - float:
msg->sig = (float)(((float)raw * (float)factor) + (float)offset);
- double:
- Encode
- double:
double tmp = ((double)phys - offset) / factor; raw = round(tmp); - float:
float tmp = ((float)phys - (float)offset) / (float)factor; raw = llroundf(tmp);
- double:
- Note: no 10^-n integer fast path in float mode.
-
phys_type: fixed
- Fields: still float (API consistency)
- Fast path (always when applicable)
- Conditions:
factor = 10^-nand integraloffset - Decode:
scale = 10^n;phys = (raw + offset*scale) / scale - Encode:
raw = llround((phys - offset) * scale)
- Conditions:
- Fallback (when fast path not applicable)
- fixed_double: same as float/double path
- fixed_float: same as float/float path (encode uses llroundf)
Selection guide (MCU/FPU)
- MCUs with single-precision FPU where double is costly:
- Mostly 10^-n scales → phys_type=fixed + phys_mode=fixed_float
- Mixed arbitrary scales → phys_type=float + phys_mode=float
- Precision-first (host/large MCU) → phys_type=float + phys_mode=double or phys_type=fixed + fixed_double
Compiler flags tips
-Wdouble-promotion(warn on implicit double promotion)-fsingle-precision-constant(treat FP literals as float; beware global impact)- You can customize templates to add
fsuffix on FP literals if desired
Range checking
- With
RangeCheck/range_check = true, encode/decode enforce min/max; out-of-range fails.
Measured on Apple Silicon (arm64), gcc -O2, representative scenarios:
- Simple message roundtrip: ~5–8M ops/sec
- Complex/multiplexed message roundtrip: ~1–4M ops/sec
- Registry dispatch throughput: ~7–72M ops/sec (depends on ID spread and strategy)
- Large external DBC (27 messages): stable end-to-end generation and build
Details can be reproduced via the stress suite and bulk runner in scripts/bulk_stress.ps1. Aggregated CSV lives under tmp/stress_reports/summary.csv when generated. A human-readable overview of tests and results is in TEST_SUMMARY.md.
- Harness: the generated C test runner executes tight encode/decode loops per case and measures wall-clock time with a monotonic timer; ops/sec = iterations / elapsed.
- Environment: Apple Silicon (arm64) with clang/gcc at
-O2unless noted. CPU scaling/thermals can affect results. - Repro:
- Build:
make -C gen build - Quick smoke:
./gen/build/test_runner test_stress_suite - Corpus run:
pwsh ./scripts/bulk_stress.ps1→ seetmp/stress_reports/summary.csv
- Build:
- Reporting: multiple trials per case; warm-up is discarded; we typically report the median.
- Portability: use your target compiler/flags/hardware to get realistic numbers for production.
- Compiler:
-O2or-O3, enable LTO if your toolchain supports it - Floating point: On single-precision FPU MCUs, prefer
phys_type: fixed+phys_mode: fixed_floatwhen 10^-n scales dominate - Dispatch:
direct_mapis O(1) but can cost memory if IDs are sparse;binary_searchis O(log N) and compact - Link-time: Build generated sources as a static library to improve incremental builds
- Headers: Keep
gen/includefirst in include paths to avoid name collisions
-
fatal error: 'message_1.h' file not found
- Ensure include path: add
-I./gen/include(see Make/CMake examples)
- Ensure include path: add
-
undefined reference to MESSAGE_1_decode
- Link generated objects from
gen/src(excludegen/src/main.c)
- Link generated objects from
-
Overlapping signals detected / DLC exceeds size
- The validator rejects conflicting signals and DLC overflows. Fix the DBC or split signals.
-
Unexpected float rounding differences
- Consider
phys_type: fixedto leverage the 10^-n fast path where applicable.
- Consider
- Automatic CRC/Counter validation is not yet implemented (config flag is reserved)
- Primarily targets 8-byte classic CAN frames; extended payloads require template adjustments
- Extremely large messages with >32 signals may require widening the
validbitmask
Per-message functions are always generated:
- _encode(...) and _decode(...) for each message.
The registry provides a convenience router: decode_message(can_id, data, dlc, out_struct).
- binary_search
- A sorted table of {id, (optional) extended-flag, function pointer}; looked up with binary search. O(log N). Small memory, good for sparse IDs.
- direct_map
- A direct mapping keyed by ID (implemented as a compact switch or table depending on range). O(1) lookup, but can cost memory/code size if IDs are sparse. Rule of thumb: prefer when IDs are dense in a small contiguous range (≈30–50%+ density); otherwise choose binary_search. Value tables
- For signals with VAL_ tables, the generator emits enums and
<Msg>_<Sig>_to_string(int)helpers by default. On memory-constrained targets, consider forking templates to omit string tables; a toggle may be added in a future release.
Comparison to nanopb
- Conceptually similar in spirit to nanopb-style runtime dispatch: you call a single entry point and it routes to the specific decoder based on an identifier.
- Differences: This registry is a hand-rolled CAN-ID router for C structs, not tied to protobuf descriptors or nanopb’s field/tag system.
CRC/Counter note
- The configuration flag exists, but automatic CRC/Counter validation is deferred. Handle at a higher layer or await a future generator option that maps CRC/counter signals from YAML.
- Little-Endian (Intel) and Big-Endian (Motorola) are supported. For Motorola, both MSB-sawtooth and LSB start-bit conventions are handled (configurable via
motorola_start_bit: msb|lsb). - Motorola BE numbering quick view (MSB sawtooth across 8 bytes):
- Byte0: [7][6][5][4][3][2][1][0]
- Byte1: [15][14][13][12][11][10][9][8]
- Byte2: [23][22][21][20][19][18][17][16]
- Byte3: [31][30][29][28][27][26][25][24]
- Byte4: [39][38][37][36][35][34][33][32]
- Byte5: [47][46][45][44][43][42][41][40]
- Byte6: [55][54][53][52][51][50][49][48]
- Byte7: [63][62][61][60][59][58][57][56]
- LSB start-bit inputs are internally converted to MSB sawtooth for codegen normalization.
- Generated
<prefix>utils.{h,c}(defaultsc_utils.{h,c}) provideget_bits_le/beandset_bits_le/beused by message codecs.
src/Generator: F# source code for the code generator.templates: Scriban templates for C code generation.examples: Sample DBC file, configuration, and a main C file for testing.tests/Generator.Tests: F# unit tests for the generator.infra: CI/CD configurations (e.g., GitHub Actions).gen: Output directory for generated C code (ignored by Git).
- License: This repository is licensed under the MIT License (see LICENSE).
- Third-party: Public DBCs used for testing are not bundled; see THIRD_PARTY_NOTICES.md and the policy in
external_test/README.md. - Confidentiality: Do not commit proprietary or internal DBCs. The
external_test/folder is git-ignored by default except for placeholders. - AI assistance: Portions of this repository’s documentation and code were authored with the assistance of GitHub Copilot and other LLMs; human maintainers reviewed and accepted changes.
Pre-release checklist
- Audit repository for accidental secrets or confidential data (including DBCs under external_test/)
- Verify LICENSE and THIRD_PARTY_NOTICES.md are accurate
- Ensure README(EN/KR) are consistent and up to date
- Run validation: dotnet build/test, codegen + make -C gen build, and core tests