Skip to content

Commit d67f205

Browse files
init commit
1 parent f0e82c9 commit d67f205

File tree

9 files changed

+524
-0
lines changed

9 files changed

+524
-0
lines changed

.github/workflows/ci.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Install build tools and gtest
15+
run: |
16+
sudo apt-get update
17+
sudo apt-get install -y build-essential cmake libgtest-dev
18+
# Build and install gtest binaries (Ubuntu provides sources only)
19+
pushd /usr/src/gtest || exit 0
20+
cmake . && make || true
21+
sudo cp libgtest*.a /usr/lib/ || true
22+
popd || true
23+
- name: Build
24+
run: make
25+
- name: Run C tests
26+
run: |
27+
if [ -x tests/test_packet ]; then
28+
./tests/test_packet
29+
else
30+
echo "tests/test_packet missing"; exit 1
31+
fi
32+
- name: Build and run C tests
33+
run: |
34+
make ctest
35+
if [ -x tests/ctest ]; then
36+
./tests/ctest
37+
elif [ -x tests/ctest ] || [ -x tests/ctest ]; then
38+
./tests/ctest
39+
elif [ -x tests/ctest ]; then
40+
./tests/ctest
41+
else
42+
# older make target produces tests/ctest or tests/ctest
43+
if [ -x tests/ctest ]; then ./tests/ctest; else echo "ctest binary missing"; exit 1; fi
44+
fi

Makefile

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
CC ?= cc
2+
CFLAGS ?= -O2 -Iinclude -Wall -Wextra -std=c99
3+
4+
CC ?= cc
5+
CXX ?= g++
6+
CFLAGS ?= -O2 -Iinclude -Wall -Wextra -std=c99
7+
CXXFLAGS ?= -O2 -Iinclude -Wall -Wextra -std=gnu++11
8+
AR ?= ar
9+
10+
PREFIX ?= /usr/local
11+
12+
LIBNAME = libspacepacket.a
13+
14+
all: lib example test ctest
15+
16+
lib: src/space_packet.c
17+
$(CC) $(CFLAGS) -c src/space_packet.c -o src/space_packet.o
18+
$(AR) rcs $(LIBNAME) src/space_packet.o
19+
20+
example: lib examples/main.c
21+
$(CC) $(CFLAGS) -Iinclude -L. examples/main.c -L. -lspacepacket -o examples/spacepacket_example
22+
23+
24+
ctest: lib tests/unit_tests.c
25+
$(CC) $(CFLAGS) -Iinclude tests/unit_tests.c -L. -lspacepacket -o tests/ctest
26+
27+
clean:
28+
rm -f src/*.o $(LIBNAME) examples/spacepacket_example tests/test_packet tests/gtest_tests
29+
30+
install: lib
31+
mkdir -p $(PREFIX)/lib $(PREFIX)/include
32+
cp $(LIBNAME) $(PREFIX)/lib/
33+
cp include/space_packet.h $(PREFIX)/include/
34+
35+
.PHONY: all lib example test gtest clean install

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,29 @@
11
# EmbeddedSpacePacket
22
Minimum SpacePacket implementation in C for embedded applications
3+
4+
Contents
5+
- tiny header and implementation in `include/` and `src/`
6+
- example in `examples/`
7+
- simple host test in `tests/`
8+
- `Makefile` to build library, example and test
9+
10+
Build & run (host):
11+
12+
1. Build everything:
13+
14+
make
15+
16+
2. Run example:
17+
18+
./examples/spacepacket_example
19+
20+
3. Run tests:
21+
22+
./tests/test_packet
23+
24+
Notes
25+
- This is a minimal, small-footprint implementation designed for embedded use.
26+
- The primary header implemented is CCSDS-like (6 bytes). The packet length field
27+
in the header stores `payload_len - 1` when serializing and the parser reconstructs
28+
`payload_len = header_length + 1`.
29+

examples/main.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <stdio.h>
2+
#include <stdint.h>
3+
#include <string.h>
4+
#include "../include/space_packet.h"
5+
6+
int main(void) {
7+
const uint8_t payload[] = { 'H','e','l','l','o',' ', 'S','P' };
8+
sp_packet_t pkt = {0};
9+
pkt.ph.apid = 0x100;
10+
pkt.ph.seq_count = 1;
11+
/* Example with a minimal secondary header containing flags and zero extra bytes.
12+
* flags byte LSB=1 requests CRC; byte1=0 indicates zero remaining sec header bytes.
13+
*/
14+
const uint8_t sec_hdr[] = { 0x1, 0x0 };
15+
pkt.ph.sec_hdr_flag = 1;
16+
pkt.sec_hdr = sec_hdr;
17+
pkt.sec_hdr_len = sizeof(sec_hdr);
18+
pkt.payload = payload;
19+
pkt.payload_len = sizeof(payload);
20+
21+
uint8_t buf[256];
22+
size_t n = sp_packet_serialize(&pkt, buf, sizeof(buf));
23+
if (n == 0) {
24+
printf("serialize failed\n");
25+
return 1;
26+
}
27+
28+
printf("Serialized %zu bytes:\n", n);
29+
for (size_t i = 0; i < n; ++i) printf("%02X ", buf[i]);
30+
printf("\n");
31+
32+
sp_packet_t parsed;
33+
if (!sp_packet_parse(&parsed, buf, n)) {
34+
printf("parse failed\n");
35+
return 2;
36+
}
37+
38+
printf("Parsed APID=0x%03X seq=%u payload_len=%u\n",
39+
parsed.ph.apid, parsed.ph.seq_count, parsed.payload_len);
40+
printf("Payload as ASCII: ");
41+
fwrite(parsed.payload, 1, parsed.payload_len, stdout);
42+
printf("\n");
43+
44+
return 0;
45+
}

include/space_packet.h

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* Minimal SpacePacket header for embedded applications
2+
* Provides a tiny API to create, serialize and parse CCSDS-like packets.
3+
*/
4+
#ifndef SPACE_PACKET_H
5+
#define SPACE_PACKET_H
6+
7+
#include <stdint.h>
8+
#include <stddef.h>
9+
10+
/* Primary header is 6 bytes (CCSDS-like):
11+
* - bytes 0-1: version(3), type(1), sec_hdr(1), apid(11)
12+
* - bytes 2-3: seq_flags(2), seq_count(14)
13+
* - bytes 4-5: packet_length (payload length - 1)
14+
*/
15+
16+
typedef struct {
17+
/* Primary header represented as bitfields (CCSDS-like) */
18+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
19+
unsigned version:3;
20+
unsigned type:1;
21+
unsigned sec_hdr_flag:1; /* whether secondary header present */
22+
unsigned apid:11;
23+
#else
24+
unsigned apid:11;
25+
unsigned sec_hdr_flag:1;
26+
unsigned type:1;
27+
unsigned version:3;
28+
#endif
29+
/* sequence control */
30+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
31+
unsigned seq_flags:2;
32+
unsigned seq_count:14;
33+
#else
34+
unsigned seq_count:14;
35+
unsigned seq_flags:2;
36+
#endif
37+
uint16_t packet_length;
38+
} sp_primary_header_t;
39+
40+
typedef struct {
41+
sp_primary_header_t ph; /* primary header (bitwise representation) */
42+
43+
const uint8_t *sec_hdr; /* secondary header pointer (if ph.bits.sec_hdr_flag), as provided */
44+
uint16_t sec_hdr_len; /* total secondary header length in bytes (>=2 when present)
45+
* Layout: byte0 = flags, byte1 = remaining_sec_len (n), followed by n bytes
46+
*/
47+
const uint8_t *payload; /* points into a buffer when parsed */
48+
uint16_t payload_len;
49+
/* If the secondary header flags (byte0) LSB is 1, a 16-bit CRC (big-endian)
50+
* is appended after payload. The CRC covers the secondary header and payload.
51+
*/
52+
} sp_packet_t;
53+
54+
/* Return required buffer size to serialize this packet (header + payload) */
55+
size_t sp_packet_serialize_size(const sp_packet_t *pkt);
56+
57+
/* Serialize packet into `buf` of length `buf_len`. Returns bytes written or 0 on error. */
58+
size_t sp_packet_serialize(const sp_packet_t *pkt, uint8_t *buf, size_t buf_len);
59+
60+
/* Parse buffer into packet fields. Note: this does not allocate payload memory; it sets
61+
* `out->payload` to point into `buf`. Returns 1 on success, 0 on failure.
62+
*/
63+
int sp_packet_parse(sp_packet_t *out, uint8_t *buf, size_t buf_len);
64+
65+
/* Utility: compute CRC-16-CCITT (polynomial 0x1021, init 0xFFFF). Returns 16-bit CRC. */
66+
uint16_t sp_crc16_ccitt(const uint8_t *data, size_t len);
67+
68+
#endif /* SPACE_PACKET_H */

src/space_packet.c

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#include "../include/space_packet.h"
2+
#include <string.h>
3+
#include <stdlib.h>
4+
5+
/* Minimal serializer/parser for a 6-byte primary header + payload.
6+
* Uses big-endian network byte order for header fields.
7+
*/
8+
9+
size_t sp_packet_serialize_size(const sp_packet_t *pkt) {
10+
if (!pkt) return 0;
11+
size_t total = 6;
12+
if (pkt->ph.sec_hdr_flag) total += pkt->sec_hdr_len;
13+
total += pkt->payload_len;
14+
/* if secondary header flags request CRC, include 2 bytes
15+
* (we can't inspect sec_hdr here without checking length) */
16+
if (pkt->ph.sec_hdr_flag && pkt->sec_hdr_len >= 2) {
17+
if (pkt->sec_hdr[0] & 0x1) total += 2;
18+
}
19+
return total;
20+
}
21+
22+
size_t sp_packet_serialize(const sp_packet_t *pkt, uint8_t *buf, size_t buf_len) {
23+
if (!pkt || !buf) return 0;
24+
size_t need = sp_packet_serialize_size(pkt);
25+
if (buf_len < need) return 0;
26+
27+
uint16_t first = 0;
28+
uint16_t second = 0;
29+
uint16_t length_field = 0;
30+
31+
/* compose first and second words from bitfields */
32+
first = (uint16_t)(((pkt->ph.version & 0x7) << 13) |
33+
((pkt->ph.type & 0x1) << 12) |
34+
((pkt->ph.sec_hdr_flag & 0x1) << 11) |
35+
(pkt->ph.apid & 0x07FF));
36+
37+
second = (uint16_t)((((pkt->ph.seq_flags & 0x3) << 14) | (pkt->ph.seq_count & 0x3FFF)));
38+
39+
/* Compute total length (secondary header + payload [+crc]) */
40+
uint16_t total_len = 0;
41+
if (pkt->ph.sec_hdr_flag) total_len += pkt->sec_hdr_len;
42+
total_len += pkt->payload_len;
43+
int crc_present = 0;
44+
if (pkt->ph.sec_hdr_flag && pkt->sec_hdr_len >= 2) {
45+
crc_present = (pkt->sec_hdr[0] & 0x1) ? 1 : 0;
46+
}
47+
if (crc_present) total_len += 2;
48+
49+
if (total_len == 0) length_field = 0;
50+
else length_field = (uint16_t)(total_len - 1);
51+
52+
/* write big-endian */
53+
buf[0] = (uint8_t)((first >> 8) & 0xFF);
54+
buf[1] = (uint8_t)(first & 0xFF);
55+
buf[2] = (uint8_t)((second >> 8) & 0xFF);
56+
buf[3] = (uint8_t)(second & 0xFF);
57+
buf[4] = (uint8_t)((length_field >> 8) & 0xFF);
58+
buf[5] = (uint8_t)(length_field & 0xFF);
59+
60+
size_t off = 6;
61+
if (pkt->ph.sec_hdr_flag && pkt->sec_hdr && pkt->sec_hdr_len) {
62+
memcpy(&buf[off], pkt->sec_hdr, pkt->sec_hdr_len);
63+
off += pkt->sec_hdr_len;
64+
}
65+
if (pkt->payload_len && pkt->payload) {
66+
memcpy(&buf[off], pkt->payload, pkt->payload_len);
67+
off += pkt->payload_len;
68+
}
69+
70+
if (crc_present) {
71+
/* compute CRC over secondary header and payload */
72+
size_t crc_area_len = (pkt->ph.sec_hdr_flag ? pkt->sec_hdr_len : 0) + pkt->payload_len;
73+
uint16_t crc = sp_crc16_ccitt(&buf[6], crc_area_len);
74+
buf[off++] = (uint8_t)((crc >> 8) & 0xFF);
75+
buf[off++] = (uint8_t)(crc & 0xFF);
76+
}
77+
78+
return need;
79+
}
80+
81+
int sp_packet_parse(sp_packet_t *out, uint8_t *buf, size_t buf_len) {
82+
if (!out || !buf) return 0;
83+
if (buf_len < 6) return 0;
84+
85+
uint16_t first = ((uint16_t)buf[0] << 8) | buf[1];
86+
uint16_t second = ((uint16_t)buf[2] << 8) | buf[3];
87+
uint16_t length_field = ((uint16_t)buf[4] << 8) | buf[5];
88+
89+
out->ph.version = (first >> 13) & 0x7;
90+
out->ph.type = (first >> 12) & 0x1;
91+
out->ph.sec_hdr_flag = (first >> 11) & 0x1;
92+
out->ph.apid = first & 0x07FF;
93+
out->ph.seq_flags = (second >> 14) & 0x3;
94+
out->ph.seq_count = second & 0x3FFF;
95+
out->ph.packet_length = length_field;
96+
97+
uint16_t total_len = (uint16_t)(length_field + 1);
98+
99+
size_t off = 6;
100+
if (out->ph.sec_hdr_flag) {
101+
/* need at least 2 bytes for secondary header to read its length */
102+
if (buf_len < off + 2) return 0;
103+
uint8_t flags = buf[off];
104+
uint8_t rem_len = buf[off + 1];
105+
uint16_t sec_len = (uint16_t)(2 + rem_len);
106+
if (buf_len < off + sec_len) return 0;
107+
out->sec_hdr = &buf[off];
108+
out->sec_hdr_len = sec_len;
109+
off += sec_len;
110+
111+
int crc_present = (flags & 0x1) ? 1 : 0;
112+
113+
uint16_t payload_len_calc = 0;
114+
if (crc_present) {
115+
if (total_len < sec_len + 2) return 0; /* impossible */
116+
payload_len_calc = (uint16_t)(total_len - sec_len - 2);
117+
if (buf_len < off + payload_len_calc + 2) return 0;
118+
out->payload_len = payload_len_calc;
119+
out->payload = &buf[off];
120+
121+
/* verify CRC */
122+
size_t crc_area_len = sec_len + out->payload_len;
123+
uint16_t crc_calc = sp_crc16_ccitt(&buf[6], crc_area_len);
124+
uint16_t crc_recv = ((uint16_t)buf[off + out->payload_len] << 8) | buf[off + out->payload_len + 1];
125+
if (crc_calc != crc_recv) return 0;
126+
} else {
127+
if (total_len < sec_len) return 0;
128+
payload_len_calc = (uint16_t)(total_len - sec_len);
129+
if (buf_len < off + payload_len_calc) return 0;
130+
out->payload_len = payload_len_calc;
131+
out->payload = &buf[off];
132+
}
133+
134+
} else {
135+
/* no secondary header; CRC not supported */
136+
out->sec_hdr = NULL;
137+
out->sec_hdr_len = 0;
138+
out->payload_len = total_len;
139+
if (buf_len < off + out->payload_len) return 0;
140+
out->payload = &buf[off];
141+
}
142+
143+
return 1;
144+
}
145+
146+
uint16_t sp_crc16_ccitt(const uint8_t *data, size_t len) {
147+
uint16_t crc = 0xFFFF;
148+
for (size_t i = 0; i < len; ++i) {
149+
crc ^= (uint16_t)data[i] << 8;
150+
for (int k = 0; k < 8; ++k) {
151+
if (crc & 0x8000) crc = (crc << 1) ^ 0x1021;
152+
else crc <<= 1;
153+
}
154+
}
155+
return crc & 0xFFFF;
156+
}

tests/ctest

16.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)