Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 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
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
94 changes: 81 additions & 13 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,19 +51,32 @@ 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)
# Segment-header configuration: the library is built without it, so its sources are
# compiled together with each example, all with TC_SEGMENT_HEADER_ENABLED.
$(SEGMENT_DIR)/%: $(EXAMPLES_DIR)/%.c $(EXAMPLE_HEADERS) $(PUBLIC_HEADERS) $(SRCS) | $(SEGMENT_DIR)
$(CC) $(CFLAGS) -DTC_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 TC_SEGMENT_HEADER_ENABLED.
$(SEGMENT_TEST_BIN): $(SRCS) $(TEST_SRCS) $(TEST_HEADERS) $(PUBLIC_HEADERS) | $(SEGMENT_DIR)
$(CC) $(CFLAGS) -DTC_SEGMENT_HEADER_ENABLED $(SRCS) $(TEST_SRCS) -o $@ $(LDFLAGS)

$(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 (TC_SEGMENT_HEADER_ENABLED)..."
@./$(SEGMENT_TEST_BIN)
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ Minimal, embedded-optimized implementation of **CCSDS Space Data Link Protocol (

- **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 (`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 (`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 Down Expand Up @@ -136,14 +158,14 @@ 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)
- TM frame counts are kept per Master Channel / Virtual Channel in fixed static state (not thread-safe). At most `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.
22 changes: 16 additions & 6 deletions examples/example_crc.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,40 @@
#include <stddef.h>
#include <stdint.h>

/** @brief CRC-16-CCITT generator polynomial: x^16 + x^12 + x^5 + 1. */
#define EXAMPLE_CRC16_POLYNOMIAL 0x1021u

/** @brief CRC-16-CCITT shift register preset: all ones. */
#define EXAMPLE_CRC16_INITIAL_VALUE 0xFFFFu

/** @brief Bits shifted through the register for each input octet. */
#define EXAMPLE_CRC16_BITS_PER_OCTET 8

/**
* @brief Compute a CRC-16-CCITT (polynomial 0x1021, initial value 0xFFFF).
* @brief Compute a CRC-16-CCITT (::EXAMPLE_CRC16_POLYNOMIAL, preset to
* ::EXAMPLE_CRC16_INITIAL_VALUE).
*
* @param[in] data Input octets.
* @param[in] length Number of octets.
* @return The 16-bit CRC.
*/
static inline uint16_t example_crc16(const uint8_t *data, size_t length)
{
uint16_t crc = 0xFFFFu;
uint16_t crc = EXAMPLE_CRC16_INITIAL_VALUE;

for (size_t i = 0; i < length; i++)
{
crc ^= (uint16_t)data[i] << 8;
crc = (uint16_t)(crc ^ ((unsigned)data[i] << 8));

for (int j = 0; j < 8; j++)
for (int j = 0; j < EXAMPLE_CRC16_BITS_PER_OCTET; j++)
{
if (crc & 0x8000u)
{
crc = (crc << 1) ^ 0x1021u;
crc = (uint16_t)(((unsigned)crc << 1) ^ EXAMPLE_CRC16_POLYNOMIAL);
}
else
{
crc = crc << 1;
crc = (uint16_t)((unsigned)crc << 1);
}
}
}
Expand Down
Loading
Loading