Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 54 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 libraries, examples and run unit tests
run: make CC=${{ matrix.cc }} && make test CC=${{ matrix.cc }}
- name: Run examples
run: make example CC=${{ matrix.cc }}

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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,4 @@ dkms.conf

# project build directories
build/
.github/
.vscode/
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
56 changes: 46 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
CC ?= cc
AR ?= ar
OPT ?= -O2
CFLAGS ?= -std=c99 -Wall -Wextra -Iinclude
SANITIZE_OPT = -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined \
-fno-sanitize-recover=all
CFLAGS ?= -std=c99 -Iinclude
BUILD_DIR = build
SANITIZE_DIR = $(BUILD_DIR)/sanitize

WARNINGS = -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror
ALL_CFLAGS = $(CFLAGS) $(WARNINGS)

CUC_LIB = $(BUILD_DIR)/libcuc.a
CUC_OBJ = $(BUILD_DIR)/src/cuc.o
Expand Down Expand Up @@ -31,23 +37,23 @@ lib: $(LIBS)

$(CUC_OBJ): $(CUC_SRC) $(CUC_HDR)
mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OPT) -Iinclude -c $(CUC_SRC) -o $@
$(CC) $(ALL_CFLAGS) $(OPT) -Iinclude -c $(CUC_SRC) -o $@

$(CUC_LIB): $(CUC_OBJ)
mkdir -p $(dir $@)
$(AR) rcs $@ $(CUC_OBJ)

$(CDS_OBJ): $(CDS_SRC) $(CDS_HDR)
mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OPT) -Iinclude -c $(CDS_SRC) -o $@
$(CC) $(ALL_CFLAGS) $(OPT) -Iinclude -c $(CDS_SRC) -o $@

$(CDS_LIB): $(CDS_OBJ)
mkdir -p $(dir $@)
$(AR) rcs $@ $(CDS_OBJ)

$(CCS_OBJ): $(CCS_SRC) $(CCS_HDR)
mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OPT) -Iinclude -c $(CCS_SRC) -o $@
$(CC) $(ALL_CFLAGS) $(OPT) -Iinclude -c $(CCS_SRC) -o $@

$(CCS_LIB): $(CCS_OBJ)
mkdir -p $(dir $@)
Expand All @@ -58,31 +64,61 @@ ctest: $(CTEST)
$(CTEST): tests/unit_tests.c tests/test_cuc.c tests/test_cds.c tests/test_ccs.c tests/cunit.h \
tests/test_runners.h $(CUC_SRC) $(CUC_HDR) $(CDS_SRC) $(CDS_HDR) $(CCS_SRC) $(CCS_HDR)
mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OPT) -Iinclude -Itests \
$(CC) $(ALL_CFLAGS) $(OPT) -Iinclude -Itests \
tests/unit_tests.c tests/test_cuc.c tests/test_cds.c tests/test_ccs.c \
$(CUC_SRC) $(CDS_SRC) $(CCS_SRC) -o $@

example: $(EXAMPLES)
$(CUC_EXAMPLE)
$(CDS_EXAMPLE)
$(CCS_EXAMPLE)

$(CUC_EXAMPLE): examples/cuc_example.c $(CUC_SRC) $(CUC_HDR)
mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OPT) -Iinclude examples/cuc_example.c $(CUC_SRC) -o $@
$(CC) $(ALL_CFLAGS) $(OPT) -Iinclude examples/cuc_example.c $(CUC_SRC) -o $@

$(CDS_EXAMPLE): examples/cds_example.c $(CDS_SRC) $(CDS_HDR)
mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OPT) -Iinclude examples/cds_example.c $(CDS_SRC) -o $@
$(CC) $(ALL_CFLAGS) $(OPT) -Iinclude examples/cds_example.c $(CDS_SRC) -o $@

$(CCS_EXAMPLE): examples/ccs_example.c $(CCS_SRC) $(CCS_HDR)
mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OPT) -Iinclude examples/ccs_example.c $(CCS_SRC) -o $@
$(CC) $(ALL_CFLAGS) $(OPT) -Iinclude examples/ccs_example.c $(CCS_SRC) -o $@

run: $(CTEST)
test: $(CTEST)
$(CTEST)

# Instrumented rebuild; program output is shown only on failure and the build is removed
# afterwards, on success and on failure.
sanitize:
@$(MAKE) --no-print-directory clean >/dev/null
@$(MAKE) --no-print-directory ctest example OPT="$(SANITIZE_OPT)" >/dev/null \
|| { $(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@mkdir -p $(SANITIZE_DIR)
@echo "Sanitizers (ASan + UBSan):"
@./$(CTEST) >$(SANITIZE_DIR)/unit_tests.log \
&& echo " libraries via unit tests : no errors detected" \
|| { cat $(SANITIZE_DIR)/unit_tests.log; echo " libraries via unit tests : FAILED"; \
$(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@./$(CUC_EXAMPLE) >$(SANITIZE_DIR)/cuc_example.log \
&& echo " cuc via example : no errors detected" \
|| { cat $(SANITIZE_DIR)/cuc_example.log; echo " cuc via example : FAILED"; \
$(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@./$(CDS_EXAMPLE) >$(SANITIZE_DIR)/cds_example.log \
&& echo " cds via example : no errors detected" \
|| { cat $(SANITIZE_DIR)/cds_example.log; echo " cds via example : FAILED"; \
$(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@./$(CCS_EXAMPLE) >$(SANITIZE_DIR)/ccs_example.log \
&& echo " ccs via example : no errors detected" \
|| { cat $(SANITIZE_DIR)/ccs_example.log; echo " ccs via example : FAILED"; \
$(MAKE) --no-print-directory clean >/dev/null; exit 1; }
@$(MAKE) --no-print-directory clean >/dev/null
@echo "Result: PASS"

coverage-html:
bash tools/coverage-html.sh

clean:
rm -rf $(BUILD_DIR)

.PHONY: all lib ctest example run coverage-html clean
.PHONY: all lib ctest example test sanitize coverage-html clean
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,26 @@ make lib # produces build/libcuc.a, build/libcds.a and build/libccs.a

```bash
make example
./build/examples/cuc_example
./build/examples/cds_example
./build/examples/ccs_example
```

### Run the tests

```bash
make run # or: make ctest && ./build/tests/ctest
make test # or: make ctest && ./build/tests/ctest
```

### Sanitizers

Runs the test suite and the examples under AddressSanitizer and
UndefinedBehaviorSanitizer:

```bash
make sanitize
```

### Coverage (HTML)

Requires `gcovr` (`pip install gcovr`):
Requires `gcovr` (`pip install gcovr`). Fails below 100% line or branch coverage:

```bash
make coverage-html # writes build/coverage/index.html
Expand Down
3 changes: 3 additions & 0 deletions examples/ccs_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* Encodes a calendar date and time of day into a self-identified CCS code and
* decodes it back, printing the octets. Demonstrates CCSDS 301.0-B-4, 3.4.
*
* Copyright 2026 OpenSpaceCode contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSpaceCode — https://github.com/OpenSpaceCode
*/

Expand Down
3 changes: 3 additions & 0 deletions examples/cds_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* Encodes a day/millisecond time value into a self-identified CDS code and
* decodes it back, printing the octets. Demonstrates CCSDS 301.0-B-4, 3.3.
*
* Copyright 2026 OpenSpaceCode contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSpaceCode — https://github.com/OpenSpaceCode
*/

Expand Down
3 changes: 3 additions & 0 deletions examples/cuc_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
* cuc_time_to_seconds() / cuc_time_from_seconds() helpers are deliberately
* unused here.
*
* Copyright 2026 OpenSpaceCode contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSpaceCode — https://github.com/OpenSpaceCode
*/

Expand Down
15 changes: 11 additions & 4 deletions include/ccs.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
* is carried in a TIME SPECIFICATION FIELD (T-field) preceded by a one-octet
* TIME CODE PREAMBLE FIELD (P-field) describing its structure.
*
* Copyright 2026 OpenSpaceCode contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSpaceCode — https://github.com/OpenSpaceCode
*/

Expand Down Expand Up @@ -219,6 +222,8 @@ ccs_status_t ccs_pfield_encode(const ccs_format_t *fmt,
/**
* @brief Decode a P-field into a format.
*
* @note On failure @p fmt and @p consumed are left unchanged.
*
* @param[in] buf Input buffer positioned at the P-field.
* @param[in] buf_len Number of octets available in @p buf.
* @param[out] fmt Receives the decoded format.
Expand Down Expand Up @@ -282,10 +287,9 @@ ccs_status_t ccs_tfield_decode(const uint8_t *buf,
/**
* @brief Encode a self-identified CCS code: P-field followed by T-field.
*
* @note On failure the contents of @p buf are unspecified, since the P-field may
* already be written when the T-field stage rejects the value. Only a
* #CCS_OK return sets @p written, so a caller that checks the status never
* transmits a partial code.
* @note On failure @p buf and @p written are left unchanged: the code is assembled in a
* local buffer and copied out only once every stage has succeeded and the total
* length is known to fit.
*
* @param[in] time Time value to encode.
* @param[in] fmt Format to encode.
Expand All @@ -306,6 +310,9 @@ ccs_status_t ccs_encode(const ccs_time_t *time,
/**
* @brief Decode a self-identified CCS code: parse the P-field, then the T-field.
*
* @note On failure @p fmt, @p time and @p consumed are left unchanged; the code is
* decoded into locals and committed only once every check has passed.
*
* @param[in] buf Input buffer positioned at the P-field.
* @param[in] buf_len Number of octets available in @p buf.
* @param[out] fmt Receives the recovered format.
Expand Down
15 changes: 11 additions & 4 deletions include/cds.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
* carried in a TIME SPECIFICATION FIELD (T-field) preceded by a one-octet
* TIME CODE PREAMBLE FIELD (P-field) describing its structure.
*
* Copyright 2026 OpenSpaceCode contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSpaceCode — https://github.com/OpenSpaceCode
*/

Expand Down Expand Up @@ -200,6 +203,8 @@ cds_status_t cds_pfield_encode(const cds_format_t *fmt,
/**
* @brief Decode a P-field into a format.
*
* @note On failure @p fmt and @p consumed are left unchanged.
*
* @param[in] buf Input buffer positioned at the P-field.
* @param[in] buf_len Number of octets available in @p buf.
* @param[out] fmt Receives the decoded format.
Expand Down Expand Up @@ -256,10 +261,9 @@ cds_status_t cds_tfield_decode(const uint8_t *buf,
/**
* @brief Encode a self-identified CDS code: P-field followed by T-field.
*
* @note On failure the contents of @p buf are unspecified, since the P-field may
* already be written when the T-field stage rejects the value. Only a
* #CDS_OK return sets @p written, so a caller that checks the status never
* transmits a partial code.
* @note On failure @p buf and @p written are left unchanged: the code is assembled in a
* local buffer and copied out only once every stage has succeeded and the total
* length is known to fit.
*
* @param[in] time Time value to encode.
* @param[in] fmt Format to encode.
Expand All @@ -280,6 +284,9 @@ cds_status_t cds_encode(const cds_time_t *time,
/**
* @brief Decode a self-identified CDS code: parse the P-field, then the T-field.
*
* @note On failure @p fmt, @p time and @p consumed are left unchanged; the code is
* decoded into locals and committed only once every check has passed.
*
* @param[in] buf Input buffer positioned at the P-field.
* @param[in] buf_len Number of octets available in @p buf.
* @param[out] fmt Receives the recovered format.
Expand Down
15 changes: 11 additions & 4 deletions include/cuc.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
* carried in a TIME SPECIFICATION FIELD (T-field) that may be preceded by an
* explicit TIME CODE PREAMBLE FIELD (P-field) describing its structure.
*
* Copyright 2026 OpenSpaceCode contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSpaceCode — https://github.com/OpenSpaceCode
*/

Expand Down Expand Up @@ -187,6 +190,8 @@ cuc_status_t cuc_pfield_encode(const cuc_format_t *fmt,
/**
* @brief Decode a P-field into a format.
*
* @note On failure @p fmt and @p consumed are left unchanged.
*
* @param[in] buf Input buffer positioned at the P-field.
* @param[in] buf_len Number of octets available in @p buf.
* @param[out] fmt Receives the decoded format.
Expand Down Expand Up @@ -242,10 +247,9 @@ cuc_status_t cuc_tfield_decode(const uint8_t *buf,
/**
* @brief Encode a self-identified CUC code: P-field followed by T-field.
*
* @note On failure the contents of @p buf are unspecified, since the P-field may
* already be written when the T-field stage rejects the value. Only a
* #CUC_OK return sets @p written, so a caller that checks the status never
* transmits a partial code.
* @note On failure @p buf and @p written are left unchanged: the code is assembled in a
* local buffer and copied out only once every stage has succeeded and the total
* length is known to fit.
*
* @param[in] time Time value to encode.
* @param[in] fmt Format to encode.
Expand All @@ -265,6 +269,9 @@ cuc_status_t cuc_encode(const cuc_time_t *time,
/**
* @brief Decode a self-identified CUC code: parse the P-field, then the T-field.
*
* @note On failure @p fmt, @p time and @p consumed are left unchanged; the code is
* decoded into locals and committed only once every check has passed.
*
* @note @p consumed is the authoritative length of the code just read, and is what a
* caller must advance by when codes are concatenated. It can exceed
* cuc_size(@p fmt), which reports the shortest encoding of the recovered format
Expand Down
Loading
Loading