Files
fgl-aircon/tests/ayla/test_httpc.cpp
Petr Polezhaev e91d4605b1 core(M1): ayla-криптография, конверт, JSON (jsmn), HTTP-клиент
- crypto: KDF Ayla (двойной HMAC, suffix 0x30/31/32; app/dev направления),
  AES-256-CBC с непрерывной цепочкой (iv обновляется mbedtls на месте),
  Java-паддинг >=1 NUL; mode-latch против misuse (encrypt|decrypt);
  zeroize ключей при повторном init; векторы из APK (4 сессии × 4 сообщения,
  включая legacy-приём без NUL) — scripts/gen_kdf_vectors.py.
- envelope: pack/unpack {"enc","sign"}; расшифровка (движение цепочки)
  ДО проверки подписи; сравнение подписи в константном времени;
  extract_seq_no — depth-1 сканер без лимита токенов (OOB после escape
  исправлен, регресс-тесты по ASan-репро ревьюера).
- json: Writer (фикс. буфер, стек глубин, escape, ok()=false при
  переполнении) + Doc на jsmn (64 токена, unescape, overflow-guard).
- httpc: блокирующий POST/PUT для local_reg (статус 200-599, дренаж,
  shutdown перед close).
- third_party/jsmn (MIT, JSMN_STATIC).
- CMake: mbedtls системный (/usr/include/mbedtls3) или FetchContent;
  IDF: PRIV_REQUIRES mbedtls.
- CI: 3 конфигурации — gcc-Release, gcc-Debug+ASan/UBSan, clang-Release;
  6/6 тестов стабильно; ESP-IDF esp32 build complete.
Ревью под-агентом: 3 круга (OOB-блокер + тестовые флаки закрыты), APPROVED.
2026-09-22 15:46:18 +03:00

107 lines
3.1 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Тесты HTTP-клиента против in-process httpd.
#include "doctest/doctest.h"
#include <cstring>
#include <string>
#include "ayla/httpc.hpp"
#include "ayla/httpd.hpp"
#include "ayla/platform/platform.hpp"
namespace {
struct Ctx {
fgl::ayla::HttpRequest last;
int calls = 0;
int status_to_return = 200;
};
bool handler(const fgl::ayla::HttpRequest& req, fgl::ayla::HttpResponse& resp,
void* ctx) {
auto* c = static_cast<Ctx*>(ctx);
c->last = req;
c->calls++;
resp.status = c->status_to_return;
static const uint8_t kBody[] = "{\"x\":1}";
resp.body = kBody;
resp.body_len = sizeof(kBody) - 1;
return true;
}
} // namespace
TEST_CASE("httpc: POST с телом и query, статус пробрасывается") {
fgl::ayla::HttpServer srv;
Ctx ctx;
ctx.status_to_return = 202;
REQUIRE(srv.start(0, handler, &ctx));
const uint32_t ip = (127u << 24) | 1u; // для лога, не используется
fgl::ayla::HttpcRequest req;
req.method = "POST";
req.host = "127.0.0.1";
req.port = srv.port();
req.path = "/local_reg.json";
req.query = "dsn=AC000W00REDACTED";
const char body[] = "{\"local_reg\":{\"notify\":1}}";
req.body = reinterpret_cast<const uint8_t*>(body);
req.body_len = strlen(body);
req.timeout_ms = 3000;
fgl::ayla::HttpcResponse resp;
REQUIRE(fgl::ayla::httpc_perform(req, &resp));
CHECK(resp.transport_ok);
CHECK(resp.status == 202);
CHECK(ctx.calls == 1);
CHECK(std::string(ctx.last.method) == "POST");
CHECK(std::string(ctx.last.target) == "/local_reg.json");
CHECK(std::string(ctx.last.query) == "dsn=AC000W00REDACTED");
CHECK(ctx.last.body_len == strlen(body));
CHECK(memcmp(ctx.last.body, body, strlen(body)) == 0);
srv.stop();
(void)ip;
}
TEST_CASE("httpc: ошибки транспорта (соединение отвергнуто)") {
// Занимаем и освобождаем порт — соединение точно отвергнётся.
fgl::ayla::HttpServer tmp;
REQUIRE(tmp.start(0, nullptr, nullptr));
const uint16_t port = tmp.port();
tmp.stop();
fgl::plat::sleep_ms(100);
fgl::ayla::HttpcRequest req;
req.method = "PUT";
req.host = "127.0.0.1";
req.port = port;
req.timeout_ms = 2000;
fgl::ayla::HttpcResponse resp;
bool ok = fgl::ayla::httpc_perform(req, &resp);
// Либо connect сразу отклонён (false), либо таймаут — оба варианта ошибки.
if (ok) {
FAIL("ожидаля отказ соединения");
}
CHECK_FALSE(resp.transport_ok);
}
TEST_CASE("httpc: PUT без тела, ответ 503 c телом (дренаж)") {
fgl::ayla::HttpServer srv;
Ctx ctx;
ctx.status_to_return = 503;
REQUIRE(srv.start(0, handler, &ctx));
fgl::ayla::HttpcRequest req;
req.method = "PUT";
req.host = "127.0.0.1";
req.port = srv.port();
req.path = "/local_reg.json";
req.body = nullptr;
req.body_len = 0;
req.timeout_ms = 3000;
fgl::ayla::HttpcResponse resp;
REQUIRE(fgl::ayla::httpc_perform(req, &resp));
CHECK(resp.status == 503);
CHECK(ctx.calls == 1);
CHECK(ctx.last.body_len == 0);
srv.stop();
}