ohmu
·
2026-10-15
1#pragma once
2
3#include "common/thread.h"
4#include "common/FFstrbuf.h"
5
6#ifdef _WIN32
7 #include <minwindef.h>
8#endif
9
10struct addrinfo;
11
12typedef struct FFNetworkingState {
13#ifdef _WIN32
14 uintptr_t sockfd;
15 OVERLAPPED overlapped;
16#else
17 int sockfd;
18 struct addrinfo* addr;
19
20 #ifdef FF_HAVE_THREADS
21 FFThreadType thread;
22 #endif
23#endif
24
25 FFstrbuf command;
26 uint32_t timeout;
27 bool ipv6;
28 bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails
29} FFNetworkingState;
30
31// `headers` is optional and may be null; the other pointer arguments are dereferenced unconditionally.
32// Both functions report failure through their return value, which must be checked.
33[[gnu::nonnull(1, 2, 4), nodiscard]] const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers);
34[[gnu::nonnull(1, 2), nodiscard]] const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer);
35
36// Releases what a finished request holds and returns `state` to the idle state, so that the next
37// `ffNetworkingSendHttpRequest` starts from scratch. A request that never reached the wire can
38// still own the request buffer, so a plain zeroing of the struct is not enough.
39// The caller stays responsible for its own bookkeeping, such as clearing a "request sent" latch.
40[[gnu::nonnull(1)]] static inline void ffNetworkingResetState(FFNetworkingState* state) {
41 ffStrbufDestroy(&state->command);
42 *state = (FFNetworkingState) {};
43}
44
45// Case-insensitive header lookup restricted to the header block [0, headerEnd).
46// Restricting the range matters because the body may already share the same buffer.
47// Returns a pointer to the first character of the value; `valueLen` receives its
48// length excluding the terminating CRLF. Returns nullptr when the header is absent.
49[[gnu::nonnull(1, 3, 4), nodiscard]] const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen);
50
51// Checks whether a `Transfer-Encoding: chunked` body has been received in full, so that
52// framing does not have to rely on the server closing the connection.
53// Returns 1 when complete (`consumed` receives the body length including trailers),
54// 0 when more data is needed, and -1 when the body is malformed.
55// The caller must not wait for a fixed amount of data (e.g. `MSG_WAITALL`) while the
56// response length is still unknown, otherwise this check never gets to run.
57// Not `pure`: the `strtoul` it calls writes `errno`.
58[[gnu::nonnull(1, 3), nodiscard]] int ffNetworkingChunkedComplete(const char* body, uint32_t bodyLen, uint32_t* consumed);
59
60// Decodes a `Transfer-Encoding: chunked` body in place and rewrites the response with a
61// `Content-Length` header in place of `Transfer-Encoding`.
62// `headerEnd` is updated in place: the rewrite changes the header length, so the value the caller
63// passed in no longer describes the buffer afterwards. Callers must use the new one for anything
64// that addresses the body.
65[[gnu::nonnull(1, 2), nodiscard]] bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t* headerEnd);
66
67// Result of parsing a `Transfer-Encoding` header value
68typedef enum FFNetworkingTransferEncoding {
69 FF_NETWORKING_TE_NONE, // the value holds no coding at all
70 FF_NETWORKING_TE_CHUNKED, // exactly `chunked`, the only framing this client decodes
71 FF_NETWORKING_TE_UNSUPPORTED, // another coding or a chain of them, e.g. `gzip, chunked`
72} FFNetworkingTransferEncoding;
73
74// Parses a `Transfer-Encoding` header value. Codings are applied in the order they are
75// listed, so `chunked` has to be the last one for the framing to be readable at all --
76// and any other coding (e.g. `gzip, chunked`) leaves the payload encoded, which this
77// client cannot decode. Only a lone `chunked` is accepted; everything else is reported
78// as unsupported so that the caller fails the response instead of returning garbage.
79[[gnu::nonnull(1), gnu::pure, nodiscard]] FFNetworkingTransferEncoding ffNetworkingParseTransferEncoding(const char* value, uint32_t valueLen);