You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dvmhost/AGENTS.md

594 lines
29 KiB

# AGENTS.md
This file applies to the entire `dvmhost` repository unless a more specific
`AGENTS.md` exists below the file being edited.
## Project character and priorities
`dvmhost` is a C/C++ digital-voice modem, host, networking, and firmware project.
Much of the code implements bit-exact DMR, P25, NXDN, modem, and proprietary wire
protocols. Correct framing, byte order, field widths, timer behavior, and backward
compatibility are more important than stylistic modernization.
When changing code:
1. Preserve observable protocol and configuration behavior unless the task
explicitly changes it.
2. Match the style of the surrounding subsystem. This repository has a stable
house style, but some firmware and imported code have their own conventions.
3. Keep patches focused. Do not combine a functional change with broad cleanup,
renaming, or reformatting.
4. Add or update focused tests for protocol, parser, state-machine, and boundary
changes.
5. Do not claim standards compliance from an encode/decode round trip alone.
Prefer independent known-answer vectors or captured, documented wire values.
## Repository layout
- `src/common`: shared codecs, protocol primitives, networking, crypto, and
utility code.
- `src/host`: the modem host and DMR/P25/NXDN RF processing.
- `src/fne`: fixed-network equipment, call handlers, packet data, and OTAR.
- `src/fw/modem`, `src/fw/hotspot`, `src/fw/v24`: embedded firmware and platform
code. Treat timing, memory, and hardware constraints as part of the interface.
- `src/bridge`, `src/patch`, `src/remote`, and the TUI applications: separate
executables that reuse the common library.
- `tests`: Catch2 tests, organized by protocol or subsystem.
- `configs`: annotated example configurations and YAML schemas.
- `docs`: technical notes and interface documentation.
Do not edit build outputs or imported artifacts merely to make them match local
style. In particular, avoid formatting `build/`, platform SDK/CMSIS trees,
third-party single-header libraries, generated files, binaries, and submodules
unless the task specifically targets them.
## C and C++ formatting
There is no repository-wide clang-format file. Follow nearby code and these
observed defaults.
The root `.clang-format` is a conservative aid for newly written or selected
code, not authorization to format the whole tree. Clang-format cannot represent
all existing local choices (including mixed anonymous-namespace, `else`, test
macro, and continuation styles). Review every formatting diff, format only the
changed range when practical, and use `// clang-format off` / `on` around
bit-layout diagrams, alignment-sensitive tables, or macro structures it cannot
preserve.
### Whitespace and layout
- Use four spaces per indentation level. Do not use tabs in project-owned source.
- Keep one statement per line and leave a blank line between logical stages.
- Opening braces for functions and namespaces go on the next line:
```cpp
namespace p25
{
bool Frame::decode(const uint8_t* data)
{
// ...
}
}
```
- Class declarations and control statements keep the opening brace on the same
line:
```cpp
class Frame {
public:
void reset();
};
if (ready) {
process();
}
else {
reset();
}
```
- A single short controlled statement may omit braces, matching surrounding code.
Use braces when a branch is multiline, nested, or likely to gain more work.
- Both `} else {` and a newline before `else` occur in maintained code. Preserve
the convention of the file or block being changed. `catch` follows the same
local-style rule.
- Keep `switch` labels aligned with the `switch`; indent statements beneath each
label. For a scoped case, put the case-local `{` on the following line.
- Wrap long calls and conditions by aligning continuation arguments under the
first argument or indenting one additional level. Favor readability over a
rigid column limit; do not reflow unrelated lines.
- Remove trailing whitespace. End text files with a newline.
### Types, literals, and casts
- Use fixed-width integer types (`uint8_t`, `uint16_t`, `uint32_t`, and so on) for
protocol fields, serialized values, counters with defined widths, and firmware
interfaces.
- Apply unsigned suffixes to integer literals used with unsigned fields or sizes:
`0U`, `1U`, `0xFFU`.
- Preserve the project's explicit C-style casts where they are part of existing
low-level code. In new higher-level C++, prefer `static_cast` when it improves
safety without making a neighboring block inconsistent.
- Use `nullptr`, not `NULL`, in C++ project code.
- Use `const` aggressively for inputs and values that do not change.
- Keep byte offsets and masks explicit. Use the existing `GET_UINT*`, `SET_UINT*`,
bit helpers, protocol constants, and length constants instead of open-coded
endian manipulation.
- Check lengths before indexing or copying. Validate multiplication/addition for
overflow where untrusted lengths are involved.
### Naming
- Classes and structs use `PascalCase`.
- Functions and local variables generally use `camelCase`.
- Private/protected data members use the `m_` prefix.
- Static/shared members commonly use `s_`; follow the owning class.
- Constants, enumerators, macros, protocol fields, and wire-message identifiers
generally use `UPPER_SNAKE_CASE` or the established protocol spelling.
- Boolean names should describe a positive condition when practical (`enabled`,
`authenticated`, `hasMessageNumber`). Preserve public/configuration names for
compatibility even when their spelling is imperfect.
- Protocol terminology is part of the API. Keep established abbreviations such as
LLID, RSI, KMM, CSBK, TSBK, TGID, NAC, FNE, DLD, and DLI consistent.
### Includes and namespaces
- Keep the SPDX/license header first, then the matching project header for a
`.cpp`, then project includes, `using namespace` declarations where the file
uses them, and standard-library/system includes as established locally.
- Use quoted includes for project headers and angle brackets for standard/system
headers.
- Do not mechanically reorder includes across an entire file. Add a new include
to the appropriate existing group.
- Headers must be self-sufficient. Keep include guards in the existing
`#if !defined(...)` / `#define ...` form and retain the trailing guard comment.
- Nested namespaces use the expanded form already present in the codebase; close
them with comments such as `} // namespace kmm`.
- Avoid introducing new global `using namespace` directives in headers.
### Classes and ownership
- Order class sections to match neighboring files. The common pattern is public
API, public property macros/data when required by legacy design, then protected
or private implementation state.
- Within each access section, declare nested enums/structures/classes and type
aliases before the data members that use them, then declare data members, and
place member-function declarations last. In particular, every private data
member must appear before every private function declaration. Do not interleave
private helper functions among private state.
- Keep a nested structure next to the logically related member group: declare the
structure immediately before the first member whose type or state it defines,
then keep those member variables together. Apply the same rule to nested enums
and aliases.
- Fully document nested enums, structures, and classes with Doxygen. Nested
structures require `@brief` and `@ingroup`, and every field requires an
accurate Doxygen comment. Nested enum values require individual `//!< ...`
descriptions or equivalent adjacent Doxygen comments.
- Use `explicit` for single-argument constructors unless implicit conversion is
intentional.
- Use `override` for overridden virtual methods.
- Prefer RAII containers and smart pointers in new host/FNE code. Do not introduce
heap ownership without a clear destructor/reset path.
- Firmware code may intentionally avoid dynamic allocation or newer library
facilities. Match its constraints and toolchain.
- Preserve `DECLARE_PROPERTY`, `DECLARE_COPY`, and related project macros when
extending classes that already use them.
- Initialize members in declaration order. Keep constructor initializer lists in
that same order.
- Copy/move behavior must be explicit for owning or synchronization-bearing
classes. Follow existing deletion/copy-helper patterns.
## Source structure
- Keep declarations in the matching `.h` and definitions in `.cpp` unless the
code is intentionally inline/template-only.
- Organize project-owned headers in the established order when the sections are
applicable: file Doxygen, include guard, project includes, external/standard
includes, namespace-level class prototypes, namespace opening, constants,
namespace-local class prototypes, structure declarations, class declarations,
and inline/template implementation. Do not add empty sections merely to satisfy
this list.
- Collect forward declarations under the standard prototype banner instead of
scattering them between includes or declarations:
```cpp
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
```
Put a prototype in the same namespace as its definition and retain the
appropriate export macro when the project pattern requires it. Prefer a forward
declaration over an additional heavy include only when the header needs an
incomplete type (normally a pointer/reference or friendship). Include the full
definition when inheritance, by-value storage, inline member access, templates,
or `sizeof` requires a complete type.
- A new protocol message normally belongs in the corresponding common codec
directory, is routed through its factory, is integrated at the host/FNE layer,
is added to the relevant CMake source list or globbed location, and receives a
focused test.
- Separate parsing/validation, state transition, and output generation when
practical. Never mutate replay, grant, registration, or transaction state until
the input has passed the checks required for that mutation.
- Keep wire codecs bounded. Decode APIs that process untrusted or variable-size
input should receive or otherwise prove the available length before accessing
optional fields.
- Treat native modem, hotspot, V.24, and network transports as distinct
capabilities. Do not infer one transport's maximum frame/block size from a
common logical maximum.
- Keep duplicated firmware changes synchronized between `src/fw/modem` and
`src/fw/hotspot` when the implementations are intentionally parallel. Verify
both rather than assuming they are identical.
- Maintain backward compatibility in network packets, configuration keys, REST
fields, and modem serial framing unless a versioned migration is part of the
task.
## Comments and documentation
Comments should explain protocol intent, invariants, units, ownership, hardware
constraints, compatibility requirements, or why a non-obvious choice is safe.
Do not narrate obvious syntax.
### File and class API documentation
- Preserve the SPDX line and existing project license/copyright block. Do not
remove or rewrite prior copyright notices. Add a copyright entry only when
project policy and the actual authorship call for it; do not guess an author.
- Every class or structure declaration in a project-owned header must be fully
documented with Doxygen, regardless of access level. This applies to the type
itself and every declared constructor, destructor, operator, public method,
protected method, private method, callback, and helper. A function being
private or obvious does not exempt it from documentation.
- Every full, non-nested class and structure declaration/definition must have its
own declaration banner immediately before its Doxygen block. Forward
declarations are the exception: collect those under `Class Prototypes` as
described above rather than giving each prototype a declaration banner. Nested
structures do not receive a `Structure Declaration` banner; document them with
Doxygen directly at their location inside the owning class. Use the exact
established forms for non-nested declarations:
```cpp
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements the core traffic networking logic.
* @ingroup fne_network
*/
class TrafficNetwork {
// ...
};
```
```cpp
// ---------------------------------------------------------------------------
// Structure Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents a queued network request.
* @ingroup fne_network
*/
struct NetRequest {
// ...
};
```
Repeat the appropriate banner for each separately declared non-nested class or
structure; do not place multiple unrelated declarations under one declaration
banner.
- Every class and structure Doxygen block must contain both an accurate `@brief`
and the correct `@ingroup`. Choose the narrowest existing Doxygen group that
owns the type; define a new group only when the subsystem genuinely needs one.
- Each function declaration must have an accurate `@brief`. Add one `@param` for
every named parameter and an `@returns` entry for every non-`void` return value.
Constructors, destructors, and `void` functions do not need `@returns`.
Parameter-free functions do not need `@param`. Document template parameters
with `@tparam` and thrown exceptions with `@throws` when applicable.
- Public and private header declarations use the same complete form:
```cpp
/**
* @brief Decodes a KMM frame.
* @param[in] data Encoded frame data.
* @param length Available data length in bytes.
* @returns bool True if decoding succeeded; otherwise, false.
*/
```
- Use `@param[in]`, `@param[out]`, or `@param[in,out]` where direction matters.
Document units and ownership for lengths, timers, buffers, and returned memory.
- Keep the Doxygen adjacent to the declaration it describes. Do not use one block
to document multiple overloads unless the overloads are genuinely identical
and Doxygen explicitly associates the block with all of them.
- Property macros from `ClassProperties.h` are documented by a Doxygen block at
each macro invocation describing the logical property. The macro's own generic
documentation does not replace class-specific property documentation.
- Keep `@file` and `@ingroup` tags consistent with neighboring headers. Several
headers document the paired `.cpp`; preserve that local convention.
- Implementation definitions commonly use a short block comment immediately
above the definition, for example `/* Decode a KMM frame. */`. Keep this style
when adding methods to such a file.
### Constants and Doxygen groups
- Place namespace/file constants under the standard constants banner:
```cpp
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
```
- Constants that form part of a documented subsystem API should be enclosed in
that subsystem's Doxygen group, following `P25Defines.h`. Do not add `@name`
subgroups inside the `@addtogroup` block; they make the generated and source
documentation unnecessarily noisy:
```cpp
/**
* @addtogroup p25
* @{
*/
/** @brief Encoded frame length in bytes. */
const uint32_t FRAME_LENGTH_BYTES = 12U;
/** @} */
```
- Use `@addtogroup <existing-group>` when constants, enums, or small namespaces
belong to an existing documented module and should appear together in its API
reference. Always close every opened group with `/** @} */` at the correct
scope.
- Organize a large constants collection into logical contiguous blocks using
concise ordinary/Doxygen comments where useful, but do not use the Doxygen
`@name` parameter. Use one correctly scoped `@addtogroup` block and close it
once with `/** @} */`.
- Give individual constants an accurate `@brief` where their meaning is not
already unambiguous from a documented named group and adjacent normative
comment. Include units, encoded width, valid range, and standard section where
useful.
- Small translation-unit-private implementation constants do not require an
`@addtogroup` wrapper, but they still belong under the constants banner and
should be commented when their purpose or units are not obvious.
- Keep constants in the narrowest appropriate namespace/scope. Do not introduce
macros when a typed `const`/`constexpr` value works, but preserve macros that
are part of the existing public or preprocessor interface.
## Required common utility macros
Before open-coding bit access, byte-order conversion, class properties, copying,
or temporary runtime arrays, check `src/common/BitManipulation.h`,
`src/common/ClassProperties.h`, and `src/common/VariableLengthArray.h`. Use their
established macros where the operation matches the macro contract. Do not create
a second spelling for the same project idiom.
### `BitManipulation.h`
- Use `WRITE_BIT(buffer, bitOffset, value)` and `READ_BIT(buffer, bitOffset)` for
MSB-first bit addressing in protocol byte arrays. Do not duplicate the
`BIT_MASK_TABLE` indexing expression.
- `READ_BIT` yields a masked nonzero value, not a normalized `bool`. Compare it to
zero or explicitly convert it when a true `bool` is required.
- Use `SET_UINT16`, `SET_UINT24`, and `SET_UINT32` to serialize big-endian integer
fields, and the matching `GET_UINT16`, `GET_UINT24`, and `GET_UINT32` macros to
decode them. Do not hand-write shifts for formats these macros cover.
- Validate the complete buffer range before invoking any bit or integer macro.
The macros do not perform bounds checks.
- Pass side-effect-free arguments. Macro parameters can be evaluated more than
once; do not pass `index++`, function calls with effects, or expressions that
mutate the source buffer.
- The `SET_UINT*` macros expand to multiple statements. Use them as standalone
statements inside an already braced scope; never as the unbraced body of an
`if`, `else`, or loop.
- Ensure `common/Defines.h` is included before `BitManipulation.h`, normally via
the subsystem's standard `Defines.h` include order.
### `ClassProperties.h`
- Use `DECLARE_PROPERTY`, `DECLARE_RO_PROPERTY`, and their `PROTECTED` or `PLAIN`
variants for simple value properties in classes that already follow the
property-macro pattern. Do not manually add an `m_` field plus trivial getter
and setter beside macro-based properties.
- Select the narrowest API: prefer read-only over read/write; choose private
storage unless derived classes require protected access; use a `PLAIN` variant
only when the surrounding API intentionally omits `get`/`set` prefixes.
- Property macros inject access specifiers and storage. Place them last in the
applicable `public` property section, as required by `ClassProperties.h`, and
inspect the expanded access flow before declaring anything after them.
- Add a Doxygen block immediately above every property macro invocation. Describe
the logical value, units, valid range, and special values where applicable.
- Initialize the macro-generated `m_<variableName>` member in every constructor,
in declaration order.
- Use property macros only for simple return-by-value/set-by-value types for which
their generated signatures are appropriate. Use explicit, fully documented
methods when validation, locking, ownership transfer, reference returns, side
effects, or nontrivial invariants are required.
- Use `DECLARE_COPY(Type)` or `DECLARE_PROTECTED_COPY(Type)` in classes following
the project copy-helper pattern. Implement the required `copy(const Type& data)`
method and copy every relevant base/member field deliberately. Do not combine
these macros with an independently declared assignment operator.
### `VariableLengthArray.h`
- Use `UInt8Array`, `CharArray`, and `ShortArray` for owned runtime-sized arrays
that must cross a function boundary or otherwise retain ownership. Prefer these
aliases to raw `new[]`/`delete[]`.
- Use `DECLARE_UINT8_ARRAY(name, length)` or `DECLARE_CHAR_ARRAY(name, length)`
for a zero-initialized, scope-local runtime buffer when existing APIs require a
raw pointer. Use `DECLARE_SHORT_ARRAY(name, length)` for the corresponding
short-array ownership pattern subject to its zeroing caveat below. Do not use
compiler-specific C/C++ variable-length stack arrays.
- Treat the exposed raw pointer as a non-owning view. Do not `delete[]` it, return
it, store it beyond the macro's scope, or move the hidden owner independently.
- Validate the requested length and any size arithmetic before allocation. Pass a
stable, side-effect-free length expression because the macro uses it more than
once.
- Lengths passed to `DECLARE_UINT8_ARRAY` and `DECLARE_CHAR_ARRAY` are byte/element
counts. `DECLARE_SHORT_ARRAY` currently allocates `len` shorts but its zeroing
expression clears `len` bytes; do not rely on full-array zero initialization
without first correcting or explicitly accounting for that implementation.
- Prefer `std::array` for compile-time fixed sizes and `std::vector` when resizing,
iteration, or value semantics are required. The project macros are for their
established ownership/raw-pointer bridge, not a replacement for every
container.
### Section banners and inline comments
- Major implementation sections use the existing banner form:
```cpp
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
```
- New inline comments are sentence fragments in lowercase when they describe the
next operation, and complete sentences with punctuation when they explain a
decision. Match the surrounding block.
- Put comments above the code they explain. Use end-of-line comments only for
short, local facts.
- Use actionable tagged debt comments, preferably with a subsystem identifier:
`TODO(P25P2-FW): ...`. Explain the missing behavior and the condition for
removal. Do not add vague `TODO`, `FIXME`, joke, profanity, or author-addressed
comments.
- Existing informal or blunt legacy comments are not the style for new code.
Do not broaden a task solely to rewrite them.
- Cite the relevant standard section/table when implementing a subtle normative
rule, but do not paste copyrighted standards text into source comments.
## Logging and errors
- Use the existing logging facilities and subsystem/category constants instead of
`printf`, `std::cout`, or ad hoc diagnostics.
- Match established message vocabulary and include identifiers needed to trace a
transaction: protocol, direction, slot/channel, source/destination ID, message
type, and reason/status as applicable.
- Never log encryption keys, temporary keys, passwords, preshared keys, full
sensitive KMM bodies, or other secret material. Packet dumps involving secrets
must be redacted or omitted even in debug mode.
- Validate first, log a concise reason, clean up partial state, and return the
established error form. Avoid logging the same failure at every layer.
- Preserve the distinction among malformed input, unsupported service, policy
denial, transient resource failure, and internal error.
## YAML and configuration files
Configuration compatibility and comments are user-facing interfaces.
### Mandatory AI/LLM notice
The example YAML files state that DVMProject does not support AI/LLM-generated or
AI/LLM-modified configurations. Before editing or generating a user configuration:
1. Tell the user that DVMProject support will not troubleshoot or validate an
AI-generated or AI-modified configuration.
2. Direct them to the official generator at `tools/dvmcfggen` (the public project
link is `https://github.com/DVMProject/dvmhost/tree/master/tools/dvmcfggen`).
3. Prefer the generator, schemas, and existing examples for creation/validation.
Do not remove or weaken the notice in example files.
### YAML style
- Preserve the indentation style of the file being changed. Main configuration
examples use four spaces for nested mappings; list-oriented files such as
`talkgroup_rules.example.yml` use their established two-space sequence nesting.
Never use tabs.
- Keep keys in the established section order. The loaders may tolerate arbitrary
order, but the annotated examples are maintained as human-readable reference
documents.
- Use lower camel case for configuration keys and preserve existing public key
names exactly.
- Use lowercase `true` and `false`. Use decimal integers unless a field's existing
contract/documentation uses another representation.
- Quote strings when they could be parsed as another YAML type, contain special
characters, are secrets/placeholders, or neighboring examples quote the field.
- Keep one space after `:` and remove trailing whitespace.
- Preserve blank lines and the `#` section-banner style used by the example.
- Place a concise explanatory comment immediately above each example key. State
units, valid ranges, dependencies, security implications, and restart behavior
where relevant.
- Align continuation comments using the local `# ...` convention. Wrap long
comments without changing their meaning.
- Update every relevant example, schema, loader, default, generator template, and
documentation location when adding or renaming a configuration option.
- Never silently change example IDs, ports, credentials, RF parameters, keys,
site relationships, or safety acknowledgments as part of formatting work.
- Parse or schema-validate changed YAML when tooling is available. Syntactic YAML
validity alone is not proof that a DVM configuration is operationally valid.
## CMake, scripts, and documentation
- Match existing CMake command casing and four-space continuation indentation.
Keep explicit closing conditions such as `endif (COMPILE_WIN32)` where the
surrounding file uses them.
- Add new project sources to the correct component list. Do not depend on an
existing build directory accidentally discovering a new file.
- Shell scripts should retain their current shell and portability assumptions.
Quote variables used as paths or user input, and avoid new Bash-only syntax in
scripts intended for `/bin/sh`.
- Python should follow the existing tool's local style; do not apply a repository-
wide formatter as part of an unrelated change.
- Technical documentation should use the project's established protocol names,
units, and field spelling. Update documentation when behavior, configuration,
packet formats, or compatibility changes.
## Testing and verification
Build from the repository's CMake build directory. Reuse an existing configured
build when possible:
```sh
cmake --build build -j2
```
The Catch2 test executable is normally `build/dvmtests` when working inside the
`dvmhost` repository. Run the smallest relevant tag set first, for example:
```sh
./build/dvmtests '[dmr]' --reporter compact
./build/dvmtests '[p25][kmm]' --reporter compact
./build/dvmtests '[p25][pdu]' --reporter compact
./build/dvmtests '[nxdn]' --reporter compact
```
The enclosing superproject may place the executable at
`../build/dvmhost/dvmtests`; use the path produced by the active build tree.
For a change:
- Build every affected target, including firmware variants when firmware changes.
- Run focused tests and then the broader protocol/subsystem suite when practical.
- Add boundary tests for zero, minimum, maximum, one-past-maximum, truncation,
duplicate, reordered, timeout, and retry cases as applicable.
- For wire codecs, test exact encoded bytes from an independent source in addition
to round trips.
- For concurrency/state changes, test duplicate delivery, stale completion,
teardown, restart/cleanup, and lock ordering where feasible.
- For YAML, parse the file and validate against its schema/generator if one exists.
- State clearly when hardware, RF, external equipment, privileged sockets, or a
restricted environment prevents complete verification.
Do not treat expected negative-test log messages as failures without checking the
test result. Conversely, a successful build is not sufficient verification for a
protocol or configuration change.
## Scope discipline for automated changes
- Inspect the relevant factory, codec, caller, transport, tests, configuration,
and documentation before changing a protocol feature.
- Preserve unrelated working-tree changes.
- Do not mass-format the repository or normalize legacy files unless explicitly
requested.
- Do not edit generated output to hide a generator/source problem; fix the source
and regenerate when the generator is available.
- Do not create standards, protocol constants, configuration options, or hardware
behavior from assumption. Locate the existing definition or authoritative local
specification material.
- If behavior is intentionally unsupported, fail explicitly and document the
supported profile rather than adding a permissive fallback.

Powered by TurnKey Linux.