aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/common
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/common
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/common')
-rw-r--r--prototypes/common/tls-link.c217
-rw-r--r--prototypes/common/tls-link.h32
2 files changed, 249 insertions, 0 deletions
diff --git a/prototypes/common/tls-link.c b/prototypes/common/tls-link.c
new file mode 100644
index 0000000..adfaac2
--- /dev/null
+++ b/prototypes/common/tls-link.c
@@ -0,0 +1,217 @@
+#define _POSIX_C_SOURCE 200809L
+#include "tls-link.h"
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+uint64_t pure_link_now_ms(void) {
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts)) abort();
+ return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
+}
+static bool ready(int fd, short events, uint64_t deadline) {
+ for (;;) {
+ uint64_t now = pure_link_now_ms();
+ if (now >= deadline) return false;
+ int remaining = (int)(deadline - now);
+ struct pollfd p = {.fd = fd, .events = events};
+ int result = poll(&p, 1, remaining);
+ if (result > 0) return !(p.revents & (POLLERR | POLLNVAL)) && (p.revents & (events | POLLHUP));
+ if (!result) return false;
+ if (errno != EINTR) return false;
+ }
+}
+bool pure_link_nonblocking(int fd) {
+ int flags = fcntl(fd, F_GETFL, 0);
+ return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
+}
+static bool tls_retry(PureLink *channel, int code, uint64_t deadline) {
+ if (code == SSL_ERROR_WANT_READ) return ready(channel->fd, POLLIN, deadline);
+ if (code == SSL_ERROR_WANT_WRITE) return ready(channel->fd, POLLOUT, deadline);
+ return false;
+}
+int pure_link_read(PureLink *channel, void *output, size_t size, uint64_t deadline) {
+ size_t at = 0;
+ while (at < size) {
+ if (pure_link_now_ms() >= deadline) return -1;
+ size_t received = 0;
+ ERR_clear_error();
+ int result = SSL_read_ex(channel->ssl, (unsigned char *)output + at, size - at, &received);
+ if (result == 1) { if (!received) return -1; at += received; continue; }
+ int code = SSL_get_error(channel->ssl, result);
+ if (code == SSL_ERROR_ZERO_RETURN && !at) return 0;
+ if (!tls_retry(channel, code, deadline)) return -1;
+ }
+ return 1;
+}
+bool pure_link_write(PureLink *channel, const void *input, size_t size, uint64_t deadline) {
+ size_t at = 0;
+ while (at < size) {
+ if (pure_link_now_ms() >= deadline) return false;
+ size_t sent = 0;
+ size_t chunk = size - at;
+ if (chunk > 16384) chunk = 16384;
+ ERR_clear_error();
+ int result = SSL_write_ex(channel->ssl, (const unsigned char *)input + at, chunk, &sent);
+ if (result == 1) { if (!sent) return false; at += sent; continue; }
+ int code = SSL_get_error(channel->ssl, result);
+ if (!tls_retry(channel, code, deadline)) return false;
+ }
+ return true;
+}
+int pure_link_read_frame(PureLink *channel, unsigned char *output, size_t capacity,
+ size_t *size, uint64_t deadline) {
+ unsigned char prefix[2];
+ int first = pure_link_read(channel, prefix, 2, deadline);
+ if (first != 1) return first;
+ size_t length = ((size_t)prefix[0] << 8) | prefix[1];
+ if (!length || length > capacity || pure_link_read(channel, output, length, deadline) != 1) return -1;
+ *size = length;
+ return 1;
+}
+bool pure_link_write_frame(PureLink *channel, const unsigned char *data, size_t size,
+ uint64_t deadline) {
+ if (!size || size > UINT16_MAX) return false;
+ unsigned char prefix[2] = {(unsigned char)(size >> 8), (unsigned char)size};
+ return pure_link_write(channel, prefix, 2, deadline) && pure_link_write(channel, data, size, deadline);
+}
+void pure_link_close(PureLink *channel, bool healthy) {
+ if (channel->ssl) {
+ if (healthy) (void)SSL_shutdown(channel->ssl);
+ SSL_free(channel->ssl);
+ }
+ if (channel->fd >= 0) close(channel->fd);
+ *channel = (PureLink){.fd = -1};
+}
+static int select_alpn(SSL *ssl, const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int length, void *arg) {
+ (void)ssl;
+ const unsigned char *alpn = arg;
+ for (unsigned int at = 0; at < length;) {
+ unsigned int n = in[at++];
+ if (!n || n > length - at) return SSL_TLSEXT_ERR_ALERT_FATAL;
+ if (n == alpn[0] && !memcmp(in + at, alpn + 1, n)) {
+ *out = alpn + 1; *outlen = alpn[0]; return SSL_TLSEXT_ERR_OK;
+ }
+ at += n;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+SSL_CTX *pure_link_context(bool server, const char *ca, const char *cert,
+ const char *key, const unsigned char *alpn) {
+ SSL_CTX *ctx = SSL_CTX_new(server ? TLS_server_method() : TLS_client_method());
+ if (!ctx) return NULL;
+ if (!alpn || !alpn[0] || !SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) ||
+ !SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) ||
+ !SSL_CTX_set_max_early_data(ctx, 0) ||
+ !SSL_CTX_load_verify_locations(ctx, ca, NULL) ||
+ SSL_CTX_use_certificate_chain_file(ctx, cert) != 1 ||
+ SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) != 1 ||
+ SSL_CTX_check_private_key(ctx) != 1) {
+ SSL_CTX_free(ctx); return NULL;
+ }
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | (server ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0), NULL);
+ if (server) SSL_CTX_set_alpn_select_cb(ctx, select_alpn, (void *)alpn);
+ return ctx;
+}
+bool pure_link_handshake(PureLink *channel, SSL_CTX *ctx, bool server,
+ const char *peer_name, const unsigned char *alpn) {
+ /* This protocol writes a small reply header followed by a separate TLS
+ * body record. Disable Nagle on the connected TCP socket so that the
+ * header cannot hold the body behind a delayed acknowledgement. */
+ int enabled = 1;
+ if (setsockopt(channel->fd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof(enabled))) return false;
+ channel->ssl = SSL_new(ctx);
+ if (!channel->ssl || SSL_set_fd(channel->ssl, channel->fd) != 1) return false;
+ if (server) SSL_set_accept_state(channel->ssl);
+ else {
+ SSL_set_connect_state(channel->ssl);
+ SSL_set_hostflags(channel->ssl, X509_CHECK_FLAG_NO_WILDCARDS | X509_CHECK_FLAG_NEVER_CHECK_SUBJECT);
+ if (SSL_set1_host(channel->ssl, peer_name) != 1 ||
+ SSL_set_tlsext_host_name(channel->ssl, peer_name) != 1 ||
+ SSL_set_alpn_protos(channel->ssl, alpn, alpn[0] + 1u) != 0) return false;
+ }
+ uint64_t deadline = pure_link_now_ms() + PURE_LINK_DEADLINE_MS;
+ for (;;) {
+ ERR_clear_error();
+ int result = SSL_do_handshake(channel->ssl);
+ if (result == 1) break;
+ int code = SSL_get_error(channel->ssl, result);
+ if (!tls_retry(channel, code, deadline)) return false;
+ }
+ if (SSL_version(channel->ssl) != TLS1_3_VERSION || SSL_get_verify_result(channel->ssl) != X509_V_OK)
+ return false;
+ X509 *peer = SSL_get1_peer_certificate(channel->ssl);
+ if (!peer) return false;
+ bool verified = !server || X509_check_host(peer, peer_name, 0,
+ X509_CHECK_FLAG_NO_WILDCARDS | X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, NULL) == 1;
+ X509_free(peer);
+ const unsigned char *chosen = NULL;
+ unsigned int length = 0;
+ SSL_get0_alpn_selected(channel->ssl, &chosen, &length);
+ return verified && length == alpn[0] && !memcmp(chosen, alpn + 1, length);
+}
+bool pure_link_port_number(const char *text, bool allow_zero) {
+ if (!text || !*text) return false;
+ unsigned long value = 0;
+ for (const char *p = text; *p; ++p) {
+ if (*p < '0' || *p > '9') return false;
+ unsigned digit = (unsigned)(*p - '0');
+ if (value > (65535ul - digit) / 10) return false;
+ value = value * 10 + digit;
+ }
+ return allow_zero || value != 0;
+}
+int pure_link_listen_at(const char *host, const char *port) {
+ struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_PASSIVE | AI_NUMERICHOST};
+ struct addrinfo *addresses = NULL;
+ if (getaddrinfo(host, port, &hints, &addresses)) return -1;
+ int listener = -1;
+ for (struct addrinfo *it = addresses; it; it = it->ai_next) {
+ int fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
+ if (fd < 0) continue;
+ int enabled = 1;
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled));
+ if (bind(fd, it->ai_addr, it->ai_addrlen) == 0 && listen(fd, 32) == 0 && pure_link_nonblocking(fd)) {
+ listener = fd; break;
+ }
+ close(fd);
+ }
+ freeaddrinfo(addresses);
+ return listener;
+}
+int pure_link_connect_to(const char *host, const char *port) {
+ struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM};
+ struct addrinfo *addresses = NULL;
+ if (getaddrinfo(host, port, &hints, &addresses)) return -1;
+ int connected = -1;
+ uint64_t deadline = pure_link_now_ms() + PURE_LINK_DEADLINE_MS;
+ for (struct addrinfo *it = addresses; it; it = it->ai_next) {
+ int fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
+ if (fd < 0) continue;
+ if (pure_link_nonblocking(fd)) {
+ int result = connect(fd, it->ai_addr, it->ai_addrlen);
+ if (result == 0 || (result < 0 && errno == EINPROGRESS && ready(fd, POLLOUT, deadline))) {
+ int error = 0; socklen_t size = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &size) == 0 && !error) {
+ connected = fd; break;
+ }
+ }
+ }
+ close(fd);
+ }
+ freeaddrinfo(addresses);
+ return connected;
+}
diff --git a/prototypes/common/tls-link.h b/prototypes/common/tls-link.h
new file mode 100644
index 0000000..53e05ff
--- /dev/null
+++ b/prototypes/common/tls-link.h
@@ -0,0 +1,32 @@
+#ifndef PURE_TLS_LINK_H
+#define PURE_TLS_LINK_H
+#include <openssl/ssl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+enum { PURE_LINK_DEADLINE_MS = 5000 };
+typedef struct { int fd; SSL *ssl; } PureLink;
+
+uint64_t pure_link_now_ms(void);
+bool pure_link_nonblocking(int fd);
+/* read returns 1 for all bytes, 0 for clean EOF before any byte, -1 for an
+ * incomplete frame, TLS failure or deadline. Never treat -1 as an EOF. */
+int pure_link_read(PureLink *, void *output, size_t size, uint64_t deadline);
+bool pure_link_write(PureLink *, const void *input, size_t size, uint64_t deadline);
+int pure_link_read_frame(PureLink *, unsigned char *output, size_t capacity,
+ size_t *size, uint64_t deadline);
+bool pure_link_write_frame(PureLink *, const unsigned char *data, size_t size,
+ uint64_t deadline);
+void pure_link_close(PureLink *, bool healthy);
+
+/* alpn is a static length-prefixed protocol name, retained by SSL_CTX's
+ * callback until the context is freed. Its content has exactly alpn[0]+1 bytes. */
+SSL_CTX *pure_link_context(bool server, const char *ca, const char *cert,
+ const char *key, const unsigned char *alpn);
+bool pure_link_handshake(PureLink *, SSL_CTX *, bool server, const char *peer_name,
+ const unsigned char *alpn);
+bool pure_link_port_number(const char *text, bool allow_zero);
+int pure_link_listen_at(const char *host, const char *port);
+int pure_link_connect_to(const char *host, const char *port);
+#endif