feat(sacn): E1.31-Pakete pruefen und zerlegen

This commit is contained in:
Michael Sauer
2026-09-19 23:44:55 +02:00
parent 71a579c0f9
commit cdbbe31831
5 changed files with 286 additions and 0 deletions
+1
View File
@@ -89,6 +89,7 @@ set(NL_FW_SOURCES
src/sys/sdk_stubs.c src/sys/sdk_stubs.c
src/net/nl_names.c src/net/nl_names.c
src/net/nl_http.c src/net/nl_http.c
src/net/nl_e131.c
src/net/nl_api.c src/net/nl_api.c
src/net/nl_wifi_sm.c src/net/nl_wifi_sm.c
src/net/nl_wifi_w600.c src/net/nl_wifi_w600.c
+57
View File
@@ -0,0 +1,57 @@
#include "net/nl_e131.h"
#include <string.h>
static uint16_t be16(const uint8_t *p) { return (uint16_t)(((uint16_t)p[0] << 8) | p[1]); }
static uint32_t be32(const uint8_t *p)
{
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
}
/* Flags (obere 4 Bit) muessen 0x7 sein, die Laenge (untere 12 Bit) muss exakt den Rest des
* Datagramms ab `from` beschreiben. Ein Paket, dessen Laengenfelder nicht zueinander passen,
* ist entweder verstuemmelt oder ein Angriffsversuch - in beiden Faellen: weg damit. */
static bool pdu_len_ok(const uint8_t *p, size_t len, size_t from)
{
uint16_t fl = be16(p + from);
if ((fl & 0xf000u) != 0x7000u)
return false;
return (size_t)(fl & 0x0fffu) == len - from;
}
bool nl_e131_parse(const uint8_t *buf, size_t len, nl_e131_packet_t *out)
{
static const uint8_t acn_pid[12] = { 'A', 'S', 'C', '-', 'E', '1', '.', '1', '7', 0, 0, 0 };
uint16_t pvc;
if (len < NL_E131_MIN_PACKET || len > NL_E131_MIN_PACKET + 512u)
return false;
if (be16(buf) != 0x0010u || be16(buf + 2) != 0x0000u)
return false;
if (memcmp(buf + 4, acn_pid, sizeof acn_pid) != 0)
return false;
if (!pdu_len_ok(buf, len, 16) || be32(buf + 18) != 0x00000004u)
return false;
if (!pdu_len_ok(buf, len, 38) || be32(buf + 40) != 0x00000002u)
return false;
if (!pdu_len_ok(buf, len, 115) || buf[117] != 0x02u)
return false;
if (buf[118] != 0xa1u || be16(buf + 119) != 0x0000u || be16(buf + 121) != 0x0001u)
return false;
pvc = be16(buf + 123);
if (pvc < 1u || (size_t)(pvc - 1u) != len - NL_E131_MIN_PACKET)
return false;
if (buf[125] != 0x00u) /* nur Start-Code 0 ist Lichtinhalt (0xdd waere Kanalprioritaet) */
return false;
if (buf[112] & 0x80u) /* Preview-Daten duerfen laut E1.31 nicht ausgegeben werden */
return false;
out->universe = be16(buf + 113);
out->priority = buf[108];
out->sequence = buf[111];
out->terminated = (buf[112] & 0x40u) != 0u;
out->slot_count = (uint16_t)(pvc - 1u);
out->slots = buf + NL_E131_MIN_PACKET;
return true;
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef NL_E131_H
#define NL_E131_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define NL_E131_PORT 5568u
#define NL_E131_MAX_PACKET 640u /* ein volles Paket ist 638 Byte; 640 laesst Luft zum Erkennen
* ueberlanger Datagramme, ohne den Puffer unnoetig aufzublaehen */
#define NL_E131_MIN_PACKET 126u
typedef struct {
uint16_t universe;
uint8_t priority;
uint8_t sequence;
bool terminated; /* Options-Bit "stream terminated" */
uint16_t slot_count; /* Nutzdaten ohne Start-Code, 0..512 */
const uint8_t *slots; /* zeigt in den uebergebenen Puffer, wird nicht kopiert */
} nl_e131_packet_t;
/* true = gueltiges DMX-Datenpaket (Start-Code 0, kein Preview). Alles andere: false.
* `out` wird nur bei true beschrieben. */
bool nl_e131_parse(const uint8_t *buf, size_t len, nl_e131_packet_t *out);
#endif
+1
View File
@@ -44,6 +44,7 @@ nl_test(test_reset_counter test_reset_counter.c fake_flash.c ${NL_SRC}/config/nl
nl_test(test_console_cmd test_console_cmd.c ${NL_SRC}/sys/nl_console_cmd.c) nl_test(test_console_cmd test_console_cmd.c ${NL_SRC}/sys/nl_console_cmd.c)
nl_test(test_names test_names.c ${NL_SRC}/net/nl_names.c) nl_test(test_names test_names.c ${NL_SRC}/net/nl_names.c)
nl_test(test_http test_http.c ${NL_SRC}/net/nl_http.c) nl_test(test_http test_http.c ${NL_SRC}/net/nl_http.c)
nl_test(test_e131 test_e131.c ${NL_SRC}/net/nl_e131.c)
nl_test(test_api test_api.c fake_api_ctx.c ${NL_SRC}/net/nl_api.c ${NL_SRC}/board/nl_board.c nl_test(test_api test_api.c fake_api_ctx.c ${NL_SRC}/net/nl_api.c ${NL_SRC}/board/nl_board.c
${NL_SRC}/light/nl_boot_state.c ${NL_SRC}/config/nl_settings.c ${NL_SRC}/util/nl_crc.c fake_flash.c) ${NL_SRC}/light/nl_boot_state.c ${NL_SRC}/config/nl_settings.c ${NL_SRC}/util/nl_crc.c fake_flash.c)
target_link_libraries(test_api PRIVATE nl_cjson) target_link_libraries(test_api PRIVATE nl_cjson)
+201
View File
@@ -0,0 +1,201 @@
#include <string.h>
#include "unity.h"
#include "net/nl_e131.h"
static uint8_t pkt[NL_E131_MAX_PACKET];
static size_t pkt_len;
static void be16(size_t off, uint16_t v) { pkt[off] = (uint8_t)(v >> 8); pkt[off + 1] = (uint8_t)v; }
static void be32(size_t off, uint32_t v)
{
pkt[off] = (uint8_t)(v >> 24); pkt[off + 1] = (uint8_t)(v >> 16);
pkt[off + 2] = (uint8_t)(v >> 8); pkt[off + 3] = (uint8_t)v;
}
/* Baut ein gueltiges Paket mit `slots` Slots, aufsteigend gefuellt (1, 2, 3, ...). */
static void build(uint16_t universe, uint8_t prio, uint8_t seq, uint8_t options, uint16_t slots)
{
pkt_len = 126u + slots;
memset(pkt, 0, sizeof pkt);
be16(0, 0x0010);
be16(2, 0x0000);
memcpy(pkt + 4, "ASC-E1.17\0\0\0", 12);
be16(16, (uint16_t)(0x7000u | (pkt_len - 16u)));
be32(18, 0x00000004u);
be16(38, (uint16_t)(0x7000u | (pkt_len - 38u)));
be32(40, 0x00000002u);
memcpy(pkt + 44, "ninlight test", 13);
pkt[108] = prio;
pkt[111] = seq;
pkt[112] = options;
be16(113, universe);
be16(115, (uint16_t)(0x7000u | (pkt_len - 115u)));
pkt[117] = 0x02;
pkt[118] = 0xa1;
be16(119, 0x0000);
be16(121, 0x0001);
be16(123, (uint16_t)(slots + 1u));
pkt[125] = 0x00; /* Start-Code */
for (uint16_t i = 0; i < slots; i++)
pkt[126 + i] = (uint8_t)(i + 1u);
}
void setUp(void) { build(1, 100, 0, 0, 6); }
void tearDown(void) {}
static void test_valid_packet(void)
{
nl_e131_packet_t p;
TEST_ASSERT_TRUE(nl_e131_parse(pkt, pkt_len, &p));
TEST_ASSERT_EQUAL_UINT16(1, p.universe);
TEST_ASSERT_EQUAL_UINT8(100, p.priority);
TEST_ASSERT_EQUAL_UINT8(0, p.sequence);
TEST_ASSERT_FALSE(p.terminated);
TEST_ASSERT_EQUAL_UINT16(6, p.slot_count);
TEST_ASSERT_EQUAL_UINT8(1, p.slots[0]);
TEST_ASSERT_EQUAL_UINT8(6, p.slots[5]);
}
static void test_full_universe(void)
{
nl_e131_packet_t p;
build(1, 100, 0, 0, 512);
TEST_ASSERT_EQUAL_size_t(638, pkt_len);
TEST_ASSERT_TRUE(nl_e131_parse(pkt, pkt_len, &p));
TEST_ASSERT_EQUAL_UINT16(512, p.slot_count);
}
static void test_empty_slot_set(void)
{
nl_e131_packet_t p;
build(1, 100, 0, 0, 0);
TEST_ASSERT_EQUAL_size_t(126, pkt_len);
TEST_ASSERT_TRUE(nl_e131_parse(pkt, pkt_len, &p));
TEST_ASSERT_EQUAL_UINT16(0, p.slot_count);
}
static void test_terminated_bit(void)
{
nl_e131_packet_t p;
build(1, 100, 0, 0x40, 6);
TEST_ASSERT_TRUE(nl_e131_parse(pkt, pkt_len, &p));
TEST_ASSERT_TRUE(p.terminated);
}
static void test_preview_bit_rejected(void)
{
nl_e131_packet_t p;
build(1, 100, 0, 0x80, 6);
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_bad_preamble(void)
{
nl_e131_packet_t p;
pkt[1] = 0x11;
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_bad_package_identifier(void)
{
nl_e131_packet_t p;
pkt[4] = 'X';
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_bad_root_vector(void)
{
nl_e131_packet_t p;
be32(18, 0x00000008u);
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_bad_framing_vector(void)
{
nl_e131_packet_t p;
be32(40, 0x00000001u); /* VECTOR_E131_EXTENDED_SYNCHRONIZATION */
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_bad_dmp_vector(void)
{
nl_e131_packet_t p;
pkt[117] = 0x03;
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_bad_address_type(void)
{
nl_e131_packet_t p;
pkt[118] = 0xa2;
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_per_channel_priority_start_code_rejected(void)
{
nl_e131_packet_t p;
pkt[125] = 0xdd;
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_truncated_length_fields(void)
{
nl_e131_packet_t p;
/* Root-Laenge behauptet mehr, als das Datagramm hergibt. */
be16(16, (uint16_t)(0x7000u | (pkt_len - 16u + 4u)));
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
build(1, 100, 0, 0, 6);
be16(38, (uint16_t)(0x7000u | (pkt_len - 38u - 2u)));
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
build(1, 100, 0, 0, 6);
be16(115, (uint16_t)(0x7000u | (pkt_len - 115u + 1u)));
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_property_value_count_mismatch(void)
{
nl_e131_packet_t p;
be16(123, 9); /* behauptet 8 Slots, das Paket traegt 6 */
TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p));
}
static void test_too_short_and_too_long(void)
{
nl_e131_packet_t p;
TEST_ASSERT_FALSE(nl_e131_parse(pkt, 125, &p));
TEST_ASSERT_FALSE(nl_e131_parse(pkt, 0, &p));
build(1, 100, 0, 0, 512);
TEST_ASSERT_FALSE(nl_e131_parse(pkt, 639, &p)); /* laenger als jedes gueltige Paket */
}
static void test_universe_is_read(void)
{
nl_e131_packet_t p;
build(63999, 200, 7, 0, 6);
TEST_ASSERT_TRUE(nl_e131_parse(pkt, pkt_len, &p));
TEST_ASSERT_EQUAL_UINT16(63999, p.universe);
TEST_ASSERT_EQUAL_UINT8(200, p.priority);
TEST_ASSERT_EQUAL_UINT8(7, p.sequence);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_valid_packet);
RUN_TEST(test_full_universe);
RUN_TEST(test_empty_slot_set);
RUN_TEST(test_terminated_bit);
RUN_TEST(test_preview_bit_rejected);
RUN_TEST(test_bad_preamble);
RUN_TEST(test_bad_package_identifier);
RUN_TEST(test_bad_root_vector);
RUN_TEST(test_bad_framing_vector);
RUN_TEST(test_bad_dmp_vector);
RUN_TEST(test_bad_address_type);
RUN_TEST(test_per_channel_priority_start_code_rejected);
RUN_TEST(test_truncated_length_fields);
RUN_TEST(test_property_value_count_mismatch);
RUN_TEST(test_too_short_and_too_long);
RUN_TEST(test_universe_is_read);
return UNITY_END();
}