Skip to content

Latest commit

 

History

History
609 lines (431 loc) · 20 KB

contribution.md

File metadata and controls

609 lines (431 loc) · 20 KB

Contributing to Synta

Thank you for your interest in contributing to Synta. This document describes everything you need to know: how to set up a development environment, the contribution workflow, what we expect from licensing and sign-off, how to run the same checks that CI runs, and how to write good commit messages.

Table of Contents


Licensing and Sign-off

Dual licence

Synta is dual-licensed under MIT OR Apache-2.0. By submitting a contribution you agree that your work will be made available under those same terms and that you have the right to do so.

When adding new source files, include a SPDX header at the top:

// SPDX-License-Identifier: MIT OR Apache-2.0

Developer Certificate of Origin

Synta uses the Developer Certificate of Origin (DCO) instead of a Contributor Licence Agreement (CLA). Every commit must carry a Signed-off-by trailer certifying that you wrote the code or have the right to submit it under the project licence.

Add the sign-off with the -s flag when committing:

git commit -s

This appends a line like the following to your commit message:

Signed-off-by: Jane Smith <jane@example.com>

Commits without a Signed-off-by line will not be accepted. If you are contributing on behalf of an employer, ensure your employer permits open-source contributions before submitting.

AI-assisted contributions

AI coding tools (such as GitHub Copilot, Claude, or similar) may be used to assist with writing code, tests, and documentation. The following rules apply:

  • You are responsible for everything you submit. Review, understand, and test any AI-generated content before including it in a PR. Submitting output you cannot explain or vouch for is not acceptable.
  • The DCO sign-off still applies. Signing off with git commit -s certifies that you have the right to submit the contribution and that you understand its content — regardless of how it was produced.
  • All CI checks must pass. AI-generated code is subject to the same quality bar as hand-written code: cargo fmt, clippy -D warnings, tests, and documentation sample validation.
  • Disclose AI assistance in the PR description when a significant portion of the contribution was generated or substantially rewritten by an AI tool. A brief note is sufficient; the goal is transparency, not restriction.

Contribution Workflow

The canonical repository is at codeberg.org/abbra/synta. Pull requests should target the main branch.

1. Fork and clone

Fork the repository on Codeberg, then clone your fork:

git clone ssh://git@codeberg.org/<your-username>/synta.git
cd synta
git remote add upstream https://codeberg.org/abbra/synta.git

2. Create a branch

Always work on a dedicated branch, not directly on main:

git fetch upstream
git checkout -b my-feature upstream/main

Choose a short, descriptive branch name (fix-ber-length-decoding, add-generalizedtime-serde, krb5-pa-data-types, etc.).

3. Make your changes

Implement your change and add tests (see Writing Tests). Keep each commit focused on a single concern; avoid bundling unrelated fixes in the same PR.

4. Run local CI

Run the full CI suite before pushing to catch problems early:

./contrib/ci/local-ci.sh all

See Running CI Locally for details on individual jobs and flags.

5. Commit

Use git commit -s to sign off each commit. Follow the commit message conventions described below.

6. Open a pull request

Push your branch and open a pull request against abbra/synta:main:

git push origin my-feature

Then open a PR on Codeberg. In the PR description:

  • Summarise what the change does and why.
  • Reference any related issues with Fixes #NNN or See #NNN.
  • Mention if the change is a breaking API change.

The CI pipeline runs automatically on every PR. All jobs must pass before a PR can be merged.

7. Review and merge

A maintainer will review your PR. If changes are requested:

  • Push additional commits; do not force-push (it makes review history harder to follow).
  • Mark review comments as resolved after addressing them.

Once the PR is approved and CI is green it will be merged.


Commit Message Conventions

Synta uses Conventional Commits with a scope in parentheses:

<type>(<scope>): <short description>

<optional body — wrap at 72 characters>

Fixes: https://codeberg.org/abbra/synta/issues/<NNN>

Signed-off-by: Jane Smith <jane@example.com>

Fixes: is optional; include it when the commit resolves a Codeberg issue. Use the full URL so the reference is unambiguous in any context. Multiple Fixes: lines are allowed when a commit addresses several issues.

Signed-off-by: is required on every commit (see Developer Certificate of Origin). Add it with git commit -s.

Commit message template

The repository ships a commit message template at .gitmessage that pre-fills the format, lists all valid types and scopes, and keeps the Fixes: and Signed-off-by: trailers visible as a reminder. Activate it once after cloning:

git config commit.template .gitmessage

After that, git commit (without -m) opens your editor with the template pre-loaded. Use git commit -s to have the Signed-off-by: trailer appended automatically.

Types:

Type When to use
feat New feature or capability
fix Bug fix
perf Performance improvement
refactor Restructuring without behaviour change
test Adding or fixing tests
docs Documentation only
ci CI configuration
build Build system / dependency changes
chore Maintenance tasks that don't fit elsewhere

Scope is the crate or subsystem affected: synta, synta-ffi, synta-python, synta-derive, synta-codegen, synta-certificate, synta-krb5, synta-bench, contrib/ci, docs, etc.

Examples:

feat(synta): add lazy Sequence iterator for zero-copy traversal

fix(synta-ffi): prevent double-free in synta_decoder_free on NULL input

Fixes: https://codeberg.org/abbra/synta/issues/42

Signed-off-by: Jane Smith <jane@example.com>
perf(synta): replace eager Vec allocation in OctetString decode

docs(synta-python): document Certificate.subject_public_key_info field

ci(github): add toc job to GitHub Actions workflow

Keep the subject line under 72 characters and written in the imperative mood ("add", "fix", "remove" — not "added", "fixes", "removing").


Code Style

Rust

Formatting is enforced by rustfmt with the project defaults (no rustfmt.toml). Run before committing:

cargo fmt --all

CI fails if any file would be reformatted.

Linting is enforced by Clippy with all warnings treated as errors:

cargo clippy --workspace -- -D warnings

Fix all Clippy diagnostics before opening a PR. If a lint is a false positive, suppress it with #[allow(...)] on the smallest possible scope and add a comment explaining why.

API documentation is enforced with RUSTDOCFLAGS=-D warnings:

RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps

Public items must have doc comments. Code examples in doc comments are compiled and type-checked by the doc-rust CI job.

Style guidelines:

  • Prefer zero-copy types (&[u8], &str, RawDer<'_>) over owned equivalents where the API permits it.
  • Avoid unnecessary heap allocations on hot paths; the benchmark suite will catch regressions.
  • Use #[derive(Debug, Clone, PartialEq)] on public types where appropriate.
  • Prefer impl Trait in argument position over generic bounds for readability when there is only one trait bound.
  • Error types should implement std::error::Error and be non-exhaustive (#[non_exhaustive]) when public.

Python

Python files in contrib/ and python/ are linted with Ruff:

ruff check contrib/
ruff format --check contrib/

Fix issues before committing. CI runs Ruff via uv when available.

Markdown

Every Markdown file must have an up-to-date doctoc-compatible table of contents block (see Markdown docs). After editing a document, regenerate its TOC:

./contrib/ci/local-ci.sh --update toc

Development Environment

Required tools

Tool Notes
Bash 4+ Required by contrib/ci/local-ci.sh and all other shell scripts. macOS users may need to install a newer bash via Homebrew (brew install bash), as macOS ships with bash 3.2. Each script checks the version on startup and exits with an error if bash 3 or earlier is detected.
Rust stable toolchain Install via rustup. Must include the rustfmt and clippy components (rustup component add rustfmt clippy).
C compiler and make gcc or clang, plus GNU make. Required by the c-test job.
Python 3.8+ Required by the Python bindings (synta-python) and by contrib/ tooling.

Optional tools

Tool Notes
uv Recommended for managing Python environments. Install from astral.sh/uv. The local CI script falls back to python3 -m venv + pip when uv is absent.
cargo-c Required for cargo cbuild in the build job, which produces the host-triple–qualified library consumed by synta-ffi/examples/c/. Install with cargo install cargo-c. Skipped with a warning when absent; the c-test job is unaffected.
cbindgen Required to regenerate include/synta.h from Rust code. Invoked automatically by synta-ffi/build.rs during cargo build. Install with cargo install cbindgen. Build succeeds with a warning when absent, but the header remains stale.
valgrind Required only when running ./contrib/ci/local-ci.sh --valgrind. Memory-checks both C and Rust test binaries.
actionlint Validates GitHub Actions workflow files. Falls back to yamllint when absent.

System libraries

The C-based jobs require the following native libraries. On Debian/Ubuntu:

apt-get install build-essential libssl-dev llvm pkg-config clang python3-dev

The python3-dev package is needed only for the Python bindings job. The benchmark jobs need the same set minus python3-dev.

The default crypto backend is OpenSSL (libssl-dev). To build with the NSS backend instead, install the NSS development libraries and pass --no-default-features --features nss to Cargo:

# Debian/Ubuntu
apt-get install libnss3-dev
cargo build --no-default-features --features nss -p synta-ffi

Running CI Locally

contrib/ci/local-ci.sh mirrors the GitHub Actions workflow exactly. Every CI job is implemented in the script and can be run individually or together.

Quick start

# Run all jobs in canonical order
./contrib/ci/local-ci.sh all

# List all available job names
./contrib/ci/local-ci.sh --list

Selected jobs

Run only the checks relevant to your change:

# Formatting and linting
./contrib/ci/local-ci.sh fmt clippy

# Core tests (implies build)
./contrib/ci/local-ci.sh test

# C FFI tests (implies build)
./contrib/ci/local-ci.sh c-test

# Python bindings (implies build)
./contrib/ci/local-ci.sh python-test

# Markdown TOC check
./contrib/ci/local-ci.sh toc

# Update stale TOCs
./contrib/ci/local-ci.sh --update toc

# Validate C/C++ code samples in docs
./contrib/ci/local-ci.sh doc-c

# Validate Rust code samples in docs (with serde)
./contrib/ci/local-ci.sh doc-rust

# Memory-check C and Rust tests with Valgrind
./contrib/ci/local-ci.sh --valgrind c-test test

Isolated build directory

To keep CI build artefacts separate from your incremental build:

CARGO_TARGET_DIR=/tmp/synta-target ./contrib/ci/local-ci.sh all

CI job reference

Job What it checks Prerequisites
build Compiles the full workspace (debug + release) with the default openssl backend; dual-backend crates (synta-ffi, synta-python, synta-x509-verification) are built separately to avoid linking both backends simultaneously; produces libcsynta.so and include/synta.h —
fmt Rust formatting (cargo fmt --all -- --check) —
ruff Python style (ruff check + ruff format --check) —
toc Markdown TOC blocks are up to date —
doc-python Python doc-sample syntax (python3 -m py_compile) —
clippy Clippy with -D warnings build
doc Rust API docs with -D warnings build
doc-c C/C++ doc samples compile with -fsyntax-only build
doc-rust Rust doc samples type-check via cargo check build
python-test Python binding tests (pytest) build
c-test C FFI tests (make -C tests/c test) build
test-codegen synta-codegen unit tests build
test-certificate synta-certificate tests build
test-krb5 synta-krb5 tests including build.rs codegen build
test-serde serde feature in isolation build
bench Benchmark suite compiles without errors (--no-run) build
test Full workspace tests on stable, beta, and nightly build

The build job runs first; all jobs that depend on it fan out in parallel on GitHub Actions. Locally, the script runs them sequentially and auto-triggers prerequisites when needed.

See contrib/ci/README.md for a complete reference including flags (--valgrind, --update, --show-results, --no-run, --no-deps) and benchmark-specific environment variables.


Workspace Structure

The repository is a Cargo workspace with fifteen crates:

Crate Path Purpose
synta . Core ASN.1 DER/BER/CER parser, encoder, and type system. Zero-copy where possible.
synta-derive synta-derive/ Procedural macros (#[derive(Asn1Sequence)], #[derive(Asn1Set)], #[derive(Asn1Choice)], #[derive(Tagged)]) for deriving Encode and Decode on Rust structs and enums.
synta-codegen synta-codegen/ Library and CLI tool that parses ASN.1 schema files and generates Rust source code using synta and synta-derive.
synta-certificate synta-certificate/ High-level X.509 certificate parser and PKI toolkit built on top of synta. Handles certificate field access, extension builders, CRL/CSR/OCSP structures, and X.509 path validation support (RFC 5280).
synta-krb5 synta-krb5/ Kerberos V5 ASN.1 types derived from RFC 4120, RFC 4121, RFC 4178, RFC 6113, and related RFCs, generated via synta-codegen.
synta-mtc synta-mtc/ Merkle Tree Certificate (MTC) implementation including builder, validator, and crypto primitives per the IETF PLANTS draft.
synta-x509-verification synta-x509-verification/ RFC 5280 X.509 certificate path validation (crypto-agnostic — delegates signature verification to a caller-supplied backend).
synta-ffi synta-ffi/ C/C++ FFI library (libcsynta). The C header include/synta.h is auto-generated by cbindgen during the build.
synta-tools synta-tools/ CLI utilities (synta-tool) for X.509 and MTC certificate inspection, including MTC standalone/landmark display and inclusion proof verification.
synta-python synta-python/ PyO3-based Python extension module. Compiled by maturin; installed as synta._synta. Exposes decoding, encoding, and certificate parsing to Python 3.8+.
synta-python-common synta-python-common/ Shared utilities used by multiple PyO3 extension crates (error mapping, OID helpers).
synta-python-krb5 synta-python-krb5/ PyO3 extension for Kerberos V5 and SPNEGO types (synta._krb5).
synta-python-mtc synta-python-mtc/ PyO3 extension for Merkle Tree Certificate types (synta._mtc).
synta-bench synta-bench/ Criterion-based benchmarks comparing synta against x509-parser, x509-cert, OpenSSL, and the cryptography Python package. Also covers C FFI and Python binding overhead, and CA root-store parsing (Mozilla NSS, CCADB).
synta-fuzz synta-fuzz/ Structured ASN.1 fuzzer for exercising the parser and encoder with random and seeded inputs.

When adding a new feature, consider which crate it belongs in. Changes to the core parser belong in synta; new high-level helpers for a specific protocol may warrant a new crate (follow the synta-krb5 or synta-mtc pattern).


Writing Tests

Rust unit and integration tests

Tests live alongside the code they test in #[cfg(test)] modules, or as integration tests in tests/ at the crate root.

Run tests for a specific crate:

cargo test -p synta
cargo test -p synta-certificate
cargo test -p synta-codegen --all-features

Run the full workspace test suite (matches what CI runs):

cargo test --workspace --all-features

DER/BER test vectors live under tests/vectors/. When adding a new vector:

  • Add the raw binary file under the appropriate subdirectory.
  • Add a test that decodes it and asserts the expected field values.
  • If the vector came from a third-party source, document its origin and licence in tests/vectors/README.md.

C FFI tests

C tests live in tests/c/. Each test is a small C program that links against libcsynta.

Build and run the C tests:

cargo build --release -p synta-ffi
make -C tests/c test

To check for memory errors:

make -C tests/c valgrind

Python tests

Python binding tests live in tests/python/. They require the synta extension to be compiled first:

uv venv
uv pip install maturin pytest
uv run maturin develop
uv run pytest tests/python/ -v

Documentation

Rust API docs

Public items must have /// doc comments. Code examples in doc comments should be self-contained and runnable:

/// Decodes a DER-encoded integer.
///
/// # Example
///
/// ```rust
/// use synta::{Decoder, Encoding};
///
/// let data = [0x02, 0x01, 0x2a];
/// let mut decoder = Decoder::new(&data, Encoding::Der);
/// let n = decoder.decode_integer().unwrap();
/// assert_eq!(i64::try_from(n).unwrap(), 42);
/// ```
pub fn decode_integer(&mut self) -> Result<Integer> { ... }

The doc-rust CI job compiles every Rust code block in the docs/ directory as well as in doc comments. If an example is intentionally incomplete, mark it no_run or ignore:

```rust,no_run
// This example requires a network connection
```

Markdown docs

Narrative documentation lives in docs/. After adding or renaming headings, regenerate the table of contents:

./contrib/ci/local-ci.sh --update toc

The toc CI job verifies that every workspace Markdown file has an up-to-date TOC block and fails the build if any are stale.

Code samples in docs

C and C++ code blocks in Markdown files are compiled with -fsyntax-only by the doc-c CI job. Python code blocks are syntax-checked with python3 -m py_compile. Keep examples realistic and compilable; see contrib/validation/README.md for how blocks are classified and when they are skipped automatically.


Benchmarks

The benchmark suite lives in synta-bench/benches/. Before running benchmarks, ensure you have a release build:

cargo build --release -p synta-ffi

Run the library comparison benchmarks:

./contrib/ci/local-ci.sh --show-results bench-compare

Run the CA root-store benchmarks (downloads ~20 MB on first run):

./contrib/ci/local-ci.sh --show-results bench-ca-roots

Display results from a previous run without re-running:

./contrib/ci/local-ci.sh --no-run --show-results bench-compare

When contributing a performance improvement:

  1. Run the relevant benchmarks before and after your change on the same machine under the same conditions.
  2. Include the benchmark output (or a summary) in your PR description.
  3. Ensure the bench CI job still compiles without errors.

See synta-bench/README.md and contrib/ci/README.md for a full description of benchmark flags, environment variables, and result interpretation.