diff --git a/.gitignore b/.gitignore index 86c01e2..e7b9b6c 100644 --- a/.gitignore +++ b/.gitignore @@ -55,12 +55,7 @@ dkms.conf *.dwo # Build outputs -examples/spacepacket_example -tests/ctest build/ -external/ -spacewire_example -spacewire_tests # Editor configs .vscode/ diff --git a/Makefile b/Makefile index 1c5cd0a..174448b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CC ?= cc CFLAGS ?= -O2 -Iinclude -Wall -Wextra -Wpedantic -Wconversion -Wshadow \ -Wcast-align -Wcast-qual -Wpointer-arith -Wformat=2 \ -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wundef \ - -std=c11 + -std=c99 AR ?= ar CFLAGS += -I./external/EmbeddedSpacePacket/include -fPIC @@ -13,8 +13,7 @@ LIB_DIR := $(BUILD_DIR)/lib BIN_DIR := $(BUILD_DIR)/bin # Source files -CORE_SRCS := src/spacewire_codec.c \ - src/spacewire_frame.c \ +CORE_SRCS := src/spacewire_frame.c \ src/spacewire_router.c \ src/spacewire_packet.c @@ -22,7 +21,6 @@ ESP_SRCS := external/EmbeddedSpacePacket/src/space_packet.c EXAMPLE_SRCS := examples/main.c TEST_SRCS := tests/unit_tests.c \ - tests/test_codec.c \ tests/test_frame.c \ tests/test_router.c \ tests/test_packet.c @@ -42,7 +40,7 @@ TEST_BIN := $(BIN_DIR)/spacewire_tests # Build targets .PHONY: all clean test example lib coverage-html help distclean -all: lib example +all: lib test lib: $(LIB_STATIC) $(LIB_SHARED) @@ -76,7 +74,7 @@ clean: rm -f libspacewire.a libspacewire.so spacewire_example spacewire_tests coverage-html: - bash scripts/coverage_html.sh + bash tools/coverage_html.sh distclean: clean find . -name "*.o" -delete diff --git a/README.md b/README.md index ae5f831..1dc59cc 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,62 @@ # EmbeddedSpaceWire -Minimal, embedded-optimized implementation of **CCSDS Space Wire Protocol** combined with **CCSDS Space Packet Transfer** protocol, following international standards. +Minimal, embedded-optimized implementation of the **SpaceWire** packet and network layers (ECSS-E-ST-50-12C) carrying the **CCSDS Packet Transfer Protocol** (ECSS-E-ST-50-53C). ## Standards Compliance -- **CCSDS 131.0-B-2**: Space Wire - Communication Protocol -- **ECSS-E-ST-50-53C**: Space Wire Protocol Specification -- **CCSDS 133.0-B-2**: CCSDS Space Packet +- **ECSS-E-ST-50-12C** Rev.1: SpaceWire — links, nodes, routers and networks +- **ECSS-E-ST-50-53C**: SpaceWire — CCSDS packet transfer protocol +- **ECSS-E-ST-50-51C**: SpaceWire — protocol identification (Protocol ID `0x02`) +- **CCSDS 133.0-B-2**: Space Packet Protocol ## Features ### Core Protocol Implementation -- **Character Codec**: 9-bit character encoding with parity support -- **Frame Layer**: Space Wire frame structure with CRC-16-CCITT -- **Router**: Packet routing with virtual channel support -- **Link Layer**: Link state management and flow control -- **CCSDS Integration**: Combined Space Wire + Space Packet transmission +- **Network layer**: SpaceWire packets and routing — path (0–31) and logical + (32–254) addressing with header deletion (ECSS-E-ST-50-12C §5.6) +- **CCSDS Packet Transfer Protocol**: encapsulation/extraction of CCSDS Space + Packets with EOP/EEP receive status (ECSS-E-ST-50-53C) +- **CCSDS Integration**: built on the EmbeddedSpacePacket library + +### Scope (hardware boundary) + +This is a **packet- and network-layer** library. The character/signal and +data-link levels — character encoding and parity, data-strobe signalling, link +initialisation and flow control, and EOP/EEP generation and detection — are +provided by the **SpaceWire hardware CODEC**. EOP/EEP are exchanged with this +library as out-of-band metadata, not as bytes within packet buffers. ### Design Principles -- **Minimal footprint**: ~5KB library size (stripped) -- **Zero allocation**: Stack-based, no dynamic memory -- **Embedded-optimized**: No external dependencies except EmbeddedSpacePacket -- **Portable**: Pure C11, big-endian network byte order -- **Fast**: Lookup-table CRC computation +- **Minimal footprint**: small static library, no dynamic allocation +- **Zero allocation**: stack- and caller-owned buffers only +- **Embedded-optimized**: no external dependencies except EmbeddedSpacePacket +- **Portable**: pure C99, big-endian network byte order +- **Standards-driven**: no non-standard framing or checksums on the wire ## Project Structure ``` EmbeddedSpaceWire/ ├── include/ -│ ├── spacewire.h # Core Space Wire protocol -│ └── spacewire_packet.h # CCSDS integration +│ ├── spacewire.h # SpaceWire packet + network (routing) layer +│ └── spacewire_packet.h # CCSDS packet transfer protocol (ECSS-E-ST-50-53C) ├── src/ -│ ├── spacewire_codec.c # Character codec + CRC -│ ├── spacewire_frame.c # Frame layer -│ ├── spacewire_router.c # Router + link layer -│ └── spacewire_packet.c # CCSDS integration +│ ├── spacewire_frame.c # SpaceWire packet builder +│ ├── spacewire_router.c # Routing switch + link state +│ └── spacewire_packet.c # CCSDS packet transfer protocol ├── external/ -│ └── EmbeddedSpacePacket/ # CCSDS Space Packet library +│ └── EmbeddedSpacePacket/ # CCSDS Space Packet library (submodule) ├── examples/ -│ └── main.c # Example usage +│ └── main.c # End-to-end usage ├── tests/ │ ├── cunit.h # Tiny C test helpers -│ └── unit_tests.c # Unit tests -├── scripts/ +│ ├── test_frame.c # Packet-builder tests +│ ├── test_router.c # Routing + link tests +│ ├── test_packet.c # CCSDS PTP tests (+ golden wire vector) +│ └── unit_tests.c # Test runner +├── tools/ │ └── coverage_html.sh # HTML coverage generator ├── Makefile └── README.md @@ -98,136 +109,36 @@ make distclean # Deep clean including object files ## Quick Start -### Create and Send a Space Wire Packet - -```c -#include "spacewire.h" -#include "spacewire_packet.h" - -// Create a CCSDS packet wrapped in Space Wire frame -uint8_t buffer[256]; -const char *data = "Hello Space Wire"; - -size_t frame_size = sw_packet_create( - 0x01, // Local device address - 0x02, // Target device address - 0x0042, // APID (Application ID) - (uint8_t *)data, // Payload - strlen(data), // Payload length - buffer, // Output buffer - sizeof(buffer) // Buffer size -); - -if (frame_size > 0) { - printf("Packet created: %zu bytes\n", frame_size); -} -``` - -### Parse Incoming Packet - -```c -sw_packet_frame_t pf; -sw_packet_config_t config = { - .device_addr = 0x01, - .target_addr = 0x02, - .protocol_id = 1, - .enable_crc = 1 -}; - -sw_packet_init(&pf, &config); - -if (sw_packet_decode(&pf, buffer, frame_size)) { - printf("APID: 0x%04X, Payload: %d bytes\n", - pf.packet.ph.apid, - pf.packet.payload_len); -} -``` - -### Frame Routing - -```c -sw_router_t router; -sw_router_init(&router, 0x01, 2); // Device 0x01, 2 ports - -// Add routing rule: packets to 0x03 go out port 1 -sw_router_add_route(&router, 0x03, 1); - -// Route a frame -uint8_t out_port; -if (sw_router_route_frame(&router, &frame, &out_port)) { - printf("Route to port: %u\n", out_port); -} -``` - -## API Reference +See [examples/main.c](examples/main.c). -### Character Codec - -```c -// Decode 9-bit character -sw_char_result_t sw_decode_char(uint8_t byte, uint8_t parity_bit, uint8_t *data); - -// Encode character with parity -uint8_t sw_encode_char(uint8_t data, uint8_t *byte); - -// Compute CRC -uint16_t sw_crc16(const uint8_t *data, size_t len); -``` - -### Frame Layer - -```c -void sw_frame_init(sw_frame_t *frame); -size_t sw_frame_size(const sw_frame_t *frame); -size_t sw_frame_encode(const sw_frame_t *frame, uint8_t *buf, size_t buf_len); -int sw_frame_decode(sw_frame_t *frame, uint8_t *buf, size_t buf_len); -``` - -### Router - -```c -void sw_router_init(sw_router_t *router, uint8_t device_addr, uint8_t num_ports); -void sw_router_add_route(sw_router_t *router, uint8_t dest_addr, uint8_t output_port); -int sw_router_open_channel(sw_router_t *router, uint8_t channel_id); -int sw_router_route_frame(sw_router_t *router, const sw_frame_t *frame, uint8_t *output_port); -``` - -### Packet Integration - -```c -void sw_packet_init(sw_packet_frame_t *pf, const sw_packet_config_t *config); -size_t sw_packet_encode(const sw_packet_frame_t *pf, uint8_t *buf, size_t buf_len); -int sw_packet_decode(sw_packet_frame_t *pf, uint8_t *buf, size_t buf_len); -size_t sw_packet_create(uint8_t device_addr, uint8_t target_addr, uint16_t apid, - const uint8_t *payload, uint16_t payload_len, - uint8_t *buf, size_t buf_len); -``` -## Memory Usage (Estimated) +## Memory Usage -- **Library (stripped)**: ~4-5 KB -- **Per-frame buffer**: Frame size + 4 bytes overhead -- **Router state**: ~200 bytes base + 16 bytes per port -- **Packet frame**: ~100 bytes (including CCSDS header) +- **Library code**: ~3 KB (`.text`); no dynamic allocation, all buffers caller-owned +- **`sw_packet_frame_t`**: 40 bytes (CCSDS PTP packet state) +- **`sw_router_t`**: ~1.4 KB — a 256-entry routing table (3 B/entry) plus per-port + link state; set `-DSW_NUM_PORTS=n` to shrink the per-router footprint ## Limitations and Extensions -Current implementation focuses on core protocol features: +The library implements the packet and network layers; the following are out of +scope or not yet implemented: -- Single-destination routing (no path routing) -- Flow control (basic credit-based) -- CRC validation -- No automatic retransmission handling -- No bandwidth management -- No advanced QoS features +- Character/signal and data-link levels — provided by the SpaceWire hardware CODEC +- Time-codes, broadcast codes and distributed interrupts (ECSS-E-ST-50-12C §5.6) +- Group adaptive routing (one output port per logical address) +- Guaranteed delivery — per ECSS-E-ST-50-53C the service is unconfirmed and + incomplete (no acknowledgement, retransmission or QoS) -These can be extended as needed for specific mission requirements. +These can be added as needed for specific mission requirements. ## References -- CCSDS 131.0-B-2: Space Wire - Communication Protocol -- ECSS-E-ST-50-53C: European Cooperation for Space Standardization -- CCSDS 133.0-B-2: CCSDS Space Packet Protocol +- ECSS-E-ST-50-12C Rev.1 — SpaceWire: links, nodes, routers and networks +- ECSS-E-ST-50-53C — SpaceWire: CCSDS packet transfer protocol +- ECSS-E-ST-50-51C — SpaceWire: protocol identification +- CCSDS 133.0-B-2 — Space Packet Protocol +- https://www.spacewire.esa.int — SpaceWire website ## License diff --git a/docs/ECSS-E-ST-50-12C-Rev.1(15May2019).pdf b/docs/ECSS-E-ST-50-12C-Rev.1(15May2019).pdf new file mode 100644 index 0000000..b220d6e Binary files /dev/null and b/docs/ECSS-E-ST-50-12C-Rev.1(15May2019).pdf differ diff --git a/docs/ECSS-E-ST-50-53C5February2010.pdf b/docs/ECSS-E-ST-50-53C5February2010.pdf new file mode 100644 index 0000000..d93c793 Binary files /dev/null and b/docs/ECSS-E-ST-50-53C5February2010.pdf differ diff --git a/examples/main.c b/examples/main.c index 8f56a14..d2e200b 100644 --- a/examples/main.c +++ b/examples/main.c @@ -1,5 +1,8 @@ /* - * EmbeddedSpaceWire Example - Basic packet transmission and routing + * EmbeddedSpaceWire example — SpaceWire packets, routing and CCSDS packet transfer. + * + * The character/signal and link levels are handled by the SpaceWire hardware + * CODEC; this example exercises the packet and network layers in software. */ #include "spacewire.h" @@ -11,70 +14,51 @@ int main(void) { printf("===============================================\n"); - printf("CCSDS Space Wire Protocol - Example\n"); + printf("SpaceWire packet + network layer - Example\n"); printf("===============================================\n\n"); - /* ========== CHARACTER CODEC EXAMPLE ========== */ - printf("[1] Character Codec Test\n"); - printf(" Encoding/decoding 9-bit characters with parity\n"); - - uint8_t data = 0x42; /* 'B' */ - uint8_t encoded; - uint8_t parity = sw_encode_char(data, &encoded); - printf(" Input: 0x%02X, Encoded: 0x%02X, Parity: %u\n", data, encoded, parity); - - uint8_t decoded; - sw_char_result_t result = sw_decode_char(encoded, parity, &decoded); - printf(" Decoded: 0x%02X, Result: %d\n", decoded, result); - printf(" ✓ Character codec working\n\n"); - - /* ========== FRAME ENCODING EXAMPLE ========== */ - printf("[2] Space Wire Frame Test\n"); - printf(" Creating and serializing a Space Wire frame\n"); - - uint8_t payload_data[] = {0x01, 0x02, 0x03, 0x04, 0x05}; - sw_frame_t frame; - sw_frame_init(&frame); - frame.target_addr = 0x02; - frame.protocol_id = 1; - frame.payload = payload_data; - frame.payload_len = sizeof(payload_data); - - uint8_t frame_buf[256]; - size_t frame_size = sw_frame_encode(&frame, frame_buf, sizeof(frame_buf)); - printf(" Frame size: %zu bytes\n", frame_size); - printf(" Frame data (hex): "); - for (size_t i = 0; i < frame_size; i++) - { - printf("%02X ", frame_buf[i]); - } - printf("\n ✓ Frame encoding working\n\n"); + /* ========== SPACEWIRE PACKET (LOGICAL ADDRESSING) ========== */ + printf("[1] SpaceWire Packet Test\n"); + printf(" Building a logical-addressed packet (no CRC; EOP added by link)\n"); - /* ========== FRAME DECODING EXAMPLE ========== */ - printf("[3] Space Wire Frame Decoding Test\n"); - printf(" Parsing and validating frame CRC\n"); + const uint8_t cargo_data[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + const uint8_t logical_dest[] = {0x40}; /* logical address 64 */ - sw_frame_t decoded_frame; - if (sw_frame_decode(&decoded_frame, frame_buf, frame_size)) + uint8_t spw_buf[256]; + size_t spw_len = sw_spw_packet_build(logical_dest, sizeof(logical_dest), cargo_data, + sizeof(cargo_data), spw_buf, sizeof(spw_buf)); + printf(" Packet size: %zu bytes\n", spw_len); + printf(" Packet data (hex): "); + for (size_t i = 0; i < spw_len; i++) { - printf(" Target address: 0x%02X\n", decoded_frame.target_addr); - printf(" Protocol ID: %u\n", decoded_frame.protocol_id); - printf(" Payload length: %u bytes\n", decoded_frame.payload_len); - printf(" ✓ Frame decoding and CRC validation successful\n\n"); + printf("%02X ", spw_buf[i]); } - else + printf("\n ✓ Packet build working\n\n"); + + /* ========== SPACEWIRE PACKET (PATH ADDRESSING) ========== */ + printf("[2] SpaceWire Path Addressing Test\n"); + printf(" Building a multi-hop path-addressed packet\n"); + + const uint8_t path_dest[] = {2, 1, 3}; /* take port 2, then 1, then 3 */ + uint8_t path_buf[256]; + size_t path_len = sw_spw_packet_build(path_dest, sizeof(path_dest), cargo_data, + sizeof(cargo_data), path_buf, sizeof(path_buf)); + printf(" Path address: %u hops, packet %zu bytes\n", (unsigned)sizeof(path_dest), path_len); + printf(" Packet data (hex): "); + for (size_t i = 0; i < path_len; i++) { - printf(" ✗ Frame decoding failed\n\n"); + printf("%02X ", path_buf[i]); } + printf("\n ✓ Path-addressed packet built\n\n"); - /* ========== CCSDS PACKET + SPACE WIRE INTEGRATION ========== */ - printf("[4] CCSDS Packet + Space Wire Integration\n"); - printf(" Creating complete packet frame\n"); + /* ========== CCSDS PACKET + SPACEWIRE INTEGRATION ========== */ + printf("[3] CCSDS Packet Transfer Protocol\n"); + printf(" Encapsulating a CCSDS packet (ECSS-E-ST-50-53C)\n"); - sw_packet_config_t pkt_config = {.device_addr = 0x01, - .target_addr = 0x02, - .protocol_id = 1, - .enable_crc = 1}; + sw_packet_config_t pkt_config = {.path = NULL, + .path_len = 0, + .logical_addr = SW_PTP_LOGICAL_ADDR_DEFAULT, + .user_app = 0x00}; sw_packet_frame_t pf; sw_packet_init(&pf, &pkt_config); @@ -83,8 +67,8 @@ int main(void) pf.packet.ph.apid = 0x0042; /* APID = 0x0042 */ pf.packet.ph.seq_count = 1; const char *msg = "Hello Space Wire"; - pf.packet.payload = (const uint8_t *)msg; - pf.packet.payload_len = (uint16_t)strlen(msg); + pf.packet.data = (const uint8_t *)msg; + pf.packet.data_len = (uint16_t)strlen(msg); uint8_t pkt_buf[512]; size_t pkt_size = sw_packet_encode(&pf, pkt_buf, sizeof(pkt_buf)); @@ -97,17 +81,20 @@ int main(void) } /* ========== PACKET DECODING EXAMPLE ========== */ - printf("[5] Packet Decoding Test\n"); - printf(" Parsing Space Wire frame and CCSDS packet\n"); + printf("[4] Packet Decoding Test\n"); + printf(" Extracting the CCSDS packet at the target\n"); sw_packet_frame_t decoded_pf; - if (sw_packet_decode(&decoded_pf, pkt_buf, pkt_size)) + sw_ptp_status_t status; + if (sw_packet_decode(&decoded_pf, pkt_buf, pkt_size, SW_END_EOP, &status)) { printf(" Decoded APID: 0x%04X\n", decoded_pf.packet.ph.apid); - printf(" Decoded payload length: %u bytes\n", decoded_pf.packet.payload_len); + printf(" Decoded user application: 0x%02X\n", decoded_pf.user_app); + printf(" Decoded payload length: %u bytes\n", decoded_pf.packet.data_len); printf(" Decoded payload: \"%.*s\"\n", - decoded_pf.packet.payload_len, - (const char *)decoded_pf.packet.payload); + decoded_pf.packet.data_len, + (const char *)decoded_pf.packet.data); + printf(" Status: %u (0 = OK)\n", (unsigned)status); printf(" ✓ Packet decoding successful\n\n"); } else @@ -116,72 +103,63 @@ int main(void) } /* ========== ROUTER EXAMPLE ========== */ - printf("[6] Router Configuration Test\n"); - printf(" Setting up routing table\n"); + printf("[5] Router Configuration Test\n"); + printf(" Setting up a routing table (port 0 = configuration port)\n"); sw_router_t router; - sw_router_init(&router, 0x01, 3); /* Device 0x01, 3 ports */ + sw_router_init(&router, 4); /* ports 0..3 (port 0 = configuration port) */ - sw_router_add_route(&router, 0x02, 0); - sw_router_add_route(&router, 0x03, 1); - sw_router_add_route(&router, 0x04, 2); + sw_router_add_route(&router, 0x40, 1, 0); /* logical 0x40 -> port 1, retain */ + sw_router_add_route(&router, 0x41, 2, 1); /* logical 0x41 -> port 2, delete */ - printf(" Device address: 0x%02X\n", router.device_addr); printf(" Number of ports: %u\n", router.num_ports); printf(" Routing table configured:\n"); - printf(" 0x02 -> Port 0\n"); - printf(" 0x03 -> Port 1\n"); - printf(" 0x04 -> Port 2\n"); - - /* Simulate link connection */ - router.links[0].state = SW_LINK_CONNECTED; - router.links[1].state = SW_LINK_CONNECTED; - router.links[2].state = SW_LINK_CONNECTED; - + printf(" logical 0x40 -> port 1 (address retained)\n"); + printf(" logical 0x41 -> port 2 (address deleted)\n"); printf(" ✓ Router initialized\n\n"); /* ========== ROUTING EXAMPLE ========== */ - printf("[7] Frame Routing Test\n"); - printf(" Testing packet routing\n"); + printf("[6] Packet Routing Test\n"); + printf(" Routing by leading destination-address character\n"); - sw_frame_t route_frame; - sw_frame_init(&route_frame); - route_frame.target_addr = 0x03; - route_frame.protocol_id = 1; + uint8_t out_port = 0; + uint8_t delete_leading = 0; - uint8_t output_port; - if (sw_router_route_frame(&router, &route_frame, &output_port)) + /* Logical addressing: leading char 0x40 is looked up in the routing table. */ + const uint8_t logical_pkt[] = {0x40, 0xAA, 0xBB}; + if (sw_router_route(&router, logical_pkt, sizeof(logical_pkt), &out_port, &delete_leading) == + SW_ROUTE_OK) { - printf(" Packet for 0x03 -> routed to port %u\n", output_port); - printf(" ✓ Routing successful\n\n"); + printf(" logical 0x40 -> port %u (delete leading: %u)\n", out_port, delete_leading); } - else + + /* Path addressing: leading char 2 selects port 2 directly and is deleted. */ + const uint8_t path_pkt[] = {2, 0xAA, 0xBB}; + if (sw_router_route(&router, path_pkt, sizeof(path_pkt), &out_port, &delete_leading) == + SW_ROUTE_OK) { - printf(" ✗ Routing failed\n\n"); + printf(" path 2 -> port %u (delete leading: %u)\n", out_port, delete_leading); } - /* ========== CRC TEST ========== */ - printf("[8] CRC-16-CCITT Test\n"); - printf(" Computing CRC for test data\n"); - - const uint8_t test_data[] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; - uint16_t crc = sw_crc16(test_data, sizeof(test_data)); - printf(" Data: \"123456789\"\n"); - printf(" CRC-16-CCITT: 0x%04X\n", crc); - if (crc == 0x31C3) + /* An unconfigured logical address is discarded with an invalid-address error. */ + const uint8_t bad_pkt[] = {0x77, 0xAA}; + if (sw_router_route(&router, bad_pkt, sizeof(bad_pkt), &out_port, &delete_leading) == + SW_ROUTE_DISCARD) { - printf(" ✓ CRC matches expected value (0x31C3)\n\n"); + printf(" logical 0x77 -> DISCARD (invalid-address errors: %u)\n", + router.invalid_address_errors); } + printf(" ✓ Routing successful\n\n"); /* ========== STATISTICS ========== */ - printf("[9] Statistics\n"); + printf("[7] Statistics\n"); sw_statistics_t stats; sw_get_statistics(&stats); printf(" Packets sent: %u\n", stats.packets_sent); printf(" Packets received: %u\n", stats.packets_received); printf(" Bytes sent: %u\n", stats.bytes_sent); printf(" Bytes received: %u\n", stats.bytes_received); - printf(" CRC errors: %u\n", stats.crc_errors); + printf(" Packets discarded: %u\n", stats.packets_discarded); printf(" ✓ Statistics available\n\n"); printf("===============================================\n"); diff --git a/external/EmbeddedSpacePacket b/external/EmbeddedSpacePacket index 3ce6f75..9f92cce 160000 --- a/external/EmbeddedSpacePacket +++ b/external/EmbeddedSpacePacket @@ -1 +1 @@ -Subproject commit 3ce6f752dbba424ad90ce810cdc706fe38d54f00 +Subproject commit 9f92cce63f7979cb3521a2f34b9745b4f9346483 diff --git a/include/spacewire.h b/include/spacewire.h index 6a4045c..c77476b 100644 --- a/include/spacewire.h +++ b/include/spacewire.h @@ -1,8 +1,19 @@ /* - * CCSDS Space Wire Protocol Implementation + * SpaceWire — packet and network layers. + * * Minimal, embedded-optimized implementation following: - * - CCSDS 131.0-B-2 (Space Wire - Communication Protocol) - * - ECSS-E-ST-50-53C (SpaceWire Protocol) + * - ECSS-E-ST-50-12C Rev.1 (SpaceWire — links, nodes, routers and networks): + * the SpaceWire packet structure and routing (this file). + * - ECSS-E-ST-50-53C (SpaceWire — CCSDS packet transfer protocol): + * see spacewire_packet.h. + * + * Scope: this library implements the packet and network layers only. The + * character/signal and data-link levels — character encoding and parity, + * data-strobe (DS) signalling, link initialisation and flow control, and the + * generation/detection of the EOP/EEP end-of-packet markers — are provided by + * the SpaceWire hardware CODEC. EOP/EEP are therefore exchanged with this + * library as out-of-band metadata (see sw_end_marker_t in spacewire_packet.h), + * not as bytes within packet buffers. */ #ifndef SPACEWIRE_H @@ -12,106 +23,62 @@ #include /* ============================================================================ - * SPACEWIRE CHARACTER CODEC - * Space Wire encodes 9-bit data (8 data bits + parity) using special characters + * SPACEWIRE PACKET (ECSS-E-ST-50-12C clause 5.6.2) + * + * A SpaceWire packet is a sequence of data characters terminated by an end of + * packet marker (EOP or EEP). The leading data characters form the destination + * address; the remaining characters form the cargo: + * + * [ Destination Address ][ Cargo ][ EOP | EEP ] + * + * The end marker is a control character emitted by the link layer, not a data + * octet, so it is not stored in the packet buffer. No checksum is added. * ============================================================================ */ -/* Special characters in Space Wire */ -#define SPACEWIRE_ESC 0x00 /* Escape character (triggers character mapping) */ -#define SPACEWIRE_FCT 0x01 /* Flow Control Token */ -#define SPACEWIRE_EOP 0x02 /* End Of Packet */ -#define SPACEWIRE_EEP 0x03 /* End Of Error Packet */ - -/* Escaped character mappings (ESC followed by code) */ -#define SPACEWIRE_ESC_ESC 0x00 /* Escaped ESC */ -#define SPACEWIRE_ESC_FCT 0x01 /* Escaped FCT */ -#define SPACEWIRE_ESC_EOP 0x02 /* Escaped EOP */ -#define SPACEWIRE_ESC_EEP 0x03 /* Escaped EEP */ - -/* Parity types */ -typedef enum -{ - SW_PARITY_EVEN = 0, - SW_PARITY_ODD = 1 -} sw_parity_t; - -/* Character codec result */ -typedef enum -{ - SW_CHAR_OK = 0, - SW_CHAR_ESCAPE = 1, /* Special character */ - SW_CHAR_FCT = 2, /* Flow control token */ - SW_CHAR_EOP = 3, /* End of packet */ - SW_CHAR_EEP = 4, /* End of error packet */ - SW_CHAR_PARITY_ERROR = 5, - SW_CHAR_INVALID = 6 -} sw_char_result_t; - -/* Decode single character (9-bit) from two bytes (8-bit + parity bit) */ -sw_char_result_t sw_decode_char(uint8_t byte, uint8_t parity_bit, uint8_t *data); - -/* Encode single character to byte + parity (returns parity bit) */ -uint8_t sw_encode_char(uint8_t data, uint8_t *byte); +/* Build a SpaceWire packet by prefixing a destination address to a cargo. + * Copies [dest][cargo] into buf; either part may be empty (NULL with length 0). + * Returns the total length written, or 0 on error (a NULL pointer paired with a + * non-zero length, an empty result, or a buffer that is too small). */ +size_t sw_spw_packet_build(const uint8_t *dest, + size_t dest_len, + const uint8_t *cargo, + size_t cargo_len, + uint8_t *buf, + size_t buf_len); /* ============================================================================ - * SPACEWIRE FRAMING - * Implements frame structure and error detection + * SPACEWIRE ROUTING (ECSS-E-ST-50-12C clause 5.6.8) + * + * A routing switch forwards a packet to an output port selected by the leading + * data character of the packet's destination address (Table 5-11): + * - 0 configuration port (path addressing only) (clause 5.6.8.2) + * - 1..31 external output port with that number (path addr) (clause 5.6.8.3) + * - 32..254 logical address, mapped via the routing table (clause 5.6.8.4) + * - 255 reserved logical address, must not be used + * A path address character is always deleted after use; a logical address is + * retained unless its routing-table entry requests deletion (clause 5.6.8.6). * ============================================================================ */ -/* Frame structure: - * - Header (1-4 bytes): Address path, Protocol ID - * - Payload (0-255 bytes): CCSDS packet or raw data - * - CRC (2 bytes): CRC-16-CCITT - */ - -#define SW_FRAME_MAX_PAYLOAD 65535 -#define SW_FRAME_HEADER_MAX 4 -#define SW_FRAME_CRC_LEN 2 - -typedef struct -{ - uint8_t target_addr; /* Target logical address (0-254) */ - uint8_t protocol_id; /* Protocol identifier (1=CCSDS, 2=Raw) */ - uint8_t *payload; /* Non-const: decode path must pass to sp_packet_parse(uint8_t *) */ - uint16_t payload_len; -} sw_frame_t; - -/* Initialize frame structure */ -void sw_frame_init(sw_frame_t *frame); - -/* Calculate required buffer size for frame (header + payload + CRC) */ -size_t sw_frame_size(const sw_frame_t *frame); - -/* Serialize frame into buffer with CRC calculation */ -size_t sw_frame_encode(const sw_frame_t *frame, uint8_t *buf, size_t buf_len); - -/* Parse frame from buffer and verify CRC */ -int sw_frame_decode(sw_frame_t *frame, uint8_t *buf, size_t buf_len); - -/* ============================================================================ - * SPACEWIRE ROUTER - * Packet routing with virtual channel support - * ============================================================================ */ +/* Port 0 is the configuration port; external ports are 1..31 (clause 5.6.8.2). */ +#define SW_PORT_CONFIG 0u +#define SW_PORT_EXTERNAL_MAX 31u -#define SW_MAX_VIRTUAL_CHANNELS 16 -#define SW_MAX_PORTS 8 +/* Number of ports a router can have (0..31). Override with -DSW_NUM_PORTS=n to + * shrink the per-router footprint for small nodes. */ +#ifndef SW_NUM_PORTS +#define SW_NUM_PORTS 32u +#endif -/* Routing entry for packet forwarding */ -typedef struct -{ - uint8_t dest_addr; - uint8_t output_port; -} sw_route_t; +/* Address ranges (clause 5.6.8, Table 5-11). */ +#define SW_PATH_ADDR_MAX 31u /* 0..31 path address */ +#define SW_LOGICAL_ADDR_MIN 32u /* 32..255 logical address */ +#define SW_LOGICAL_ADDR_RESERVED 255u +#define SW_LOGICAL_ADDR_DEFAULT 254u -/* Virtual channel (circuit) */ -typedef struct -{ - uint8_t channel_id; - uint8_t active; - uint16_t fct_credits; /* Flow Control Credits */ -} sw_virtual_channel_t; +/* The routing table is indexed directly by the leading data character. */ +#define SW_ROUTE_TABLE_SIZE 256u -/* Space Wire link state */ +/* SpaceWire link state (clause 5.5.7 link initialisation). */ typedef enum { SW_LINK_UNINITIALIZED = 0, @@ -130,30 +97,61 @@ typedef struct uint32_t errors; } sw_link_t; +/* One routing-table entry mapping a logical address to an output port. */ typedef struct { - sw_link_t links[SW_MAX_PORTS]; - sw_virtual_channel_t channels[SW_MAX_VIRTUAL_CHANNELS]; - sw_route_t routes[SW_MAX_PORTS]; - uint8_t device_addr; - uint8_t num_ports; -} sw_router_t; - -/* Initialize router */ -void sw_router_init(sw_router_t *router, uint8_t device_addr, uint8_t num_ports); + uint8_t output_port; /* port the packet is forwarded through */ + uint8_t configured; /* 1 if this logical address has a valid route */ + uint8_t delete_addr; /* 1 to delete the logical address before forwarding (clause 5.6.8.6) */ +} sw_route_entry_t; -/* Configure routing entry */ -void sw_router_add_route(sw_router_t *router, uint8_t dest_addr, uint8_t output_port); - -/* Open virtual channel */ -int sw_router_open_channel(sw_router_t *router, uint8_t channel_id); +typedef struct +{ + sw_link_t links[SW_NUM_PORTS]; + sw_route_entry_t routes[SW_ROUTE_TABLE_SIZE]; + uint8_t num_ports; /* ports present: 1..SW_NUM_PORTS (port 0 is config) */ + uint32_t invalid_address_errors; /* invalid / unconfigured address discards (clause 5.6.8.5) */ + uint32_t packets_routed; + uint32_t packets_discarded; +} sw_router_t; -/* Route frame to appropriate port */ -int sw_router_route_frame(sw_router_t *router, const sw_frame_t *frame, uint8_t *output_port); +/* Result of a routing decision. */ +typedef enum +{ + SW_ROUTE_OK = 0, /* forward via the returned output port */ + SW_ROUTE_DISCARD = 1 /* discard: empty packet, non-existent port, or unconfigured address */ +} sw_route_result_t; + +/* Initialize a router. num_ports is clamped to [1, SW_NUM_PORTS] and counts the + * configuration port 0. */ +void sw_router_init(sw_router_t *router, uint8_t num_ports); + +/* Configure a logical-address route. logical_addr must be 32..254; output_port + * must be an existing port; delete_addr requests logical-address deletion + * (clause 5.6.8.6). Returns 1 on success, 0 on invalid arguments. */ +int sw_router_add_route(sw_router_t *router, + uint8_t logical_addr, + uint8_t output_port, + int delete_addr); + +/* Decide the output port for a packet from its leading destination-address + * character. On SW_ROUTE_OK, *output_port is set and *delete_leading is 1 when + * the leading character must be removed before forwarding (always for path + * addressing; for logical addressing only when configured for deletion), else 0. + * On SW_ROUTE_DISCARD the packet must be dropped; the router error/discard + * counters are updated accordingly. */ +sw_route_result_t sw_router_route(sw_router_t *router, + const uint8_t *packet, + size_t len, + uint8_t *output_port, + uint8_t *delete_leading); /* ============================================================================ * SPACEWIRE LINK LAYER - * Low-level link operations + * + * The link layer proper (encoding, flow control, link initialisation) is in the + * SpaceWire hardware CODEC. These types model the small amount of host-visible + * link configuration and state used to drive and observe that hardware. * ============================================================================ */ /* Link configuration */ @@ -162,7 +160,7 @@ typedef struct uint32_t bit_rate; /* bits per second */ uint32_t disconnect_timeout; /* microseconds */ uint8_t rx_credit_max; /* max rx buffer credits */ - uint8_t enable_crc; /* 1 = enable CRC, 0 = disable */ + uint8_t enable_crc; /* optional HW link-level CRC: 1 = enable, 0 = disable */ } sw_link_config_t; typedef struct @@ -182,14 +180,4 @@ sw_link_state_t sw_link_get_state(const sw_link_layer_t *link); /* Update link state */ void sw_link_set_state(sw_link_layer_t *link, sw_link_state_t state); -/* ============================================================================ - * CRC UTILITIES (shared with Space Packet) - * ============================================================================ */ - -/* Compute CRC-16-CCITT (polynomial 0x1021, init 0xFFFF) */ -uint16_t sw_crc16(const uint8_t *data, size_t len); - -/* CRC table for faster computation */ -extern const uint16_t sw_crc16_table[256]; - #endif /* SPACEWIRE_H */ diff --git a/include/spacewire_packet.h b/include/spacewire_packet.h index 4eaffb1..bbe8e5f 100644 --- a/include/spacewire_packet.h +++ b/include/spacewire_packet.h @@ -1,6 +1,12 @@ -/* - * CCSDS Space Wire + CCSDS Space Packet Integration - * High-level API for Space Wire packet transmission +/** + * @file spacewire_packet.h + * @brief CCSDS Packet Transfer Protocol over SpaceWire (ECSS-E-ST-50-53C). + * + * Encapsulates a CCSDS Space Packet (CCSDS 133.0-B-2) into a SpaceWire packet on + * the send path and extracts it on the receive path. The protocol adds no + * checksum of its own — ECSS-E-ST-50-53C defines none; error detection is + * provided by the underlying SpaceWire link (character parity) and by the + * EOP/EEP end-of-packet markers. */ #ifndef SPACEWIRE_PACKET_H @@ -9,63 +15,236 @@ #include "../external/EmbeddedSpacePacket/include/space_packet.h" #include "spacewire.h" -/* ============================================================================ - * SPACEWIRE PACKET TRANSMISSION - * ============================================================================ */ +/** @brief Protocol Identifier for the CCSDS Packet Transfer Protocol (clause 5.3.3). */ +#define SW_PTP_PROTOCOL_ID 0x02u -/* Packet frame with CCSDS Space Packet payload */ +/** @brief Reserved field value (clause 5.3.4). */ +#define SW_PTP_RESERVED 0x00u + +/** + * @brief Default Target Logical Address when the target has no specific one + * (clause 5.3.2, NOTE 2). + * + * @note Logical address 255 (0xFF) is reserved and must not be used. + */ +#define SW_PTP_LOGICAL_ADDR_DEFAULT 0xFEu + +/** + * @brief Encapsulation header length in octets. + * + * Target Logical Address + Protocol Identifier + Reserved + User Application. + */ +#define SW_PTP_HEADER_LEN 4u + +/** @brief Minimum CCSDS packet length: 6-octet primary header + 1 data octet (clause 5.1.2). */ +#define SW_PTP_CCSDS_MIN_LEN 7u + +/** @brief Maximum CCSDS packet length: 6-octet primary header + 65536 data octets (clause 5.1.2). */ +#define SW_PTP_CCSDS_MAX_LEN 65542u + +/** + * @brief Largest valid path-address octet. + * + * A SpaceWire path address character selects an output port and is in the range + * 0..31 (ECSS-E-ST-50-12C clause 5.6.8.3). + */ +#define SW_PTP_PATH_OCTET_MAX 31u + +/** + * @brief End-of-packet marker supplied by the SpaceWire link layer + * (ECSS-E-ST-50-12C clause 5.4.3.2). + * + * In a SpaceWire network the terminator is a control character, not a data + * octet, so it is carried here as out-of-band metadata rather than appended to + * the encoded buffer. + */ +typedef enum +{ + SW_END_EOP = 0, /**< Normal End Of Packet. */ + SW_END_EEP = 1 /**< Error End of Packet. */ +} sw_end_marker_t; + +/** + * @brief Receive status reported to the target user application (clauses 5.1.3, 5.2.3.2). + * + * The first three values are the status codes defined by the standard. + * ::SW_PTP_STATUS_INVALID is an implementation sentinel for input that is not a + * recognisable CCSDS PTP packet. + */ +typedef enum +{ + SW_PTP_STATUS_OK = 0x00, /**< Packet arrived with no known error. */ + SW_PTP_STATUS_EEP = 0x01, /**< Packet terminated by EEP. */ + SW_PTP_STATUS_RESERVED_NONZERO = 0x02, /**< Reserved field was non-zero. */ + SW_PTP_STATUS_INVALID = 0xFF /**< Not a CCSDS PTP packet / malformed. */ +} sw_ptp_status_t; + +/** + * @brief A CCSDS Packet Transfer Protocol packet. + * + * The wire layout produced by sw_packet_encode() follows ECSS-E-ST-50-53C + * Figure 5-1: + * + * [ Target SpW Address: path, 0..N octets, each 0-31 ] (optional) + * [ Target Logical Address | 0x02 | 0x00 | User Application ] + * [ CCSDS Space Packet: 6 + data_len octets ] + * + * followed by an EOP that the link layer appends (not stored in the buffer). + * + * @note The Target SpaceWire (path) Address is stripped by the network before + * the packet reaches the target, so it is absent on the receive path. + */ typedef struct { - sw_frame_t frame; /* Space Wire frame */ - sp_packet_t packet; /* CCSDS Space Packet */ + const uint8_t *path; /**< Target SpaceWire (path) Address; NULL if unused (clause 5.3.1). */ + uint8_t path_len; /**< Number of path octets (each must be 0..31). */ + uint8_t logical_addr; /**< Target Logical Address (clause 5.3.2). */ + uint8_t user_app; /**< User Application field; may carry a virtual channel (clause 5.3.5). */ + sp_packet_t packet; /**< Encapsulated CCSDS Space Packet. */ } sw_packet_frame_t; -/* Configuration for packet transmission */ +/** + * @brief Configuration for a CCSDS PTP packet endpoint. + */ typedef struct { - uint8_t device_addr; - uint8_t target_addr; - uint8_t protocol_id; /* Should be 1 for CCSDS packets */ - uint8_t enable_crc; + const uint8_t *path; /**< Optional path address; NULL if unused. */ + uint8_t path_len; /**< Number of path octets. */ + uint8_t logical_addr; /**< Target Logical Address. */ + uint8_t user_app; /**< User Application value. */ } sw_packet_config_t; -/* Initialize packet frame */ +/** + * @brief Virtual channel number. + * + * ECSS-E-ST-50-53C clause 5.3.5 (NOTE 2) allows the User Application field to + * carry a virtual channel number, so a virtual channel is represented as that + * 8-bit value rather than as separate router state. + */ +typedef uint8_t sw_virtual_channel_t; + +/** + * @brief Select a virtual channel by setting the User Application field. + * + * @param[out] pf Packet frame. No-op if NULL. + * @param[in] vc Virtual channel number. + */ +static inline void sw_packet_set_virtual_channel(sw_packet_frame_t *pf, sw_virtual_channel_t vc) +{ + if (pf) + { + pf->user_app = vc; + } +} + +/** + * @brief Read the virtual channel carried in the User Application field. + * + * @param[in] pf Packet frame. + * @return Virtual channel number, or 0 if @p pf is NULL. + */ +static inline sw_virtual_channel_t sw_packet_virtual_channel(const sw_packet_frame_t *pf) +{ + return pf ? pf->user_app : (sw_virtual_channel_t)0; +} + +/** + * @brief Initialise a packet frame from configuration. + * + * The encapsulated CCSDS packet is zero-initialised; set its header fields and + * data before encoding. + * + * @param[out] pf Packet frame to initialise. No-op if NULL. + * @param[in] config Endpoint configuration. No-op if NULL. + */ void sw_packet_init(sw_packet_frame_t *pf, const sw_packet_config_t *config); -/* Build and serialize Space Wire frame containing CCSDS packet */ +/** + * @brief Encode a complete CCSDS PTP packet into a buffer. + * + * Writes the optional path address, the 4-octet encapsulation header and the + * serialised CCSDS packet. No checksum and no in-band EOP are added. + * + * @param[in] pf Packet frame to encode. + * @param[out] buf Output buffer. + * @param[in] buf_len Buffer capacity in octets. + * @return Octets written, or 0 on error (NULL args, invalid path octet, CCSDS + * length out of bounds, or buffer too small). + */ size_t sw_packet_encode(const sw_packet_frame_t *pf, uint8_t *buf, size_t buf_len); -/* Parse incoming Space Wire frame and extract CCSDS packet */ -int sw_packet_decode(sw_packet_frame_t *pf, uint8_t *buf, size_t buf_len); +/** + * @brief Decode a received CCSDS PTP packet. + * + * The input is the packet as delivered to the target, i.e. with the path address + * already stripped by the network, beginning at the Target Logical Address: + * + * [ Target Logical Address | 0x02 | 0x00 | User Application | CCSDS packet ] + * + * On a successful return the decoded CCSDS packet, Target Logical Address and + * User Application value are available in @p pf. On discard the delivered CCSDS + * packet and User Application value are cleared (clause 5.2.3.2 b). + * + * @param[out] pf Decoded packet frame. + * @param[in] buf Received buffer to parse. + * @param[in] buf_len Buffer length in octets. + * @param[in] end End-of-packet marker reported by the link layer. + * @param[out] status Receive status (clause 5.1.3); may be NULL. Set to + * ::SW_PTP_STATUS_OK on delivery, ::SW_PTP_STATUS_EEP or + * ::SW_PTP_STATUS_RESERVED_NONZERO on a recognised discard, + * or ::SW_PTP_STATUS_INVALID for a bad Protocol Id / malformed input. + * @return 1 if a valid packet is delivered, 0 if the packet is discarded. + */ +int sw_packet_decode(sw_packet_frame_t *pf, + const uint8_t *buf, + size_t buf_len, + sw_end_marker_t end, + sw_ptp_status_t *status); -/* Utility: Create complete packet frame in one call */ -size_t sw_packet_create(uint8_t device_addr, - uint8_t target_addr, +/** + * @brief Build a complete CCSDS PTP packet carrying a single APID payload. + * + * Convenience wrapper that uses logical addressing only (no path address). + * + * @param[in] logical_addr Target Logical Address. + * @param[in] user_app User Application value. + * @param[in] apid APID — excess bits beyond 11 are masked. + * @param[in] payload CCSDS Packet Data Field (not copied). + * @param[in] payload_len Length of @p payload in octets. + * @param[out] buf Output buffer. + * @param[in] buf_len Buffer capacity in octets. + * @return Octets written, or 0 on error. + */ +size_t sw_packet_create(uint8_t logical_addr, + uint8_t user_app, uint16_t apid, const uint8_t *payload, uint16_t payload_len, uint8_t *buf, size_t buf_len); -/* ============================================================================ - * STATISTICS AND DEBUG - * ============================================================================ */ - +/** + * @brief Cumulative CCSDS PTP traffic counters. + */ typedef struct { - uint32_t packets_sent; - uint32_t packets_received; - uint32_t crc_errors; - uint32_t frame_errors; - uint32_t link_errors; - uint32_t bytes_sent; - uint32_t bytes_received; + uint32_t packets_sent; /**< Successfully encoded packets. */ + uint32_t packets_received; /**< Successfully decoded packets. */ + uint32_t packets_discarded; /**< Discarded (EEP / Reserved!=0 / bad ID / malformed). */ + uint32_t bytes_sent; /**< Total octets emitted by sw_packet_encode(). */ + uint32_t bytes_received; /**< Total octets consumed by successful decode. */ } sw_statistics_t; -/* Get current statistics */ +/** + * @brief Copy the current statistics. + * + * @param[out] stats Destination. No-op if NULL. + */ void sw_get_statistics(sw_statistics_t *stats); -/* Reset statistics */ +/** + * @brief Reset all statistics counters to zero. + */ void sw_reset_statistics(void); #endif /* SPACEWIRE_PACKET_H */ diff --git a/src/spacewire_codec.c b/src/spacewire_codec.c deleted file mode 100644 index 5f498b7..0000000 --- a/src/spacewire_codec.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Space Wire Character Codec - Minimal Implementation - * Handles 9-bit character encoding/decoding and parity - */ - -#include "../include/spacewire.h" - -/* ============================================================================ - * PARITY CALCULATION - * ============================================================================ */ - -static uint8_t sw_calculate_parity(uint8_t byte) -{ - /* Count bits, return parity (even parity = 0 if even number of 1s) */ - uint8_t parity = 0; - while (byte) - { - parity ^= (byte & 1); - byte >>= 1; - } - return parity; -} - -/* ============================================================================ - * CHARACTER ENCODING/DECODING - * ============================================================================ */ - -/* Decode single 9-bit character (byte + parity) */ -sw_char_result_t sw_decode_char(uint8_t byte, uint8_t parity_bit, uint8_t *data) -{ - if (!data) - return SW_CHAR_INVALID; - - /* Check parity */ - uint8_t expected_parity = sw_calculate_parity(byte); - if ((expected_parity ^ parity_bit) != 0) - return SW_CHAR_PARITY_ERROR; - - /* Check for special characters (values 0-3) */ - if (byte == SPACEWIRE_ESC) - return SW_CHAR_ESCAPE; - if (byte == SPACEWIRE_FCT) - return SW_CHAR_FCT; - if (byte == SPACEWIRE_EOP) - return SW_CHAR_EOP; - if (byte == SPACEWIRE_EEP) - return SW_CHAR_EEP; - - *data = byte; - return SW_CHAR_OK; -} - -/* Encode single 8-bit character with parity */ -uint8_t sw_encode_char(uint8_t data, uint8_t *byte) -{ - if (!byte) - return 0; - - *byte = data; - return sw_calculate_parity(data); -} - -/* ============================================================================ - * CRC-16-CCITT TABLE-BASED COMPUTATION - * ============================================================================ */ - -/* CRC-16-CCITT lookup table (polynomial 0x1021, init 0xFFFF) */ -const uint16_t sw_crc16_table[256] = { - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, - 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x5295, 0x42B4, 0x72D7, 0x62F6, - 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD39D, 0xC3BC, 0xF3DF, 0xE3FE, 0x2462, 0x3443, 0x0420, 0x1401, - 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, - 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, - 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, - 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, - 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, - 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, - 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, - 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, - 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, - 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, - 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, - 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, - 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, - 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, - 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; - -/* Compute CRC-16-CCITT */ -uint16_t sw_crc16(const uint8_t *data, size_t len) -{ - uint16_t crc = 0xFFFF; - if (!data) - return crc; - - for (size_t i = 0; i < len; i++) - { - uint8_t tbl_idx = (uint8_t)((crc >> 8) ^ data[i]); - crc = (uint16_t)((crc << 8) ^ sw_crc16_table[tbl_idx]); - } - - return crc; -} diff --git a/src/spacewire_frame.c b/src/spacewire_frame.c index ee42266..9d2cff6 100644 --- a/src/spacewire_frame.c +++ b/src/spacewire_frame.c @@ -1,114 +1,37 @@ /* - * Space Wire Frame Layer - Framing and CRC + * SpaceWire packet helpers (ECSS-E-ST-50-12C clause 5.6.2). + * + * A SpaceWire packet is [destination address][cargo] terminated by an EOP/EEP + * marker. The marker is a link-layer control character and is not part of the + * packet buffer, and no checksum is added. */ #include "../include/spacewire.h" #include -/* ============================================================================ - * FRAME INITIALIZATION - * ============================================================================ */ - -void sw_frame_init(sw_frame_t *frame) -{ - if (!frame) - return; - - frame->target_addr = 0; - frame->protocol_id = 1; /* CCSDS by default */ - frame->payload = NULL; - frame->payload_len = 0; -} - -/* ============================================================================ - * FRAME SIZE CALCULATION - * ============================================================================ */ - -size_t sw_frame_size(const sw_frame_t *frame) -{ - if (!frame) - return 0; - - /* Header: Target Address (1) + Protocol ID (1) */ - size_t size = 2; - - /* Payload */ - size += frame->payload_len; - - /* CRC (2 bytes) */ - size += 2; - - return size; -} - -/* ============================================================================ - * FRAME ENCODING (SERIALIZATION) - * ============================================================================ */ - -size_t sw_frame_encode(const sw_frame_t *frame, uint8_t *buf, size_t buf_len) +size_t sw_spw_packet_build(const uint8_t *dest, + size_t dest_len, + const uint8_t *cargo, + size_t cargo_len, + uint8_t *buf, + size_t buf_len) { - if (!frame || !buf) + if (!buf) return 0; - - size_t needed = sw_frame_size(frame); - if (buf_len < needed) + if (dest_len > 0 && !dest) return 0; - - size_t offset = 0; - - /* Write header */ - buf[offset++] = frame->target_addr; - buf[offset++] = frame->protocol_id; - - /* Write payload */ - if (frame->payload && frame->payload_len > 0) - { - memcpy(&buf[offset], frame->payload, frame->payload_len); - offset += frame->payload_len; - } - - /* Calculate and write CRC over header + payload */ - uint16_t crc = sw_crc16(buf, offset); - buf[offset++] = (uint8_t)((crc >> 8) & 0xFF); - buf[offset++] = (uint8_t)(crc & 0xFF); - - return offset; -} - -/* ============================================================================ - * FRAME DECODING (PARSING) - * ============================================================================ */ - -int sw_frame_decode(sw_frame_t *frame, uint8_t *buf, size_t buf_len) -{ - if (!frame || !buf) + if (cargo_len > 0 && !cargo) return 0; - /* Minimum frame: header (2) + CRC (2) */ - if (buf_len < 4) + const size_t total = dest_len + cargo_len; + if (total == 0 || total > buf_len) return 0; - size_t offset = 0; - - /* Parse header */ - frame->target_addr = buf[offset++]; - frame->protocol_id = buf[offset++]; - - /* Payload is everything except the CRC */ - size_t payload_start = offset; - size_t payload_len = buf_len - 4; /* Exclude 2 header + 2 CRC bytes */ - - /* Verify CRC */ - uint16_t received_crc = ((uint16_t)buf[buf_len - 2] << 8) | buf[buf_len - 1]; - uint16_t calculated_crc = sw_crc16(buf, buf_len - 2); - - if (received_crc != calculated_crc) - return 0; /* CRC mismatch */ - - /* Set payload pointer */ - frame->payload = &buf[payload_start]; - frame->payload_len = (uint16_t)payload_len; + if (dest_len > 0) + memcpy(buf, dest, dest_len); + if (cargo_len > 0) + memcpy(&buf[dest_len], cargo, cargo_len); - return 1; /* Success */ + return total; } diff --git a/src/spacewire_packet.c b/src/spacewire_packet.c index 801bdda..0bb6908 100644 --- a/src/spacewire_packet.c +++ b/src/spacewire_packet.c @@ -1,6 +1,10 @@ /* - * Space Wire + CCSDS Space Packet Integration - * High-level API combining both protocols + * CCSDS Packet Transfer Protocol over SpaceWire (ECSS-E-ST-50-53C). + * + * Encapsulates a CCSDS Space Packet into a SpaceWire packet on the send path + * and extracts it on the receive path. No checksum is added: ECSS-E-ST-50-53C + * relies on SpaceWire character parity and the EOP/EEP markers for error + * detection. */ #include "../include/spacewire_packet.h" @@ -11,7 +15,7 @@ static sw_statistics_t g_sw_stats = {0}; /* ============================================================================ - * PACKET INITIALIZATION + * PACKET INITIALISATION * ============================================================================ */ void sw_packet_init(sw_packet_frame_t *pf, const sw_packet_config_t *config) @@ -19,22 +23,22 @@ void sw_packet_init(sw_packet_frame_t *pf, const sw_packet_config_t *config) if (!pf || !config) return; - memset(pf, 0, sizeof(sw_packet_frame_t)); + memset(pf, 0, sizeof(*pf)); - /* Initialize frame */ - pf->frame.target_addr = config->target_addr; - pf->frame.protocol_id = config->protocol_id; + pf->path = config->path; + pf->path_len = config->path_len; + pf->logical_addr = config->logical_addr; + pf->user_app = config->user_app; - /* Initialize packet */ sp_packet_init(&pf->packet); pf->packet.ph.version = 0; - pf->packet.ph.type = 1; /* Telemetry */ + pf->packet.ph.type = SP_PACKET_TYPE_TC; /* default Packet Type (CCSDS: TM=0, TC=1) */ pf->packet.ph.sec_hdr_flag = 0; pf->packet.ph.apid = 0; } /* ============================================================================ - * PACKET ENCODING (SERIALIZATION) + * PACKET ENCODING (clause 5.4) * ============================================================================ */ size_t sw_packet_encode(const sw_packet_frame_t *pf, uint8_t *buf, size_t buf_len) @@ -42,73 +46,135 @@ size_t sw_packet_encode(const sw_packet_frame_t *pf, uint8_t *buf, size_t buf_le if (!pf || !buf) return 0; - if (pf->packet.payload_len > 0 && pf->packet.payload == NULL) + if (pf->packet.data_len > 0 && pf->packet.data == NULL) return 0; - if (pf->packet.ph.sec_hdr_flag) + /* Path octets must be valid SpaceWire path addresses (0..31). */ + if (pf->path_len > 0 && pf->path == NULL) + return 0; + for (uint8_t i = 0; i < pf->path_len; i++) { - if (pf->packet.sec_hdr == NULL || pf->packet.sec_hdr_len < 2) + if (pf->path[i] > SW_PTP_PATH_OCTET_MAX) return 0; } - /* First, serialize the CCSDS packet */ - size_t pkt_size = sp_packet_serialize_size(&pf->packet); - if (pkt_size == 0 || pkt_size > SW_FRAME_MAX_PAYLOAD) + /* CCSDS packet length must be within bounds (clause 5.1.2). */ + const size_t ccsds_len = sp_packet_serialize_size(&pf->packet); + if (ccsds_len < SW_PTP_CCSDS_MIN_LEN || ccsds_len > SW_PTP_CCSDS_MAX_LEN) return 0; - /* Create temporary buffer for serialized packet */ - uint8_t pkt_buf[SW_FRAME_MAX_PAYLOAD]; - size_t serialized = sp_packet_serialize(&pf->packet, pkt_buf, sizeof(pkt_buf)); - if (serialized == 0) + const size_t total = (size_t)pf->path_len + SW_PTP_HEADER_LEN + ccsds_len; + if (buf_len < total) return 0; - /* Now create Space Wire frame with packet as payload */ - sw_frame_t frame = pf->frame; - frame.payload = pkt_buf; - frame.payload_len = (uint16_t)serialized; + size_t offset = 0; - size_t frame_size = sw_frame_encode(&frame, buf, buf_len); - if (frame_size > 0) + /* Target SpaceWire (path) Address (clause 5.3.1). */ + if (pf->path_len > 0) { - g_sw_stats.packets_sent++; - g_sw_stats.bytes_sent += (uint32_t)frame_size; + memcpy(&buf[offset], pf->path, pf->path_len); + offset += pf->path_len; } - return frame_size; + /* Encapsulation header (clause 5.4.1). */ + buf[offset++] = pf->logical_addr; /* Target Logical Address (5.3.2) */ + buf[offset++] = (uint8_t)SW_PTP_PROTOCOL_ID; /* Protocol Identifier = 0x02 */ + buf[offset++] = (uint8_t)SW_PTP_RESERVED; /* Reserved = 0x00 */ + buf[offset++] = pf->user_app; /* User Application (5.3.5) */ + + /* CCSDS Space Packet (clause 5.3.6). The length bounds (sp_packet_serialize_size) + * and the buffer size were validated above, so serialisation cannot fail here. */ + const size_t written = sp_packet_serialize(&pf->packet, &buf[offset], buf_len - offset); + offset += written; + + g_sw_stats.packets_sent++; + g_sw_stats.bytes_sent += (uint32_t)offset; + + return offset; } /* ============================================================================ - * PACKET DECODING (PARSING) + * PACKET DECODING (clause 5.5.4) * ============================================================================ */ -int sw_packet_decode(sw_packet_frame_t *pf, uint8_t *buf, size_t buf_len) +/* Clear the delivered packet/user-application fields (clause 5.2.3.2 b). */ +static void sw_packet_clear_payload(sw_packet_frame_t *pf) +{ + pf->path = NULL; + pf->path_len = 0; + pf->logical_addr = 0; + pf->user_app = 0; + sp_packet_init(&pf->packet); +} + +/* Discard a received packet: clear the delivered fields, count it, and report + * the status code (clause 5.5.4). Always returns 0; @p pf must be non-NULL. */ +static int sw_packet_discard(sw_packet_frame_t *pf, sw_ptp_status_t *status, sw_ptp_status_t code) +{ + sw_packet_clear_payload(pf); + g_sw_stats.packets_discarded++; + if (status) + *status = code; + return 0; +} + +int sw_packet_decode(sw_packet_frame_t *pf, + const uint8_t *buf, + size_t buf_len, + sw_end_marker_t end, + sw_ptp_status_t *status) { if (!pf || !buf) + { + if (status) + *status = SW_PTP_STATUS_INVALID; return 0; + } - /* First, decode the Space Wire frame */ - if (!sw_frame_decode(&pf->frame, buf, buf_len)) - return 0; + /* clause 5.5.4.4: a packet terminated by EEP shall be discarded. */ + if (end == SW_END_EEP) + return sw_packet_discard(pf, status, SW_PTP_STATUS_EEP); - /* Then, parse the CCSDS packet from frame payload */ - uint8_t *payload_buf = pf->frame.payload; - size_t payload_len = pf->frame.payload_len; + /* Need the encapsulation header plus a minimal CCSDS packet. */ + if (buf_len < (size_t)SW_PTP_HEADER_LEN + SW_PTP_CCSDS_MIN_LEN) + return sw_packet_discard(pf, status, SW_PTP_STATUS_INVALID); - if (!sp_packet_parse(&pf->packet, payload_buf, payload_len)) - return 0; + const uint8_t logical = buf[0]; + const uint8_t proto = buf[1]; + const uint8_t reserved = buf[2]; + const uint8_t user_app = buf[3]; + + /* clause 5.5.4.1: only Protocol Identifier 0x02 is a CCSDS PTP packet. */ + if (proto != SW_PTP_PROTOCOL_ID) + return sw_packet_discard(pf, status, SW_PTP_STATUS_INVALID); + + /* clause 5.5.4.3: Reserved field non-zero -> discard, packet/user app null. */ + if (reserved != SW_PTP_RESERVED) + return sw_packet_discard(pf, status, SW_PTP_STATUS_RESERVED_NONZERO); + + /* clause 5.5.4.2: extract the CCSDS packet and User Application value. */ + if (!sp_packet_parse(&pf->packet, &buf[SW_PTP_HEADER_LEN], buf_len - SW_PTP_HEADER_LEN)) + return sw_packet_discard(pf, status, SW_PTP_STATUS_INVALID); + + pf->path = NULL; + pf->path_len = 0; + pf->logical_addr = logical; + pf->user_app = user_app; g_sw_stats.packets_received++; g_sw_stats.bytes_received += (uint32_t)buf_len; - return 1; /* Success */ + if (status) + *status = SW_PTP_STATUS_OK; + return 1; } /* ============================================================================ * CONVENIENCE FUNCTION * ============================================================================ */ -size_t sw_packet_create(uint8_t device_addr, - uint8_t target_addr, +size_t sw_packet_create(uint8_t logical_addr, + uint8_t user_app, uint16_t apid, const uint8_t *payload, uint16_t payload_len, @@ -118,21 +184,16 @@ size_t sw_packet_create(uint8_t device_addr, if (!buf) return 0; - /* Create packet frame */ - sw_packet_config_t config = {.device_addr = device_addr, - .target_addr = target_addr, - .protocol_id = 1, /* CCSDS */ - .enable_crc = 1}; + const sw_packet_config_t config = { + .path = NULL, .path_len = 0, .logical_addr = logical_addr, .user_app = user_app}; sw_packet_frame_t pf; sw_packet_init(&pf, &config); - /* Set CCSDS packet fields */ - pf.packet.ph.apid = (unsigned)(apid & 0x7FFU); - pf.packet.payload = payload; - pf.packet.payload_len = payload_len; + pf.packet.ph.apid = (unsigned)(apid & 0x7FFu); + pf.packet.data = payload; + pf.packet.data_len = payload_len; - /* Serialize and return */ return sw_packet_encode(&pf, buf, buf_len); } diff --git a/src/spacewire_router.c b/src/spacewire_router.c index 9acfed4..5719781 100644 --- a/src/spacewire_router.c +++ b/src/spacewire_router.c @@ -1,5 +1,5 @@ /* - * Space Wire Router - Packet routing and virtual channels + * SpaceWire routing switch (ECSS-E-ST-50-12C clause 5.6.8) and link-state helpers. */ #include "../include/spacewire.h" @@ -7,90 +7,109 @@ #include /* ============================================================================ - * ROUTER INITIALIZATION + * ROUTER INITIALISATION * ============================================================================ */ -void sw_router_init(sw_router_t *router, uint8_t device_addr, uint8_t num_ports) +void sw_router_init(sw_router_t *router, uint8_t num_ports) { if (!router) return; - memset(router, 0, sizeof(sw_router_t)); - router->device_addr = device_addr; - router->num_ports = (num_ports <= SW_MAX_PORTS) ? num_ports : SW_MAX_PORTS; + memset(router, 0, sizeof(*router)); + + if (num_ports == 0) + num_ports = 1; + if (num_ports > SW_NUM_PORTS) + num_ports = SW_NUM_PORTS; + router->num_ports = num_ports; - /* Initialize ports */ for (uint8_t i = 0; i < router->num_ports; i++) { router->links[i].port_id = i; router->links[i].state = SW_LINK_UNINITIALIZED; } - - /* Initialize virtual channels */ - for (uint8_t i = 0; i < SW_MAX_VIRTUAL_CHANNELS; i++) - { - router->channels[i].channel_id = i; - router->channels[i].active = 0; - router->channels[i].fct_credits = 64; /* Default credits */ - } } /* ============================================================================ * ROUTING CONFIGURATION * ============================================================================ */ -void sw_router_add_route(sw_router_t *router, uint8_t dest_addr, uint8_t output_port) +int sw_router_add_route(sw_router_t *router, + uint8_t logical_addr, + uint8_t output_port, + int delete_addr) { - if (!router || dest_addr >= SW_MAX_PORTS || output_port >= router->num_ports) - return; - - router->routes[dest_addr].dest_addr = dest_addr; - router->routes[dest_addr].output_port = output_port; -} - -/* ============================================================================ - * VIRTUAL CHANNEL MANAGEMENT - * ============================================================================ */ + if (!router) + return 0; -int sw_router_open_channel(sw_router_t *router, uint8_t channel_id) -{ - if (!router || channel_id >= SW_MAX_VIRTUAL_CHANNELS) + /* Only logical addresses 32..254 are configurable (clause 5.6.8.4). */ + if (logical_addr < SW_LOGICAL_ADDR_MIN || logical_addr == SW_LOGICAL_ADDR_RESERVED) + return 0; + if (output_port >= router->num_ports) return 0; - router->channels[channel_id].active = 1; + router->routes[logical_addr].output_port = output_port; + router->routes[logical_addr].configured = 1; + router->routes[logical_addr].delete_addr = delete_addr ? 1u : 0u; return 1; } /* ============================================================================ - * FRAME ROUTING + * ROUTING DECISION * ============================================================================ */ -int sw_router_route_frame(sw_router_t *router, const sw_frame_t *frame, uint8_t *output_port) +/* Discard a packet whose leading address references a non-existent port or an + * unconfigured routing-table entry (clause 5.6.8.5). Returns SW_ROUTE_DISCARD. */ +static sw_route_result_t sw_router_invalid_address(sw_router_t *router) { - if (!router || !frame || !output_port) - return 0; - - /* If destination is this device, don't route (local delivery) */ - if (frame->target_addr == router->device_addr) - return 0; + router->invalid_address_errors++; + router->packets_discarded++; + return SW_ROUTE_DISCARD; +} - /* Look up destination in routing table */ - if (frame->target_addr >= SW_MAX_PORTS) - return 0; /* Invalid destination */ +sw_route_result_t sw_router_route(sw_router_t *router, + const uint8_t *packet, + size_t len, + uint8_t *output_port, + uint8_t *delete_leading) +{ + if (!router || !packet || !output_port || !delete_leading) + return SW_ROUTE_DISCARD; - uint8_t port = router->routes[frame->target_addr].output_port; + *delete_leading = 0; - /* Check port exists and is connected */ - if (port >= router->num_ports) - return 0; + /* An empty packet is discarded by the first routing switch (clause 5.6.2.1). */ + if (len == 0) + { + router->packets_discarded++; + return SW_ROUTE_DISCARD; + } - if (router->links[port].state != SW_LINK_CONNECTED) - return 0; + const uint8_t lead = packet[0]; - *output_port = port; - router->links[port].tx_packets++; + if (lead <= SW_PATH_ADDR_MAX) + { + /* Path addressing (clause 5.6.8.3): the character names the output port. */ + if (lead >= router->num_ports) + return sw_router_invalid_address(router); + *output_port = lead; + *delete_leading = 1; /* a path address is always deleted (Table 5-11) */ + router->packets_routed++; + return SW_ROUTE_OK; + } - return 1; /* Success */ + /* Logical addressing (clause 5.6.8.4): look up the routing table. A logical + * address that is unconfigured (including the reserved address 255) or that + * maps to a non-existent port is discarded with an invalid-address error + * (clause 5.6.8.5). */ + const sw_route_entry_t *entry = &router->routes[lead]; + if (!entry->configured || entry->output_port >= router->num_ports) + return sw_router_invalid_address(router); + + *output_port = entry->output_port; + *delete_leading = entry->delete_addr ? 1u : 0u; /* clause 5.6.8.6 */ + router->packets_routed++; + return SW_ROUTE_OK; } /* ============================================================================ diff --git a/tests/test_codec.c b/tests/test_codec.c deleted file mode 100644 index b394ca6..0000000 --- a/tests/test_codec.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "cunit.h" -#include "spacewire.h" -#include "test_runners.h" - -static int test_encode_decode(void) -{ - for (int i = 4; i < 256; i++) - { - uint8_t encoded; - uint8_t parity = sw_encode_char((uint8_t)i, &encoded); - - uint8_t decoded; - sw_char_result_t result = sw_decode_char(encoded, parity, &decoded); - - ASSERT_EQ_INT(SW_CHAR_OK, result); - ASSERT_EQ_INT((uint8_t)i, decoded); - } - - return 0; -} - -static int test_parity_error(void) -{ - uint8_t encoded; - uint8_t parity = sw_encode_char(0x55, &encoded); - - uint8_t decoded; - uint8_t wrong_parity = parity ^ 1; - sw_char_result_t result = sw_decode_char(encoded, wrong_parity, &decoded); - - ASSERT_EQ_INT(SW_CHAR_PARITY_ERROR, result); - return 0; -} - -static int test_crc_computation(void) -{ - const uint8_t data[] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; - uint16_t crc = sw_crc16(data, sizeof(data)); - - ASSERT_TRUE(crc != 0); - - uint16_t crc2 = sw_crc16(data, sizeof(data)); - ASSERT_EQ_INT(crc, crc2); - - return 0; -} - -static int test_codec_special_chars_and_invalid_args(void) -{ - uint8_t decoded = 0xAA; - - ASSERT_EQ_INT(SW_CHAR_INVALID, sw_decode_char(0x10, 0, NULL)); - - uint8_t encoded; - uint8_t parity; - - parity = sw_encode_char(SPACEWIRE_ESC, &encoded); - ASSERT_EQ_INT(SW_CHAR_ESCAPE, sw_decode_char(encoded, parity, &decoded)); - - parity = sw_encode_char(SPACEWIRE_FCT, &encoded); - ASSERT_EQ_INT(SW_CHAR_FCT, sw_decode_char(encoded, parity, &decoded)); - - parity = sw_encode_char(SPACEWIRE_EOP, &encoded); - ASSERT_EQ_INT(SW_CHAR_EOP, sw_decode_char(encoded, parity, &decoded)); - - parity = sw_encode_char(SPACEWIRE_EEP, &encoded); - ASSERT_EQ_INT(SW_CHAR_EEP, sw_decode_char(encoded, parity, &decoded)); - - ASSERT_EQ_INT(0, sw_encode_char(0xAB, NULL)); - ASSERT_EQ_INT(0xFFFF, sw_crc16(NULL, 12)); - - return 0; -} - -pus_test_result_t test_spacewire_codec_run_all(void) -{ - RUN_TEST(test_encode_decode); - RUN_TEST(test_parity_error); - RUN_TEST(test_crc_computation); - RUN_TEST(test_codec_special_chars_and_invalid_args); - return (pus_test_result_t){cunit_total_tests - cunit_overall_failures, cunit_total_tests}; -} diff --git a/tests/test_frame.c b/tests/test_frame.c index 5255158..9eb57b9 100644 --- a/tests/test_frame.c +++ b/tests/test_frame.c @@ -2,106 +2,74 @@ #include "spacewire.h" #include "test_runners.h" -static int test_frame_encode_decode(void) -{ - uint8_t payload[] = {0x01, 0x02, 0x03, 0x04, 0x05}; - sw_frame_t frame; - sw_frame_init(&frame); - frame.target_addr = 0x42; - frame.protocol_id = 1; - frame.payload = payload; - frame.payload_len = sizeof(payload); +#include - uint8_t buf[256]; - size_t encoded_size = sw_frame_encode(&frame, buf, sizeof(buf)); - ASSERT_TRUE(encoded_size > 0); +/* A logical-addressed packet: one address octet followed by the cargo. */ +static int test_packet_build_logical(void) +{ + const uint8_t dest[1] = {0x40}; + const uint8_t cargo[3] = {0xAA, 0xBB, 0xCC}; + uint8_t buf[16]; - sw_frame_t decoded; - int result = sw_frame_decode(&decoded, buf, encoded_size); - ASSERT_EQ_INT(1, result); - ASSERT_EQ_INT(0x42, decoded.target_addr); - ASSERT_EQ_INT(1, decoded.protocol_id); - ASSERT_EQ_INT(sizeof(payload), decoded.payload_len); - ASSERT_EQ_MEM(decoded.payload, payload, sizeof(payload)); + size_t n = sw_spw_packet_build(dest, sizeof(dest), cargo, sizeof(cargo), buf, sizeof(buf)); + ASSERT_EQ_INT(4, (int)n); + const uint8_t expected[4] = {0x40, 0xAA, 0xBB, 0xCC}; + ASSERT_EQ_MEM(buf, expected, sizeof(expected)); return 0; } -static int test_frame_crc_validation(void) +/* A path-addressed packet: a multi-hop path address followed by the cargo. */ +static int test_packet_build_path(void) { - uint8_t payload[] = {0xAA, 0xBB}; - sw_frame_t frame; - sw_frame_init(&frame); - frame.target_addr = 0x01; - frame.protocol_id = 1; - frame.payload = payload; - frame.payload_len = sizeof(payload); - - uint8_t buf[256]; - size_t size = sw_frame_encode(&frame, buf, sizeof(buf)); - ASSERT_TRUE(size > 0); - - buf[3] ^= 0xFF; + const uint8_t dest[3] = {2, 1, 3}; + const uint8_t cargo[2] = {0xDE, 0xAD}; + uint8_t buf[16]; - sw_frame_t decoded; - int result = sw_frame_decode(&decoded, buf, size); - ASSERT_EQ_INT(0, result); + size_t n = sw_spw_packet_build(dest, sizeof(dest), cargo, sizeof(cargo), buf, sizeof(buf)); + ASSERT_EQ_INT(5, (int)n); + const uint8_t expected[5] = {2, 1, 3, 0xDE, 0xAD}; + ASSERT_EQ_MEM(buf, expected, sizeof(expected)); return 0; } -static int test_frame_size_calculation(void) +/* A point-to-point link needs no destination address (clause 4.2.3.3). */ +static int test_packet_build_cargo_only(void) { - sw_frame_t frame; - sw_frame_init(&frame); - - uint8_t payload[100]; - frame.payload = payload; - frame.payload_len = 100; - - size_t calc_size = sw_frame_size(&frame); - ASSERT_EQ_INT(104, calc_size); + const uint8_t cargo[2] = {0x11, 0x22}; + uint8_t buf[8]; + size_t n = sw_spw_packet_build(NULL, 0, cargo, sizeof(cargo), buf, sizeof(buf)); + ASSERT_EQ_INT(2, (int)n); + ASSERT_EQ_MEM(buf, cargo, sizeof(cargo)); return 0; } -static int test_frame_invalid_args_and_minimal_frame(void) +static int test_packet_build_errors(void) { - sw_frame_init(NULL); - ASSERT_EQ_INT(0, sw_frame_size(NULL)); - - sw_frame_t frame; - sw_frame_init(&frame); - frame.target_addr = 0x05; - frame.protocol_id = 0x01; - - ASSERT_EQ_INT(0, sw_frame_encode(NULL, (uint8_t *)&frame, 4)); - - uint8_t buf[8] = {0}; - ASSERT_EQ_INT(0, sw_frame_encode(&frame, NULL, sizeof(buf))); - ASSERT_EQ_INT(0, sw_frame_encode(&frame, buf, 3)); - - size_t encoded = sw_frame_encode(&frame, buf, sizeof(buf)); - ASSERT_EQ_INT(4, encoded); - - sw_frame_t decoded; - ASSERT_EQ_INT(0, sw_frame_decode(NULL, buf, encoded)); - ASSERT_EQ_INT(0, sw_frame_decode(&decoded, NULL, encoded)); - ASSERT_EQ_INT(0, sw_frame_decode(&decoded, buf, 3)); - - ASSERT_EQ_INT(1, sw_frame_decode(&decoded, buf, encoded)); - ASSERT_EQ_INT(0x05, decoded.target_addr); - ASSERT_EQ_INT(0x01, decoded.protocol_id); - ASSERT_EQ_INT(0, decoded.payload_len); - + const uint8_t dest[1] = {0x40}; + const uint8_t cargo[2] = {0x11, 0x22}; + uint8_t buf[2]; + + /* NULL output buffer. */ + ASSERT_EQ_INT(0, sw_spw_packet_build(dest, 1, cargo, 2, NULL, 8)); + /* dest_len > 0 but dest NULL. */ + ASSERT_EQ_INT(0, sw_spw_packet_build(NULL, 1, cargo, 2, buf, sizeof(buf))); + /* cargo_len > 0 but cargo NULL. */ + ASSERT_EQ_INT(0, sw_spw_packet_build(dest, 1, NULL, 2, buf, sizeof(buf))); + /* Total length exceeds the buffer (1 + 2 > 2). */ + ASSERT_EQ_INT(0, sw_spw_packet_build(dest, 1, cargo, 2, buf, sizeof(buf))); + /* Empty packet (no data characters). */ + ASSERT_EQ_INT(0, sw_spw_packet_build(NULL, 0, NULL, 0, buf, sizeof(buf))); return 0; } -pus_test_result_t test_spacewire_frame_run_all(void) +test_result_t test_spacewire_frame_run_all(void) { - RUN_TEST(test_frame_encode_decode); - RUN_TEST(test_frame_crc_validation); - RUN_TEST(test_frame_size_calculation); - RUN_TEST(test_frame_invalid_args_and_minimal_frame); - return (pus_test_result_t){cunit_total_tests - cunit_overall_failures, cunit_total_tests}; + RUN_TEST(test_packet_build_logical); + RUN_TEST(test_packet_build_path); + RUN_TEST(test_packet_build_cargo_only); + RUN_TEST(test_packet_build_errors); + return (test_result_t){cunit_total_tests - cunit_overall_failures, cunit_total_tests}; } diff --git a/tests/test_packet.c b/tests/test_packet.c index 7090498..ab92dcb 100644 --- a/tests/test_packet.c +++ b/tests/test_packet.c @@ -5,57 +5,174 @@ #include -static int test_packet_encode_decode(void) +/* Build a representative packet: logical addressing, no path. */ +static void make_sample(sw_packet_frame_t *pf, const uint8_t *data, uint16_t data_len) { - sw_packet_config_t config = {.device_addr = 0x01, - .target_addr = 0x02, - .protocol_id = 1, - .enable_crc = 1}; + const sw_packet_config_t config = { + .path = NULL, .path_len = 0, .logical_addr = 0x20, .user_app = 0x05}; + sw_packet_init(pf, &config); + pf->packet.ph.apid = 0x0042; + pf->packet.data = data; + pf->packet.data_len = data_len; +} +/* The encoded packet must match the ECSS-E-ST-50-53C Figure 5-1 layout exactly: + * [logical | 0x02 | 0x00 | user_app | CCSDS primary header | data]. */ +static int test_packet_wire_format(void) +{ + static const uint8_t data[3] = {0xAA, 0xBB, 0xCC}; sw_packet_frame_t pf; - sw_packet_init(&pf, &config); + make_sample(&pf, data, sizeof(data)); - pf.packet.ph.apid = 0x0100; - const char *payload_str = "Test payload"; - uint16_t payload_len = (uint16_t)strlen(payload_str); - pf.packet.payload = (const uint8_t *)payload_str; - pf.packet.payload_len = payload_len; + uint8_t buf[64]; + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); - uint8_t buf[512]; - size_t encoded_size = sw_packet_encode(&pf, buf, sizeof(buf)); - ASSERT_TRUE(encoded_size > 0); + /* 4-octet header + 6-octet CCSDS primary header + 3 data octets. */ + ASSERT_EQ_INT(4 + 6 + 3, (int)n); - sw_packet_frame_t decoded_pf; - int result = sw_packet_decode(&decoded_pf, buf, encoded_size); - ASSERT_EQ_INT(1, result); - ASSERT_EQ_INT(0x0100, decoded_pf.packet.ph.apid); - ASSERT_EQ_INT(payload_len, decoded_pf.packet.payload_len); + const uint8_t expected[13] = { + 0x20, 0x02, 0x00, 0x05, /* logical, proto=0x02, reserved=0, user_app */ + 0x10, 0x42, 0x00, 0x00, 0x00, 0x02, /* CCSDS hdr: TC, APID 0x042, seq 0, len=2 */ + 0xAA, 0xBB, 0xCC /* data field */ + }; + ASSERT_EQ_MEM(buf, expected, sizeof(expected)); + return 0; +} +static int test_packet_encode_decode_roundtrip(void) +{ + static const uint8_t data[12] = "Test payload"; + sw_packet_frame_t pf; + make_sample(&pf, data, sizeof(data)); + + uint8_t buf[64]; + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); + ASSERT_TRUE(n > 0); + + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_INVALID; + int ok = sw_packet_decode(&out, buf, n, SW_END_EOP, &status); + + ASSERT_EQ_INT(1, ok); + ASSERT_EQ_INT(SW_PTP_STATUS_OK, status); + ASSERT_EQ_INT(0x20, out.logical_addr); + ASSERT_EQ_INT(0x05, out.user_app); + ASSERT_EQ_INT(0x0042, out.packet.ph.apid); + ASSERT_EQ_INT((int)sizeof(data), out.packet.data_len); + ASSERT_EQ_MEM(out.packet.data, data, sizeof(data)); return 0; } -static int test_packet_create_convenience(void) +/* A path address is emitted ahead of the header and stripped before decode. */ +static int test_packet_with_path(void) { - uint8_t payload[] = {0x11, 0x22, 0x33}; - uint8_t buf[512]; + static const uint8_t path[3] = {1, 2, 3}; + static const uint8_t data[4] = {0xDE, 0xAD, 0xBE, 0xEF}; + sw_packet_frame_t pf; + make_sample(&pf, data, sizeof(data)); + pf.path = path; + pf.path_len = sizeof(path); + + uint8_t buf[64]; + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); + ASSERT_EQ_INT(3 + 4 + 6 + 4, (int)n); /* path + header + CCSDS hdr + data */ + ASSERT_EQ_MEM(buf, path, sizeof(path)); + ASSERT_EQ_INT(0x20, buf[3]); /* logical address follows the path */ + ASSERT_EQ_INT(0x02, buf[4]); /* protocol identifier */ + + /* The network strips the path; the target decodes from the logical address. */ + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_INVALID; + int ok = sw_packet_decode(&out, buf + sizeof(path), n - sizeof(path), SW_END_EOP, &status); + ASSERT_EQ_INT(1, ok); + ASSERT_EQ_INT(SW_PTP_STATUS_OK, status); + ASSERT_EQ_MEM(out.packet.data, data, sizeof(data)); + return 0; +} + +static int test_packet_decode_eep_discarded(void) +{ + static const uint8_t data[4] = {1, 2, 3, 4}; + sw_packet_frame_t pf; + make_sample(&pf, data, sizeof(data)); - size_t size = sw_packet_create(0x01, 0x02, 0x0042, payload, sizeof(payload), buf, sizeof(buf)); + uint8_t buf[64]; + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); - ASSERT_TRUE(size > 0); - ASSERT_TRUE(size > sizeof(payload) + 6); + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_OK; + int ok = sw_packet_decode(&out, buf, n, SW_END_EEP, &status); + ASSERT_EQ_INT(0, ok); /* clause 5.5.4.4 */ + ASSERT_EQ_INT(SW_PTP_STATUS_EEP, status); + ASSERT_TRUE(out.packet.data == NULL); /* clause 5.2.3.2 b */ return 0; } -static int test_packet_init_invalid_args_and_defaults(void) +static int test_packet_decode_reserved_nonzero(void) { - sw_packet_config_t config = { - .device_addr = 0xAA, - .target_addr = 0x55, - .protocol_id = 7, - .enable_crc = 0, + static const uint8_t data[4] = {1, 2, 3, 4}; + sw_packet_frame_t pf; + make_sample(&pf, data, sizeof(data)); + + uint8_t buf[64]; + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); + buf[2] = 0x01; /* corrupt the Reserved field */ + + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_OK; + int ok = sw_packet_decode(&out, buf, n, SW_END_EOP, &status); + + ASSERT_EQ_INT(0, ok); /* clause 5.5.4.3 */ + ASSERT_EQ_INT(SW_PTP_STATUS_RESERVED_NONZERO, status); + ASSERT_TRUE(out.packet.data == NULL); + return 0; +} + +static int test_packet_decode_bad_protocol_id(void) +{ + static const uint8_t data[4] = {1, 2, 3, 4}; + sw_packet_frame_t pf; + make_sample(&pf, data, sizeof(data)); + + uint8_t buf[64]; + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); + buf[1] = 0x01; /* not the CCSDS PTP protocol identifier */ + + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_OK; + int ok = sw_packet_decode(&out, buf, n, SW_END_EOP, &status); + + ASSERT_EQ_INT(0, ok); /* clause 5.5.4.1 */ + ASSERT_EQ_INT(SW_PTP_STATUS_INVALID, status); + return 0; +} + +/* Valid PTP header, but the CCSDS length field declares more data than is + * present, so the encapsulated CCSDS parse fails (clause 5.5.4.2). */ +static int test_packet_decode_malformed_ccsds(void) +{ + const uint8_t buf[11] = { + 0x20, 0x02, 0x00, 0x05, /* logical, proto=0x02, reserved=0, user_app */ + 0x10, 0x42, 0x00, 0x00, 0x00, 0x05, /* CCSDS hdr: length field 5 -> data_len 6 */ + 0xAA /* only 1 of the 6 declared data octets */ }; + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_OK; + int ok = sw_packet_decode(&out, buf, sizeof(buf), SW_END_EOP, &status); + + ASSERT_EQ_INT(0, ok); + ASSERT_EQ_INT(SW_PTP_STATUS_INVALID, status); + ASSERT_TRUE(out.packet.data == NULL); /* cleared on discard */ + return 0; +} + +static int test_packet_init_defaults(void) +{ + const sw_packet_config_t config = { + .path = NULL, .path_len = 0, .logical_addr = 0x55, .user_app = 0x99}; + sw_packet_frame_t pf; memset(&pf, 0xA5, sizeof(pf)); @@ -63,133 +180,169 @@ static int test_packet_init_invalid_args_and_defaults(void) sw_packet_init(&pf, NULL); sw_packet_init(&pf, &config); - ASSERT_EQ_INT(0x55, pf.frame.target_addr); - ASSERT_EQ_INT(7, pf.frame.protocol_id); + ASSERT_EQ_INT(0x55, pf.logical_addr); + ASSERT_EQ_INT(0x99, pf.user_app); + ASSERT_EQ_INT(0, pf.path_len); ASSERT_EQ_INT(0, pf.packet.ph.version); - ASSERT_EQ_INT(1, pf.packet.ph.type); + ASSERT_EQ_INT(SP_PACKET_TYPE_TC, pf.packet.ph.type); ASSERT_EQ_INT(0, pf.packet.ph.sec_hdr_flag); ASSERT_EQ_INT(0, pf.packet.ph.apid); - return 0; } static int test_packet_encode_error_paths(void) { - sw_packet_config_t config = {.device_addr = 0x01, - .target_addr = 0x02, - .protocol_id = 1, - .enable_crc = 1}; + static const uint8_t data[4] = {1, 2, 3, 4}; sw_packet_frame_t pf; - sw_packet_init(&pf, &config); + make_sample(&pf, data, sizeof(data)); - uint8_t buf[512]; + uint8_t buf[64]; ASSERT_EQ_INT(0, sw_packet_encode(NULL, buf, sizeof(buf))); ASSERT_EQ_INT(0, sw_packet_encode(&pf, NULL, sizeof(buf))); - static uint8_t dummy_payload = 0x11; - pf.packet.payload = &dummy_payload; - pf.packet.payload_len = UINT16_MAX; + /* CCSDS data pointer NULL with non-zero length. */ + make_sample(&pf, NULL, 4); ASSERT_EQ_INT(0, sw_packet_encode(&pf, buf, sizeof(buf))); - pf.packet.payload = NULL; - pf.packet.payload_len = 8; + /* Empty CCSDS data field is below the minimum packet length. */ + make_sample(&pf, data, 0); ASSERT_EQ_INT(0, sw_packet_encode(&pf, buf, sizeof(buf))); - pf.packet.payload = &dummy_payload; - pf.packet.payload_len = 0; - pf.packet.ph.sec_hdr_flag = 1; - pf.packet.sec_hdr = NULL; - pf.packet.sec_hdr_len = 2; + /* path_len set but path pointer NULL. */ + make_sample(&pf, data, sizeof(data)); + pf.path = NULL; + pf.path_len = 2; ASSERT_EQ_INT(0, sw_packet_encode(&pf, buf, sizeof(buf))); - pf.packet.sec_hdr = &dummy_payload; - pf.packet.sec_hdr_len = 1; + /* path octet outside the valid 0..31 range. */ + static const uint8_t bad_path[1] = {32}; + make_sample(&pf, data, sizeof(data)); + pf.path = bad_path; + pf.path_len = 1; ASSERT_EQ_INT(0, sw_packet_encode(&pf, buf, sizeof(buf))); + /* Output buffer too small. */ + make_sample(&pf, data, sizeof(data)); + ASSERT_EQ_INT(0, sw_packet_encode(&pf, buf, 5)); return 0; } -static int test_packet_decode_error_paths_and_stats(void) +static int test_packet_decode_error_paths(void) { - sw_packet_config_t config = {.device_addr = 0x01, - .target_addr = 0x02, - .protocol_id = 1, - .enable_crc = 1}; - sw_packet_frame_t pf; - sw_packet_init(&pf, &config); + const uint8_t buf[16] = {0}; + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_OK; - uint8_t tiny[3] = {0}; - ASSERT_EQ_INT(0, sw_packet_decode(NULL, tiny, sizeof(tiny))); - ASSERT_EQ_INT(0, sw_packet_decode(&pf, NULL, sizeof(tiny))); - ASSERT_EQ_INT(0, sw_packet_decode(&pf, tiny, sizeof(tiny))); + ASSERT_EQ_INT(0, sw_packet_decode(NULL, buf, sizeof(buf), SW_END_EOP, &status)); + ASSERT_EQ_INT(SW_PTP_STATUS_INVALID, status); + + status = SW_PTP_STATUS_OK; + ASSERT_EQ_INT(0, sw_packet_decode(&out, NULL, sizeof(buf), SW_END_EOP, &status)); + ASSERT_EQ_INT(SW_PTP_STATUS_INVALID, status); + + /* Shorter than header + minimal CCSDS packet (11 octets). */ + status = SW_PTP_STATUS_OK; + ASSERT_EQ_INT(0, sw_packet_decode(&out, buf, 10, SW_END_EOP, &status)); + ASSERT_EQ_INT(SW_PTP_STATUS_INVALID, status); + + /* A NULL status pointer is tolerated. */ + ASSERT_EQ_INT(0, sw_packet_decode(&out, buf, 10, SW_END_EOP, NULL)); + return 0; +} + +static int test_packet_create_convenience(void) +{ + const uint8_t payload[3] = {0x11, 0x22, 0x33}; + uint8_t buf[64]; + + size_t n = sw_packet_create(0xFE, 0x07, 0x0042, payload, sizeof(payload), buf, sizeof(buf)); + ASSERT_EQ_INT(4 + 6 + 3, (int)n); + ASSERT_EQ_INT(0xFE, buf[0]); + ASSERT_EQ_INT(0x02, buf[1]); + + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_INVALID; + ASSERT_EQ_INT(1, sw_packet_decode(&out, buf, n, SW_END_EOP, &status)); + ASSERT_EQ_INT(SW_PTP_STATUS_OK, status); + ASSERT_EQ_INT(0x07, out.user_app); + ASSERT_EQ_INT(0x0042, out.packet.ph.apid); + + ASSERT_EQ_INT(0, sw_packet_create(0xFE, 0, 0x0042, payload, sizeof(payload), NULL, sizeof(buf))); + return 0; +} - uint8_t small_payload[] = {0xAB}; - sw_frame_t frame; - sw_frame_init(&frame); - frame.target_addr = 0x22; - frame.protocol_id = 1; - frame.payload = small_payload; - frame.payload_len = sizeof(small_payload); +static int test_packet_statistics(void) +{ + static const uint8_t data[4] = {1, 2, 3, 4}; + sw_packet_frame_t pf; + make_sample(&pf, data, sizeof(data)); - uint8_t frame_buf[64]; - size_t frame_len = sw_frame_encode(&frame, frame_buf, sizeof(frame_buf)); - ASSERT_TRUE(frame_len > 0); - ASSERT_EQ_INT(0, sw_packet_decode(&pf, frame_buf, frame_len)); + uint8_t buf[64]; sw_reset_statistics(); sw_statistics_t stats; - sw_get_statistics(NULL); + sw_get_statistics(NULL); /* tolerate NULL */ sw_get_statistics(&stats); ASSERT_EQ_INT(0, stats.packets_sent); - ASSERT_EQ_INT(0, stats.packets_received); - ASSERT_EQ_INT(0, stats.bytes_sent); - ASSERT_EQ_INT(0, stats.bytes_received); - - sw_packet_frame_t tx_pf; - sw_packet_init(&tx_pf, &config); - tx_pf.packet.ph.apid = 0x22; - static uint8_t payload[] = {1, 2, 3, 4}; - tx_pf.packet.payload = payload; - tx_pf.packet.payload_len = sizeof(payload); - uint8_t packet_buf[512]; - size_t packet_len = sw_packet_encode(&tx_pf, packet_buf, sizeof(packet_buf)); - ASSERT_TRUE(packet_len > 0); - - sw_packet_frame_t rx_pf; - ASSERT_EQ_INT(1, sw_packet_decode(&rx_pf, packet_buf, packet_len)); + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); + sw_packet_frame_t out; + ASSERT_EQ_INT(1, sw_packet_decode(&out, buf, n, SW_END_EOP, NULL)); + ASSERT_EQ_INT(0, sw_packet_decode(&out, buf, n, SW_END_EEP, NULL)); /* discarded */ sw_get_statistics(&stats); ASSERT_EQ_INT(1, stats.packets_sent); ASSERT_EQ_INT(1, stats.packets_received); + ASSERT_EQ_INT(1, stats.packets_discarded); ASSERT_TRUE(stats.bytes_sent > 0); ASSERT_TRUE(stats.bytes_received > 0); sw_reset_statistics(); sw_get_statistics(&stats); - ASSERT_EQ_INT(0, stats.packets_sent); ASSERT_EQ_INT(0, stats.packets_received); - ASSERT_EQ_INT(0, stats.bytes_sent); - ASSERT_EQ_INT(0, stats.bytes_received); - return 0; } -static int test_packet_create_null_buffer(void) +/* The virtual channel rides in the User Application field (clause 5.3.5 NOTE 2). */ +static int test_packet_virtual_channel(void) { - uint8_t payload[] = {0x11, 0x22}; - ASSERT_EQ_INT(0, sw_packet_create(0x01, 0x02, 0x0042, payload, sizeof(payload), NULL, 128)); + static const uint8_t data[4] = {1, 2, 3, 4}; + sw_packet_frame_t pf; + make_sample(&pf, data, sizeof(data)); + + sw_packet_set_virtual_channel(&pf, 0x07); + ASSERT_EQ_INT(0x07, sw_packet_virtual_channel(&pf)); + ASSERT_EQ_INT(0x07, pf.user_app); + + uint8_t buf[64]; + size_t n = sw_packet_encode(&pf, buf, sizeof(buf)); + ASSERT_TRUE(n > 0); + + sw_packet_frame_t out; + sw_ptp_status_t status = SW_PTP_STATUS_INVALID; + ASSERT_EQ_INT(1, sw_packet_decode(&out, buf, n, SW_END_EOP, &status)); + ASSERT_EQ_INT(0x07, sw_packet_virtual_channel(&out)); + + sw_packet_set_virtual_channel(NULL, 0x09); /* no-op, must not crash */ + ASSERT_EQ_INT(0, sw_packet_virtual_channel(NULL)); return 0; } -pus_test_result_t test_spacewire_packet_run_all(void) +test_result_t test_spacewire_packet_run_all(void) { - RUN_TEST(test_packet_encode_decode); - RUN_TEST(test_packet_create_convenience); - RUN_TEST(test_packet_init_invalid_args_and_defaults); + RUN_TEST(test_packet_wire_format); + RUN_TEST(test_packet_encode_decode_roundtrip); + RUN_TEST(test_packet_with_path); + RUN_TEST(test_packet_decode_eep_discarded); + RUN_TEST(test_packet_decode_reserved_nonzero); + RUN_TEST(test_packet_decode_bad_protocol_id); + RUN_TEST(test_packet_decode_malformed_ccsds); + RUN_TEST(test_packet_init_defaults); RUN_TEST(test_packet_encode_error_paths); - RUN_TEST(test_packet_decode_error_paths_and_stats); - RUN_TEST(test_packet_create_null_buffer); - return (pus_test_result_t){cunit_total_tests - cunit_overall_failures, cunit_total_tests}; + RUN_TEST(test_packet_decode_error_paths); + RUN_TEST(test_packet_create_convenience); + RUN_TEST(test_packet_virtual_channel); + RUN_TEST(test_packet_statistics); + return (test_result_t){cunit_total_tests - cunit_overall_failures, cunit_total_tests}; } diff --git a/tests/test_router.c b/tests/test_router.c index 3b4c45c..46b3a11 100644 --- a/tests/test_router.c +++ b/tests/test_router.c @@ -2,99 +2,131 @@ #include "spacewire.h" #include "test_runners.h" -static int test_router_initialization(void) +#include + +static int test_router_init(void) { sw_router_t router; - sw_router_init(&router, 0x42, 3); - - ASSERT_EQ_INT(0x42, router.device_addr); - ASSERT_EQ_INT(3, router.num_ports); + sw_router_init(&router, 4); + ASSERT_EQ_INT(4, router.num_ports); ASSERT_EQ_INT(0, router.links[0].port_id); - ASSERT_EQ_INT(0, router.channels[0].active); + ASSERT_EQ_INT(SW_LINK_UNINITIALIZED, router.links[0].state); + ASSERT_EQ_INT(0, (int)router.invalid_address_errors); + /* num_ports is clamped to [1, SW_NUM_PORTS]. */ + sw_router_init(&router, 0); + ASSERT_EQ_INT(1, router.num_ports); + sw_router_init(&router, 200); + ASSERT_EQ_INT(SW_NUM_PORTS, router.num_ports); + + sw_router_init(NULL, 4); /* must not crash */ return 0; } -static int test_router_routing(void) +static int test_router_path_addressing(void) { sw_router_t router; - sw_router_init(&router, 0x01, 2); - - sw_router_add_route(&router, 0x02, 0); - sw_router_add_route(&router, 0x03, 1); - - router.links[0].state = SW_LINK_CONNECTED; - router.links[1].state = SW_LINK_CONNECTED; - - sw_frame_t frame; - sw_frame_init(&frame); - frame.target_addr = 0x02; - - uint8_t output_port; - int result = sw_router_route_frame(&router, &frame, &output_port); - ASSERT_EQ_INT(1, result); - ASSERT_EQ_INT(0, output_port); - - frame.target_addr = 0x03; - result = sw_router_route_frame(&router, &frame, &output_port); - ASSERT_EQ_INT(1, result); - ASSERT_EQ_INT(1, output_port); - + sw_router_init(&router, 8); /* ports 0..7 */ + + uint8_t port = 0xFF; + uint8_t del = 0xFF; + + /* Leading char 3 -> output port 3, always deleted (clause 5.6.8.3). */ + const uint8_t pkt[3] = {3, 0xAA, 0xBB}; + ASSERT_EQ_INT(SW_ROUTE_OK, sw_router_route(&router, pkt, sizeof(pkt), &port, &del)); + ASSERT_EQ_INT(3, port); + ASSERT_EQ_INT(1, del); + + /* Leading char 0 -> configuration port. */ + const uint8_t cfg[2] = {0, 0x11}; + ASSERT_EQ_INT(SW_ROUTE_OK, sw_router_route(&router, cfg, sizeof(cfg), &port, &del)); + ASSERT_EQ_INT(SW_PORT_CONFIG, port); + ASSERT_EQ_INT(1, del); + + /* A path char referencing a non-existent port is discarded (clause 5.6.8.5). */ + const uint8_t bad[2] = {20, 0x11}; + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(&router, bad, sizeof(bad), &port, &del)); + ASSERT_EQ_INT(1, (int)router.invalid_address_errors); return 0; } -static int test_router_error_paths_and_channels(void) +static int test_router_logical_addressing(void) { - sw_router_init(NULL, 0x01, 2); - sw_router_t router; - sw_router_init(&router, 0x44, SW_MAX_PORTS + 2); - ASSERT_EQ_INT(SW_MAX_PORTS, router.num_ports); - - sw_router_add_route(NULL, 0x01, 0); - sw_router_add_route(&router, SW_MAX_PORTS, 0); - - sw_router_add_route(&router, 0x01, 1); - ASSERT_EQ_INT(1, router.routes[0x01].output_port); - sw_router_add_route(&router, 0x01, SW_MAX_PORTS); - ASSERT_EQ_INT(1, router.routes[0x01].output_port); - - ASSERT_EQ_INT(0, sw_router_open_channel(NULL, 0)); - ASSERT_EQ_INT(0, sw_router_open_channel(&router, SW_MAX_VIRTUAL_CHANNELS)); - ASSERT_EQ_INT(1, sw_router_open_channel(&router, 3)); - ASSERT_EQ_INT(1, router.channels[3].active); - - sw_frame_t frame; - sw_frame_init(&frame); - frame.target_addr = 0x01; - - uint8_t output_port = 0xFF; - ASSERT_EQ_INT(0, sw_router_route_frame(NULL, &frame, &output_port)); - ASSERT_EQ_INT(0, sw_router_route_frame(&router, NULL, &output_port)); - ASSERT_EQ_INT(0, sw_router_route_frame(&router, &frame, NULL)); - - frame.target_addr = router.device_addr; - ASSERT_EQ_INT(0, sw_router_route_frame(&router, &frame, &output_port)); - - frame.target_addr = SW_MAX_PORTS; - ASSERT_EQ_INT(0, sw_router_route_frame(&router, &frame, &output_port)); - - sw_router_t one_port_router; - sw_router_init(&one_port_router, 0x10, 1); - one_port_router.routes[0x01].output_port = 1; - frame.target_addr = 0x01; - ASSERT_EQ_INT(0, sw_router_route_frame(&one_port_router, &frame, &output_port)); + sw_router_init(&router, 8); + + ASSERT_EQ_INT(1, sw_router_add_route(&router, 0x40, 5, 0)); /* retain address */ + ASSERT_EQ_INT(1, sw_router_add_route(&router, 0x41, 6, 1)); /* delete address */ + + uint8_t port = 0xFF; + uint8_t del = 0xFF; + + /* Logical address retained by default (clause 5.6.8.4 f). */ + const uint8_t p1[2] = {0x40, 0x99}; + ASSERT_EQ_INT(SW_ROUTE_OK, sw_router_route(&router, p1, sizeof(p1), &port, &del)); + ASSERT_EQ_INT(5, port); + ASSERT_EQ_INT(0, del); + + /* Logical address deleted when configured to (clause 5.6.8.6). */ + const uint8_t p2[2] = {0x41, 0x99}; + ASSERT_EQ_INT(SW_ROUTE_OK, sw_router_route(&router, p2, sizeof(p2), &port, &del)); + ASSERT_EQ_INT(6, port); + ASSERT_EQ_INT(1, del); + + /* Unconfigured logical address -> discard + error. */ + const uint8_t p3[2] = {0x77, 0x99}; + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(&router, p3, sizeof(p3), &port, &del)); + + /* Reserved logical address 255 -> discard + error (clause 5.6.8.5 NOTE). */ + const uint8_t p4[2] = {0xFF, 0x99}; + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(&router, p4, sizeof(p4), &port, &del)); + ASSERT_EQ_INT(2, (int)router.invalid_address_errors); + return 0; +} - sw_router_add_route(&one_port_router, 0x02, 0); - frame.target_addr = 0x02; - ASSERT_EQ_INT(0, sw_router_route_frame(&one_port_router, &frame, &output_port)); +static int test_router_add_route_validation(void) +{ + sw_router_t router; + sw_router_init(&router, 4); /* ports 0..3 */ + + ASSERT_EQ_INT(0, sw_router_add_route(NULL, 0x40, 1, 0)); + /* A path-range address may not be used as a logical route. */ + ASSERT_EQ_INT(0, sw_router_add_route(&router, 0x10, 1, 0)); + /* The reserved address 255 may not be configured. */ + ASSERT_EQ_INT(0, sw_router_add_route(&router, 0xFF, 1, 0)); + /* Output port must exist. */ + ASSERT_EQ_INT(0, sw_router_add_route(&router, 0x40, 4, 0)); + /* Boundaries: lowest logical address and highest valid port. */ + ASSERT_EQ_INT(1, sw_router_add_route(&router, 0x20, 3, 0)); + ASSERT_EQ_INT(1, sw_router_add_route(&router, 0xFE, 0, 0)); + return 0; +} +static int test_router_route_invalid_args_and_empty(void) +{ + sw_router_t router; + sw_router_init(&router, 4); + + const uint8_t pkt[2] = {0x40, 0x00}; + uint8_t port = 0; + uint8_t del = 0; + + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(NULL, pkt, sizeof(pkt), &port, &del)); + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(&router, NULL, sizeof(pkt), &port, &del)); + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(&router, pkt, sizeof(pkt), NULL, &del)); + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(&router, pkt, sizeof(pkt), &port, NULL)); + /* Invalid arguments are rejected before any counter is touched. */ + ASSERT_EQ_INT(0, (int)router.packets_discarded); + + /* An empty packet is discarded by the first routing switch (clause 5.6.2.1). */ + ASSERT_EQ_INT(SW_ROUTE_DISCARD, sw_router_route(&router, pkt, 0, &port, &del)); + ASSERT_EQ_INT(1, (int)router.packets_discarded); return 0; } static int test_link_layer_state_helpers(void) { - sw_link_config_t config = { + const sw_link_config_t config = { .bit_rate = 1000000, .disconnect_timeout = 2500, .rx_credit_max = 12, @@ -124,11 +156,13 @@ static int test_link_layer_state_helpers(void) return 0; } -pus_test_result_t test_spacewire_router_run_all(void) +test_result_t test_spacewire_router_run_all(void) { - RUN_TEST(test_router_initialization); - RUN_TEST(test_router_routing); - RUN_TEST(test_router_error_paths_and_channels); + RUN_TEST(test_router_init); + RUN_TEST(test_router_path_addressing); + RUN_TEST(test_router_logical_addressing); + RUN_TEST(test_router_add_route_validation); + RUN_TEST(test_router_route_invalid_args_and_empty); RUN_TEST(test_link_layer_state_helpers); - return (pus_test_result_t){cunit_total_tests - cunit_overall_failures, cunit_total_tests}; + return (test_result_t){cunit_total_tests - cunit_overall_failures, cunit_total_tests}; } diff --git a/tests/test_runners.h b/tests/test_runners.h index 685617b..d745ce9 100644 --- a/tests/test_runners.h +++ b/tests/test_runners.h @@ -5,11 +5,10 @@ typedef struct { int passed; int total; -} pus_test_result_t; +} test_result_t; -pus_test_result_t test_spacewire_codec_run_all(void); -pus_test_result_t test_spacewire_frame_run_all(void); -pus_test_result_t test_spacewire_router_run_all(void); -pus_test_result_t test_spacewire_packet_run_all(void); +test_result_t test_spacewire_frame_run_all(void); +test_result_t test_spacewire_router_run_all(void); +test_result_t test_spacewire_packet_run_all(void); #endif /* TEST_RUNNERS_H */ diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 0e585b9..94af76a 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -6,15 +6,10 @@ int main(void) { - pus_test_result_t r; + test_result_t r; int total_passed = 0; int total_tests = 0; - r = test_spacewire_codec_run_all(); - REPORT("codec", r); - total_passed += r.passed; - total_tests += r.total; - r = test_spacewire_frame_run_all(); REPORT("frame", r); total_passed += r.passed; diff --git a/scripts/coverage_html.sh b/tools/coverage_html.sh similarity index 97% rename from scripts/coverage_html.sh rename to tools/coverage_html.sh index 9c1e782..1ed5a7e 100644 --- a/scripts/coverage_html.sh +++ b/tools/coverage_html.sh @@ -9,7 +9,7 @@ if [[ "${OUT_FILE}" != /* ]]; then OUT_FILE="${ROOT_DIR}/${OUT_FILE}" fi -COVERAGE_CFLAGS='-O0 -g --coverage -Iinclude -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Wcast-align -Wcast-qual -Wpointer-arith -Wformat=2 -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wundef -std=c11' +COVERAGE_CFLAGS='-O0 -g --coverage -Iinclude -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Wcast-align -Wcast-qual -Wpointer-arith -Wformat=2 -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wundef -std=c99' cd "${ROOT_DIR}"