refactor: network - harden TCP/IP and unify HTTP clients

This commit is contained in:
2026-07-29 15:35:17 +01:00
parent 75ae7ede56
commit a288dee7df
28 changed files with 1852 additions and 1361 deletions
+25 -252
View File
@@ -1272,197 +1272,17 @@ inline uint32_t next_request_id() {
return request_id++;
}
inline int send_all_plain(int fd, const uint8_t* data, int len) {
static constexpr int MAX_SEND_CHUNK = 32768;
uint64_t deadline = montauk::get_milliseconds() + 15000;
int off = 0;
while (off < len) {
int chunk = len - off;
if (chunk > MAX_SEND_CHUNK) chunk = MAX_SEND_CHUNK;
int n = montauk::send(fd, data + off, (uint32_t)chunk);
if (n > 0) {
off += n;
deadline = montauk::get_milliseconds() + 15000;
continue;
}
if (n < 0) return -1;
uint32_t sig = montauk::wait_handle(fd,
montauk::abi::IPC_SIGNAL_WRITABLE | montauk::abi::IPC_SIGNAL_PEER_CLOSED,
1000);
if (sig & montauk::abi::IPC_SIGNAL_PEER_CLOSED) return -1;
if (montauk::get_milliseconds() >= deadline) return -1;
montauk::sleep_ms(1);
}
return off;
}
inline bool response_has_no_body(int status) {
return (status >= 100 && status < 200) || status == 204 || status == 304;
}
inline int parse_content_length_value(const http::Response* resp) {
char value[32] = {};
if (!http::get_header(resp, "Content-Length", value, sizeof(value))) return -1;
char* end = nullptr;
long n = strtol(value, &end, 10);
if (end == value || n < 0) return -1;
return (int)n;
}
inline bool chunked_body_complete(const char* src, int src_len) {
if (src == nullptr || src_len <= 0) return false;
int pos = 0;
while (pos < src_len) {
int line_start = pos;
while (pos < src_len && src[pos] != '\n') pos++;
if (pos >= src_len) return false;
int line_end = pos;
pos++;
while (line_end > line_start && (src[line_end - 1] == '\r' || src[line_end - 1] == '\n'))
line_end--;
char hex[16] = {};
int hex_pos = 0;
for (int i = line_start; i < line_end && hex_pos < (int)sizeof(hex) - 1; i++) {
if (src[i] == ';') break;
hex[hex_pos++] = src[i];
}
if (hex_pos == 0) return false;
char* end = nullptr;
unsigned long chunk = strtoul(hex, &end, 16);
if (end == hex) return false;
if (chunk == 0) {
if (pos >= src_len) return false;
if (src[pos] == '\n') return true;
if (src[pos] == '\r' && pos + 1 < src_len && src[pos + 1] == '\n') return true;
return http::find_header_block_end(src + pos, src + src_len) != nullptr;
}
if (pos + (int)chunk > src_len) return false;
pos += (int)chunk;
if (pos < src_len && src[pos] == '\r') pos++;
if (pos < src_len && src[pos] == '\n') pos++;
}
return false;
}
inline bool response_is_chunked(const http::Response* resp);
inline bool http_response_complete(char* buf, int len, bool peer_closed) {
if (buf == nullptr || len <= 0) return false;
http::Response resp = {};
if (http::parse_response(buf, len, &resp) < 0) return false;
if (resp.body == nullptr) return false;
if (response_has_no_body(resp.status)) return true;
if (response_is_chunked(&resp)) return chunked_body_complete(resp.body, resp.body_len);
int content_length = parse_content_length_value(&resp);
if (content_length >= 0) return resp.body_len >= content_length;
return peer_closed;
}
inline int recv_http_plain(int fd, char* buf, int cap) {
int total = 0;
bool peer_closed = false;
uint64_t deadline = montauk::get_milliseconds() + 90000;
while (total < cap - 1) {
if (http_response_complete(buf, total, peer_closed)) break;
if (montauk::get_milliseconds() >= deadline) break;
uint32_t sig = montauk::wait_handle(fd,
montauk::abi::IPC_SIGNAL_READABLE | montauk::abi::IPC_SIGNAL_PEER_CLOSED,
1000);
if (sig == 0) continue;
if (sig & montauk::abi::IPC_SIGNAL_PEER_CLOSED)
peer_closed = true;
if (!(sig & montauk::abi::IPC_SIGNAL_READABLE)) {
if (peer_closed) break;
continue;
}
int n = montauk::recv(fd, buf + total, (uint32_t)(cap - 1 - total));
if (n < 0) break;
if (n == 0) {
if (peer_closed) break;
continue;
}
total += n;
deadline = montauk::get_milliseconds() + 90000;
}
buf[total] = '\0';
return total;
}
inline bool response_is_chunked(const http::Response* resp) {
char value[64] = {};
if (!http::get_header(resp, "Transfer-Encoding", value, sizeof(value))) return false;
for (int i = 0; value[i]; i++) {
if (value[i] >= 'A' && value[i] <= 'Z') value[i] = (char)(value[i] - 'A' + 'a');
}
return strstr(value, "chunked") != nullptr;
}
inline bool extract_http_body(const http::Response* resp, uint8_t** out_body, int* out_len) {
if (out_body) *out_body = nullptr;
if (out_len) *out_len = 0;
if (resp == nullptr || resp->body == nullptr || resp->body_len < 0) return false;
if (!response_is_chunked(resp)) {
uint8_t* body = (uint8_t*)malloc((size_t)(resp->body_len > 0 ? resp->body_len : 1));
if (!body) return false;
if (resp->body_len > 0) memcpy(body, resp->body, (size_t)resp->body_len);
if (out_body) *out_body = body;
if (out_len) *out_len = resp->body_len;
return true;
}
const char* src = resp->body;
int src_len = resp->body_len;
int pos = 0;
int out_pos = 0;
uint8_t* body = (uint8_t*)malloc((size_t)src_len);
uint8_t* body = (uint8_t*)malloc((size_t)(resp->body_len > 0 ? resp->body_len : 1));
if (!body) return false;
while (pos < src_len) {
int line_start = pos;
while (pos < src_len && src[pos] != '\n') pos++;
int line_end = pos;
if (pos < src_len && src[pos] == '\n') pos++;
while (line_end > line_start && (src[line_end - 1] == '\r' || src[line_end - 1] == '\n'))
line_end--;
char hex[16] = {};
int hex_pos = 0;
for (int i = line_start; i < line_end && hex_pos < (int)sizeof(hex) - 1; i++) {
if (src[i] == ';') break;
hex[hex_pos++] = src[i];
}
unsigned long chunk = strtoul(hex, nullptr, 16);
if (chunk == 0) {
if (out_body) *out_body = body;
if (out_len) *out_len = out_pos;
return true;
}
if (pos + (int)chunk > src_len) break;
memcpy(body + out_pos, src + pos, chunk);
out_pos += (int)chunk;
pos += (int)chunk;
if (pos < src_len && src[pos] == '\r') pos++;
if (pos < src_len && src[pos] == '\n') pos++;
}
free(body);
return false;
if (resp->body_len > 0) memcpy(body, resp->body, (size_t)resp->body_len);
if (out_body) *out_body = body;
if (out_len) *out_len = resp->body_len;
return true;
}
inline bool ipp_http_post(const IppUri* uri,
@@ -1487,86 +1307,39 @@ inline bool ipp_http_post(const IppUri* uri,
else
snprintf(host_header, sizeof(host_header), "%s:%u", uri->host, (unsigned)uri->port);
int req_cap = body_len + 1024;
char* req = (char*)malloc((size_t)req_cap);
if (!req) {
safe_copy(err, err_len, "out of memory");
return false;
}
int req_len = snprintf(req, (size_t)req_cap,
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: MontaukOS Print/1.0\r\n"
"Content-Type: application/ipp\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"\r\n",
uri->path, host_header, body_len);
if (req_len < 0 || req_len + body_len >= req_cap) {
free(req);
safe_copy(err, err_len, "IPP request is too large");
return false;
}
memcpy(req + req_len, body, (size_t)body_len);
req_len += body_len;
char* raw = (char*)malloc(HTTP_RESPONSE_MAX);
if (!raw) {
free(req);
safe_copy(err, err_len, "out of memory");
return false;
}
int raw_len = -1;
tls::TrustAnchors tas = {};
if (uri->use_tls) {
tls::TrustAnchors tas = tls::load_trust_anchors();
raw_len = tls::https_fetch(uri->host, uri->ip, uri->port, req, req_len,
tas, raw, HTTP_RESPONSE_MAX - 1);
if (tas.anchors) free(tas.anchors);
} else {
int fd = montauk::socket(montauk::abi::SOCK_TCP);
if (fd < 0) {
tas = tls::load_trust_anchors();
if (tas.count == 0) {
free(raw);
free(req);
snprintf(err, (size_t)err_len, "failed to create socket for %s:%u", uri->host, (unsigned)uri->port);
safe_copy(err, err_len, "no CA certificates loaded");
return false;
}
if (montauk::connect(fd, uri->ip, uri->port) < 0) {
montauk::closesocket(fd);
free(raw);
free(req);
snprintf(err, (size_t)err_len, "failed to connect to %s (%s):%u",
uri->host, ip_text, (unsigned)uri->port);
return false;
}
if (send_all_plain(fd, (const uint8_t*)req, req_len) < 0) {
montauk::closesocket(fd);
free(raw);
free(req);
snprintf(err, (size_t)err_len, "failed to send print request to %s (%s):%u",
uri->host, ip_text, (unsigned)uri->port);
return false;
}
raw_len = recv_http_plain(fd, raw, HTTP_RESPONSE_MAX);
montauk::closesocket(fd);
}
free(req);
if (raw_len <= 0) {
http::RequestOptions options;
options.secure = uri->use_tls;
options.port = uri->port;
options.resolved_ip = uri->ip;
options.host_header = host_header;
options.extra_headers = "User-Agent: MontaukOS Print/1.0\r\n";
options.timeout_ms = 90000;
http::Response resp = http::request_into(
"POST", uri->host, uri->path, "application/ipp",
(const char*)body, body_len, uri->use_tls ? &tas : nullptr,
raw, HTTP_RESPONSE_MAX, options);
tls::free_trust_anchors(&tas);
if (resp.error != http::Error::NONE) {
free(raw);
snprintf(err, (size_t)err_len, "printer returned no response from %s (%s):%u",
uri->host, ip_text, (unsigned)uri->port);
return false;
}
raw[raw_len] = '\0';
http::Response resp = {};
if (http::parse_response(raw, raw_len, &resp) < 0) {
free(raw);
snprintf(err, (size_t)err_len, "printer returned no final HTTP response from %s (%s):%u",
uri->host, ip_text, (unsigned)uri->port);
snprintf(err, (size_t)err_len, "%s from %s (%s):%u",
http::error_string(resp.error), uri->host, ip_text,
(unsigned)uri->port);
return false;
}
if (out_http_status) *out_http_status = resp.status;