requirements: enhanced with general implementation requirements

Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
This commit is contained in:
Nikos Mavrogiannopoulos
2026-06-17 05:35:02 +02:00
parent cfb0e0a2b2
commit bda207237a
3 changed files with 450 additions and 63 deletions
+35 -62
View File
@@ -306,16 +306,18 @@ Do not edit the generated `*.pb-c.c` / `*.pb-c.h` files by hand.
### Canonical Technology Choices
One tool for each job. These decisions are not open for per-feature debate — departing
from any of them requires a design-discussion issue and explicit maintainer approval.
One tool for each job. These decisions are requirements, not preferences — the
normative form with acceptance criteria is in
`doc/requirements/internal/general.md`. Departing from any of them requires a
design-discussion issue and explicit maintainer approval.
| Concern | Tool | Rule |
|---------|------|------|
| Memory management | talloc | Use `talloc_zero`, `talloc_strdup`, `talloc_array`, etc. everywhere. **Exception:** use `gnutls_malloc()` / `gnutls_free()` only for memory whose lifetime GnuTLS owns — never mix the two for the same allocation. Check every return before use. Error paths: `goto cleanup` with a single freeing label. |
| Cryptography | GnuTLS / nettle | No OpenSSL. Call GnuTLS through `src/tlslib.c` wrappers — not directly from worker or main code. |
| IPC serialization | protobuf-c | All cross-process messages are defined in `src/ipc.proto` and `src/ctl.proto`. Regenerate C bindings with `protoc-c` after any `.proto` edit; never hand-edit generated files. |
| Configuration parsing | inih (INI) | Flat `key = value` pairs; bracketed sub-sections for per-module options (`auth = pam[...]`). No new structured sub-formats (no JSON, YAML, or additional config files for things expressible as key=value). |
| Utility constructs | CCAN (`src/ccan/`) | Check here before writing a new helper or adding a dependency. CCAN provides hash tables, linked lists, string utilities, and more as copy-in modules with no extra build overhead. To bring in a new CCAN module, copy it from https://github.com/rustyrussell/ccan into `src/ccan/` and add it to the build. |
| Concern | Tool | Requirement |
|---------|------|-------------|
| Memory management | talloc | Use `talloc_zero`, `talloc_strdup`, `talloc_array`, etc. everywhere. Exception: `gnutls_malloc()`/`gnutls_free()` only for GnuTLS-owned memory — never mix the two for the same allocation. Error paths: `goto cleanup` with a single freeing label. See **REQ-GEN-TECH-001**. |
| Cryptography | GnuTLS / nettle | No OpenSSL. Call GnuTLS through `src/tlslib.c` wrappers — not directly from worker or main code. See **REQ-GEN-TECH-002**. |
| IPC serialization | protobuf-c | All cross-process messages are defined in `src/ipc.proto` and `src/ctl.proto`. Regenerate C bindings with `protoc-c` after any `.proto` edit; never hand-edit generated files. See **REQ-GEN-TECH-003**. |
| Configuration parsing | inih (INI) | Flat `key = value` pairs; bracketed sub-sections for per-module options (`auth = pam[...]`). No new structured sub-formats (no JSON, YAML, or additional config files for things expressible as key=value). See **REQ-GEN-TECH-004**. |
| New external dependency | requires approval | Check `src/ccan/` first; consider a trivial inline implementation; if neither suffices, open a design-discussion issue before adding. See **REQ-GEN-TECH-005**. |
### Design Principles
@@ -325,15 +327,11 @@ cross-module state, callbacks, and "utility" files are a design smell — if you
requires them, reconsider the module boundary first. When complexity must be hidden,
hide it inside a module with a clean header interface; do not scatter it.
**Resist dependency growth.** Before adding anything external: (1) check `src/ccan/`
first — it likely has what you need; (2) consider a trivial inline implementation over
a new dependency; (3) if a new external library is truly necessary, open a design
discussion issue. Every dependency is a build, packaging, and security-audit cost paid
forever.
**Resist dependency growth.** See **REQ-GEN-TECH-005** for the normative rule.
In short: check `src/ccan/` first, then consider a trivial inline implementation;
only then open a design-discussion issue for a new external library.
**Configuration stays in INI.** Do not introduce embedded structured formats for things
expressible as flat key=value pairs. Complexity in configuration is a cost paid by every
administrator forever.
**Configuration stays in INI.** See **REQ-GEN-TECH-004** for the normative rule.
**Explicit over implicit.** No auto-discovery, no runtime plugin loading, no silent
defaults that change behavior. Every configurable behavior must be expressible in
@@ -341,51 +339,27 @@ defaults that change behavior. Every configurable behavior must be expressible i
### Code Style
- C99 standard
- Linux kernel coding style: tabs, 8-space tab width, 80-column limit
- Format check: `clang-format --dry-run -Werror <file>` (run on all files under `src/` and `tests/`)
See **REQ-GEN-STYLE-001** and **REQ-GEN-STYLE-002** for the normative rules.
In brief:
- C99 standard; Linux kernel coding style (tabs, 8-space tab width, 80-column limit)
- `clang-format --dry-run -Werror` must pass on all files under `src/` and `tests/`
- Header guards: `#ifndef FILENAME_H` / `#define FILENAME_H` / `#endif /* FILENAME_H */`
- **Comments:** Prefer self-documenting code — meaningful names and short functions
that do one thing. Add a comment only when the *why* is non-obvious: a hidden
constraint, a protocol expectation, or a workaround. Do not comment what the
code does; well-named identifiers already do that.
### Preprocessor Conditionals
Deeply nested or long `#ifdef` chains obscure the actual code flow and make review
difficult. The rules:
- **`#ifdef` in a function body**: one level of nesting maximum, at most 5 lines per
branch. If the block is longer, extract it into a separately-defined function.
- **Stub pattern for optional features**: define a no-op (or error-returning) stub in
the header for the disabled case, so call sites need no `#ifdef` at all:
```c
#ifdef HAVE_SECCOMP
int worker_apply_seccomp_filter(worker_st *ws);
#else
static inline int worker_apply_seccomp_filter(worker_st *ws) { return 0; }
#endif
```
- **Significant feature code** (more than one function) belongs in its own `.c` file
included or excluded by the build system — not behind inline conditionals.
The existing `src/auth/` and `src/acct/` structure is the model to follow.
- Always annotate `#endif` with the condition it closes: `#endif /* HAVE_SECCOMP */`.
- `#ifdef` in function bodies: ≤ 1 nesting level, ≤ 5 lines per branch; optional
features use the stub pattern; `#endif` always annotated
- **Comments:** Prefer self-documenting code. Add a comment only when the *why* is
non-obvious: a hidden constraint, a protocol expectation, or a workaround. Do not
comment what the code does; well-named identifiers already do that.
### Testing New Functionality
Every new feature or bug fix is incomplete without tests:
See **REQ-GEN-TEST-001** for the normative rule. In brief:
- Write a **positive test** (correct behavior when the feature is exercised) and a
**negative test** (correct rejection of bad input or error conditions).
- For security-relevant code (auth, cookies, IPC validation) the negative test is the
more important of the two — write it first.
- For **bug fixes**, write the test that reproduces the bug and confirm it fails *before*
applying the fix. A test written after the fix cannot prove it is meaningful.
- **Tests must be self-diagnosing.** A failure must be explainable from the test output
alone — no silent exit-code-only failures that require local reproduction to understand.
Shell tests must print what they were testing and why it failed (e.g. `echo "FAIL: expected
X, got Y"`). C unit tests must print the failing condition and relevant values before
returning non-zero. Reviewers will reject tests whose failures are opaque.
- Write a **positive test** and a **negative test** for every feature or fix.
- For security-relevant code (auth, cookies, IPC validation) write the negative test first.
- For **bug fixes**, confirm the reproducing test fails before applying the fix.
- Tests must be **self-diagnosing** — failure explainable from output alone; shell tests
print `"FAIL: expected X, got Y"`; C unit tests print the failing condition and values.
- Register all new tests in `tests/meson.build`.
### Adding Configuration Options
@@ -405,11 +379,10 @@ See `src/auth/plain.c` or `src/auth/pam.c` for reference.
### Platform Portability
ocserv is a Linux service. BSD (FreeBSD, OpenBSD) compatibility is maintained on a
best-effort basis: patches should not gratuitously break BSD, but Linux-only features
are accepted. When adding Linux-specific code, use `#ifdef __linux__` so BSD builds
continue to compile. On BSD, the absence of procfs means configuration changes
require a server restart — document this if your change is affected by it.
See **REQ-GEN-COMPAT-001** for the normative rule. In brief: Linux is the primary
target; BSD is best-effort. Guard Linux-specific code with `#ifdef __linux__`. Note
in `doc/ocserv.8.md` any option whose behavior differs on BSD (e.g. no live reload
without procfs).
---
+5 -1
View File
@@ -35,6 +35,7 @@ the file.
| Document | ID prefix | Process(es) | Sources |
|----------|-----------|-------------|---------|
| `internal/general.md` | `REQ-GEN-` | all (policy) | `AGENTS.md`, `doc/ocserv.8.md`, `doc/sample.config` |
| `internal/ipc.md` | `REQ-IPC-` | all (cross-process) | `src/ipc.proto`, `src/ctl.proto`, `doc/design.md#ipc-communication*` |
| `internal/config.md` | `REQ-CONFIG-` | all (cross-process) | `src/config.c`, `src/config-ports.c`, `src/config-kkdcp.c`, `src/subconfig.c`, `src/sup-config/file.c`, `src/cfg.proto`, `src/vpn.h`, `src/vhost.h`, `doc/sample.config`, `tests/check-config-scope.py`, `tests/config-inherit.c` |
| `internal/sec-mod.md` | `REQ-SECMOD-` | sec-mod | `src/sec-mod*.c`, `src/sec-mod-auth.h`, `src/auth/*`, `src/acct/*` |
@@ -58,7 +59,10 @@ REQ-<PREFIX><CATEGORY>-<NNN>
- `<PREFIX>` identifies the document (table above); `IPC` has no
further category — IDs are `REQ-IPC-NNN`, grouped by message name in
the document body.
- `<CATEGORY>` for `internal/*` documents uses the tags from
- `<CATEGORY>` for `internal/general.md` uses: `SEC`, `TECH`, `STYLE`,
`TEST`, `COMPAT` (cross-cutting policy categories; see that file's
frontmatter for definitions).
- `<CATEGORY>` for other `internal/*` documents uses the tags from
`requirements-from-implementation.md`: `INIT`, `AUTH`, `ACCT`,
`SESSION`, `CFG`, `NET`, `SEC`, `ERR`, `TEARDOWN`.
- `<CATEGORY>` for `protocol/unified.md` uses the tags from
+410
View File
@@ -0,0 +1,410 @@
---
title: general and cross-cutting requirements
generator: requirements-elicitation
process: all
id-prefix: REQ-GEN
categories:
SEC: security and privilege invariants
TECH: canonical technology stack constraints
STYLE: code style and structure rules enforced by CI or review
TEST: test quality requirements
COMPAT: platform portability policy
sources:
- AGENTS.md
- doc/design.md
- doc/ocserv.8.md
- doc/sample.config
- .gitlab-ci.yml
---
# General and Cross-Cutting Requirements
This document captures requirements that span all three processes (main,
sec-mod, worker) or that encode project-wide policy — constraints on how
ocserv may evolve that cannot be attributed to a single subsystem or source
file.
Process-specific behavioral requirements belong in their own documents
(`internal/config.md`, `internal/authentication.md`, etc.), even when they
carry security implications. A requirement belongs here only when it applies
regardless of which subsystem is being changed.
The `AGENTS.md` "Canonical Technology Choices" table cites these IDs as its
authoritative source; the table in that file is a quick-reference summary only.
---
## SEC — security and privilege invariants
### REQ-GEN-SEC-001 — A semantic change to an operator-managed file MUST NOT silently grant more access than the previous semantics allowed
**Requirement:** Any change to the interpreted meaning of a value in an
operator-managed artifact — `ocserv.conf`, a plain-auth password file, an OTP
file, a supplemental per-user/per-group config file, or any other file whose
path appears in `ocserv.conf` and whose content is authored by the
administrator — MUST NOT, on upgrade, cause a client or user to gain access to
groups, VPN tunnels, or network resources that the administrator did not
explicitly grant under the previous version's documented semantics.
If such a meaning change is necessary, exactly one of the following MUST apply:
(a) **Explicit opt-in.** The administrator must take a deliberate action —
setting a new configuration option, or editing the affected file with
awareness of the new semantics — before the new, broader behavior takes
effect. The default for the new option MUST preserve the old behavior.
(b) **Startup rejection.** ocserv detects the old value at startup, logs a
clear error message identifying the affected option and file, and exits
with `EXIT_FAILURE`, forcing the administrator to migrate before the
server will accept connections.
Silent reinterpretation that expands access — a value that previously granted
no access or limited access and, after an upgrade, grants broader access
without any administrator action — MUST NOT be introduced, regardless of how
useful the new semantics would be for fresh deployments.
**Strength:** MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md; `doc/ocserv.8.md`; `doc/sample.config`
**Acceptance:** [SEC] code-review — every proposed change that alters the
interpretation of an existing value in an operator-managed file MUST be
evaluated against this requirement before merging.
Concrete failing example (gitlab#748): changing the `*` entry in a plain-auth
passwd file's group field from "no effective group membership" (current
behavior: `break_group_list` discards single-character entries,
`src/auth/plain.c:119135`) to "membership in all configured groups" would
violate this requirement — on upgrade, any user with `*` in their passwd entry
would silently gain access to all groups without any administrator action.
Compliant alternative for the same feature: introduce a new per-vhost option
(e.g. `plain-wildcard-group = true`, defaulting to `false`) that the
administrator must explicitly enable; `plain_auth_group()` checks this flag
before treating `*` as a wildcard.
**Links:** REQ-AUTH-AUTH-012, REQ-AUTH-AUTH-016, REQ-CONFIG-SEC-001
---
### REQ-GEN-SEC-002 — The three-process privilege boundary MUST NOT be collapsed by any patch
**Requirement:** The division of responsibilities across main (root, listeners
and IP allocation), sec-mod (root, authentication and private keys), and worker
(unprivileged + seccomp, per-client TLS/DTLS) is a security invariant. No
patch MAY:
(a) move credential handling, private-key operations, or session-state
storage into the worker process;
(b) grant a worker direct filesystem access to paths it resolves itself
(outside the root-created config snapshot, `REQ-CONFIG-SEC-001`);
(c) grant a worker a direct socket connection to sec-mod or main outside
the defined Unix-socket IPC channels (`src/ipc.proto`, `src/ctl.proto`);
(d) silently remove or bypass the seccomp filter that confines the worker.
If a proposed change requires crossing or relaxing a privilege boundary, it
MUST be flagged explicitly in the merge request for maintainer review and MUST
NOT be merged without explicit maintainer acknowledgment.
**Strength:** MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md ("Hard rule"); `doc/design.md`; `src/main.c`,
`src/sec-mod.c`, `src/worker.c`
**Acceptance:** [SEC] code-review — confirm by call-graph inspection that no
function reachable from `src/worker-*.c` at runtime: (a) opens or reads a
credential file directly; (b) holds a private key in worker-local memory after
`AUTH_COOKIE_REP`; (c) connects a socket to sec-mod's Unix socket path directly
(all worker↔sec-mod traffic routes through main's forwarding). Any MR touching
the seccomp filter (`src/seccomp.c`) or adding a new IPC channel requires
maintainer sign-off citing this requirement.
**Links:** REQ-CONFIG-SEC-001, REQ-IPC-010, REQ-AUTH-AUTH-007,
REQ-GEN-TECH-003
---
## TECH — canonical technology stack constraints
### REQ-GEN-TECH-001 — talloc is the sole memory allocator; GnuTLS-owned memory is the only exception
**Requirement:** All heap allocations in ocserv source code MUST use talloc
functions (`talloc_zero`, `talloc_strdup`, `talloc_array`, etc.) with an
explicit talloc parent context. The plain C allocators `malloc`, `calloc`,
`realloc`, and `free` MUST NOT appear in new code under `src/` (outside vendored
subtrees: `src/ccan/`, `src/inih/`, `src/llhttp/`, `src/pcl/`,
`src/protobuf/`, `src/gnulib/`).
The sole exception: memory whose lifetime GnuTLS manages internally MUST be
allocated with `gnutls_malloc()` and freed with `gnutls_free()`. The two
allocator domains MUST NOT be mixed for the same logical allocation — a buffer
allocated with `talloc_*` MUST NOT be passed to `gnutls_free()`, and vice versa.
Error paths that must release multiple allocations MUST use a `goto cleanup`
pattern with a single freeing label rather than duplicating free calls on every
error branch.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Canonical Technology Choices — Memory management);
pervasive in `src/*.c`
**Acceptance:** negative, code-review — `grep -rn '\b\(malloc\|calloc\|realloc\|free\)\s*(` src/` excluding vendored subtrees; any hit in non-vendored code is a violation. Confirm every new function that returns an allocated pointer documents its talloc parent in a comment or by convention (e.g. allocated on the passed-in `pool` argument).
**Links:** REQ-GEN-TECH-002
---
### REQ-GEN-TECH-002 — GnuTLS is the sole cryptographic library; no OpenSSL; TLS calls go through `src/tlslib.c`
**Requirement:** All TLS, DTLS, certificate, and cryptographic operations MUST
use GnuTLS (or its companion library nettle for low-level primitives). OpenSSL
and any other TLS/crypto library MUST NOT be introduced as a dependency.
Code in `src/worker-*.c` and `src/main-*.c` MUST NOT call GnuTLS API functions
directly — all GnuTLS interaction from those files MUST go through the wrapper
functions in `src/tlslib.c` and `src/tlslib.h`. Direct GnuTLS calls are
permitted only within `src/tlslib.c` itself and in `src/sec-mod-*.c` for
private-key operations that are inherently sec-mod's responsibility.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Canonical Technology Choices — Cryptography);
`src/tlslib.c`, `src/tlslib.h`
**Acceptance:** negative, code-review — `grep -rn 'SSL_\|EVP_\|openssl\|OPENSSL' src/` (excluding vendored subtrees); any hit is a violation. `grep -rn 'gnutls_' src/worker-*.c src/main-*.c`; any hit that is not a call to a `tlslib.h`-declared wrapper is a violation.
**Links:** REQ-GEN-TECH-001
---
### REQ-GEN-TECH-003 — All cross-process messages MUST be defined in `.proto` files; generated bindings MUST NOT be hand-edited
**Requirement:** Every message that crosses a process boundary (worker ↔ main,
worker ↔ sec-mod, main ↔ sec-mod, `occtl` ↔ main) MUST be defined in
`src/ipc.proto` or `src/ctl.proto` using the protobuf-c schema language. No
ad-hoc binary format, fixed-layout struct, or text protocol MAY be introduced
for inter-process communication.
After any edit to a `.proto` file, the C bindings MUST be regenerated with:
```
protoc-c --c_out=src/ src/ipc.proto
protoc-c --c_out=src/ src/ctl.proto
```
The generated files `src/ipc.pb-c.c`, `src/ipc.pb-c.h`, `src/ctl.pb-c.c`,
and `src/ctl.pb-c.h` MUST NOT be edited by hand. Any manual edit will be
silently overwritten by the next `protoc-c` invocation and invalidates the
requirement that the `.proto` file is the authoritative source of the message
schema.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Canonical Technology Choices — IPC serialization;
Modifying IPC); `src/ipc.proto`, `src/ctl.proto`
**Acceptance:** negative, code-review — confirm that `src/ipc.pb-c.*` and
`src/ctl.pb-c.*` are not modified in any MR that does not also modify the
corresponding `.proto` file. Positive — after a `.proto` edit, run `protoc-c`
and confirm `ninja -C build` succeeds and `git diff src/*.pb-c.*` matches the
`protoc-c` output exactly.
**Links:** REQ-GEN-SEC-002, REQ-IPC-010
---
### REQ-GEN-TECH-004 — Configuration MUST use INI `key = value` format; no structured sub-formats
**Requirement:** All administrator-facing configuration options MUST be
expressible as flat INI `key = value` pairs or as the existing bracketed
sub-section syntax (`auth = module[key=value, ...]`) parsed by `src/inih/` and
`src/subconfig.c`. No new structured format — JSON objects, YAML documents,
TOML tables, XML, or any multi-line structured syntax — MAY be introduced for
options that are expressible as key=value pairs.
A new configuration file format (separate from `ocserv.conf`) MAY only be
introduced for data that is inherently structured and cannot be expressed as
key=value (e.g. the OIDC JSON configuration, which is defined by an external
standard). In that case the new format MUST be documented in `doc/ocserv.8.md`
and `doc/sample.config`, and its introduction requires a design-discussion
issue.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Canonical Technology Choices — Configuration parsing;
Design Principles — Configuration stays in INI); `src/config.c`,
`src/subconfig.c`, `src/inih/`
**Acceptance:** code-review — any MR that adds a new `ini_parse()`-bypassing
config path or introduces a call to a JSON/YAML parser for a new option
(outside the existing OIDC path) is a violation. Positive — every new option
added in an MR appears in `doc/sample.config` as a `key = value` line.
**Links:** REQ-CONFIG-CFG-001, REQ-GEN-SEC-001
---
### REQ-GEN-TECH-005 — A new external library dependency requires a design-discussion issue and explicit maintainer approval
**Requirement:** Before introducing a new external library (i.e. a new
`dependency()` call in `meson.build` that is not already present), the
contributor MUST:
(a) check `src/ccan/` — if the required functionality exists as a CCAN
module, copy it in and use it rather than adding an external dependency;
(b) consider whether a small inline implementation (≤ ~50 lines) would
suffice — if so, prefer it over a new dependency;
(c) if neither (a) nor (b) applies, open a design-discussion issue documenting
the proposed dependency, why existing options are insufficient, and the
build/packaging/security-audit impact; the MR MUST NOT be merged until a
maintainer explicitly approves the dependency in that issue.
Every external dependency is a permanent build, packaging, and security-audit
cost. The bar for approval is that no existing in-tree alternative exists and
the dependency is actively maintained and widely packaged.
**Strength:** MUST
**Status:** DERIVED
**Source:** AGENTS.md (Canonical Technology Choices — Utility constructs;
Design Principles — Resist dependency growth)
**Acceptance:** code-review — any MR that adds a `dependency()` call not
present in the base branch MUST cite a design-discussion issue number in the MR
description. The absence of such a citation is grounds for blocking the MR.
**Links:** REQ-GEN-TECH-001, REQ-GEN-TECH-002, REQ-GEN-TECH-003,
REQ-GEN-TECH-004
---
## STYLE — code style and structure rules
### REQ-GEN-STYLE-001 — Source files MUST conform to C99, Linux kernel style, and pass `clang-format`; header guards MUST use the canonical form
**Requirement:** All source files under `src/` and `tests/` (excluding vendored
subtrees: `src/ccan/`, `src/inih/`, `src/llhttp/`, `src/pcl/`, `src/protobuf/`,
`src/gnulib/`, and generated `*.pb-c.*` files) MUST:
(a) **C standard**: use C99 features only. C11 or later features (e.g.
`_Atomic`, `_Generic`, `_Static_assert` without a C99-compatible wrapper,
anonymous struct/union not as a GCC extension) MUST NOT be used.
(b) **Coding style**: follow Linux kernel style — tabs for indentation,
8-space tab width, 80-column line limit.
(c) **Format tool**: pass `clang-format --dry-run -Werror` without errors.
CI enforces this on every MR.
(d) **Header guards**: every `.h` file MUST use the form:
```c
#ifndef FILENAME_H
#define FILENAME_H
/* ... */
#endif /* FILENAME_H */
```
where `FILENAME_H` matches the file's base name uppercased with `.`
replaced by `_`.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Code Style); `.gitlab-ci.yml` (clang-format CI job)
**Acceptance:** CI — the `clang-format` CI job runs `clang-format --dry-run
-Werror` on all non-vendored, non-generated files under `src/` and `tests/`
and fails the pipeline on any formatting violation. C standard and header-guard
conformance are verified by code review; no automated check currently exists
for C99-only compliance beyond what the compiler warns on with `-std=c99 -Wall`.
**Links:** REQ-GEN-STYLE-002
---
### REQ-GEN-STYLE-002 — `#ifdef` blocks in function bodies MUST be shallow and short; optional features MUST use the stub pattern
**Requirement:** Preprocessor conditionals inside function bodies obscure
control flow and make review difficult. The following rules apply to all source
files under `src/` and `tests/` (excluding vendored subtrees):
(a) **Depth**: `#ifdef`/`#if` blocks inside a function body MUST NOT be
nested more than one level deep. A conditional that needs deeper nesting
MUST be extracted into a separately-named function.
(b) **Length**: each branch of an in-function `#ifdef` MUST contain at most
5 lines. Longer branches MUST be extracted into separately-named
functions.
(c) **Stub pattern**: when a feature is conditionally compiled, the header
MUST define a no-op (or safe error-returning) inline stub for the disabled
case, so call sites require no `#ifdef` at all. Example:
```c
#ifdef HAVE_FEATURE
int feature_init(worker_st *ws);
#else
static inline int feature_init(worker_st *ws) { return 0; }
#endif
```
(d) **Significant feature code**: any optional feature spanning more than one
function MUST live in its own `.c` file included or excluded by the build
system (`meson.build`), following the pattern of `src/auth/` and
`src/acct/`. It MUST NOT be implemented as a large inline `#ifdef` block
in an existing file.
(e) **`#endif` annotation**: every `#endif` MUST be annotated with the
condition it closes: `#endif /* HAVE_FEATURE */`.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Preprocessor Conditionals); `src/auth/`,
`src/acct/` (model structure)
**Acceptance:** code-review — no automated tool currently enforces (a)(e);
review is the gate. When an MR is submitted, the reviewer MUST check every
modified function for `#ifdef` depth and length violations and for missing
`#endif` annotations. The `src/auth/pam.c` / `src/auth/gssapi.c` files
illustrate compliant optional-feature structure.
**Links:** REQ-GEN-STYLE-001
---
## TEST — test quality requirements
### REQ-GEN-TEST-001 — Every feature or fix MUST have both a positive and a negative test; tests MUST be self-diagnosing and registered in `tests/meson.build`
**Requirement:** No feature addition or bug fix is complete without tests.
The following apply to all tests in `tests/`:
(a) **Coverage**: every new feature or changed behavior MUST have at least
one positive test (the correct behavior is exercised and confirmed) and
at least one negative test (invalid input or error conditions are
correctly rejected). For `SEC`, `AUTH`, and `IPC` changes, the negative
test is the more important of the two and MUST be written first.
(b) **Bug-fix test order**: for a bug fix, the reproducing test MUST be
written and confirmed to fail against the unmodified code before the fix
is applied. A test written after the fix cannot demonstrate it is
meaningful.
(c) **Self-diagnosing output**: a test failure MUST be explainable from the
test's own output without local reproduction. Shell tests MUST print what
they were testing and why it failed (e.g.
`echo "FAIL: expected exit 0, got $ret"`). C unit tests MUST print the
failing condition and relevant values before returning non-zero. Tests
that exit non-zero with no diagnostic output MUST NOT be accepted.
(d) **Registration**: every new test MUST be registered in
`tests/meson.build`. An unregistered test is not run by CI and provides
no coverage guarantee.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Testing New Functionality); `tests/meson.build`;
`tests/common.sh`
**Acceptance:** code-review — confirm for every MR that: (1) `tests/meson.build`
contains an entry for each new test file; (2) at least one test is a negative
case; (3) running the negative test against the pre-fix code produces a
non-zero exit and a human-readable failure message. CI runs all registered
tests on every MR; a passing CI run with no newly registered test for a
behavior change is itself a review finding.
**Links:** REQ-GEN-SEC-001, REQ-GEN-SEC-002
---
## COMPAT — platform portability policy
### REQ-GEN-COMPAT-001 — Linux is the primary target; BSD compatibility is best-effort; Linux-specific code MUST be guarded with `#ifdef __linux__`
**Requirement:** ocserv is a Linux service. Linux-specific features (epoll,
TUN device ioctls, procfs, namespaces, seccomp, `SO_REUSEPORT`, etc.) are
accepted without requiring a BSD equivalent.
BSD (FreeBSD, OpenBSD) compatibility is maintained on a best-effort basis:
(a) Patches MUST NOT gratuitously break BSD builds. A change that breaks BSD
solely because the author did not check is a defect; a change that is
inherently Linux-only is accepted.
(b) Any code path that invokes a Linux-specific system call, header, or
kernel interface MUST be guarded with `#ifdef __linux__` (or a more
specific feature-test macro) so that BSD builds continue to compile,
even if the BSD path is a no-op stub.
(c) Features that rely on procfs (e.g. config snapshots, REQ-CONFIG-SEC-001)
are Linux-only by nature; on BSD, the absence of procfs means the
corresponding behavior is unavailable and the administrator MUST restart
the server to apply configuration changes. This difference MUST be noted
in `doc/ocserv.8.md` for any option where it applies.
(d) A feature MUST NOT be rejected from merging solely because it does not
work on BSD, provided (a) and (b) are satisfied.
**Strength:** MUST / MUST NOT
**Status:** DERIVED
**Source:** AGENTS.md (Project Overview — BSD; Platform Portability);
`src/config.c` (`PROC_FS_SUPPORTED`); `.gitlab-ci.yml`
**Acceptance:** CI — the cross-compilation job (i386/Debian) catches most
Linux-specific implicit assumptions. BSD-specific: code-review confirms that
any new `#include <linux/...>` or Linux-specific `ioctl`/syscall is inside
`#ifdef __linux__`. REQ-CONFIG-SEC-001's `[Divergence]` note documents the
accepted procfs gap as a model for how to document platform differences.
**Links:** REQ-CONFIG-SEC-001, REQ-GEN-STYLE-002