core(M2): машина состояний сессии Ayla LAN + mock-модуль + интеграционные сценарии
- session.{hpp,cpp}: state machine (idle/registering/online/recovering/
offline/key_error); httpd-обработчики key_exchange (200/426/412, re-key
прозрачно), commands (одна команда, 206/200, envelope, глобальный seq_no),
datapoint (unpack -> PropertyEvent / 401+тишина 50с для re-key-восстановления);
сессионный поток: local_reg POST?dsn/PUT (local_ip_for), keep-alive, backoff
x1.6->60с, 503->offline/NoSlot, activation-timeout->recovering, delete_session
с ожиданием выдачи; очередь с coalescing + batch; телеметрия; колбэки из
двух потоков с задокументированным контрактом; буферы datapoint-пути в Impl.
- platform: local_ip_for (UDP-connect) posix+esp-idf; стек httpd 24576
(переполнение 16КБ поймано gdb на Release).
- mock_ac.py: мок-модуль, stdlib-only чистый python AES-256 (свёрстан с
pycryptodome); сценарии: 503, no-poll, rekey-every, stale-gap (эмуляция
'вернувшегося' приложения), fail-pushes (битая подпись), garbage-pushes
(обрыв блока), break-outbound (исходящий десинк -> модуль ре-кает на
local_reg, как probe1-3), push-every, fail-first-ke.
- session_runner + test_session_mock.py: 9 сценариев через ctest, включая
самосинхронизацию CBC и восстановление после исходящего десинка.
- Прибор AP-WC1E: активация <=1с; re-key семантика ИСПРАВЛЕНА по живым
тестам: re-key при зазоре local_reg >= ~44-50с (не по возрасту сессии!);
при честном keep-alive 15с сессия стабильна без re-key; PROTOCOL/LEGACY/
PLAN обновлены; восстановление = тишина >порога + возврат.
- CI: 7/7 x3 (gcc-Rel, gcc-ASan/UBSan, clang); ESP-IDF esp32 build complete.
Ревью под-агентом: 2 круга (стек httpd, залипание состояний, dangling cfg,
физика десинка) — APPROVED.
This commit is contained in:
@@ -60,7 +60,12 @@ bool thread_create(void (*fn)(void*), void* ctx, const char* name,
|
||||
pthread_t tid;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
if (stack_bytes > 0) pthread_attr_setstacksize(&attr, stack_bytes);
|
||||
if (stack_bytes > 0) {
|
||||
if (pthread_attr_setstacksize(&attr, stack_bytes) != 0) {
|
||||
// glibc отвергает < PTHREAD_STACK_MIN; остаётся дефолт (больше — не меньше)
|
||||
// логируем только: ядро запрашивает >= PTHREAD_STACK_MIN.
|
||||
}
|
||||
}
|
||||
int rc = pthread_create(&tid, &attr, thread_trampoline, start);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (rc != 0) {
|
||||
@@ -168,6 +173,35 @@ int tcp_connect(const char* host, uint16_t port, uint32_t timeout_ms) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
bool local_ip_for(const char* host, char* out, size_t out_cap) {
|
||||
struct addrinfo hints {};
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
struct addrinfo* list = nullptr;
|
||||
if (::getaddrinfo(host, "80", &hints, &list) != 0 || list == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int fd = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd < 0) {
|
||||
::freeaddrinfo(list);
|
||||
return false;
|
||||
}
|
||||
bool ok = ::connect(fd, list->ai_addr, list->ai_addrlen) == 0;
|
||||
struct sockaddr_in local {};
|
||||
socklen_t slen = sizeof(local);
|
||||
if (ok && ::getsockname(fd, reinterpret_cast<struct sockaddr*>(&local),
|
||||
&slen) == 0) {
|
||||
const char* s = inet_ntop(AF_INET, &local.sin_addr, out,
|
||||
static_cast<socklen_t>(out_cap));
|
||||
ok = s != nullptr;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
::freeaddrinfo(list);
|
||||
::close(fd);
|
||||
return ok;
|
||||
}
|
||||
|
||||
long tcp_send(int fd, const void* buf, size_t len) {
|
||||
const uint8_t* p = static_cast<const uint8_t*>(buf);
|
||||
size_t done = 0;
|
||||
|
||||
Reference in New Issue
Block a user