Files
fgl-aircon/src/ayla/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

129 lines
3.8 KiB
C++

#include "ayla/httpc.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "ayla/log.hpp"
#include "ayla/platform/platform.hpp"
namespace fgl::ayla {
namespace {
// Читает статус-строку "HTTP/1.x NNN ..."; возвращает код или -1.
int read_status(int fd) {
char line[64];
size_t len = 0;
for (;;) {
if (len + 1 >= sizeof(line)) return -1;
uint8_t ch;
long n = fgl::plat::tcp_recv(fd, &ch, 1);
if (n <= 0) return -1;
if (ch == '\n') break;
line[len++] = static_cast<char>(ch);
}
// "HTTP/1.1 202 Accepted"
if (len < 12 || strncmp(line, "HTTP/", 5) != 0) return -1;
const char* sp = strchr(line, ' ');
if (sp == nullptr) return -1;
int code = atoi(sp + 1);
return (code >= 200 && code <= 599) ? code : -1;
}
// Дочитывает заголовки до пустой строки и тело по Content-Length (до лимита).
bool drain_response(int fd) {
char line[256];
long content_length = 0;
for (;;) {
size_t i = 0;
for (;;) {
if (i + 1 >= sizeof(line)) return false;
uint8_t ch;
long n = fgl::plat::tcp_recv(fd, &ch, 1);
if (n <= 0) return false;
if (ch == '\n') break;
line[i++] = static_cast<char>(ch);
}
if (i > 0 && line[i - 1] == '\r') i--;
line[i] = '\0';
if (i == 0) break; // конец заголовков
if ((line[0] | 0x20) == 'c' && strncasecmp(line, "Content-Length:", 15) == 0) {
content_length = strtol(line + 15, nullptr, 10);
}
}
if (content_length > 0) {
uint8_t sink[256];
long remaining = content_length;
if (remaining > 4096) remaining = 4096; // тела local_reg нет — ограничим
while (remaining > 0) {
size_t chunk = remaining < static_cast<long>(sizeof(sink))
? static_cast<size_t>(remaining)
: sizeof(sink);
long n = fgl::plat::tcp_recv(fd, sink, chunk);
if (n <= 0) break;
remaining -= n;
}
}
return true;
}
} // namespace
bool httpc_perform(const HttpcRequest& req, HttpcResponse* resp) {
if (resp == nullptr || req.host == nullptr) return false;
resp->status = 0;
resp->transport_ok = false;
int fd = fgl::plat::tcp_connect(req.host, req.port, req.timeout_ms);
if (fd < 0) {
FGL_LOGD("httpc: connect %s:%u failed", req.host,
static_cast<unsigned>(req.port));
return false;
}
fgl::plat::tcp_set_timeout(fd, req.timeout_ms, req.timeout_ms);
char head[256];
int n = snprintf(head, sizeof(head),
"%s %s%s%s HTTP/1.1\r\n"
"Host: %s\r\n"
"Accept: application/json\r\n"
"Connection: close\r\n"
"Content-Type: %s\r\n"
"Content-Length: %u\r\n"
"\r\n",
req.method, req.path, req.query != nullptr ? "?" : "",
req.query != nullptr ? req.query : "", req.host,
req.content_type, static_cast<unsigned>(req.body_len));
if (n <= 0 || static_cast<size_t>(n) >= sizeof(head)) {
fgl::plat::tcp_close(fd);
return false;
}
bool sent = fgl::plat::tcp_send(fd, head, static_cast<size_t>(n)) == n;
if (sent && req.body != nullptr && req.body_len > 0) {
sent = fgl::plat::tcp_send(fd, req.body, req.body_len) ==
static_cast<long>(req.body_len);
}
if (!sent) {
FGL_LOGD("httpc: send failed");
fgl::plat::tcp_close(fd);
return false;
}
int status = read_status(fd);
if (status < 0) {
fgl::plat::tcp_close(fd);
return false;
}
drain_response(fd);
fgl::plat::tcp_shutdown(fd);
fgl::plat::tcp_close(fd);
resp->status = status;
resp->transport_ok = true;
FGL_LOGD("httpc: %s %s -> %d", req.method, req.path, status);
return true;
}
} // namespace fgl::ayla