Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7f822dd
validation before writing
Michal-Pogorzelec Oct 5, 2026
5b865b4
Enhance error handling in frame encoding functions and add tests for …
Michal-Pogorzelec Oct 5, 2026
2b1b730
validation against the secondary header len = 0
Michal-Pogorzelec Oct 5, 2026
5cc22d6
improve handling for resource limits
Michal-Pogorzelec Oct 7, 2026
5d42198
enhance compiler flags and remove warnings
Michal-Pogorzelec Oct 7, 2026
23462d4
add sanitize
Michal-Pogorzelec Oct 7, 2026
dacff2a
update coverage
Michal-Pogorzelec Oct 7, 2026
b84d804
add CI
Michal-Pogorzelec Oct 7, 2026
38f99ac
split UT execution for: with segment hdr enabled and without
Michal-Pogorzelec Oct 7, 2026
604a5bb
build examples with segment hdr
Michal-Pogorzelec Oct 7, 2026
ce0385a
headers dependency
Michal-Pogorzelec Oct 7, 2026
91634ba
clang-format update
Michal-Pogorzelec Oct 7, 2026
f2c3468
add missing parantheses and braces
Michal-Pogorzelec Oct 7, 2026
2f3bc49
refactor: update frame size calculations and constants in TC and TM e…
Michal-Pogorzelec Oct 7, 2026
cdaf6f3
add missing prefixes
Michal-Pogorzelec Oct 7, 2026
b3e6fb9
update make example
Michal-Pogorzelec Oct 7, 2026
786eb2e
update UT with edge cases
Michal-Pogorzelec Oct 7, 2026
17c3e47
update macros doxygen
Michal-Pogorzelec Oct 7, 2026
f8d466f
update doxygen params indicators
Michal-Pogorzelec Oct 7, 2026
ad542a2
update segment header handling for Control Command frames and add cor…
Michal-Pogorzelec Oct 7, 2026
49fca18
remove docs/
Michal-Pogorzelec Oct 7, 2026
337e61f
add module prefix for macros
Michal-Pogorzelec Nov 14, 2026
f6f83b7
update licene copyright
Michal-Pogorzelec Nov 14, 2026
ca19d17
add missing hdr guard
Michal-Pogorzelec Nov 14, 2026
263c452
update limitations in readme
Michal-Pogorzelec Nov 14, 2026
c8710af
update files with license information
Michal-Pogorzelec Nov 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
Language: C
Language: Cpp
BasedOnStyle: LLVM

# Indentation
Expand All @@ -13,7 +13,7 @@ ColumnLimit: 100

# Braces
BreakBeforeBraces: Allman
InsertBraces: false
InsertBraces: true

# Spaces
SpaceAfterCStyleCast: false
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
unit-tests:
name: Unit tests (${{ matrix.cc }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: Build library, examples and run unit tests
run: make all test CC=${{ matrix.cc }}
- name: Run examples
run: |
./build/bin/tm_example
./build/bin/tc_example
./build/segment_header/tm_example
./build/segment_header/tc_example
coverage:
name: Coverage (100% line and branch)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install gcovr
run: sudo apt-get update && sudo apt-get install -y gcovr
- name: Build instrumented tests and generate report
run: make coverage-html
- name: Enforce 100% line and branch coverage
run: >
gcovr -r . --filter src
--fail-under-line 100
--fail-under-branch 100
- name: Upload HTML report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-html
path: build/coverage/
if-no-files-found: ignore

sanitizers:
name: ASan + UBSan
runs-on: ubuntu-latest
env:
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1
UBSAN_OPTIONS: print_stacktrace=1
steps:
- uses: actions/checkout@v4
- name: Build and run unit tests and examples under ASan and UBSan
run: make sanitize
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,5 @@ dkms.conf
build/
obj/
bin/
.github/
.vscode/
TODO.md
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]
Copyright 2026 OpenSpaceCode contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
96 changes: 82 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
CC = gcc
CFLAGS ?= -O2 -Iinclude -Wall -Wextra -Wpedantic -Wshadow \
CC ?= cc
# Optimisation / instrumentation flags; overridden by the sanitize target and by
# tools/coverage_html.sh
OPT ?= -O2
SANITIZE_OPT = -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined \
-fno-sanitize-recover=all
# Stronger warnings for code quality; any warning fails the build
CFLAGS ?= $(OPT) -Iinclude -Werror -Wall -Wextra -Wpedantic -Wconversion -Wshadow \
-Wcast-align -Wcast-qual -Wpointer-arith -Wformat=2 \
-Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wundef \
-std=c11
-std=c99
LDFLAGS =

SRC_DIR = src
Expand All @@ -12,18 +18,31 @@ TEST_DIR = tests
BUILD_DIR = build
OBJ_DIR = $(BUILD_DIR)/obj
BIN_DIR = $(BUILD_DIR)/bin
SANITIZE_DIR = $(BUILD_DIR)/sanitize

SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRCS))

# Headers are prerequisites of everything that includes them, so editing one rebuilds
# the objects, examples and tests that depend on it.
PUBLIC_HEADERS = $(wildcard $(INC_DIR)/*.h)
EXAMPLE_HEADERS = $(wildcard $(EXAMPLES_DIR)/*.h)
TEST_HEADERS = $(wildcard $(TEST_DIR)/*.h)

EXAMPLES = $(wildcard $(EXAMPLES_DIR)/*.c)
EXAMPLE_BINS = $(patsubst $(EXAMPLES_DIR)/%.c,$(BIN_DIR)/%,$(EXAMPLES))
TEST_SRCS = $(wildcard $(TEST_DIR)/*.c)
TEST_BIN = $(BIN_DIR)/unit_tests
# The unit tests and the examples are built a second time with the optional TC Segment
# Header compiled in. That build has its own directory, so its coverage data stays apart
# from the default one.
SEGMENT_DIR = $(BUILD_DIR)/segment_header
SEGMENT_TEST_BIN = $(SEGMENT_DIR)/unit_tests
SEGMENT_EXAMPLE_BINS = $(patsubst $(EXAMPLES_DIR)/%.c,$(SEGMENT_DIR)/%,$(EXAMPLES))

LIB = $(BUILD_DIR)/libsdlp.a

.PHONY: all clean examples lib unit-tests test coverage-html
.PHONY: all clean example examples lib unit-tests test coverage-html sanitize

all: lib examples

Expand All @@ -32,20 +51,33 @@ lib: $(LIB)
$(LIB): $(OBJS)
ar rcs $@ $^

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(PUBLIC_HEADERS) | $(OBJ_DIR)
$(CC) $(CFLAGS) -c $< -o $@

examples: $(EXAMPLE_BINS)
examples: $(EXAMPLE_BINS) $(SEGMENT_EXAMPLE_BINS)

# Build the examples and run the ones linked against the library as `make lib` builds it.
example: examples
@for example in $(EXAMPLE_BINS); do ./$$example || exit 1; done

unit-tests: $(TEST_BIN)
unit-tests: $(TEST_BIN) $(SEGMENT_TEST_BIN)

$(BIN_DIR)/%: $(EXAMPLES_DIR)/%.c $(LIB) | $(BIN_DIR)
$(BIN_DIR)/%: $(EXAMPLES_DIR)/%.c $(EXAMPLE_HEADERS) $(PUBLIC_HEADERS) $(LIB) | $(BIN_DIR)
$(CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)

# Compile the sources and tests together with TC_SEGMENT_HEADER_ENABLED so the
# segment-header code paths are built and exercised.
$(TEST_BIN): $(SRCS) $(TEST_SRCS) | $(BIN_DIR)
$(CC) $(CFLAGS) -DTC_SEGMENT_HEADER_ENABLED $(SRCS) $(TEST_SRCS) -o $@ $(LDFLAGS)
# Segment-header configuration: the library is built without it, so its sources are
# compiled together with each example, all with SDLP_TC_SEGMENT_HEADER_ENABLED.
$(SEGMENT_DIR)/%: $(EXAMPLES_DIR)/%.c $(EXAMPLE_HEADERS) $(PUBLIC_HEADERS) $(SRCS) | $(SEGMENT_DIR)
$(CC) $(CFLAGS) -DSDLP_TC_SEGMENT_HEADER_ENABLED $< $(SRCS) -o $@ $(LDFLAGS)

# Default configuration: the tests link the library exactly as `make lib` builds it.
$(TEST_BIN): $(TEST_SRCS) $(TEST_HEADERS) $(PUBLIC_HEADERS) $(LIB) | $(BIN_DIR)
$(CC) $(CFLAGS) $(TEST_SRCS) $(LIB) -o $@ $(LDFLAGS)

# Segment-header configuration: the library is built without it, so the sources are
# compiled together with the tests, all with SDLP_TC_SEGMENT_HEADER_ENABLED.
$(SEGMENT_TEST_BIN): $(SRCS) $(TEST_SRCS) $(TEST_HEADERS) $(PUBLIC_HEADERS) | $(SEGMENT_DIR)
$(CC) $(CFLAGS) -DSDLP_TC_SEGMENT_HEADER_ENABLED $(SRCS) $(TEST_SRCS) -o $@ $(LDFLAGS)

$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
Expand All @@ -57,12 +89,48 @@ $(OBJ_DIR): | $(BUILD_DIR)
$(BIN_DIR): | $(BUILD_DIR)
mkdir -p $(BIN_DIR)

$(SEGMENT_DIR): | $(BUILD_DIR)
mkdir -p $(SEGMENT_DIR)

clean:
rm -rf $(BUILD_DIR) obj bin libsdlp.a
rm -rf $(BUILD_DIR)

coverage-html:
bash tools/coverage_html.sh

# Rebuild everything with ASan + UBSan and run the unit tests and the examples.
# Program output is shown only on failure; sanitizer reports go to stderr and are always
# visible. The instrumented build is removed afterwards, on success and on failure.
sanitize:
@$(MAKE) --no-print-directory clean >/dev/null
@$(MAKE) --no-print-directory lib examples unit-tests OPT="$(SANITIZE_OPT)" >/dev/null \
|| { $(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@mkdir -p $(SANITIZE_DIR)
@echo "Sanitizers (ASan + UBSan):"
@./$(TEST_BIN) >$(SANITIZE_DIR)/unit_tests.log \
&& echo " library via unit tests : no errors detected" \
|| { cat $(SANITIZE_DIR)/unit_tests.log; \
echo " library via unit tests : FAILED"; \
$(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@./$(SEGMENT_TEST_BIN) >$(SANITIZE_DIR)/unit_tests_segment_header.log \
&& echo " library via unit tests (segment header) : no errors detected" \
|| { cat $(SANITIZE_DIR)/unit_tests_segment_header.log; \
echo " library via unit tests (segment header) : FAILED"; \
$(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@for example in $(EXAMPLE_BINS) $(SEGMENT_EXAMPLE_BINS); do \
name=$$(basename $$example); \
case $$example in $(SEGMENT_DIR)/*) name="$$name (segment header)";; esac; \
log=$(SANITIZE_DIR)/$$(echo $$example | tr '/' '_').log; \
./$$example >$$log \
&& printf " library via %-27s : no errors detected\n" "$$name" \
|| { cat $$log; printf " library via %-27s : FAILED\n" "$$name"; \
$(MAKE) --no-print-directory clean >/dev/null; exit 1; }; \
done
@$(MAKE) --no-print-directory clean >/dev/null
@echo "Result: PASS"

test: unit-tests
@echo "Running unit tests (TC_SEGMENT_HEADER_ENABLED)..."
@echo "Running unit tests (default configuration)..."
@./$(TEST_BIN)
@echo "Running unit tests (SDLP_TC_SEGMENT_HEADER_ENABLED)..."
@./$(SEGMENT_TEST_BIN)
51 changes: 38 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@ Minimal, embedded-optimized implementation of **CCSDS Space Data Link Protocol (
- **TM Secondary Header**: Optional Transfer Frame Secondary Header (1–63 data octets) via `sdlp_tm_set_secondary_header`, signaled by the Secondary Header Flag and parsed automatically on decode
- **TM Operational Control Field**: Optional 4-octet OCF carried verbatim via `sdlp_tm_set_ocf` (the caller decides whether to set the OCF Flag); its content, e.g. a CLCW, is mission-specific and left to the application
- **TC Frame Types**: Type-AD/BD/BC frames via `sdlp_tc_set_frame_type`, plus Unlock and Set V(R) control-command helpers (`sdlp_tc_create_unlock_frame`, `sdlp_tc_create_set_vr_frame`)
- **TC Segment Header**: Optional MAP-based segmentation support (enabled with `TC_SEGMENT_HEADER_ENABLED`)
- **TC Segment Header**: Optional MAP-based segmentation support (enabled with `SDLP_TC_SEGMENT_HEADER_ENABLED`)

### Design Principles

- **Minimal footprint**: Small library size (stripped)
- **Zero allocation**: Stack-based, no dynamic memory
- **Embedded-optimized**: Pure C11, no external dependencies
- **Portable**: Standard C11, big-endian network byte order
- **Embedded-optimized**: Pure C99, no external dependencies
- **Portable**: Standard C99, big-endian network byte order

## Project Structure

```
EmbeddedSDLP/
├── .github/
│ └── workflows/
│ └── ci.yml # CI: unit tests, coverage gate, sanitizers
├── include/
│ ├── sdlp_common.h # Common definitions and error codes
│ ├── sdlp_tm.h # TM frame definitions
Expand All @@ -50,15 +53,15 @@ EmbeddedSDLP/
│ └── unit_tests.c # Test entry point (aggregates per-module results)
├── tools/
│ └── coverage_html.sh # Coverage (HTML) report generator
├── docs/
│ ├── 132x0b3_TM_SDLP.pdf # CCSDS 132.0-B-3 standard
│ └── 232x0b4e1c1_TC_SDLP.pdf # CCSDS 232.0-B-4 standard
├── Makefile
└── README.md
```

## Building

Warnings are treated as errors (`-Werror`). The build is checked with both `gcc` and
`clang`; select the compiler with `make CC=clang`.

### Build Everything

```bash
Expand All @@ -69,6 +72,8 @@ This will create:
- `build/libsdlp.a` - Static library
- `build/bin/tm_example` - TM frame example
- `build/bin/tc_example` - TC frame example
- `build/segment_header/tm_example` and `build/segment_header/tc_example` - the same
examples built with the optional TC Segment Header (`SDLP_TC_SEGMENT_HEADER_ENABLED`)

### Build Library Only

Expand All @@ -80,7 +85,8 @@ make lib
### Build Examples

```bash
make examples
make examples # build only
make example # build, then run tm_example and tc_example
```

### Run Tests
Expand All @@ -89,6 +95,9 @@ make examples
make test
```

The unit tests are built and run twice: against the library as `make lib` builds it, and
again with the optional TC Segment Header compiled in (`SDLP_TC_SEGMENT_HEADER_ENABLED`).

### Coverage (HTML)

Requires `gcovr` installed in your system:
Expand All @@ -103,12 +112,25 @@ Generate coverage report:
make coverage-html
```

The command fails unless line and branch coverage of `src/` are both 100% in each of the
two build configurations (without and with the TC Segment Header). It rebuilds the unit
tests from scratch with the Makefile's own flags plus coverage instrumentation.

Output report:

```text
build/coverage/index.html
```

### Sanitizers (ASan + UBSan)

```bash
make sanitize # rebuild with ASan + UBSan, run tests and examples, then clean up
```

The target starts from a clean build and removes `build/` again when it finishes, so a
coverage report generated earlier is deleted with it.

### Clean

```bash
Expand All @@ -122,9 +144,9 @@ Look at the examples/
## Memory Usage (Estimated)

- **Library (stripped)**: < 5 KB
- **Per TM frame buffer**: `TM_PRIMARY_HEADER_SIZE` (6) + optional secondary header (≤ 64) + data + optional OCF (4) + 2 bytes FECF
- **Per TC frame buffer**: `TC_PRIMARY_HEADER_SIZE` (5) + optional segment header (1) + data + 2 bytes FECF
- **Maximum data per frame**: `TM_MAX_DATA_SIZE` = 1024 bytes (TM); `TC_MAX_DATA_SIZE` = 1017 bytes (TC — the whole frame is capped at 1024 octets per CCSDS 232.0-B-4; one less when the TC segment header is enabled)
- **Per TM frame buffer**: `SDLP_TM_PRIMARY_HEADER_SIZE` (6) + optional secondary header (≤ 64) + data + optional OCF (4) + 2 bytes FECF
- **Per TC frame buffer**: `SDLP_TC_PRIMARY_HEADER_SIZE` (5) + optional segment header (1) + data + 2 bytes FECF
- **Maximum data per frame**: `SDLP_TM_MAX_DATA_SIZE` = 1024 bytes (TM); `SDLP_TC_MAX_DATA_SIZE` = 1017 bytes (TC — the whole frame is capped at 1024 octets per CCSDS 232.0-B-4; one less when the TC segment header is enabled)

## Limitations and Extensions

Expand All @@ -136,14 +158,17 @@ Current implementation focuses on core protocol features:
- No SDLS (Space Data Link Security) option
- No TM Only-Idle-Data (OID) frame generation or PN randomization
- TM Transfer Frames are variable length; the mission-fixed frame length must be enforced by the caller
- TM frame counts are kept per Master Channel / Virtual Channel in fixed static state (up to `TM_MAX_MASTER_CHANNELS` Master Channels; not thread-safe)
- The 2-octet FECF is always emitted, although both standards make it optional
- The TC Segment Header is a build-time switch for all Virtual Channels, not per Virtual Channel; it changes `sdlp_tc_frame_t`, so build the library and the application with the same setting
- Decoders do not check the Transfer Frame Version Number
- TM frame counts are kept per Master Channel / Virtual Channel in fixed static state (not thread-safe). At most `SDLP_TM_MAX_MASTER_CHANNELS` (default 8) Spacecraft IDs are tracked; `sdlp_tm_create_frame` returns `SDLP_ERROR_NO_RESOURCE` for one more, and `sdlp_tm_reset_frame_counts` empties the table

These can be extended as needed for specific mission requirements.

## References

- CCSDS 132.0-B-3: TM Space Data Link Protocol ([docs/132x0b3_TM_SDLP.pdf](docs/132x0b3_TM_SDLP.pdf))
- CCSDS 232.0-B-4: TC Space Data Link Protocol ([docs/232x0b4e1c1_TC_SDLP.pdf](docs/232x0b4e1c1_TC_SDLP.pdf))
- [CCSDS 132.0-B-3: TM Space Data Link Protocol](https://public.ccsds.org/Pubs/132x0b3.pdf)
- [CCSDS 232.0-B-4: TC Space Data Link Protocol](https://public.ccsds.org/Pubs/232x0b4e1c1.pdf)

## License

Expand Down
Binary file removed docs/132x0b3_TM_SDLP.pdf
Binary file not shown.
Binary file removed docs/232x0b4e1c1_TC_SDLP.pdf
Binary file not shown.
Loading
Loading