core(M0): монорепо-каркас — CMake (posix+esp-idf), платслой, лог, мини-httpd

- CMakeLists в корне: ветвление ESP_PLATFORM (idf_component_register,
  lwip/esp_timer/esp_hw_support/pthread) / POSIX (статическая библиотека
  fgl-aircon, C++20, -fno-exceptions -fno-rtti, -Werror).
- src/ayla/platform: сокеты/потоки/CSPRNG/время; posix (getrandom, poll,
  pthread_join) и esp-idf (lwip_select, esp_fill_random, pthread-слой IDF);
  tcp_shutdown/tcp_local_port/thread_join для управляемой остановки.
- src/ayla: log (sink, без printf); мини-httpd/1.1 (keep-alive, Content-Length,
  лимиты заголовков/тела, ephemeral-порт, жизненный цикл с гарантией
  завершения потока: shutdown(active)→join→close).
- tests/ayla: platform (join, loopback+shutdown) и httpd (404, keep-alive,
  обработчик/парсинг, oversize-400, stop при живом соединении, стрим
  заголовков). doctest через FetchContent.
- scripts/ci.sh: сборка+ctest. ESP-IDF v5.5.5 esp32: смоук-сборка с ядром
  как компонентом — Project build complete.
Ревью под-агентом: 3 круга, все блокеры (жизненный цикл httpd) закрыты, APPROVED.
This commit is contained in:
2026-09-22 00:27:33 +03:00
parent b21817ab9f
commit b44004627b
15 changed files with 1362 additions and 1 deletions

View File

@@ -0,0 +1,57 @@
// Платформенный слой: сокеты, таймеры, CSPRNG, потоки.
// Одна реализация на платформу: platform/posix (Linux) и platform/esp-idf.
// Все функции — блокирующие; коды ошибок через возвращаемое значение (-1),
// без исключений. IP-адреса — uint32_t в host byte order.
#pragma once
#include <cstddef>
#include <cstdint>
namespace fgl::plat {
// ---- время ----
// Монотонные миллисекунды с неопределённой точки отсчёта.
uint64_t now_ms();
// ---- CSPRNG ----
// Заполняет buf cryptographically-secure случайными байтами.
bool random(uint8_t* buf, size_t len);
// ---- потоки ----
// Идентификатор потока (непрозрачный). Валиден между thread_create и
// завершением thread_join.
using ThreadId = void*;
// Создаёт поток с именем name (для отладки) и стеком stack_bytes (0 — по
// умолчанию платформы; для ESP-IDF задавать явно, >= 4096).
// out_id может быть nullptr (тогда присоединиться нельзя).
bool thread_create(void (*fn)(void*), void* ctx, const char* name,
uint32_t stack_bytes, ThreadId* out_id);
// Блокирующе ждёт завершения потока. Гарантирует, что код потока больше не
// исполняется (объекты потока можно освобождать после возврата).
void thread_join(ThreadId id);
// Мягкая блокирующая задержка текущего потока.
void sleep_ms(uint32_t ms);
// ---- TCP (клиент и сервер) ----
// Создаёт слушающий сокет; возвращает fd или -1. port==0 — любой свободный
// (узнать выбранный: tcp_local_port).
int tcp_listen(uint16_t port);
// Локальный порт сокета (для ephemeral-слушателем).
uint16_t tcp_local_port(int fd);
// Принимает соединение (блокирующе); возвращает fd клиента или -1.
// peer_ip (host byte order) / peer_port могут быть nullptr.
int tcp_accept(int listen_fd, uint32_t* peer_ip, uint16_t* peer_port);
// Подключается к host:port (host — DNS-имя или dotted-quad) с таймаутом.
int tcp_connect(const char* host, uint16_t port, uint32_t timeout_ms);
long tcp_send(int fd, const void* buf, size_t len); // >0 / -1
// Блокирующее чтение; 0 — EOF, -1 — ошибка/таймаут.
long tcp_recv(int fd, void* buf, size_t len);
// Таймауты на последующие recv/send (0 — без таймаута).
bool tcp_set_timeout(int fd, uint32_t rx_ms, uint32_t tx_ms);
// Ожидание читаемости fd (для выходa из блокирующего accept). Таймаут в мс.
bool tcp_poll_readable(int fd, uint32_t timeout_ms);
// Прерывает блокированные recv/send на сокете (для остановки сервера).
bool tcp_shutdown(int fd);
void tcp_close(int fd);
} // namespace fgl::plat