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.
This commit is contained in:
106
tests/ayla/test_httpc.cpp
Normal file
106
tests/ayla/test_httpc.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// Тесты 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();
|
||||
}
|
||||
Reference in New Issue
Block a user