В процессе разработки

This commit is contained in:
Maxim
2026-07-17 09:00:19 +03:00
parent 7e275ed574
commit 1472531f2f
3 changed files with 147 additions and 5 deletions

View File

@@ -20,7 +20,7 @@ PLUTO_B ?= 192.168.3.1
BIN := build
SRC := src
all: $(BIN)/transmitter $(BIN)/receiver $(BIN)/ofdm_bench $(BIN)/udp_gw
all: $(BIN)/transmitter $(BIN)/receiver $(BIN)/ofdm_bench $(BIN)/udp_gw $(BIN)/udp_probe
$(BIN):
mkdir -p $(BIN)
@@ -39,6 +39,10 @@ $(BIN)/ofdm_bench: $(SRC)/ofdm_bench.c | $(BIN)
$(BIN)/udp_gw: $(SRC)/udp_gw.c | $(BIN)
$(CC) $(CFLAGS) $< -o $@
# Тестовый генератор/приёмник UDP для бринг-апа udp_gw (busybox без nc/socat).
$(BIN)/udp_probe: $(SRC)/udp_probe.c | $(BIN)
$(CC) $(CFLAGS) $< -o $@
# Оффлайн-самопроверка GF(256) — нативная сборка (gcc хоста, напр. RPi/aarch64)
$(BIN)/gftest: $(SRC)/group_fec.c $(SRC)/group_fec.h | $(BIN)
cc -O2 -Wall -DGF_SELFTEST $(SRC)/group_fec.c -o $@
@@ -51,6 +55,7 @@ tx: $(BIN)/transmitter
rx: $(BIN)/receiver
bench: $(BIN)/ofdm_bench
gw: $(BIN)/udp_gw
probe: $(BIN)/udp_probe
gftest: $(BIN)/gftest
$(BIN)/gftest
gwtest: $(BIN)/gwtest
@@ -76,8 +81,8 @@ clean:
rm -rf $(BIN)
help:
@echo "make [all] — собрать transmitter, receiver, ofdm_bench, udp_gw (static armhf)"
@echo "make tx|rx|bench|gw — собрать отдельную цель"
@echo "make [all] — собрать transmitter, receiver, ofdm_bench, udp_gw, udp_probe (static armhf)"
@echo "make tx|rx|bench|gw|probe — собрать отдельную цель"
@echo "make gftest — оффлайн-самопроверка group-FEC (нативный cc)"
@echo "make gwtest — оффлайн-самопроверка парсера udp_gw (нативный cc)"
@echo "make deploy — залить бинарники на обе платы (scp -O в /tmp)"
@@ -86,4 +91,4 @@ help:
@echo "make clean — удалить build/"
@echo "переменные: PLUTO_A=$(PLUTO_A) PLUTO_B=$(PLUTO_B) XARM=$(XARM)"
.PHONY: all tx rx bench gw gftest gwtest deploy test check-env clean help
.PHONY: all tx rx bench gw probe gftest gwtest deploy test check-env clean help

View File

@@ -17,7 +17,7 @@ else
SCP=(scp -O)
fi
FILES=(build/transmitter build/receiver build/ofdm_bench build/udp_gw)
FILES=(build/transmitter build/receiver build/ofdm_bench build/udp_gw build/udp_probe)
TARGETS=()
case "${1:-}" in

137
src/udp_probe.c Normal file
View File

@@ -0,0 +1,137 @@
// udp_probe.c — минимальный генератор/приёмник UDP для тестирования udp_gw
// (roadmap §10 п.8). НЕ часть штатного тракта — только инструмент бринг-апа
// и E2E-тестов: busybox-rootfs Pluto без nc/socat (проверено на железе),
// а Python на хосте запрещён (CLAUDE.md правило №1) — значит свой маленький
// C-бинарь, тот же кросс-компилятор, что и весь тракт.
//
// send: udp_probe send ip:port РАЗМЕРАНКААУЗА_МС] stdin -> UDP
// recv: udp_probe recv ПОРТ ТАЙМАУТРОСТОЯ_С UDP -> stdout
//
// recv сам завершается после ТАЙМАУТРОСТОЯ_С секунд без новых датаграмм
// (busybox без `timeout` — та же причина, что и в scripts/test_file_link.sh).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static volatile sig_atomic_t g_stop = 0;
static void on_signal(int s) { (void)s; g_stop = 1; }
static int parse_hostport(const char *hostport, struct sockaddr_in *out) {
const char *colon = strrchr(hostport, ':');
if (!colon || colon == hostport) return -1;
char host[64];
size_t hlen = (size_t)(colon - hostport);
if (hlen >= sizeof(host)) return -1;
memcpy(host, hostport, hlen); host[hlen] = 0;
memset(out, 0, sizeof(*out));
out->sin_family = AF_INET;
out->sin_port = htons((uint16_t)atoi(colon + 1));
return inet_pton(AF_INET, host, &out->sin_addr) == 1 ? 0 : -1;
}
// stdin читается чанками по `chunk` байт, каждый чанк — одна UDP-датаграмма.
// Короткий чанк (fread < chunk) — признак EOF (как fread в transmitter.c).
static int mode_send(const char *hostport, int chunk, int pause_ms) {
struct sockaddr_in dst;
if (parse_hostport(hostport, &dst) != 0) {
fprintf(stderr, "udp_probe: не разобрал '%s' (ожидался ip:port)\n", hostport);
return 1;
}
if (chunk <= 0 || chunk > 65000) {
fprintf(stderr, "udp_probe: размер чанка вне диапазона\n");
return 1;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) { perror("socket"); return 1; }
uint8_t *buf = malloc((size_t)chunk);
uint64_t sent = 0, bytes = 0;
for (;;) {
size_t n = fread(buf, 1, (size_t)chunk, stdin);
if (n == 0) break;
if (sendto(fd, buf, n, 0, (struct sockaddr *)&dst, sizeof(dst)) < 0)
fprintf(stderr, "udp_probe: sendto: %s\n", strerror(errno));
else { sent++; bytes += n; }
if (n < (size_t)chunk) break; // короткое чтение = EOF
if (pause_ms > 0) usleep((useconds_t)pause_ms * 1000);
}
fprintf(stderr, "udp_probe send: отправлено %llu датаграмм, %llu байт\n",
(unsigned long long)sent, (unsigned long long)bytes);
free(buf);
close(fd);
return 0;
}
// Дампит содержимое каждой полученной датаграммы в stdout как есть (порядок
// прихода). Для сверки с исходником через md5sum, как в остальных e2e-тестах
// репозитория — если ничего не потерялось и не переупорядочилось, конкатенация
// совпадёт байт-в-байт.
static int mode_recv(int port, int idle_timeout_s) {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) { perror("socket"); return 1; }
int one = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons((uint16_t)port);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind"); close(fd); return 1;
}
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
uint8_t buf[65536];
uint64_t recvd = 0, bytes = 0;
time_t last_rx = time(NULL);
while (!g_stop) {
fd_set rfds; FD_ZERO(&rfds); FD_SET(fd, &rfds);
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
int sel = select(fd + 1, &rfds, NULL, NULL, &tv);
if (sel < 0) { if (errno == EINTR) continue; break; }
if (sel > 0 && FD_ISSET(fd, &rfds)) {
ssize_t n = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
if (n < 0) { if (errno == EINTR) continue; break; }
fwrite(buf, 1, (size_t)n, stdout);
fflush(stdout);
recvd++; bytes += (uint64_t)n;
last_rx = time(NULL);
}
if (idle_timeout_s > 0 && time(NULL) - last_rx >= idle_timeout_s) break;
}
fprintf(stderr, "udp_probe recv: получено %llu датаграмм, %llu байт\n",
(unsigned long long)recvd, (unsigned long long)bytes);
close(fd);
return 0;
}
static void usage(const char *prog) {
fprintf(stderr, "udp_probe — тестовый генератор/приёмник UDP (busybox без nc)\n\n");
fprintf(stderr, " %s send ip:port РАЗМЕРАНКААУЗА_МС] stdin -> UDP\n", prog);
fprintf(stderr, " %s recv ПОРТ ТАЙМАУТРОСТОЯ_С UDP -> stdout\n", prog);
}
int main(int argc, char *argv[]) {
if (argc >= 4 && !strcmp(argv[1], "send"))
return mode_send(argv[2], atoi(argv[3]), argc >= 5 ? atoi(argv[4]) : 0);
if (argc >= 4 && !strcmp(argv[1], "recv"))
return mode_recv(atoi(argv[2]), atoi(argv[3]));
usage(argv[0]);
return 1;
}