- 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.
107 lines
3.1 KiB
C++
107 lines
3.1 KiB
C++
// Тесты 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();
|
||
}
|