From a288dee7df823ac733f6c09f98de741b177edbec Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Wed, 29 Jul 2026 15:34:56 +0100 Subject: [PATCH] refactor: network - harden TCP/IP and unify HTTP clients --- GNUmakefile | 12 +- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Ipc/Ipc.cpp | 7 +- kernel/src/Net/Arp.cpp | 83 ++- kernel/src/Net/Dns.cpp | 167 +++-- kernel/src/Net/Ipv4.cpp | 59 +- kernel/src/Net/Tcp.cpp | 186 ++++-- kernel/src/Net/Tcp.hpp | 2 +- kernel/src/Net/Udp.cpp | 24 +- programs/include/http/http.hpp | 813 ++++++++++++++++--------- programs/include/print/print.hpp | 277 +-------- programs/include/tls/tls.hpp | 1 + programs/lib/tls/libtls.a | Bin 112738 -> 113598 bytes programs/lib/tls/obj/tls.o | Bin 112344 -> 113152 bytes programs/lib/tls/tls.cpp | 143 +++-- programs/src/fetch/main.cpp | 208 +------ programs/src/weather/main.cpp | 64 +- programs/src/wiki/main.cpp | 113 +--- programs/src/wikipedia/network.cpp | 62 +- programs/src/wikipedia/text.cpp | 20 - programs/src/wikipedia/wikipedia.h | 6 +- template/docs/gui-apps.md | 38 +- template/docs/syscalls.md | 9 +- template/sysroot/include/http/http.hpp | 813 ++++++++++++++++--------- template/sysroot/include/tls/tls.hpp | 1 + template/sysroot/lib/libtls.a | Bin 111626 -> 113598 bytes tests/Makefile | 18 + tests/http_test.cpp | 85 +++ 28 files changed, 1852 insertions(+), 1361 deletions(-) create mode 100644 tests/Makefile create mode 100644 tests/http_test.cpp diff --git a/GNUmakefile b/GNUmakefile index 6dc134f..dd0c71d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -31,25 +31,23 @@ run-hdd: run-hdd-$(ARCH) .PHONY: run-x86_64 run-x86_64: $(IMAGE_NAME).iso - sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -enable-kvm \ -M q35 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ - -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ + -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: run-hdd-x86_64 run-hdd-x86_64: $(IMAGE_NAME).hdd - sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -M q35 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ - -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ + -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: run-aarch64 @@ -133,23 +131,21 @@ run-hdd-loongarch64: $(IMAGE_NAME).hdd .PHONY: run-bios run-bios: $(IMAGE_NAME).iso - sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -M q35 \ -cdrom $(IMAGE_NAME).iso \ -boot d \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ - -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ + -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: run-hdd-bios run-hdd-bios: $(IMAGE_NAME).hdd - sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -M q35 \ -hda $(IMAGE_NAME).hdd \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ - -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ + -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: toolchain diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 4adff58..b3a52e6 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 7 +#define MONTAUK_BUILD_NUMBER 8 diff --git a/kernel/src/Ipc/Ipc.cpp b/kernel/src/Ipc/Ipc.cpp index c933f12..4e84bba 100644 --- a/kernel/src/Ipc/Ipc.cpp +++ b/kernel/src/Ipc/Ipc.cpp @@ -1382,7 +1382,8 @@ namespace Ipc { Net::Tcp::Connection* conn = socket->tcpConn; socket->socketLock.Release(); if (conn == nullptr) return -1; - return Net::Tcp::Send(conn, data, (uint16_t)len); + if (len > 0x7FFFFFFFu) return -1; + return Net::Tcp::Send(conn, data, len); } int SocketRecvHandle(int handle, uint8_t* buffer, uint32_t maxLen) { @@ -1399,13 +1400,15 @@ namespace Ipc { socket->socketLock.Release(); if (conn == nullptr) return -1; - int result = Net::Tcp::ReceiveNonBlocking(conn, buffer, (uint16_t)maxLen); + uint16_t cappedLen = maxLen > 0xFFFFu ? 0xFFFFu : (uint16_t)maxLen; + int result = Net::Tcp::ReceiveNonBlocking(conn, buffer, cappedLen); if (result != 0) NotifyObjectChanged((Object*)socket); return result; } int SocketSendToHandle(int handle, const uint8_t* data, uint32_t len, uint32_t destIp, uint16_t destPort) { if (data == nullptr) return -1; + if (len > 1472) return -1; if (len > 0 && !montauk::abi::UserMemory::Range((uint64_t)data, len, false)) return -1; Socket* socket = nullptr; diff --git a/kernel/src/Net/Arp.cpp b/kernel/src/Net/Arp.cpp index 934c4c9..ed34fe2 100644 --- a/kernel/src/Net/Arp.cpp +++ b/kernel/src/Net/Arp.cpp @@ -15,6 +15,7 @@ #include #include #include +#include using namespace Kt; @@ -38,6 +39,16 @@ namespace Net::Arp { static constexpr uint64_t ARP_CACHE_TIMEOUT_MS = 60000; // 60 seconds static CacheEntry g_cache[ARP_CACHE_SIZE] = {}; + static kcp::Spinlock g_cacheLock; + + struct PendingRequest { + uint32_t Ip; + uint64_t Timestamp; + bool Valid; + }; + static constexpr uint32_t PENDING_REQUEST_SIZE = 16; + static constexpr uint64_t REQUEST_RETRY_MS = 1000; + static PendingRequest g_pendingRequests[PENDING_REQUEST_SIZE] = {}; void Initialize() { for (uint32_t i = 0; i < ARP_CACHE_SIZE; i++) { @@ -47,43 +58,91 @@ namespace Net::Arp { } static void CacheInsert(uint32_t ip, const uint8_t* mac) { + g_cacheLock.Acquire(); // Look for existing entry or empty slot uint32_t emptySlot = ARP_CACHE_SIZE; + uint32_t oldestSlot = 0; + uint64_t oldestTimestamp = ~0ULL; for (uint32_t i = 0; i < ARP_CACHE_SIZE; i++) { if (g_cache[i].Valid && g_cache[i].Ip == ip) { // Update existing entry memcpy(g_cache[i].Mac, mac, 6); g_cache[i].Timestamp = Timekeeping::GetMilliseconds(); + for (uint32_t j = 0; j < PENDING_REQUEST_SIZE; ++j) + if (g_pendingRequests[j].Valid && g_pendingRequests[j].Ip == ip) + g_pendingRequests[j].Valid = false; + g_cacheLock.Release(); return; } if (!g_cache[i].Valid && emptySlot == ARP_CACHE_SIZE) { emptySlot = i; } + if (g_cache[i].Valid && g_cache[i].Timestamp < oldestTimestamp) { + oldestTimestamp = g_cache[i].Timestamp; + oldestSlot = i; + } } - if (emptySlot < ARP_CACHE_SIZE) { - g_cache[emptySlot].Ip = ip; - memcpy(g_cache[emptySlot].Mac, mac, 6); - g_cache[emptySlot].Timestamp = Timekeeping::GetMilliseconds(); - g_cache[emptySlot].Valid = true; - } + uint32_t slot = emptySlot < ARP_CACHE_SIZE ? emptySlot : oldestSlot; + g_cache[slot].Ip = ip; + memcpy(g_cache[slot].Mac, mac, 6); + g_cache[slot].Timestamp = Timekeeping::GetMilliseconds(); + g_cache[slot].Valid = true; + for (uint32_t j = 0; j < PENDING_REQUEST_SIZE; ++j) + if (g_pendingRequests[j].Valid && g_pendingRequests[j].Ip == ip) + g_pendingRequests[j].Valid = false; + g_cacheLock.Release(); } static bool CacheLookup(uint32_t ip, uint8_t* outMac) { + g_cacheLock.Acquire(); uint64_t now = Timekeeping::GetMilliseconds(); for (uint32_t i = 0; i < ARP_CACHE_SIZE; i++) { if (g_cache[i].Valid && g_cache[i].Ip == ip) { if ((now - g_cache[i].Timestamp) > ARP_CACHE_TIMEOUT_MS) { g_cache[i].Valid = false; + g_cacheLock.Release(); return false; } memcpy(outMac, g_cache[i].Mac, 6); + g_cacheLock.Release(); return true; } } + g_cacheLock.Release(); return false; } + static bool ShouldSendRequest(uint32_t ip) { + g_cacheLock.Acquire(); + uint64_t now = Timekeeping::GetMilliseconds(); + uint32_t slot = PENDING_REQUEST_SIZE; + uint32_t oldest = 0; + uint64_t oldestTimestamp = ~0ULL; + for (uint32_t i = 0; i < PENDING_REQUEST_SIZE; ++i) { + if (g_pendingRequests[i].Valid && g_pendingRequests[i].Ip == ip) { + if (now - g_pendingRequests[i].Timestamp < REQUEST_RETRY_MS) { + g_cacheLock.Release(); + return false; + } + slot = i; + break; + } + if (!g_pendingRequests[i].Valid && slot == PENDING_REQUEST_SIZE) slot = i; + if (g_pendingRequests[i].Valid && + g_pendingRequests[i].Timestamp < oldestTimestamp) { + oldestTimestamp = g_pendingRequests[i].Timestamp; + oldest = i; + } + } + if (slot == PENDING_REQUEST_SIZE) slot = oldest; + g_pendingRequests[slot].Ip = ip; + g_pendingRequests[slot].Timestamp = now; + g_pendingRequests[slot].Valid = true; + g_cacheLock.Release(); + return true; + } + void OnPacketReceived(const uint8_t* data, uint16_t length) { if (length < sizeof(Packet)) { return; @@ -92,10 +151,14 @@ namespace Net::Arp { const Packet* pkt = (const Packet*)data; if (Ntohs(pkt->HardwareType) != HW_TYPE_ETHERNET || - Ntohs(pkt->ProtocolType) != PROTO_TYPE_IPV4) { + Ntohs(pkt->ProtocolType) != PROTO_TYPE_IPV4 || + pkt->HardwareAddrLen != 6 || pkt->ProtocolAddrLen != 4) { return; } + uint16_t op = Ntohs(pkt->Operation); + if (op != OP_REQUEST && op != OP_REPLY) return; + uint32_t senderIp = pkt->SenderIp; // Already in network byte order in struct uint32_t targetIp = pkt->TargetIp; @@ -103,8 +166,6 @@ namespace Net::Arp { CacheInsert(senderIp, pkt->SenderMac); Ipv4::FlushPending(); - uint16_t op = Ntohs(pkt->Operation); - if (op == OP_REQUEST && targetIp == GetIpAddress()) { // Someone is asking for our MAC address -- send a reply Packet reply; @@ -135,8 +196,8 @@ namespace Net::Arp { return true; } - // Not in cache, send a request - SendRequest(ip); + // Coalesce bursts of packets awaiting the same next hop. + if (ShouldSendRequest(ip)) SendRequest(ip); return false; } diff --git a/kernel/src/Net/Dns.cpp b/kernel/src/Net/Dns.cpp index 34a15a2..95f2eca 100644 --- a/kernel/src/Net/Dns.cpp +++ b/kernel/src/Net/Dns.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace Net::Dns { @@ -36,16 +37,22 @@ namespace Net::Dns { }; static CacheEntry g_cache[CACHE_SIZE] = {}; + static kcp::Spinlock g_cacheLock; static bool streq(const char* a, const char* b) { while (*a && *b) { - if (*a != *b) return false; + char ca = *a; + char cb = *b; + if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A'; + if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A'; + if (ca != cb) return false; a++; b++; } return *a == *b; } static uint32_t CacheLookup(const char* hostname) { + g_cacheLock.Acquire(); uint64_t now = Timekeeping::GetMilliseconds(); for (int i = 0; i < CACHE_SIZE; i++) { if (!g_cache[i].valid) continue; @@ -53,17 +60,22 @@ namespace Net::Dns { // Check TTL uint64_t elapsed = (now - g_cache[i].timestamp) / 1000; if (elapsed < g_cache[i].ttl) { - return g_cache[i].ip; + uint32_t ip = g_cache[i].ip; + g_cacheLock.Release(); + return ip; } // Expired g_cache[i].valid = false; + g_cacheLock.Release(); return 0; } + g_cacheLock.Release(); return 0; } static void CacheStore(const char* hostname, uint32_t ip, uint32_t ttl) { if (ttl == 0) ttl = 60; // Minimum 60s TTL + g_cacheLock.Acquire(); // Find free or oldest slot int slot = 0; @@ -85,6 +97,7 @@ namespace Net::Dns { e.ttl = ttl; e.timestamp = Timekeeping::GetMilliseconds(); e.valid = true; + g_cacheLock.Release(); } // ---- DNS query building ---- @@ -178,8 +191,10 @@ namespace Net::Dns { maxJumps--; continue; } + if ((len & 0xC0) != 0) return -1; // Regular label + if (offset + 1 + len > packetLen) return -1; offset += 1 + len; maxJumps--; } @@ -209,6 +224,7 @@ namespace Net::Dns { // Check RCODE (must be 0 = no error) uint8_t rcode = packet[3] & 0x0F; if (rcode != 0) return result; + if (packet[2] & 0x02) return result; // Truncated UDP response uint16_t qdcount = ((uint16_t)packet[4] << 8) | packet[5]; uint16_t ancount = ((uint16_t)packet[6] << 8) | packet[7]; @@ -228,7 +244,7 @@ namespace Net::Dns { if (offset < 0 || offset + 10 > packetLen) return result; uint16_t atype = ((uint16_t)packet[offset] << 8) | packet[offset + 1]; - // uint16_t aclass = ((uint16_t)packet[offset + 2] << 8) | packet[offset + 3]; + uint16_t aclass = ((uint16_t)packet[offset + 2] << 8) | packet[offset + 3]; uint32_t attl = ((uint32_t)packet[offset + 4] << 24) | ((uint32_t)packet[offset + 5] << 16) | ((uint32_t)packet[offset + 6] << 8) | @@ -238,7 +254,7 @@ namespace Net::Dns { if (offset + rdlen > packetLen) return result; - if (atype == DNS_QTYPE_A && rdlen == 4) { + if (atype == DNS_QTYPE_A && aclass == DNS_QCLASS_IN && rdlen == 4) { // A record: 4-byte IPv4 address (already in network byte order) result.ip = ((uint32_t)packet[offset]) | ((uint32_t)packet[offset + 1] << 8) @@ -255,26 +271,38 @@ namespace Net::Dns { return result; } - // ---- Resolve state (shared with UDP callback) ---- + // ---- Concurrent resolve state (shared with UDP callback) ---- - static volatile bool g_gotResponse = false; - static volatile uint16_t g_currentId = 0; - static uint8_t g_responseBuffer[512]; - static volatile int g_responseLen = 0; + static constexpr int MAX_QUERIES = 8; + struct Query { + volatile bool active; + volatile bool gotResponse; + uint16_t id; + uint16_t localPort; + uint32_t serverIp; + uint8_t response[512]; + volatile int responseLen; + }; + static Query g_queries[MAX_QUERIES] = {}; + static kcp::Spinlock g_queriesLock; static void DnsRecvCallback(uint32_t srcIp, uint16_t srcPort, uint16_t dstPort, const uint8_t* data, uint16_t length) { - (void)srcIp; - (void)srcPort; - (void)dstPort; - - if (g_gotResponse) return; // Already got a response - if (length > sizeof(g_responseBuffer)) length = sizeof(g_responseBuffer); - - memcpy(g_responseBuffer, data, length); - g_responseLen = length; - g_gotResponse = true; + if (srcPort != DNS_PORT || length < 2) return; + uint16_t responseId = ((uint16_t)data[0] << 8) | data[1]; + for (int i = 0; i < MAX_QUERIES; ++i) { + Query& query = g_queries[i]; + if (!query.active || query.gotResponse || + query.localPort != dstPort || query.serverIp != srcIp || + query.id != responseId) continue; + if (length > sizeof(query.response)) length = sizeof(query.response); + memcpy(query.response, data, length); + query.responseLen = length; + asm volatile("" ::: "memory"); + query.gotResponse = true; + return; + } } // ---- Simple PRNG for transaction IDs ---- @@ -288,21 +316,26 @@ namespace Net::Dns { // ---- Check if string is already an IP address ---- - static bool IsIpAddress(const char* s) { - int dotCount = 0; - bool hasDigit = false; - for (int i = 0; s[i]; i++) { - if (s[i] >= '0' && s[i] <= '9') { - hasDigit = true; - } else if (s[i] == '.') { - if (!hasDigit) return false; - dotCount++; - hasDigit = false; - } else { + static bool ParseIpAddress(const char* s, uint32_t* out) { + uint32_t ip = 0; + for (int octet = 0; octet < 4; ++octet) { + if (*s < '0' || *s > '9') return false; + uint32_t value = 0; + int digits = 0; + while (*s >= '0' && *s <= '9') { + value = value * 10 + (uint32_t)(*s - '0'); + if (value > 255 || ++digits > 3) return false; + ++s; + } + ip |= value << (octet * 8); + if (octet < 3) { + if (*s++ != '.') return false; + } else if (*s != '\0') { return false; } } - return hasDigit && dotCount == 3; + *out = ip; + return true; } // ---- Public API ---- @@ -310,8 +343,8 @@ namespace Net::Dns { uint32_t Resolve(const char* hostname, uint32_t timeoutMs) { if (hostname == nullptr || hostname[0] == '\0') return 0; - // Don't try to resolve IP addresses - if (IsIpAddress(hostname)) return 0; + uint32_t literalIp = 0; + if (ParseIpAddress(hostname, &literalIp)) return literalIp; // Check cache first uint32_t cached = CacheLookup(hostname); @@ -321,51 +354,77 @@ namespace Net::Dns { uint32_t dnsServer = Net::GetDnsServer(); if (dnsServer == 0) return 0; - // Pick a local port for receiving the response (ephemeral range) - uint16_t localPort = 10000 + (NextId() % 50000); - uint16_t txId = NextId(); + g_queriesLock.Acquire(); + Query* query = nullptr; + for (int i = 0; i < MAX_QUERIES; ++i) { + if (!g_queries[i].active) { + query = &g_queries[i]; + query->active = true; + break; + } + } + if (!query) { + g_queriesLock.Release(); + return 0; + } + query->gotResponse = false; + query->responseLen = 0; + query->serverIp = dnsServer; + query->id = NextId(); + uint16_t txId = query->id; + g_queriesLock.Release(); // Build DNS query uint8_t queryPacket[512]; int queryLen = BuildQuery(txId, hostname, queryPacket, sizeof(queryPacket)); - if (queryLen == 0) return 0; + if (queryLen == 0) { + query->active = false; + return 0; + } - // Reset response state - g_gotResponse = false; - g_responseLen = 0; - g_currentId = txId; - - // Bind our receive port - if (!Net::Udp::Bind(localPort, DnsRecvCallback)) { - // Port might be in use, try another - localPort = 10000 + (NextId() % 50000); - if (!Net::Udp::Bind(localPort, DnsRecvCallback)) { - return 0; + bool bound = false; + for (int attempt = 0; attempt < 16; ++attempt) { + g_queriesLock.Acquire(); + uint16_t localPort = (uint16_t)(10000 + (NextId() % 50000)); + g_queriesLock.Release(); + if (Net::Udp::Bind(localPort, DnsRecvCallback)) { + query->localPort = localPort; + bound = true; + break; } } + if (!bound) { + query->active = false; + return 0; + } // Send the query to DNS server port 53 - bool sent = Net::Udp::Send(dnsServer, localPort, DNS_PORT, queryPacket, (uint16_t)queryLen); + bool sent = Net::Udp::Send(dnsServer, query->localPort, DNS_PORT, + queryPacket, (uint16_t)queryLen); if (!sent) { - Net::Udp::Unbind(localPort); + Net::Udp::Unbind(query->localPort); + query->active = false; return 0; } // Wait for response with timeout uint64_t start = Timekeeping::GetMilliseconds(); - while (!g_gotResponse) { + while (!query->gotResponse) { if (Timekeeping::GetMilliseconds() - start >= timeoutMs) { - Net::Udp::Unbind(localPort); + Net::Udp::Unbind(query->localPort); + query->active = false; return 0; } Sched::Schedule(); } // Unbind the port - Net::Udp::Unbind(localPort); + Net::Udp::Unbind(query->localPort); // Parse the response - DnsAnswer answer = ParseResponse(txId, g_responseBuffer, g_responseLen); + asm volatile("" ::: "memory"); + DnsAnswer answer = ParseResponse(txId, query->response, query->responseLen); + query->active = false; if (!answer.found) return 0; // Cache the result diff --git a/kernel/src/Net/Ipv4.cpp b/kernel/src/Net/Ipv4.cpp index 929dd68..5444f97 100644 --- a/kernel/src/Net/Ipv4.cpp +++ b/kernel/src/Net/Ipv4.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include using namespace Kt; @@ -28,11 +30,15 @@ namespace Net::Ipv4 { uint8_t Protocol; uint8_t Data[Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE]; uint16_t Length; + uint64_t Timestamp; bool Active; + bool Processing; }; - static constexpr uint32_t PENDING_QUEUE_SIZE = 8; + static constexpr uint32_t PENDING_QUEUE_SIZE = 64; + static constexpr uint64_t PENDING_TIMEOUT_MS = 30000; static PendingPacket g_pendingQueue[PENDING_QUEUE_SIZE] = {}; + static kcp::Spinlock g_pendingLock; void Initialize() { g_identification = 0; @@ -125,6 +131,11 @@ namespace Net::Ipv4 { return; } + // Fragment reassembly is not implemented yet. Never pass a fragment + // to TCP/UDP as though it were a complete transport packet. + uint16_t fragment = Ntohs(hdr->FlagsFragment); + if (fragment & 0x3FFFu) return; + // Check destination: accept packets addressed to us or broadcast uint32_t ourIp = GetIpAddress(); if (hdr->DstIp != ourIp && hdr->DstIp != 0xFFFFFFFF) { @@ -158,7 +169,9 @@ namespace Net::Ipv4 { hdr->VersionIhl = (4 << 4) | 5; // IPv4, 5 dwords (20 bytes) hdr->Tos = 0; hdr->TotalLength = Htons(HEADER_SIZE + payloadLen); - hdr->Identification = Htons(g_identification++); + uint16_t identification = + __atomic_fetch_add(&g_identification, 1, __ATOMIC_RELAXED); + hdr->Identification = Htons(identification); hdr->FlagsFragment = 0; hdr->Ttl = DEFAULT_TTL; hdr->Protocol = protocol; @@ -187,16 +200,25 @@ namespace Net::Ipv4 { } // ARP request already sent by Resolve(), queue the packet for later + g_pendingLock.Acquire(); + uint64_t now = Timekeeping::GetMilliseconds(); for (uint32_t i = 0; i < PENDING_QUEUE_SIZE; i++) { + if (g_pendingQueue[i].Active && !g_pendingQueue[i].Processing && + now - g_pendingQueue[i].Timestamp >= PENDING_TIMEOUT_MS) + g_pendingQueue[i].Active = false; if (!g_pendingQueue[i].Active) { g_pendingQueue[i].DestIp = destIp; g_pendingQueue[i].Protocol = protocol; g_pendingQueue[i].Length = payloadLen; + g_pendingQueue[i].Timestamp = now; memcpy(g_pendingQueue[i].Data, payload, payloadLen); g_pendingQueue[i].Active = true; + g_pendingQueue[i].Processing = false; + g_pendingLock.Release(); return true; } } + g_pendingLock.Release(); // Queue full, drop the packet return false; @@ -204,17 +226,42 @@ namespace Net::Ipv4 { void FlushPending() { for (uint32_t i = 0; i < PENDING_QUEUE_SIZE; i++) { - if (!g_pendingQueue[i].Active) { + uint32_t destIp; + uint8_t protocol; + uint16_t length; + uint8_t data[Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE]; + + g_pendingLock.Acquire(); + if (!g_pendingQueue[i].Active || g_pendingQueue[i].Processing) { + g_pendingLock.Release(); continue; } + if (Timekeeping::GetMilliseconds() - g_pendingQueue[i].Timestamp >= + PENDING_TIMEOUT_MS) { + g_pendingQueue[i].Active = false; + g_pendingLock.Release(); + continue; + } + g_pendingQueue[i].Processing = true; + destIp = g_pendingQueue[i].DestIp; + protocol = g_pendingQueue[i].Protocol; + length = g_pendingQueue[i].Length; + memcpy(data, g_pendingQueue[i].Data, length); + g_pendingLock.Release(); - uint32_t nextHop = GetNextHop(g_pendingQueue[i].DestIp); + uint32_t nextHop = GetNextHop(destIp); uint8_t destMac[6]; if (Arp::Resolve(nextHop, destMac)) { - SendDirect(g_pendingQueue[i].DestIp, g_pendingQueue[i].Protocol, - destMac, g_pendingQueue[i].Data, g_pendingQueue[i].Length); + g_pendingLock.Acquire(); g_pendingQueue[i].Active = false; + g_pendingQueue[i].Processing = false; + g_pendingLock.Release(); + SendDirect(destIp, protocol, destMac, data, length); + } else { + g_pendingLock.Acquire(); + g_pendingQueue[i].Processing = false; + g_pendingLock.Release(); } } } diff --git a/kernel/src/Net/Tcp.cpp b/kernel/src/Net/Tcp.cpp index a7af84e..1eb0754 100644 --- a/kernel/src/Net/Tcp.cpp +++ b/kernel/src/Net/Tcp.cpp @@ -23,7 +23,8 @@ namespace Net::Tcp { // Receive buffer size per connection static constexpr uint16_t RECV_BUFFER_SIZE = 32768; static constexpr uint16_t WINDOW_SIZE = RECV_BUFFER_SIZE; - static constexpr uint32_t MAX_CONNECTIONS = 16; + static constexpr uint32_t MAX_CONNECTIONS = 64; + static constexpr uint8_t ACCEPT_BACKLOG_SIZE = 8; static constexpr uint64_t RETRANSMIT_TIMEOUT_MS = 1000; static constexpr int MAX_RETRANSMITS = 5; static constexpr uint64_t TIME_WAIT_MS = 2000; @@ -51,12 +52,18 @@ namespace Net::Tcp { uint16_t RetransmitLen; uint64_t RetransmitTime; int RetransmitCount; + bool SendBusy; // For Listen/Accept - bool PendingAccept; - uint32_t PendingRemoteIp; - uint16_t PendingRemotePort; - uint32_t PendingSeq; + struct PendingSyn { + uint32_t RemoteIp; + uint16_t RemotePort; + uint32_t Seq; + }; + PendingSyn Pending[ACCEPT_BACKLOG_SIZE]; + uint8_t PendingHead; + uint8_t PendingTail; + uint8_t PendingCount; bool Active; @@ -71,6 +78,17 @@ namespace Net::Tcp { return (uint32_t)(Timekeeping::GetMilliseconds() * 2654435761u); } + // TCP sequence numbers are compared modulo 2^32. This is valid for + // ranges smaller than 2^31 bytes, which all of our send windows are. + static bool SeqLessOrEqual(uint32_t a, uint32_t b) { + return (int32_t)(a - b) <= 0; + } + + static bool AckIsValid(const Connection* conn, uint32_t ack) { + return SeqLessOrEqual(conn->SendUnack, ack) && + SeqLessOrEqual(ack, conn->SendNext); + } + static Connection* FindConnection(uint32_t remoteIp, uint16_t remotePort, uint16_t localPort) { for (uint32_t i = 0; i < MAX_CONNECTIONS; i++) { @@ -109,6 +127,13 @@ namespace Net::Tcp { return nullptr; } + static void ReleaseConnection(Connection* conn) { + if (!conn) return; + g_connectionsLock.Acquire(); + conn->Active = false; + g_connectionsLock.Release(); + } + static bool SendSegment(Connection* conn, uint8_t flags, const uint8_t* payload, uint16_t payloadLen) { uint8_t packet[1500]; @@ -213,15 +238,35 @@ namespace Net::Tcp { if (flags & FLAG_SYN) { Connection* listener = FindListener(dstPort); if (listener != nullptr) { - // Signal the listener about this incoming connection + // Queue the SYN for accept(). Retransmitted SYNs refresh + // their sequence number instead of consuming backlog. listener->Lock.Acquire(); - listener->PendingAccept = true; - listener->PendingRemoteIp = srcIp; - listener->PendingRemotePort = srcPort; - listener->PendingSeq = seqNum; + bool duplicate = false; + for (uint8_t i = 0; i < listener->PendingCount; ++i) { + uint8_t index = (uint8_t)((listener->PendingHead + i) % + ACCEPT_BACKLOG_SIZE); + Connection::PendingSyn& pending = listener->Pending[index]; + if (pending.RemoteIp == srcIp && pending.RemotePort == srcPort) { + pending.Seq = seqNum; + duplicate = true; + break; + } + } + if (!duplicate && listener->PendingCount < ACCEPT_BACKLOG_SIZE) { + Connection::PendingSyn& pending = + listener->Pending[listener->PendingTail]; + pending.RemoteIp = srcIp; + pending.RemotePort = srcPort; + pending.Seq = seqNum; + listener->PendingTail = + (uint8_t)((listener->PendingTail + 1) % ACCEPT_BACKLOG_SIZE); + listener->PendingCount++; + } listener->Lock.Release(); - Sched::WakeObjectWaiters(listener); - Ipc::NotifyTcpConnectionChanged(listener); + if (duplicate || listener->PendingCount > 0) { + Sched::WakeObjectWaiters(listener); + Ipc::NotifyTcpConnectionChanged(listener); + } return; } } @@ -246,7 +291,9 @@ namespace Net::Tcp { // RST handling if (flags & FLAG_RST) { conn->CurrentState = State::Closed; - conn->Active = false; + // Keep the slot reserved until its socket handle is closed. + // Reusing it here would leave that handle pointing at an + // unrelated future connection. conn->Lock.Release(); Sched::WakeObjectWaiters(conn); Ipc::NotifyTcpConnectionChanged(conn); @@ -285,8 +332,10 @@ namespace Net::Tcp { case State::Established: { // Handle incoming data if (flags & FLAG_ACK) { - conn->SendUnack = ackNum; - notify = true; + if (AckIsValid(conn, ackNum)) { + conn->SendUnack = ackNum; + notify = true; + } } uint16_t accepted = 0; @@ -365,9 +414,9 @@ namespace Net::Tcp { } case State::LastAck: { - if (flags & FLAG_ACK) { + if ((flags & FLAG_ACK) && ackNum == conn->SendNext) { + conn->SendUnack = ackNum; conn->CurrentState = State::Closed; - conn->Active = false; notify = true; } break; @@ -401,7 +450,9 @@ namespace Net::Tcp { conn->LocalIp = Net::GetIpAddress(); conn->LocalPort = port; conn->CurrentState = State::Listen; - conn->PendingAccept = false; + conn->PendingHead = 0; + conn->PendingTail = 0; + conn->PendingCount = 0; KernelLogStream(INFO, "Net") << "TCP listening on port " << base::dec << (uint64_t)port; return conn; @@ -415,12 +466,15 @@ namespace Net::Tcp { // Block until a SYN arrives while (true) { listener->Lock.Acquire(); - if (listener->PendingAccept) { - listener->PendingAccept = false; + if (listener->PendingCount > 0) { + Connection::PendingSyn pending = listener->Pending[listener->PendingHead]; + listener->PendingHead = + (uint8_t)((listener->PendingHead + 1) % ACCEPT_BACKLOG_SIZE); + listener->PendingCount--; - uint32_t remoteIp = listener->PendingRemoteIp; - uint16_t remotePort = listener->PendingRemotePort; - uint32_t remoteSeq = listener->PendingSeq; + uint32_t remoteIp = pending.RemoteIp; + uint16_t remotePort = pending.RemotePort; + uint32_t remoteSeq = pending.Seq; listener->Lock.Release(); // Allocate a new connection for this client @@ -480,9 +534,9 @@ namespace Net::Tcp { } // Timed out waiting for ACK - conn->Active = false; Sched::WakeObjectWaiters(conn); Ipc::NotifyTcpConnectionChanged(conn); + ReleaseConnection(conn); return nullptr; } listener->Lock.Release(); @@ -568,25 +622,56 @@ namespace Net::Tcp { } // Failed to connect - conn->Active = false; Sched::WakeObjectWaiters(conn); Ipc::NotifyTcpConnectionChanged(conn); + ReleaseConnection(conn); return nullptr; } - int Send(Connection* conn, const uint8_t* data, uint16_t length) { - if (conn == nullptr || conn->CurrentState != State::Established) { - return -1; + int Send(Connection* conn, const uint8_t* data, uint32_t length) { + if (conn == nullptr || data == nullptr) return -1; + if (length == 0) return 0; + + // A connection has one send sequence/retransmission stream. Serialize + // writers so concurrent threads cannot overwrite its in-flight + // retransmit buffer or allocate overlapping sequence numbers. + while (true) { + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + conn->Lock.Acquire(); + if (conn->CurrentState != State::Established) { + conn->Lock.Release(); + asm volatile("push %0; popfq" :: "r"(flags) : "memory"); + return -1; + } + if (!conn->SendBusy) { + conn->SendBusy = true; + conn->Lock.Release(); + asm volatile("push %0; popfq" :: "r"(flags) : "memory"); + break; + } + conn->Lock.Release(); + asm volatile("push %0; popfq" :: "r"(flags) : "memory"); + Sched::BlockOnObject(conn, 50); } + auto finishSend = [&]() { + uint64_t flags; + asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); + conn->Lock.Acquire(); + conn->SendBusy = false; + conn->Lock.Release(); + asm volatile("push %0; popfq" :: "r"(flags) : "memory"); + Sched::WakeObjectWaiters(conn); + Ipc::NotifyTcpConnectionChanged(conn); + }; + constexpr uint16_t MSS = 1460; - uint16_t sent = 0; + uint32_t sent = 0; while (sent < length) { - uint16_t segLen = length - sent; - if (segLen > MSS) { - segLen = MSS; - } + uint32_t remaining = length - sent; + uint16_t segLen = remaining > MSS ? MSS : (uint16_t)remaining; uint32_t segSeq = 0; uint32_t expectedAck = 0; @@ -597,7 +682,9 @@ namespace Net::Tcp { if (conn->CurrentState != State::Established) { conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); - return sent > 0 ? sent : -1; + int result = sent > 0 ? (int)sent : -1; + finishSend(); + return result; } segSeq = conn->SendNext; @@ -607,7 +694,9 @@ namespace Net::Tcp { if (!ok) { conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); - return sent > 0 ? sent : -1; + int result = sent > 0 ? (int)sent : -1; + finishSend(); + return result; } conn->SendNext = expectedAck; @@ -626,7 +715,7 @@ namespace Net::Tcp { asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory"); conn->Lock.Acquire(); - if (conn->SendUnack >= expectedAck) { + if (SeqLessOrEqual(expectedAck, conn->SendUnack)) { conn->RetransmitLen = 0; conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); @@ -639,7 +728,9 @@ namespace Net::Tcp { conn->RetransmitLen = 0; conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); - return sent > 0 ? sent : -1; + int result = sent > 0 ? (int)sent : -1; + finishSend(); + return result; } uint64_t now = Timekeeping::GetMilliseconds(); @@ -653,7 +744,9 @@ namespace Net::Tcp { conn->RetransmitLen = 0; conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); - return sent > 0 ? sent : -1; + int result = sent > 0 ? (int)sent : -1; + finishSend(); + return result; } uint32_t savedNext = conn->SendNext; @@ -667,7 +760,9 @@ namespace Net::Tcp { conn->RetransmitLen = 0; conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); - return sent > 0 ? sent : -1; + int result = sent > 0 ? (int)sent : -1; + finishSend(); + return result; } } @@ -684,7 +779,8 @@ namespace Net::Tcp { } } - return sent; + finishSend(); + return (int)sent; } int Receive(Connection* conn, uint8_t* buffer, uint16_t bufferSize) { @@ -815,9 +911,9 @@ namespace Net::Tcp { } Sched::BlockOnObject(conn, 50); } - conn->Active = false; Sched::WakeObjectWaiters(conn); Ipc::NotifyTcpConnectionChanged(conn); + ReleaseConnection(conn); return; } @@ -835,29 +931,29 @@ namespace Net::Tcp { } Sched::BlockOnObject(conn, 50); } - conn->Active = false; Sched::WakeObjectWaiters(conn); Ipc::NotifyTcpConnectionChanged(conn); + ReleaseConnection(conn); return; } case State::Listen: case State::SynSent: { conn->CurrentState = State::Closed; - conn->Active = false; conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); Sched::WakeObjectWaiters(conn); Ipc::NotifyTcpConnectionChanged(conn); + ReleaseConnection(conn); return; } default: conn->Lock.Release(); asm volatile("push %0; popfq" :: "r"(flags) : "memory"); - conn->Active = false; Sched::WakeObjectWaiters(conn); Ipc::NotifyTcpConnectionChanged(conn); + ReleaseConnection(conn); return; } } @@ -875,7 +971,7 @@ namespace Net::Tcp { bool HasPendingAccept(Connection* conn) { if (conn == nullptr) return false; conn->Lock.Acquire(); - bool pending = conn->PendingAccept; + bool pending = conn->PendingCount > 0; conn->Lock.Release(); return pending; } diff --git a/kernel/src/Net/Tcp.hpp b/kernel/src/Net/Tcp.hpp index 09de699..25efca0 100644 --- a/kernel/src/Net/Tcp.hpp +++ b/kernel/src/Net/Tcp.hpp @@ -65,7 +65,7 @@ namespace Net::Tcp { Connection* Connect(uint32_t destIp, uint16_t destPort, uint16_t srcPort); // Send data on an established connection. Returns number of bytes sent. - int Send(Connection* conn, const uint8_t* data, uint16_t length); + int Send(Connection* conn, const uint8_t* data, uint32_t length); // Receive data from an established connection. Returns number of bytes received. // Blocks until data is available or connection is closed. diff --git a/kernel/src/Net/Udp.cpp b/kernel/src/Net/Udp.cpp index 4c1cbb2..348d5cc 100644 --- a/kernel/src/Net/Udp.cpp +++ b/kernel/src/Net/Udp.cpp @@ -11,6 +11,7 @@ #include #include #include +#include using namespace Kt; @@ -22,8 +23,9 @@ namespace Net::Udp { bool Active; }; - static constexpr uint32_t MAX_BINDINGS = 16; + static constexpr uint32_t MAX_BINDINGS = 64; static PortBinding g_bindings[MAX_BINDINGS] = {}; + static kcp::Spinlock g_bindingsLock; void Initialize() { for (uint32_t i = 0; i < MAX_BINDINGS; i++) { @@ -58,13 +60,18 @@ namespace Net::Udp { const uint8_t* payload = data + HEADER_SIZE; uint16_t payloadLen = udpLen - HEADER_SIZE; - // Dispatch to bound callback + // Snapshot the callback under the binding lock, then invoke it after + // releasing the lock so callbacks may safely bind/unbind other ports. + RecvCallback callback = nullptr; + g_bindingsLock.Acquire(); for (uint32_t i = 0; i < MAX_BINDINGS; i++) { if (g_bindings[i].Active && g_bindings[i].Port == dstPort) { - g_bindings[i].Callback(srcIp, srcPort, dstPort, payload, payloadLen); - return; + callback = g_bindings[i].Callback; + break; } } + g_bindingsLock.Release(); + if (callback) callback(srcIp, srcPort, dstPort, payload, payloadLen); } bool Send(uint32_t destIp, uint16_t srcPort, uint16_t destPort, @@ -96,9 +103,12 @@ namespace Net::Udp { } bool Bind(uint16_t port, RecvCallback callback) { + if (port == 0 || callback == nullptr) return false; + g_bindingsLock.Acquire(); // Check for duplicate for (uint32_t i = 0; i < MAX_BINDINGS; i++) { if (g_bindings[i].Active && g_bindings[i].Port == port) { + g_bindingsLock.Release(); return false; } } @@ -109,19 +119,25 @@ namespace Net::Udp { g_bindings[i].Port = port; g_bindings[i].Callback = callback; g_bindings[i].Active = true; + g_bindingsLock.Release(); return true; } } + g_bindingsLock.Release(); return false; } void Unbind(uint16_t port) { + g_bindingsLock.Acquire(); for (uint32_t i = 0; i < MAX_BINDINGS; i++) { if (g_bindings[i].Active && g_bindings[i].Port == port) { g_bindings[i].Active = false; + g_bindings[i].Callback = nullptr; + g_bindingsLock.Release(); return; } } + g_bindingsLock.Release(); } } diff --git a/programs/include/http/http.hpp b/programs/include/http/http.hpp index e84c358..14ee092 100644 --- a/programs/include/http/http.hpp +++ b/programs/include/http/http.hpp @@ -1,7 +1,10 @@ /* * http.hpp - * Simple HTTP request builder and response parser for MontaukOS - * Wraps tls::https_fetch() and raw sockets for ergonomic HTTP usage. + * Shared HTTP/1.1 client for MontaukOS. + * + * Owns request construction, DNS/transport selection, bounded response + * collection, response parsing, and chunked-transfer decoding. Applications + * should use this layer instead of constructing HTTP messages themselves. */ #pragma once @@ -13,67 +16,113 @@ namespace http { -// ---------------------------------------------------------------------------- -// Response -// ---------------------------------------------------------------------------- +enum class Error { + NONE = 0, + INVALID_ARGUMENT, + DNS_FAILED, + NO_MEMORY, + REQUEST_TOO_LARGE, + SOCKET_FAILED, + CONNECT_FAILED, + SEND_FAILED, + RECEIVE_FAILED, + TLS_FAILED, + INVALID_RESPONSE, + RESPONSE_TOO_LARGE, + TRUNCATED_RESPONSE +}; + +inline const char* error_string(Error error) { + switch (error) { + case Error::NONE: return "no error"; + case Error::INVALID_ARGUMENT: return "invalid HTTP request"; + case Error::DNS_FAILED: return "DNS resolution failed"; + case Error::NO_MEMORY: return "out of memory"; + case Error::REQUEST_TOO_LARGE: return "HTTP request is too large"; + case Error::SOCKET_FAILED: return "could not create socket"; + case Error::CONNECT_FAILED: return "connection failed"; + case Error::SEND_FAILED: return "request send failed"; + case Error::RECEIVE_FAILED: return "response receive failed"; + case Error::TLS_FAILED: return "TLS exchange failed"; + case Error::INVALID_RESPONSE: return "invalid HTTP response"; + case Error::RESPONSE_TOO_LARGE: return "HTTP response exceeded the buffer"; + case Error::TRUNCATED_RESPONSE: return "truncated HTTP response"; + } + return "unknown HTTP error"; +} struct Response { - int status; // HTTP status code (200, 404, etc.) or -1 on error - const char* headers; // Pointer into raw buffer (header block) + int status; // HTTP status code, or -1 before a response is parsed + const char* headers; // Pointers into raw int headers_len; - const char* body; // Pointer into raw buffer (body) + const char* body; int body_len; - char* raw; // Owned buffer — caller must free with montauk::mfree() + char* raw; int raw_len; + Error error; + bool owns_raw; }; +struct RequestOptions { + bool secure; // true for HTTPS, false for HTTP + uint16_t port; // 0 selects 443 or 80 + uint32_t resolved_ip; // 0 performs DNS resolution + int response_buffer_size; // used by request(); includes trailing NUL + const char* host_header; // optional Host authority (e.g. host:port) + const char* extra_headers; // complete CRLF-terminated header lines + tls::AbortCheckFn abort_check; + uint64_t timeout_ms; // inactivity timeout for plain HTTP + + RequestOptions() + : secure(true), port(0), resolved_ip(0), response_buffer_size(32768), + host_header(nullptr), extra_headers(nullptr), abort_check(nullptr), + timeout_ms(30000) {} +}; + +inline Response empty_response(Error error = Error::NONE) { + Response resp = {}; + resp.status = -1; + resp.error = error; + return resp; +} + inline const char* find_header_block_end(const char* start, const char* end) { if (!start || !end || start >= end) return nullptr; - - for (const char* s = start; s < end - 3; s++) { - if (s[0] == '\r' && s[1] == '\n' && s[2] == '\r' && s[3] == '\n') - return s + 4; + for (const char* p = start; p + 3 < end; ++p) { + if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') + return p + 4; } - for (const char* s = start; s < end - 1; s++) { - if (s[0] == '\n' && s[1] == '\n') - return s + 2; + for (const char* p = start; p + 1 < end; ++p) { + if (p[0] == '\n' && p[1] == '\n') return p + 2; } return nullptr; } inline int parse_status_code(const char* start, const char* end) { if (!start || !end || end - start < 12) return -1; + if (start[0] != 'H' || start[1] != 'T' || start[2] != 'T' || + start[3] != 'P' || start[4] != '/' || start[5] != '1' || + start[6] != '.' || (start[7] != '0' && start[7] != '1') || + start[8] != ' ') return -1; const char* p = start; - while (p < end && *p && *p != ' ') p++; - if (p >= end || *p != ' ') return -1; - p++; - - int code = 0; - int digits = 0; - while (digits < 3 && p < end && *p >= '0' && *p <= '9') { - code = code * 10 + (*p - '0'); - p++; - digits++; - } - return digits == 3 ? code : -1; + while (p < end && *p != ' ' && *p != '\r' && *p != '\n') ++p; + if (p >= end || *p++ != ' ') return -1; + if (end - p < 3 || p[0] < '0' || p[0] > '9' || + p[1] < '0' || p[1] > '9' || p[2] < '0' || p[2] > '9') return -1; + return (p[0] - '0') * 100 + (p[1] - '0') * 10 + (p[2] - '0'); } inline const char* find_final_response_start(const char* buf, int len) { if (!buf || len <= 0) return nullptr; - const char* start = buf; const char* end = buf + len; for (;;) { int code = parse_status_code(start, end); if (code < 0) return nullptr; - if (code < 100 || code >= 200) return start; - + if (code < 100 || code == 101 || code >= 200) return start; const char* next = find_header_block_end(start, end); if (!next || next >= end) return nullptr; - if (end - next < 5) return nullptr; - if (!(next[0] == 'H' && next[1] == 'T' && next[2] == 'T' && next[3] == 'P' && next[4] == '/')) - return nullptr; start = next; } } @@ -83,295 +132,505 @@ inline const char* skip_informational_responses(const char* buf, int len) { return start ? start : buf; } -// Parse raw HTTP response in-place. Sets pointers into buf (does not copy). -// Skips leading informational 1xx responses such as "100 Continue". -// Returns status code, or -1 if unparseable. -inline int parse_response(char* buf, int len, Response* out) { - out->raw = buf; - out->raw_len = len; - out->status = -1; - out->headers = nullptr; - out->headers_len = 0; - out->body = nullptr; - out->body_len = 0; - - const char* start = find_final_response_start(buf, len); - const char* end = buf + len; - if (!start || end - start < 12) return -1; // "HTTP/1.x NNN" - - // Parse status code from "HTTP/1.x NNN" - int code = parse_status_code(start, end); - if (code < 0) return -1; - out->status = code; - - // Headers start after the status line - const char* hdr_start = start; - while (hdr_start < end - 1) { - if (*hdr_start == '\r' && *(hdr_start + 1) == '\n') { hdr_start += 2; break; } - if (*hdr_start == '\n') { hdr_start++; break; } - hdr_start++; - } - out->headers = hdr_start; - - // Find \r\n\r\n boundary between headers and body - const char* body = find_header_block_end(hdr_start, end); - if (body) { - if (body >= hdr_start + 4 && - body[-4] == '\r' && body[-3] == '\n' && body[-2] == '\r' && body[-1] == '\n') - out->headers_len = (int)((body - 4) - hdr_start); - else if (body >= hdr_start + 2 && - body[-2] == '\n' && body[-1] == '\n') - out->headers_len = (int)((body - 2) - hdr_start); - else - out->headers_len = (int)(body - hdr_start); - out->body = body; - out->body_len = len - (int)(out->body - buf); - return code; - } - - // No body separator found — entire remainder is headers - out->headers_len = len - (int)(hdr_start - buf); - return code; +inline bool ascii_equal_ci(char a, char b) { + if (a >= 'A' && a <= 'Z') a += 'a' - 'A'; + if (b >= 'A' && b <= 'Z') b += 'a' - 'A'; + return a == b; } -// Find a header value by name (case-insensitive match on the name). -// Writes value into out_val (up to max_len), returns true if found. -inline bool get_header(const Response* resp, const char* name, char* out_val, int max_len) { - if (!resp->headers || resp->headers_len == 0) return false; +inline bool get_header(const Response* resp, const char* name, + char* out_val, int max_len) { + if (!resp || !resp->headers || resp->headers_len <= 0 || !name || + !out_val || max_len <= 0) return false; + int name_len = montauk::slen(name); const char* p = resp->headers; - const char* end = resp->headers + resp->headers_len; - + const char* end = p + resp->headers_len; while (p < end) { - // Case-insensitive prefix match - bool match = true; - if (p + name_len >= end) { match = false; } - else { - for (int i = 0; i < name_len; i++) { - char a = p[i], b = name[i]; - if (a >= 'A' && a <= 'Z') a += 32; - if (b >= 'A' && b <= 'Z') b += 32; - if (a != b) { match = false; break; } - } - if (match && p[name_len] != ':') match = false; - } + const char* line_end = p; + while (line_end < end && *line_end != '\r' && *line_end != '\n') ++line_end; + bool match = line_end - p > name_len && p[name_len] == ':'; + for (int i = 0; match && i < name_len; ++i) + if (!ascii_equal_ci(p[i], name[i])) match = false; if (match) { - const char* v = p + name_len + 1; - while (v < end && *v == ' ') v++; // skip OWS - int i = 0; - while (v < end && *v != '\r' && *v != '\n' && i < max_len - 1) - out_val[i++] = *v++; - out_val[i] = 0; + const char* value = p + name_len + 1; + while (value < line_end && (*value == ' ' || *value == '\t')) ++value; + while (line_end > value && + (line_end[-1] == ' ' || line_end[-1] == '\t')) --line_end; + int n = (int)(line_end - value); + if (n >= max_len) n = max_len - 1; + if (n > 0) montauk::memcpy(out_val, value, n); + out_val[n] = '\0'; return true; } - // Skip to next line - while (p < end && *p != '\n') p++; - if (p < end) p++; + p = line_end; + while (p < end && (*p == '\r' || *p == '\n')) ++p; } return false; } -// Free a response's raw buffer. -inline void free_response(Response* resp) { - if (resp->raw) { montauk::mfree(resp->raw); resp->raw = nullptr; } +inline bool header_has_token(const Response* resp, const char* name, + const char* token) { + char value[128]; + if (!get_header(resp, name, value, sizeof(value))) return false; + int token_len = montauk::slen(token); + for (int i = 0; value[i];) { + while (value[i] == ' ' || value[i] == '\t' || value[i] == ',') ++i; + int start = i; + while (value[i] && value[i] != ',' && value[i] != ' ' && value[i] != '\t') ++i; + int len = i - start; + bool match = len == token_len; + for (int j = 0; match && j < len; ++j) + if (!ascii_equal_ci(value[start + j], token[j])) match = false; + if (match) return true; + while (value[i] && value[i] != ',') ++i; + } + return false; } -// ---------------------------------------------------------------------------- -// Request builder (internal) -// ---------------------------------------------------------------------------- +inline int parse_decimal(const char* value) { + if (!value || !*value) return -1; + int result = 0; + for (int i = 0; value[i]; ++i) { + if (value[i] < '0' || value[i] > '9') return -1; + if (result > 214748364 || (result == 214748364 && value[i] > '7')) return -1; + result = result * 10 + value[i] - '0'; + } + return result; +} + +inline int decode_chunked_body(char* body, int encoded_len) { + int read_pos = 0; + int write_pos = 0; + for (;;) { + unsigned chunk_size = 0; + int digits = 0; + while (read_pos < encoded_len && body[read_pos] != '\r' && body[read_pos] != '\n') { + char c = body[read_pos++]; + if (c == ';') { + while (read_pos < encoded_len && body[read_pos] != '\r' && + body[read_pos] != '\n') ++read_pos; + break; + } + unsigned digit; + if (c >= '0' && c <= '9') digit = (unsigned)(c - '0'); + else if (c >= 'a' && c <= 'f') digit = (unsigned)(c - 'a' + 10); + else if (c >= 'A' && c <= 'F') digit = (unsigned)(c - 'A' + 10); + else return -1; + if (chunk_size > 0x0FFFFFFFu) return -1; + chunk_size = chunk_size * 16 + digit; + ++digits; + } + if (digits == 0 || read_pos >= encoded_len) return -1; + if (body[read_pos] == '\r') { + if (read_pos + 1 >= encoded_len || body[read_pos + 1] != '\n') return -1; + read_pos += 2; + } else { + ++read_pos; + } + + if (chunk_size == 0) { + // A zero chunk is followed by either an empty trailer line or a + // trailer header block. Do not accept a response cut at "0\r\n". + if (read_pos < encoded_len && body[read_pos] == '\n') return write_pos; + if (read_pos + 1 < encoded_len && body[read_pos] == '\r' && + body[read_pos + 1] == '\n') return write_pos; + if (find_header_block_end(body + read_pos, body + encoded_len)) + return write_pos; + return -1; + } + if (chunk_size > (unsigned)(encoded_len - read_pos)) return -1; + montauk::memmove(body + write_pos, body + read_pos, chunk_size); + write_pos += (int)chunk_size; + read_pos += (int)chunk_size; + if (read_pos >= encoded_len) return -1; + if (body[read_pos] == '\r') { + if (read_pos + 1 >= encoded_len || body[read_pos + 1] != '\n') return -1; + read_pos += 2; + } else if (body[read_pos] == '\n') { + ++read_pos; + } else { + return -1; + } + } +} + +// Parses and normalizes a response in place. Chunked bodies are decoded in +// the same buffer. Content-Length mismatches are reported as truncation. +inline int parse_response(char* buf, int len, Response* out) { + if (!out) return -1; + bool owns_raw = out->owns_raw; + *out = empty_response(); + out->raw = buf; + out->raw_len = len; + out->owns_raw = owns_raw; + if (!buf || len <= 0) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + + const char* start = find_final_response_start(buf, len); + const char* end = buf + len; + if (!start) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + int code = parse_status_code(start, end); + if (code < 0) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + + const char* status_end = start; + while (status_end < end && *status_end != '\n') ++status_end; + if (status_end >= end) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + const char* headers = status_end + 1; + const char* body = find_header_block_end(headers, end); + if (!body) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + + const char* headers_end = body; + if (headers_end - headers >= 4 && headers_end[-4] == '\r') + headers_end -= 4; + else + headers_end -= 2; + + out->status = code; + out->headers = headers; + out->headers_len = (int)(headers_end - headers); + out->body = body; + out->body_len = (int)(end - body); + + if (header_has_token(out, "Transfer-Encoding", "chunked")) { + int decoded = decode_chunked_body((char*)out->body, out->body_len); + if (decoded < 0) { + out->error = Error::TRUNCATED_RESPONSE; + return code; + } + out->body_len = decoded; + ((char*)out->body)[decoded] = '\0'; + } else { + char value[32]; + if (get_header(out, "Content-Length", value, sizeof(value))) { + int expected = parse_decimal(value); + if (expected < 0) { + out->error = Error::INVALID_RESPONSE; + return code; + } + if (out->body_len < expected) { + out->error = Error::TRUNCATED_RESPONSE; + return code; + } + out->body_len = expected; + } + } + return code; +} + +inline void free_response(Response* resp) { + if (!resp) return; + if (resp->raw && resp->owns_raw) montauk::mfree(resp->raw); + *resp = empty_response(); +} + +inline bool contains_crlf(const char* value) { + if (!value) return false; + for (; *value; ++value) + if (*value == '\r' || *value == '\n') return true; + return false; +} + +inline bool contains_header_terminator(const char* value) { + if (!value) return false; + for (int i = 0; value[i]; ++i) { + if (value[i] == '\n' && value[i + 1] == '\n') return true; + if (value[i] == '\r' && value[i + 1] == '\n' && + value[i + 2] == '\r' && value[i + 3] == '\n') return true; + } + return false; +} + +struct RequestWriter { + char* p; + char* end; + bool overflow; + + void text(const char* value) { + if (!value) return; + while (*value) { + if (p >= end) { overflow = true; return; } + *p++ = *value++; + } + } + void number(int value) { + char digits[16]; + int n = 0; + if (value == 0) digits[n++] = '0'; + while (value > 0 && n < (int)sizeof(digits)) { + digits[n++] = (char)('0' + value % 10); + value /= 10; + } + while (n > 0) { + if (p >= end) { overflow = true; return; } + *p++ = digits[--n]; + } + } +}; inline int build_request(char* buf, int buf_size, const char* method, const char* host, const char* path, const char* content_type, const char* body_data, int body_len, const char* extra_headers) { - char* p = buf; - char* end = buf + buf_size - 1; + if (!buf || buf_size <= 0 || !method || !*method || !host || !*host || + !path || path[0] != '/' || body_len < 0 || + (body_len > 0 && !body_data) || contains_crlf(method) || + contains_crlf(host) || contains_crlf(path) || + (content_type && contains_crlf(content_type)) || + contains_header_terminator(extra_headers)) return -1; - auto append = [&](const char* s) { - while (*s && p < end) *p++ = *s++; - }; - auto append_int = [&](int n) { - char tmp[16]; int ti = 0; - if (n == 0) { if (p < end) *p++ = '0'; return; } - while (n > 0) { tmp[ti++] = '0' + (n % 10); n /= 10; } - for (int j = ti - 1; j >= 0 && p < end; j--) *p++ = tmp[j]; - }; - - // Request line - append(method); append(" "); append(path); append(" HTTP/1.1\r\n"); - - // Host - append("Host: "); append(host); append("\r\n"); - - // Content headers (for POST/PUT/PATCH) - if (body_data && body_len > 0) { + RequestWriter w = {buf, buf + buf_size, false}; + w.text(method); w.text(" "); w.text(path); w.text(" HTTP/1.1\r\n"); + w.text("Host: "); w.text(host); w.text("\r\n"); + if (body_data || body_len > 0) { if (content_type) { - append("Content-Type: "); append(content_type); append("\r\n"); + w.text("Content-Type: "); w.text(content_type); w.text("\r\n"); } - append("Content-Length: "); append_int(body_len); append("\r\n"); + w.text("Content-Length: "); w.number(body_len); w.text("\r\n"); } - - // Extra headers (caller-supplied, must include \r\n terminators) - if (extra_headers) append(extra_headers); - - append("Connection: close\r\n"); - append("\r\n"); - - int header_len = (int)(p - buf); - - // Append body - if (body_data && body_len > 0 && header_len + body_len < buf_size) { - montauk::memcpy(p, body_data, body_len); - p += body_len; + if (extra_headers) { + w.text(extra_headers); + int n = montauk::slen(extra_headers); + if (n > 0 && extra_headers[n - 1] != '\n') w.text("\r\n"); } - - return (int)(p - buf); + w.text("Connection: close\r\n\r\n"); + if (!w.overflow && body_len > 0) { + if (w.end - w.p < body_len) w.overflow = true; + else { + montauk::memcpy(w.p, body_data, body_len); + w.p += body_len; + } + } + return w.overflow ? -1 : (int)(w.p - buf); } -// ---------------------------------------------------------------------------- -// Public API -// ---------------------------------------------------------------------------- +inline bool plain_send_all(int fd, const char* data, int len, + uint64_t timeout_ms, tls::AbortCheckFn abort_check) { + int sent = 0; + uint64_t deadline = montauk::get_milliseconds() + timeout_ms; + while (sent < len) { + if (abort_check && abort_check()) return false; + int n = montauk::send(fd, data + sent, (uint32_t)(len - sent)); + if (n < 0) return false; + if (n > 0) { + sent += n; + deadline = montauk::get_milliseconds() + timeout_ms; + continue; + } + uint64_t now = montauk::get_milliseconds(); + if (now >= deadline) return false; + uint32_t signals = montauk::wait_handle( + fd, montauk::abi::IPC_SIGNAL_WRITABLE | + montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now); + if (signals == (uint32_t)-1 || + (signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED)) return false; + } + return true; +} + +inline int plain_receive(int fd, char* buf, int capacity, uint64_t timeout_ms, + tls::AbortCheckFn abort_check, Error* error) { + int total = 0; + uint64_t deadline = montauk::get_milliseconds() + timeout_ms; + while (total < capacity) { + if (abort_check && abort_check()) { + *error = Error::RECEIVE_FAILED; + return -1; + } + int n = montauk::recv(fd, buf + total, (uint32_t)(capacity - total)); + if (n > 0) { + total += n; + deadline = montauk::get_milliseconds() + timeout_ms; + continue; + } + if (n < 0) return total; + uint64_t now = montauk::get_milliseconds(); + if (now >= deadline) { + *error = Error::RECEIVE_FAILED; + return total > 0 ? total : -1; + } + uint32_t signals = montauk::wait_handle( + fd, montauk::abi::IPC_SIGNAL_READABLE | + montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now); + if (signals == (uint32_t)-1) { + *error = Error::RECEIVE_FAILED; + return total > 0 ? total : -1; + } + if ((signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED) && + !(signals & montauk::abi::IPC_SIGNAL_READABLE)) return total; + } + *error = Error::RESPONSE_TOO_LARGE; + return total; +} + +// Generic request using a caller-owned response buffer. +inline Response request_into(const char* method, const char* host, const char* path, + const char* content_type, const char* body_data, + int body_len, const tls::TrustAnchors* tas, + char* response_buffer, int response_buffer_size, + const RequestOptions& options = RequestOptions()) { + Response resp = empty_response(); + if (!response_buffer || response_buffer_size < 2 || !method || !host || !path || + body_len < 0 || (body_len > 0 && !body_data) || + (options.secure && (!tas || tas->count == 0))) { + resp.error = Error::INVALID_ARGUMENT; + return resp; + } + + uint64_t request_size64 = 160u + (uint64_t)montauk::slen(method) + + (uint64_t)montauk::slen(host) + (uint64_t)montauk::slen(path) + + (uint64_t)(content_type ? montauk::slen(content_type) : 0) + + (uint64_t)(options.extra_headers ? montauk::slen(options.extra_headers) : 0) + + (uint64_t)body_len; + if (request_size64 > 8u * 1024u * 1024u) { + resp.error = Error::REQUEST_TOO_LARGE; + return resp; + } + int request_size = (int)request_size64; + char* request_data = (char*)montauk::malloc(request_size); + if (!request_data) { + resp.error = Error::NO_MEMORY; + return resp; + } + int request_len = build_request(request_data, request_size, method, + options.host_header ? options.host_header : host, path, + content_type, body_data, body_len, + options.extra_headers); + if (request_len < 0) { + montauk::mfree(request_data); + resp.error = Error::REQUEST_TOO_LARGE; + return resp; + } + + uint32_t ip = options.resolved_ip ? options.resolved_ip : montauk::resolve(host); + if (!ip) { + montauk::mfree(request_data); + resp.error = Error::DNS_FAILED; + return resp; + } + uint16_t port = options.port ? options.port : (options.secure ? 443 : 80); + int received = -1; + Error transport_error = Error::NONE; + + if (options.secure) { + received = tls::https_fetch(host, ip, port, request_data, request_len, *tas, + response_buffer, response_buffer_size, + options.abort_check); + if (received < 0) transport_error = Error::TLS_FAILED; + else if (received >= response_buffer_size - 1) + transport_error = Error::RESPONSE_TOO_LARGE; + } else { + int fd = montauk::socket(montauk::abi::SOCK_TCP); + if (fd < 0) { + transport_error = Error::SOCKET_FAILED; + } else if (montauk::connect(fd, ip, port) < 0) { + transport_error = Error::CONNECT_FAILED; + montauk::closesocket(fd); + } else { + if (!plain_send_all(fd, request_data, request_len, options.timeout_ms, + options.abort_check)) { + transport_error = Error::SEND_FAILED; + } else { + received = plain_receive(fd, response_buffer, response_buffer_size - 1, + options.timeout_ms, options.abort_check, + &transport_error); + } + montauk::closesocket(fd); + } + } + montauk::mfree(request_data); + + if (received <= 0) { + resp.error = transport_error == Error::NONE ? Error::RECEIVE_FAILED : transport_error; + return resp; + } + response_buffer[received] = '\0'; + resp.owns_raw = false; + parse_response(response_buffer, received, &resp); + if (resp.error == Error::NONE && transport_error != Error::NONE) + resp.error = transport_error; + return resp; +} + +// Generic request with a library-owned response buffer. +inline Response request(const char* method, const char* host, const char* path, + const char* content_type, const char* body_data, int body_len, + const tls::TrustAnchors* tas, + const RequestOptions& options = RequestOptions()) { + if (options.response_buffer_size < 2) + return empty_response(Error::INVALID_ARGUMENT); + char* buffer = (char*)montauk::malloc(options.response_buffer_size); + if (!buffer) return empty_response(Error::NO_MEMORY); + Response resp = request_into(method, host, path, content_type, body_data, body_len, + tas, buffer, options.response_buffer_size, options); + if (!resp.raw) { + montauk::mfree(buffer); + } else { + resp.owns_raw = true; + } + return resp; +} -// GET request over HTTPS. Returns parsed response. Caller must free_response(). inline Response get(const char* host, const char* path, - const tls::TrustAnchors& tas, - int resp_buf_size = 32768, + const tls::TrustAnchors& tas, int response_buffer_size = 32768, const char* extra_headers = nullptr, tls::AbortCheckFn abort_check = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - char req[1024]; - int reqLen = build_request(req, sizeof(req), "GET", host, path, - nullptr, nullptr, 0, extra_headers); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) return resp; - - int n = tls::https_fetch(host, ip, 443, req, reqLen, tas, - buf, resp_buf_size - 1, abort_check); - if (n <= 0) { montauk::mfree(buf); return resp; } - buf[n] = 0; - - parse_response(buf, n, &resp); - return resp; + RequestOptions options; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + options.abort_check = abort_check; + return request("GET", host, path, nullptr, nullptr, 0, &tas, options); } -// POST request over HTTPS. Returns parsed response. Caller must free_response(). -inline Response post(const char* host, const char* path, - const char* content_type, +inline Response post(const char* host, const char* path, const char* content_type, const char* body_data, int body_len, - const tls::TrustAnchors& tas, - int resp_buf_size = 32768, + const tls::TrustAnchors& tas, int response_buffer_size = 32768, const char* extra_headers = nullptr, tls::AbortCheckFn abort_check = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - int req_size = 1024 + body_len; - char* req = (char*)montauk::malloc(req_size); - if (!req) return resp; - - int reqLen = build_request(req, req_size, "POST", host, path, - content_type, body_data, body_len, - extra_headers); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) { montauk::mfree(req); return resp; } - - int n = tls::https_fetch(host, ip, 443, req, reqLen, tas, - buf, resp_buf_size - 1, abort_check); - montauk::mfree(req); - - if (n <= 0) { montauk::mfree(buf); return resp; } - buf[n] = 0; - - parse_response(buf, n, &resp); - return resp; + RequestOptions options; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + options.abort_check = abort_check; + return request("POST", host, path, content_type, body_data, body_len, &tas, options); } -// Generic request over HTTPS (PUT, PATCH, DELETE, etc.). -inline Response request(const char* method, - const char* host, const char* path, - const char* content_type, - const char* body_data, int body_len, - const tls::TrustAnchors& tas, - int resp_buf_size = 32768, +inline Response request(const char* method, const char* host, const char* path, + const char* content_type, const char* body_data, int body_len, + const tls::TrustAnchors& tas, int response_buffer_size = 32768, const char* extra_headers = nullptr, tls::AbortCheckFn abort_check = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - int req_size = 1024 + (body_len > 0 ? body_len : 0); - char* req = (char*)montauk::malloc(req_size); - if (!req) return resp; - - int reqLen = build_request(req, req_size, method, host, path, - content_type, body_data, body_len, - extra_headers); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) { montauk::mfree(req); return resp; } - - int n = tls::https_fetch(host, ip, 443, req, reqLen, tas, - buf, resp_buf_size - 1, abort_check); - montauk::mfree(req); - - if (n <= 0) { montauk::mfree(buf); return resp; } - buf[n] = 0; - - parse_response(buf, n, &resp); - return resp; + RequestOptions options; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + options.abort_check = abort_check; + return request(method, host, path, content_type, body_data, body_len, &tas, options); } -// Plain HTTP (no TLS) GET over port 80. inline Response get_plain(const char* host, const char* path, - int resp_buf_size = 32768, + int response_buffer_size = 32768, const char* extra_headers = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - char req[1024]; - int reqLen = build_request(req, sizeof(req), "GET", host, path, - nullptr, nullptr, 0, extra_headers); - - int sock = montauk::socket(montauk::abi::SOCK_TCP); - if (sock < 0) return resp; - if (montauk::connect(sock, ip, 80) < 0) { montauk::closesocket(sock); return resp; } - - montauk::send(sock, req, reqLen); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) { montauk::closesocket(sock); return resp; } - - int total = 0; - while (total < resp_buf_size - 1) { - int n = montauk::recv(sock, buf + total, resp_buf_size - 1 - total); - if (n <= 0) break; - total += n; - } - montauk::closesocket(sock); - - if (total <= 0) { montauk::mfree(buf); return resp; } - buf[total] = 0; - - parse_response(buf, total, &resp); - return resp; + RequestOptions options; + options.secure = false; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + return request("GET", host, path, nullptr, nullptr, 0, nullptr, options); } } // namespace http diff --git a/programs/include/print/print.hpp b/programs/include/print/print.hpp index 37dc439..49f5817 100644 --- a/programs/include/print/print.hpp +++ b/programs/include/print/print.hpp @@ -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; diff --git a/programs/include/tls/tls.hpp b/programs/include/tls/tls.hpp index 9555a49..64fff35 100644 --- a/programs/include/tls/tls.hpp +++ b/programs/include/tls/tls.hpp @@ -23,6 +23,7 @@ struct TrustAnchors { }; TrustAnchors load_trust_anchors(); +void free_trust_anchors(TrustAnchors* tas); void get_bearssl_time(uint32_t* days, uint32_t* seconds); int tls_send_all(int fd, const unsigned char* data, size_t len); int tls_recv_some(int fd, unsigned char* buf, size_t maxlen); diff --git a/programs/lib/tls/libtls.a b/programs/lib/tls/libtls.a index 00aef42ccce1ba054936f0c2ae747e0e8029de04..3143169c76bb9af35eaec5a309a71f0498f5a490 100644 GIT binary patch literal 113598 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 literal 112738 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 diff --git a/programs/lib/tls/obj/tls.o b/programs/lib/tls/obj/tls.o index 1b4ca61af885eaf18b3b7a1f1ab5480969d071af..d023faec4dd897abe009c1291226f0f87fc4fa7c 100644 GIT binary patch literal 113152 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 aDV?-@`?L5BHv15~rrhTxKPKb&`u_tRtRg%B literal 112344 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

Gd(EA2C6#9LnIg2+u+Wg`D+1unU} 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 * 2; if (nc < a->len + len) nc = a->len + len + 4096; unsigned char* nb = (unsigned char*)malloc(nc); - if (!nb) return; + if (!nb) { a->failed = true; return; } if (a->data) { memcpy(nb, a->data, a->len); free(a->data); } a->data = nb; a->cap = nc; } @@ -42,7 +42,7 @@ void dn_append(void* ctx, const void* buf, size_t len) { size_t nc = a->cap * 2; if (nc < a->len + len) nc = a->len + len + 256; unsigned char* nb = (unsigned char*)malloc(nc); - if (!nb) return; + if (!nb) { a->failed = true; return; } if (a->data) { memcpy(nb, a->data, a->len); free(a->data); } a->data = nb; a->cap = nc; } @@ -50,53 +50,73 @@ void dn_append(void* ctx, const void* buf, size_t len) { a->len += len; } -void ta_add(tls::TrustAnchors* tas, const br_x509_trust_anchor* ta) { +bool ta_add(tls::TrustAnchors* tas, const br_x509_trust_anchor* ta) { if (tas->count >= tas->capacity) { size_t nc = tas->capacity == 0 ? 64 : tas->capacity * 2; br_x509_trust_anchor* na = (br_x509_trust_anchor*)malloc(nc * sizeof(*na)); - if (!na) return; + if (!na) return false; if (tas->anchors) { memcpy(na, tas->anchors, tas->count * sizeof(*na)); free(tas->anchors); } tas->anchors = na; tas->capacity = nc; } tas->anchors[tas->count++] = *ta; + return true; } bool process_cert_der(tls::TrustAnchors* tas, const unsigned char* der, size_t der_len) { - static br_x509_decoder_context dc; // ~2KB+, keep off stack - DnAccum dn = {nullptr, 0, 0}; - br_x509_decoder_init(&dc, dn_append, &dn); - br_x509_decoder_push(&dc, der, der_len); - br_x509_pkey* pk = br_x509_decoder_get_pkey(&dc); - if (!pk) { if (dn.data) free(dn.data); return false; } + br_x509_decoder_context* dc = + (br_x509_decoder_context*)malloc(sizeof(br_x509_decoder_context)); + if (!dc) return false; + DnAccum dn = {nullptr, 0, 0, false}; + br_x509_decoder_init(dc, dn_append, &dn); + br_x509_decoder_push(dc, der, der_len); + br_x509_pkey* pk = br_x509_decoder_get_pkey(dc); + if (!pk || dn.failed) { free(dc); if (dn.data) free(dn.data); return false; } br_x509_trust_anchor ta; memset(&ta, 0, sizeof(ta)); ta.dn.data = dn.data; ta.dn.len = dn.len; ta.flags = 0; - if (br_x509_decoder_isCA(&dc)) ta.flags |= BR_X509_TA_CA; + if (br_x509_decoder_isCA(dc)) ta.flags |= BR_X509_TA_CA; switch (pk->key_type) { case BR_KEYTYPE_RSA: ta.pkey.key_type = BR_KEYTYPE_RSA; ta.pkey.key.rsa.nlen = pk->key.rsa.nlen; ta.pkey.key.rsa.n = (unsigned char*)malloc(pk->key.rsa.nlen); - if (ta.pkey.key.rsa.n) memcpy(ta.pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen); + if (!ta.pkey.key.rsa.n) { free(dc); free(dn.data); return false; } + memcpy(ta.pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen); ta.pkey.key.rsa.elen = pk->key.rsa.elen; ta.pkey.key.rsa.e = (unsigned char*)malloc(pk->key.rsa.elen); - if (ta.pkey.key.rsa.e) memcpy(ta.pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen); + if (!ta.pkey.key.rsa.e) { + free(ta.pkey.key.rsa.n); + free(dc); + free(dn.data); + return false; + } + memcpy(ta.pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen); break; case BR_KEYTYPE_EC: ta.pkey.key_type = BR_KEYTYPE_EC; ta.pkey.key.ec.curve = pk->key.ec.curve; ta.pkey.key.ec.qlen = pk->key.ec.qlen; ta.pkey.key.ec.q = (unsigned char*)malloc(pk->key.ec.qlen); - if (ta.pkey.key.ec.q) memcpy(ta.pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen); + if (!ta.pkey.key.ec.q) { free(dc); free(dn.data); return false; } + memcpy(ta.pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen); break; default: + free(dc); if (dn.data) free(dn.data); return false; } - ta_add(tas, &ta); - return true; + free(dc); + if (ta_add(tas, &ta)) return true; + free(ta.dn.data); + if (ta.pkey.key_type == BR_KEYTYPE_RSA) { + free(ta.pkey.key.rsa.n); + free(ta.pkey.key.rsa.e); + } else { + free(ta.pkey.key.ec.q); + } + return false; } } // anonymous namespace @@ -116,36 +136,65 @@ TrustAnchors load_trust_anchors() { unsigned char* pem = (unsigned char*)malloc(fsize + 1); if (!pem) { montauk::close(fh); return tas; } - montauk::read(fh, pem, 0, fsize); + uint64_t readOffset = 0; + while (readOffset < fsize) { + int n = montauk::read(fh, pem + readOffset, readOffset, fsize - readOffset); + if (n <= 0) break; + readOffset += (uint64_t)n; + } montauk::close(fh); - pem[fsize] = 0; + if (readOffset != fsize) { free(pem); return tas; } + pem[readOffset] = 0; - static br_pem_decoder_context pc; // keep off stack - br_pem_decoder_init(&pc); - DerAccum der = {nullptr, 0, 0}; + br_pem_decoder_context* pc = + (br_pem_decoder_context*)malloc(sizeof(br_pem_decoder_context)); + if (!pc) { free(pem); return tas; } + br_pem_decoder_init(pc); + DerAccum der = {nullptr, 0, 0, false}; bool inCert = false; size_t offset = 0; while (offset < fsize) { - size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset); + size_t pushed = br_pem_decoder_push(pc, pem + offset, fsize - offset); offset += pushed; - int ev = br_pem_decoder_event(&pc); + int ev = br_pem_decoder_event(pc); if (ev == BR_PEM_BEGIN_OBJ) { - inCert = (strcmp(br_pem_decoder_name(&pc), "CERTIFICATE") == 0); - br_pem_decoder_setdest(&pc, inCert ? der_append : nullptr, inCert ? &der : nullptr); - if (inCert) der.len = 0; + inCert = (strcmp(br_pem_decoder_name(pc), "CERTIFICATE") == 0); + br_pem_decoder_setdest(pc, inCert ? der_append : nullptr, inCert ? &der : nullptr); + if (inCert) { der.len = 0; der.failed = false; } } else if (ev == BR_PEM_END_OBJ) { if (inCert && der.len > 0) process_cert_der(&tas, der.data, der.len); inCert = false; } else if (ev == BR_PEM_ERROR) { break; } + if (der.failed) break; + if (pushed == 0 && ev == 0) break; } if (der.data) free(der.data); + free(pc); free(pem); return tas; } +void free_trust_anchors(TrustAnchors* tas) { + if (!tas) return; + for (size_t i = 0; i < tas->count; ++i) { + br_x509_trust_anchor& ta = tas->anchors[i]; + free(ta.dn.data); + if (ta.pkey.key_type == BR_KEYTYPE_RSA) { + free(ta.pkey.key.rsa.n); + free(ta.pkey.key.rsa.e); + } else if (ta.pkey.key_type == BR_KEYTYPE_EC) { + free(ta.pkey.key.ec.q); + } + } + free(tas->anchors); + tas->anchors = nullptr; + tas->count = 0; + tas->capacity = 0; +} + void get_bearssl_time(uint32_t* days, uint32_t* seconds) { montauk::abi::DateTime dt; montauk::gettime(&dt); @@ -167,7 +216,15 @@ int tls_send_all(int fd, const unsigned char* data, size_t len) { int r = montauk::send(fd, data + sent, (uint32_t)(len - sent)); if (r > 0) { sent += r; deadline = montauk::get_milliseconds() + 15000; } else if (r < 0) return -1; - else { if (montauk::get_milliseconds() >= deadline) return -1; montauk::sleep_ms(1); } + else { + uint64_t now = montauk::get_milliseconds(); + if (now >= deadline) return -1; + uint32_t signals = montauk::wait_handle( + fd, montauk::abi::IPC_SIGNAL_WRITABLE | + montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now); + if (signals == (uint32_t)-1 || + (signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED)) return -1; + } } return (int)sent; } @@ -178,8 +235,14 @@ int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) { int r = montauk::recv(fd, buf, (uint32_t)maxlen); if (r > 0) return r; if (r < 0) return -1; - if (montauk::get_milliseconds() >= deadline) return -1; - montauk::sleep_ms(1); + uint64_t now = montauk::get_milliseconds(); + if (now >= deadline) return -1; + uint32_t signals = montauk::wait_handle( + fd, montauk::abi::IPC_SIGNAL_READABLE | + montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now); + if (signals == (uint32_t)-1) return -1; + if ((signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED) && + !(signals & montauk::abi::IPC_SIGNAL_READABLE)) return -1; } } @@ -187,7 +250,7 @@ int tls_exchange(int fd, br_ssl_engine_context* eng, const char* request, int reqLen, char* respBuf, int respMax, AbortCheckFn abort_check) { - bool requestSent = false; + int requestOffset = 0; int respLen = 0; uint64_t deadline = montauk::get_milliseconds() + 30000; @@ -217,14 +280,14 @@ int tls_exchange(int fd, br_ssl_engine_context* eng, br_ssl_engine_recvapp_ack(eng, len); deadline = montauk::get_milliseconds() + 30000; continue; } - if ((state & BR_SSL_SENDAPP) && !requestSent) { + if ((state & BR_SSL_SENDAPP) && requestOffset < reqLen) { size_t len; unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len); - size_t toWrite = (size_t)reqLen; + size_t toWrite = (size_t)(reqLen - requestOffset); if (toWrite > len) toWrite = len; - memcpy(buf, request, toWrite); + memcpy(buf, request + requestOffset, toWrite); br_ssl_engine_sendapp_ack(eng, toWrite); - br_ssl_engine_flush(eng, 0); - requestSent = true; + requestOffset += (int)toWrite; + if (requestOffset == reqLen) br_ssl_engine_flush(eng, 0); deadline = montauk::get_milliseconds() + 30000; continue; } if (state & BR_SSL_RECVREC) { @@ -244,6 +307,8 @@ int https_fetch(const char* host, uint32_t ip, uint16_t port, const TrustAnchors& tas, char* respBuf, int respMax, AbortCheckFn abort_check) { + if (!host || !*host || ip == 0 || port == 0 || !request || reqLen <= 0 || + !respBuf || respMax < 2 || !tas.anchors || tas.count == 0) return -1; int fd = montauk::socket(montauk::abi::SOCK_TCP); if (fd < 0) return -1; if (montauk::connect(fd, ip, port) < 0) { montauk::closesocket(fd); return -1; } @@ -262,7 +327,9 @@ int https_fetch(const char* host, uint32_t ip, uint16_t port, br_x509_minimal_set_time(xc, days, secs); unsigned char seed[32]; - montauk::getrandom(seed, sizeof(seed)); + if (montauk::getrandom(seed, sizeof(seed)) != (int64_t)sizeof(seed)) { + montauk::closesocket(fd); free(cc); free(xc); free(iobuf); return -1; + } br_ssl_engine_set_buffer(&cc->eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1); br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed)); diff --git a/programs/src/fetch/main.cpp b/programs/src/fetch/main.cpp index 072e3b7..1bdace6 100644 --- a/programs/src/fetch/main.cpp +++ b/programs/src/fetch/main.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include extern "C" { #include @@ -56,11 +56,6 @@ static bool parse_uint16(const char* s, uint16_t* out) { return true; } -static void format_ip(char* buf, uint32_t ip) { - snprintf(buf, 32, "%u.%u.%u.%u", - ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); -} - // ---- URL parser ---- struct ParsedUrl { @@ -124,38 +119,6 @@ static ParsedUrl parse_url(const char* url) { return u; } -// ---- HTTP response parser ---- - -static int find_header_end(const char* buf, int len) { - for (int i = 0; i + 3 < len; i++) { - if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n') - return i + 4; - } - return -1; -} - -static int parse_status_code(const char* buf, int len) { - int i = 0; - while (i < len && buf[i] != ' ') i++; - if (i >= len) return -1; - i++; - if (i + 2 >= len) return -1; - if (buf[i] < '0' || buf[i] > '9') return -1; - return (buf[i] - '0') * 100 + (buf[i+1] - '0') * 10 + (buf[i+2] - '0'); -} - -static void parse_status_text(const char* buf, int len, char* out, int outMax) { - int i = 0; - while (i < len && buf[i] != ' ') i++; - i++; - while (i < len && buf[i] != ' ') i++; - i++; - int j = 0; - while (i < len && buf[i] != '\r' && buf[i] != '\n' && j < outMax - 1) - out[j++] = buf[i++]; - out[j] = '\0'; -} - // ---- Keyboard abort check for TLS ---- static bool check_keyboard_abort() { @@ -167,95 +130,23 @@ static bool check_keyboard_abort() { return false; } -// ---- Plain HTTP exchange (no TLS) ---- - -static int plain_http_exchange(int fd, const char* request, int reqLen, - char* respBuf, int respMax) { - // Send request - int sent = 0; - uint64_t deadline = montauk::get_milliseconds() + 15000; - while (sent < reqLen) { - int r = montauk::send(fd, request + sent, reqLen - sent); - if (r > 0) { sent += r; deadline = montauk::get_milliseconds() + 15000; } - else if (r < 0) return -1; - else { - if (montauk::get_milliseconds() >= deadline) return -1; - montauk::sleep_ms(1); - } - } - - // Receive response - int respLen = 0; - deadline = montauk::get_milliseconds() + 15000; - while (respLen < respMax - 1) { - if (montauk::is_key_available()) { - montauk::abi::KeyEvent ev; - montauk::getkey(&ev); - if (ev.pressed && ev.ctrl && ev.ascii == 'q') return -2; // aborted - } - - int r = montauk::recv(fd, respBuf + respLen, respMax - 1 - respLen); - if (r > 0) { respLen += r; deadline = montauk::get_milliseconds() + 15000; } - else if (r < 0) break; - else { - uint64_t now = montauk::get_milliseconds(); - if (now >= deadline) break; - uint32_t signals = montauk::wait_handle( - fd, - montauk::abi::IPC_SIGNAL_READABLE | montauk::abi::IPC_SIGNAL_PEER_CLOSED, - deadline - now - ); - if (signals == 0 || signals == (uint32_t)-1) break; - } - } - return respLen; -} - // ---- Print response body ---- -static void print_response(const char* respBuf, int respLen, bool verbose) { - if (respLen <= 0) { - montauk::print("Error: empty response\n"); - return; - } - - int headerEnd = find_header_end(respBuf, respLen); - if (headerEnd < 0) { - montauk::print("Warning: malformed response (no header boundary)\n\n"); - // Print raw - char chunk[512]; - int printed = 0; - while (printed < respLen) { - int n = respLen - printed; - if (n > 511) n = 511; - memcpy(chunk, respBuf + printed, n); - chunk[n] = '\0'; - montauk::print(chunk); - printed += n; - } - montauk::putchar('\n'); - return; - } - - int statusCode = parse_status_code(respBuf, headerEnd); - char statusText[64]; - parse_status_text(respBuf, headerEnd, statusText, sizeof(statusText)); - int bodyLen = respLen - headerEnd; - +static void print_response(const http::Response& response, bool verbose) { if (verbose) { - char msg[256]; - snprintf(msg, sizeof(msg), "HTTP %d %s (%d bytes)\n\n", statusCode, statusText, bodyLen); + char msg[128]; + snprintf(msg, sizeof(msg), "HTTP %d (%d bytes)\n\n", + response.status, response.body_len); montauk::print(msg); } - if (bodyLen > 0) { - const char* body = respBuf + headerEnd; + if (response.body_len > 0) { char chunk[512]; int printed = 0; - while (printed < bodyLen) { - int n = bodyLen - printed; + while (printed < response.body_len) { + int n = response.body_len - printed; if (n > 511) n = 511; - memcpy(chunk, body + printed, n); + memcpy(chunk, response.body + printed, n); chunk[n] = '\0'; montauk::print(chunk); printed += n; @@ -339,20 +230,8 @@ extern "C" void _start() { } } - // Resolve host to IP - uint32_t serverIp; - if (!parse_ip(hostStr, &serverIp)) { - serverIp = montauk::resolve(hostStr); - if (serverIp == 0) { - montauk::print("Error: could not resolve "); - montauk::print(hostStr); - montauk::putchar('\n'); - montauk::exit(1); - } - } - - char ipStr[32]; - format_ip(ipStr, serverIp); + uint32_t numericIp = 0; + parse_ip(hostStr, &numericIp); if (verbose) { char msg[256]; @@ -361,16 +240,6 @@ extern "C" void _start() { montauk::print(msg); } - // Build HTTP request - char request[1024]; - int reqLen = snprintf(request, sizeof(request), - "GET %s HTTP/1.0\r\n" - "Host: %s\r\n" - "User-Agent: MontaukOS/1.0\r\n" - "Connection: close\r\n" - "\r\n", - path, hostStr); - if (verbose) { char msg[128]; snprintf(msg, sizeof(msg), "GET %s\n", path); @@ -385,11 +254,9 @@ extern "C" void _start() { montauk::exit(1); } - int respLen; - + tls::TrustAnchors tas = {}; if (useHttps) { - // ---- TLS handshake and exchange ---- - tls::TrustAnchors tas = tls::load_trust_anchors(); + tas = tls::load_trust_anchors(); if (verbose) { char msg[64]; snprintf(msg, sizeof(msg), "Loaded %u trust anchors\n", (unsigned)tas.count); @@ -415,43 +282,26 @@ extern "C" void _start() { montauk::print("TLS handshake...\n"); } - respLen = tls::https_fetch(hostStr, serverIp, port, - request, reqLen, tas, - respBuf, RESP_MAX, check_keyboard_abort); - - if (verbose && respLen > 0) { - montauk::print("TLS connection established\n"); - } - } else { - // ---- Plain HTTP ---- - int fd = montauk::socket(montauk::abi::SOCK_TCP); - if (fd < 0) { - montauk::print("Error: failed to create socket\n"); - montauk::exit(1); - } - - if (montauk::connect(fd, serverIp, port) < 0) { - montauk::print("Error: connection failed\n"); - montauk::closesocket(fd); - montauk::exit(1); - } - - respLen = plain_http_exchange(fd, request, reqLen, respBuf, RESP_MAX); - montauk::closesocket(fd); - - if (respLen == -2) { - montauk::print("\nAborted.\n"); - montauk::exit(0); - } } - if (respLen <= 0) { - montauk::print("Error: no response received\n"); + http::RequestOptions options; + options.secure = useHttps; + options.port = port; + options.resolved_ip = numericIp; + options.extra_headers = "User-Agent: MontaukOS/1.0\r\n"; + options.abort_check = check_keyboard_abort; + options.timeout_ms = 15000; + http::Response response = http::request_into( + "GET", hostStr, path, nullptr, nullptr, 0, useHttps ? &tas : nullptr, + respBuf, RESP_MAX, options); + if (response.error != http::Error::NONE) { + montauk::print("Error: "); + montauk::print(http::error_string(response.error)); + montauk::putchar('\n'); montauk::exit(1); } - - respBuf[respLen] = '\0'; - print_response(respBuf, respLen, verbose); + if (verbose && useHttps) montauk::print("TLS connection established\n"); + print_response(response, verbose); montauk::exit(0); } diff --git a/programs/src/weather/main.cpp b/programs/src/weather/main.cpp index a89a1c8..7f71fe9 100644 --- a/programs/src/weather/main.cpp +++ b/programs/src/weather/main.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include extern "C" { #include @@ -77,26 +77,6 @@ static bool g_tls_ready = false; static uint32_t g_server_ip = 0; static tls::TrustAnchors g_tas = {nullptr, 0, 0}; -// ============================================================================ -// HTTP parsing -// ============================================================================ - -static int find_header_end(const char* buf, int len) { - for (int i = 0; i + 3 < len; i++) - if (buf[i]=='\r' && buf[i+1]=='\n' && buf[i+2]=='\r' && buf[i+3]=='\n') - return i + 4; - return -1; -} - -static int parse_status_code(const char* buf, int len) { - int i = 0; - while (i < len && buf[i] != ' ') i++; - if (i >= len || i + 3 >= len) return -1; - i++; - if (buf[i] < '0' || buf[i] > '9') return -1; - return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0'); -} - // ============================================================================ // JSON parsing // ============================================================================ @@ -237,39 +217,27 @@ static void do_fetch() { g_tls_ready = true; } - static char request[512]; - int reqLen = snprintf(request, sizeof(request), - "GET /?format=j1 HTTP/1.0\r\n" - "Host: %s\r\n" + http::RequestOptions options; + options.resolved_ip = g_server_ip; + options.extra_headers = "User-Agent: MontaukOS/1.0 weather\r\n" - "Accept: application/json\r\n" - "Connection: close\r\n" - "\r\n", - WTTR_HOST); - - int respLen = tls::https_fetch(WTTR_HOST, g_server_ip, 443, - request, reqLen, g_tas, - g_resp_buf, RESP_MAX); - if (respLen <= 0) { - snprintf(g_status, sizeof(g_status), "Error: no response from server"); + "Accept: application/json\r\n"; + http::Response response = http::request_into( + "GET", WTTR_HOST, "/?format=j1", nullptr, nullptr, 0, &g_tas, + g_resp_buf, RESP_MAX, options); + if (response.error != http::Error::NONE) { + snprintf(g_status, sizeof(g_status), "Error: %s", + http::error_string(response.error)); g_phase = AppPhase::ERR; return; } - g_resp_buf[respLen] = '\0'; - - int headerEnd = find_header_end(g_resp_buf, respLen); - if (headerEnd < 0) { - snprintf(g_status, sizeof(g_status), "Error: malformed HTTP response"); + if (response.status != 200) { + snprintf(g_status, sizeof(g_status), "Error: HTTP %d from server", + response.status); g_phase = AppPhase::ERR; return; } - int status = parse_status_code(g_resp_buf, headerEnd); - if (status != 200) { - snprintf(g_status, sizeof(g_status), "Error: HTTP %d from server", status); - g_phase = AppPhase::ERR; return; - } - - const char* body = g_resp_buf + headerEnd; - int bodyLen = respLen - headerEnd; + const char* body = response.body; + int bodyLen = response.body_len; // Extract core weather fields static char temp_raw[16], feels_raw[16], code_raw[8]; diff --git a/programs/src/wiki/main.cpp b/programs/src/wiki/main.cpp index f379448..13022ea 100644 --- a/programs/src/wiki/main.cpp +++ b/programs/src/wiki/main.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include extern "C" { #include @@ -82,39 +82,15 @@ static bool check_keyboard_abort() { // ---- HTTPS fetch wrapper ---- -static int wiki_fetch(const char* path, char* respBuf, int respMax) { - static char request[2560]; // keep off stack - int reqLen = snprintf(request, sizeof(request), - "GET %s HTTP/1.0\r\n" - "Host: %s\r\n" +static http::Response wiki_fetch(const char* path, char* respBuf, int respMax) { + http::RequestOptions options; + options.resolved_ip = g_serverIp; + options.extra_headers = "User-Agent: MontaukOS/1.0 wiki\r\n" - "Accept: application/json\r\n" - "Connection: close\r\n" - "\r\n", - path, WIKI_HOST); - return tls::https_fetch(WIKI_HOST, g_serverIp, 443, - request, reqLen, g_tas, - respBuf, respMax, check_keyboard_abort); -} - -// ---- HTTP response parsing ---- - -static int find_header_end(const char* buf, int len) { - for (int i = 0; i + 3 < len; i++) { - if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n') - return i + 4; - } - return -1; -} - -static int parse_status_code(const char* buf, int len) { - int i = 0; - while (i < len && buf[i] != ' ') i++; - if (i >= len) return -1; - i++; - if (i + 2 >= len) return -1; - if (buf[i] < '0' || buf[i] > '9') return -1; - return (buf[i] - '0') * 100 + (buf[i+1] - '0') * 10 + (buf[i+2] - '0'); + "Accept: application/json\r\n"; + options.abort_check = check_keyboard_abort; + return http::request_into("GET", WIKI_HOST, path, nullptr, nullptr, 0, + &g_tas, respBuf, respMax, options); } // ---- URL encoding ---- @@ -725,23 +701,13 @@ extern "C" void _start() { "/w/api.php?action=query&format=json&formatversion=2" "&prop=extracts&explaintext=1&titles=%s", encoded); - int respLen = wiki_fetch(path, respBuf, RESP_MAX); - if (respLen <= 0) { + http::Response response = wiki_fetch(path, respBuf, RESP_MAX); + if (response.error != http::Error::NONE) { montauk::print("\x01"); // error sentinel montauk::sleep_ms(100); montauk::exit(1); } - respBuf[respLen] = '\0'; - - int headerEnd = find_header_end(respBuf, respLen); - if (headerEnd < 0) { - montauk::print("\x01"); - montauk::sleep_ms(100); - montauk::exit(1); - } - - int statusCode = parse_status_code(respBuf, headerEnd); - if (statusCode == 404) { + if (response.status == 404) { montauk::print("\x01"); montauk::sleep_ms(100); montauk::exit(1); @@ -749,8 +715,8 @@ extern "C" void _start() { // Output raw JSON body in chunks to avoid overflowing // the 4KB kernel ring buffer (parent polls at ~60fps) - const char* body = respBuf + headerEnd; - int bodyLen = respLen - headerEnd; + const char* body = response.body; + int bodyLen = response.body_len; static char chunk[2049]; int sent = 0; while (sent < bodyLen) { @@ -774,21 +740,13 @@ extern "C" void _start() { "/w/api.php?action=opensearch&search=%s&limit=10&format=json", encoded); - int respLen = wiki_fetch(path, respBuf, RESP_MAX); - if (respLen <= 0) { + http::Response response = wiki_fetch(path, respBuf, RESP_MAX); + if (response.error != http::Error::NONE) { montauk::print("\033[1;31mError:\033[0m no response from Wikipedia\n"); montauk::exit(1); } - respBuf[respLen] = '\0'; - - int headerEnd = find_header_end(respBuf, respLen); - if (headerEnd < 0) { - montauk::print("\033[1;31mError:\033[0m malformed response\n"); - montauk::exit(1); - } - - const char* body = respBuf + headerEnd; - int bodyLen = respLen - headerEnd; + const char* body = response.body; + int bodyLen = response.body_len; static char titles[MAX_SEARCH_RESULTS][256]; int titleCount = parse_search_titles(body, bodyLen, titles, MAX_SEARCH_RESULTS); @@ -831,8 +789,8 @@ extern "C" void _start() { snprintf(articlePath, sizeof(articlePath), "/api/rest_v1/page/summary/%s", articleEncoded); - respLen = wiki_fetch(articlePath, respBuf, RESP_MAX); - if (respLen <= 0) { + response = wiki_fetch(articlePath, respBuf, RESP_MAX); + if (response.error != http::Error::NONE) { sb_reset(); sb_cursor_to(infoRow, 3); sb_puts("\033[2K\033[1;31mFetch failed. Press any key.\033[0m"); @@ -841,16 +799,10 @@ extern "C" void _start() { montauk::abi::KeyEvent ev; montauk::getkey(&ev); continue; } - respBuf[respLen] = '\0'; + body = response.body; + bodyLen = response.body_len; - headerEnd = find_header_end(respBuf, respLen); - if (headerEnd < 0) continue; - - int statusCode = parse_status_code(respBuf, headerEnd); - body = respBuf + headerEnd; - bodyLen = respLen - headerEnd; - - if (statusCode == 404) { + if (response.status == 404) { sb_reset(); sb_cursor_to(infoRow, 3); sb_puts("\033[2K\033[1;31mArticle not found. Press any key.\033[0m"); @@ -893,24 +845,15 @@ extern "C" void _start() { "&prop=extracts&explaintext=1&titles=%s", encoded); } - int respLen = wiki_fetch(path, respBuf, RESP_MAX); - if (respLen <= 0) { + http::Response response = wiki_fetch(path, respBuf, RESP_MAX); + if (response.error != http::Error::NONE) { montauk::print("\033[1;31mError:\033[0m no response from Wikipedia\n"); montauk::exit(1); } - respBuf[respLen] = '\0'; + const char* body = response.body; + int bodyLen = response.body_len; - int headerEnd = find_header_end(respBuf, respLen); - if (headerEnd < 0) { - montauk::print("\033[1;31mError:\033[0m malformed response\n"); - montauk::exit(1); - } - - int statusCode = parse_status_code(respBuf, headerEnd); - const char* body = respBuf + headerEnd; - int bodyLen = respLen - headerEnd; - - if (statusCode == 404) { + if (response.status == 404) { montauk::print("\033[1;31mArticle not found:\033[0m "); montauk::print(query); montauk::putchar('\n'); diff --git a/programs/src/wikipedia/network.cpp b/programs/src/wikipedia/network.cpp index 71d814b..58dc73c 100644 --- a/programs/src/wikipedia/network.cpp +++ b/programs/src/wikipedia/network.cpp @@ -6,19 +6,14 @@ #include "wikipedia.h" -int wiki_fetch(const char* path, char* respBuf, int respMax) { - static char request[2560]; - int reqLen = snprintf(request, sizeof(request), - "GET %s HTTP/1.0\r\n" - "Host: %s\r\n" +http::Response wiki_fetch(const char* path, char* respBuf, int respMax) { + http::RequestOptions options; + options.resolved_ip = g_server_ip; + options.extra_headers = "User-Agent: MontaukOS/1.0 wikipedia\r\n" - "Accept: application/json\r\n" - "Connection: close\r\n" - "\r\n", - path, WIKI_HOST); - return tls::https_fetch(WIKI_HOST, g_server_ip, 443, - request, reqLen, g_tas, - respBuf, respMax); + "Accept: application/json\r\n"; + return http::request_into("GET", WIKI_HOST, path, nullptr, nullptr, 0, + &g_tas, respBuf, respMax, options); } bool ensure_wiki_tls_ready(char* err, int err_len) { @@ -74,27 +69,16 @@ void do_welcome_fetch() { return; } - int respLen = wiki_fetch(path, g_resp_buf, RESP_MAX); - if (respLen <= 0) { + http::Response response = wiki_fetch(path, g_resp_buf, RESP_MAX); + if (response.error != http::Error::NONE) { snprintf(g_welcome_status, sizeof(g_welcome_status), "Daily featured article unavailable; search is ready."); build_welcome_lines(true); return; } - g_resp_buf[respLen] = '\0'; - - int headerEnd = find_header_end(g_resp_buf, respLen); - if (headerEnd < 0) { - snprintf(g_welcome_status, sizeof(g_welcome_status), - "Daily featured article unavailable; search is ready."); - build_welcome_lines(true); - return; - } - - int status = parse_status_code(g_resp_buf, headerEnd); - const char* body = g_resp_buf + headerEnd; - int bodyLen = respLen - headerEnd; - if (status < 200 || status >= 300) { + const char* body = response.body; + int bodyLen = response.body_len; + if (response.status < 200 || response.status >= 300) { snprintf(g_welcome_status, sizeof(g_welcome_status), "Daily featured article unavailable; search is ready."); build_welcome_lines(true); @@ -148,24 +132,16 @@ void do_search(const char* query) { "/w/api.php?action=query&format=json&formatversion=2" "&prop=extracts&explaintext=1&titles=%s", encoded); - int respLen = wiki_fetch(path, g_resp_buf, RESP_MAX); - if (respLen <= 0) { - snprintf(g_status, sizeof(g_status), "Error: no response from Wikipedia"); + http::Response response = wiki_fetch(path, g_resp_buf, RESP_MAX); + if (response.error != http::Error::NONE) { + snprintf(g_status, sizeof(g_status), "Error: %s", + http::error_string(response.error)); g_phase = AppPhase::ERR; return; } - g_resp_buf[respLen] = '\0'; + const char* body = response.body; + int bodyLen = response.body_len; - int headerEnd = find_header_end(g_resp_buf, respLen); - if (headerEnd < 0) { - snprintf(g_status, sizeof(g_status), "Error: malformed HTTP response"); - g_phase = AppPhase::ERR; return; - } - - int status = parse_status_code(g_resp_buf, headerEnd); - const char* body = g_resp_buf + headerEnd; - int bodyLen = respLen - headerEnd; - - if (status == 404) { + if (response.status == 404) { snprintf(g_status, sizeof(g_status), "Article not found: %s", query); g_phase = AppPhase::ERR; return; } diff --git a/programs/src/wikipedia/text.cpp b/programs/src/wikipedia/text.cpp index e2a0209..70095bb 100644 --- a/programs/src/wikipedia/text.cpp +++ b/programs/src/wikipedia/text.cpp @@ -140,26 +140,6 @@ unsigned decode_utf8_codepoint(const char* buf, int len, int* consumed) { return '?'; } -// ============================================================================ -// HTTP parsing -// ============================================================================ - -int find_header_end(const char* buf, int len) { - for (int i = 0; i + 3 < len; i++) - if (buf[i]=='\r' && buf[i+1]=='\n' && buf[i+2]=='\r' && buf[i+3]=='\n') - return i + 4; - return -1; -} - -int parse_status_code(const char* buf, int len) { - int i = 0; - while (i < len && buf[i] != ' ') i++; - if (i >= len || i + 3 >= len) return -1; - i++; - if (buf[i] < '0' || buf[i] > '9') return -1; - return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0'); -} - int find_substr(const char* buf, int len, const char* needle) { int nlen = (int)strlen(needle); if (!buf || !needle || nlen <= 0 || nlen > len) return -1; diff --git a/programs/src/wikipedia/wikipedia.h b/programs/src/wikipedia/wikipedia.h index 19fc38b..8ffb7cf 100644 --- a/programs/src/wikipedia/wikipedia.h +++ b/programs/src/wikipedia/wikipedia.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include extern "C" { #include @@ -190,8 +190,6 @@ void underscores_to_spaces(char* text); bool is_main_page_query(const char* query); unsigned decode_utf8_codepoint(const char* buf, int len, int* consumed); -int find_header_end(const char* buf, int len); -int parse_status_code(const char* buf, int len); int find_substr(const char* buf, int len, const char* needle); int url_encode_title(const char* in, char* out, int maxLen); @@ -219,7 +217,7 @@ bool handle_reader_option_click(int mx, int my); // network.cpp -- TLS fetch, welcome fetch, search // ============================================================================ -int wiki_fetch(const char* path, char* respBuf, int respMax); +http::Response wiki_fetch(const char* path, char* respBuf, int respMax); bool ensure_wiki_tls_ready(char* err, int err_len); bool welcome_feed_path(char* path, int path_len); void do_welcome_fetch(); diff --git a/template/docs/gui-apps.md b/template/docs/gui-apps.md index e388366..292462c 100644 --- a/template/docs/gui-apps.md +++ b/template/docs/gui-apps.md @@ -643,11 +643,16 @@ The allocator uses size-class buckets (32 to 4096 bytes) with an overflow list f ## Networking and HTTPS -MontaukOS provides a shared TLS library (`tls/tls.hpp`) backed by BearSSL, and the MontaukAI dev environment adds a higher-level HTTP wrapper (`http/http.hpp`) on top. Build with `USE_TLS=1` to link TLS support. +MontaukOS provides a shared TLS library (`tls/tls.hpp`) backed by BearSSL and +a higher-level HTTP client (`http/http.hpp`) used by system and third-party +applications. Build with `USE_TLS=1` to link TLS support. ### HTTP Wrapper (`http/http.hpp`) -Header-only library that handles DNS resolution, request building, TLS, response parsing, and cleanup. All functions return an `http::Response` struct. +Header-only library that handles DNS resolution, request building, TLS, +response parsing, and cleanup. It decodes chunked responses, validates +`Content-Length`, handles informational responses and partial I/O, and reports +a specific `http::Error`. All functions return an `http::Response`. #### Setup @@ -656,13 +661,14 @@ Header-only library that handles DNS resolution, request building, TLS, response // Load CA certificates once at startup (required for HTTPS) tls::TrustAnchors tas = tls::load_trust_anchors(); +// Call tls::free_trust_anchors(&tas) during application shutdown. ``` #### GET ```cpp auto resp = http::get("api.example.com", "/v1/data", tas); -if (resp.status == 200) { +if (resp.error == http::Error::NONE && resp.status == 200) { // resp.body is a pointer to the response body // resp.body_len is its length } @@ -743,6 +749,28 @@ http::free_response(&resp); Set `g_quit = true` from your keyboard handler (e.g., on Escape) to cancel mid-request. +#### Generic HTTP/HTTPS Requests + +Use `RequestOptions` for custom ports, cached DNS results, plain HTTP, or a +caller-owned response buffer: + +```cpp +char response_buffer[65536]; +http::RequestOptions options; +options.secure = true; +options.port = 8443; +options.resolved_ip = cached_ip; // zero asks the library to resolve the host +options.extra_headers = "Accept: application/json\r\n"; + +auto resp = http::request_into( + "GET", "api.example.com", "/large", nullptr, nullptr, 0, &tas, + response_buffer, sizeof(response_buffer), options); +if (resp.error != http::Error::NONE) { + // http::error_string(resp.error) is suitable for diagnostics +} +// response_buffer is caller-owned, so do not free_response(&resp). +``` + #### Response Struct Reference ```cpp @@ -754,6 +782,8 @@ struct http::Response { int body_len; char* raw; // Owned buffer — freed by free_response() int raw_len; + http::Error error; + bool owns_raw; }; ``` @@ -798,7 +828,7 @@ int http::parse_response(char* buf, int len, http::Response* out); bool http::get_header(const http::Response* resp, const char* name, char* out_val, int max_len); -// Free the response's raw buffer +// Free the response's raw buffer when owns_raw is true void http::free_response(http::Response* resp); ``` diff --git a/template/docs/syscalls.md b/template/docs/syscalls.md index a0badca..5de2bfd 100644 --- a/template/docs/syscalls.md +++ b/template/docs/syscalls.md @@ -376,7 +376,10 @@ The optional `AbortCheckFn` callback (e.g., `bool check_quit()`) lets terminal/G ### HTTP Wrapper (`http/http.hpp`) -The MontaukAI dev environment includes a higher-level HTTP wrapper built on top of `tls::https_fetch()`. It handles DNS, request building, TLS, and response parsing automatically. See the "Networking and HTTPS" section in `gui-apps.md` for full documentation and examples. +The MontaukOS SDK includes a higher-level HTTP client built on top of the TLS +and socket layers. It handles DNS, request construction, transport, response +framing, and parsing. See the "Networking and HTTPS" section in `gui-apps.md` +for full documentation and examples. ```cpp #include @@ -384,7 +387,9 @@ The MontaukAI dev environment includes a higher-level HTTP wrapper built on top tls::TrustAnchors tas = tls::load_trust_anchors(); auto resp = http::get("api.example.com", "/v1/data", tas); -if (resp.status == 200) { /* resp.body, resp.body_len */ } +if (resp.error == http::Error::NONE && resp.status == 200) { + /* resp.body, resp.body_len */ +} http::free_response(&resp); auto resp2 = http::post("api.example.com", "/v1/submit", diff --git a/template/sysroot/include/http/http.hpp b/template/sysroot/include/http/http.hpp index e84c358..14ee092 100644 --- a/template/sysroot/include/http/http.hpp +++ b/template/sysroot/include/http/http.hpp @@ -1,7 +1,10 @@ /* * http.hpp - * Simple HTTP request builder and response parser for MontaukOS - * Wraps tls::https_fetch() and raw sockets for ergonomic HTTP usage. + * Shared HTTP/1.1 client for MontaukOS. + * + * Owns request construction, DNS/transport selection, bounded response + * collection, response parsing, and chunked-transfer decoding. Applications + * should use this layer instead of constructing HTTP messages themselves. */ #pragma once @@ -13,67 +16,113 @@ namespace http { -// ---------------------------------------------------------------------------- -// Response -// ---------------------------------------------------------------------------- +enum class Error { + NONE = 0, + INVALID_ARGUMENT, + DNS_FAILED, + NO_MEMORY, + REQUEST_TOO_LARGE, + SOCKET_FAILED, + CONNECT_FAILED, + SEND_FAILED, + RECEIVE_FAILED, + TLS_FAILED, + INVALID_RESPONSE, + RESPONSE_TOO_LARGE, + TRUNCATED_RESPONSE +}; + +inline const char* error_string(Error error) { + switch (error) { + case Error::NONE: return "no error"; + case Error::INVALID_ARGUMENT: return "invalid HTTP request"; + case Error::DNS_FAILED: return "DNS resolution failed"; + case Error::NO_MEMORY: return "out of memory"; + case Error::REQUEST_TOO_LARGE: return "HTTP request is too large"; + case Error::SOCKET_FAILED: return "could not create socket"; + case Error::CONNECT_FAILED: return "connection failed"; + case Error::SEND_FAILED: return "request send failed"; + case Error::RECEIVE_FAILED: return "response receive failed"; + case Error::TLS_FAILED: return "TLS exchange failed"; + case Error::INVALID_RESPONSE: return "invalid HTTP response"; + case Error::RESPONSE_TOO_LARGE: return "HTTP response exceeded the buffer"; + case Error::TRUNCATED_RESPONSE: return "truncated HTTP response"; + } + return "unknown HTTP error"; +} struct Response { - int status; // HTTP status code (200, 404, etc.) or -1 on error - const char* headers; // Pointer into raw buffer (header block) + int status; // HTTP status code, or -1 before a response is parsed + const char* headers; // Pointers into raw int headers_len; - const char* body; // Pointer into raw buffer (body) + const char* body; int body_len; - char* raw; // Owned buffer — caller must free with montauk::mfree() + char* raw; int raw_len; + Error error; + bool owns_raw; }; +struct RequestOptions { + bool secure; // true for HTTPS, false for HTTP + uint16_t port; // 0 selects 443 or 80 + uint32_t resolved_ip; // 0 performs DNS resolution + int response_buffer_size; // used by request(); includes trailing NUL + const char* host_header; // optional Host authority (e.g. host:port) + const char* extra_headers; // complete CRLF-terminated header lines + tls::AbortCheckFn abort_check; + uint64_t timeout_ms; // inactivity timeout for plain HTTP + + RequestOptions() + : secure(true), port(0), resolved_ip(0), response_buffer_size(32768), + host_header(nullptr), extra_headers(nullptr), abort_check(nullptr), + timeout_ms(30000) {} +}; + +inline Response empty_response(Error error = Error::NONE) { + Response resp = {}; + resp.status = -1; + resp.error = error; + return resp; +} + inline const char* find_header_block_end(const char* start, const char* end) { if (!start || !end || start >= end) return nullptr; - - for (const char* s = start; s < end - 3; s++) { - if (s[0] == '\r' && s[1] == '\n' && s[2] == '\r' && s[3] == '\n') - return s + 4; + for (const char* p = start; p + 3 < end; ++p) { + if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') + return p + 4; } - for (const char* s = start; s < end - 1; s++) { - if (s[0] == '\n' && s[1] == '\n') - return s + 2; + for (const char* p = start; p + 1 < end; ++p) { + if (p[0] == '\n' && p[1] == '\n') return p + 2; } return nullptr; } inline int parse_status_code(const char* start, const char* end) { if (!start || !end || end - start < 12) return -1; + if (start[0] != 'H' || start[1] != 'T' || start[2] != 'T' || + start[3] != 'P' || start[4] != '/' || start[5] != '1' || + start[6] != '.' || (start[7] != '0' && start[7] != '1') || + start[8] != ' ') return -1; const char* p = start; - while (p < end && *p && *p != ' ') p++; - if (p >= end || *p != ' ') return -1; - p++; - - int code = 0; - int digits = 0; - while (digits < 3 && p < end && *p >= '0' && *p <= '9') { - code = code * 10 + (*p - '0'); - p++; - digits++; - } - return digits == 3 ? code : -1; + while (p < end && *p != ' ' && *p != '\r' && *p != '\n') ++p; + if (p >= end || *p++ != ' ') return -1; + if (end - p < 3 || p[0] < '0' || p[0] > '9' || + p[1] < '0' || p[1] > '9' || p[2] < '0' || p[2] > '9') return -1; + return (p[0] - '0') * 100 + (p[1] - '0') * 10 + (p[2] - '0'); } inline const char* find_final_response_start(const char* buf, int len) { if (!buf || len <= 0) return nullptr; - const char* start = buf; const char* end = buf + len; for (;;) { int code = parse_status_code(start, end); if (code < 0) return nullptr; - if (code < 100 || code >= 200) return start; - + if (code < 100 || code == 101 || code >= 200) return start; const char* next = find_header_block_end(start, end); if (!next || next >= end) return nullptr; - if (end - next < 5) return nullptr; - if (!(next[0] == 'H' && next[1] == 'T' && next[2] == 'T' && next[3] == 'P' && next[4] == '/')) - return nullptr; start = next; } } @@ -83,295 +132,505 @@ inline const char* skip_informational_responses(const char* buf, int len) { return start ? start : buf; } -// Parse raw HTTP response in-place. Sets pointers into buf (does not copy). -// Skips leading informational 1xx responses such as "100 Continue". -// Returns status code, or -1 if unparseable. -inline int parse_response(char* buf, int len, Response* out) { - out->raw = buf; - out->raw_len = len; - out->status = -1; - out->headers = nullptr; - out->headers_len = 0; - out->body = nullptr; - out->body_len = 0; - - const char* start = find_final_response_start(buf, len); - const char* end = buf + len; - if (!start || end - start < 12) return -1; // "HTTP/1.x NNN" - - // Parse status code from "HTTP/1.x NNN" - int code = parse_status_code(start, end); - if (code < 0) return -1; - out->status = code; - - // Headers start after the status line - const char* hdr_start = start; - while (hdr_start < end - 1) { - if (*hdr_start == '\r' && *(hdr_start + 1) == '\n') { hdr_start += 2; break; } - if (*hdr_start == '\n') { hdr_start++; break; } - hdr_start++; - } - out->headers = hdr_start; - - // Find \r\n\r\n boundary between headers and body - const char* body = find_header_block_end(hdr_start, end); - if (body) { - if (body >= hdr_start + 4 && - body[-4] == '\r' && body[-3] == '\n' && body[-2] == '\r' && body[-1] == '\n') - out->headers_len = (int)((body - 4) - hdr_start); - else if (body >= hdr_start + 2 && - body[-2] == '\n' && body[-1] == '\n') - out->headers_len = (int)((body - 2) - hdr_start); - else - out->headers_len = (int)(body - hdr_start); - out->body = body; - out->body_len = len - (int)(out->body - buf); - return code; - } - - // No body separator found — entire remainder is headers - out->headers_len = len - (int)(hdr_start - buf); - return code; +inline bool ascii_equal_ci(char a, char b) { + if (a >= 'A' && a <= 'Z') a += 'a' - 'A'; + if (b >= 'A' && b <= 'Z') b += 'a' - 'A'; + return a == b; } -// Find a header value by name (case-insensitive match on the name). -// Writes value into out_val (up to max_len), returns true if found. -inline bool get_header(const Response* resp, const char* name, char* out_val, int max_len) { - if (!resp->headers || resp->headers_len == 0) return false; +inline bool get_header(const Response* resp, const char* name, + char* out_val, int max_len) { + if (!resp || !resp->headers || resp->headers_len <= 0 || !name || + !out_val || max_len <= 0) return false; + int name_len = montauk::slen(name); const char* p = resp->headers; - const char* end = resp->headers + resp->headers_len; - + const char* end = p + resp->headers_len; while (p < end) { - // Case-insensitive prefix match - bool match = true; - if (p + name_len >= end) { match = false; } - else { - for (int i = 0; i < name_len; i++) { - char a = p[i], b = name[i]; - if (a >= 'A' && a <= 'Z') a += 32; - if (b >= 'A' && b <= 'Z') b += 32; - if (a != b) { match = false; break; } - } - if (match && p[name_len] != ':') match = false; - } + const char* line_end = p; + while (line_end < end && *line_end != '\r' && *line_end != '\n') ++line_end; + bool match = line_end - p > name_len && p[name_len] == ':'; + for (int i = 0; match && i < name_len; ++i) + if (!ascii_equal_ci(p[i], name[i])) match = false; if (match) { - const char* v = p + name_len + 1; - while (v < end && *v == ' ') v++; // skip OWS - int i = 0; - while (v < end && *v != '\r' && *v != '\n' && i < max_len - 1) - out_val[i++] = *v++; - out_val[i] = 0; + const char* value = p + name_len + 1; + while (value < line_end && (*value == ' ' || *value == '\t')) ++value; + while (line_end > value && + (line_end[-1] == ' ' || line_end[-1] == '\t')) --line_end; + int n = (int)(line_end - value); + if (n >= max_len) n = max_len - 1; + if (n > 0) montauk::memcpy(out_val, value, n); + out_val[n] = '\0'; return true; } - // Skip to next line - while (p < end && *p != '\n') p++; - if (p < end) p++; + p = line_end; + while (p < end && (*p == '\r' || *p == '\n')) ++p; } return false; } -// Free a response's raw buffer. -inline void free_response(Response* resp) { - if (resp->raw) { montauk::mfree(resp->raw); resp->raw = nullptr; } +inline bool header_has_token(const Response* resp, const char* name, + const char* token) { + char value[128]; + if (!get_header(resp, name, value, sizeof(value))) return false; + int token_len = montauk::slen(token); + for (int i = 0; value[i];) { + while (value[i] == ' ' || value[i] == '\t' || value[i] == ',') ++i; + int start = i; + while (value[i] && value[i] != ',' && value[i] != ' ' && value[i] != '\t') ++i; + int len = i - start; + bool match = len == token_len; + for (int j = 0; match && j < len; ++j) + if (!ascii_equal_ci(value[start + j], token[j])) match = false; + if (match) return true; + while (value[i] && value[i] != ',') ++i; + } + return false; } -// ---------------------------------------------------------------------------- -// Request builder (internal) -// ---------------------------------------------------------------------------- +inline int parse_decimal(const char* value) { + if (!value || !*value) return -1; + int result = 0; + for (int i = 0; value[i]; ++i) { + if (value[i] < '0' || value[i] > '9') return -1; + if (result > 214748364 || (result == 214748364 && value[i] > '7')) return -1; + result = result * 10 + value[i] - '0'; + } + return result; +} + +inline int decode_chunked_body(char* body, int encoded_len) { + int read_pos = 0; + int write_pos = 0; + for (;;) { + unsigned chunk_size = 0; + int digits = 0; + while (read_pos < encoded_len && body[read_pos] != '\r' && body[read_pos] != '\n') { + char c = body[read_pos++]; + if (c == ';') { + while (read_pos < encoded_len && body[read_pos] != '\r' && + body[read_pos] != '\n') ++read_pos; + break; + } + unsigned digit; + if (c >= '0' && c <= '9') digit = (unsigned)(c - '0'); + else if (c >= 'a' && c <= 'f') digit = (unsigned)(c - 'a' + 10); + else if (c >= 'A' && c <= 'F') digit = (unsigned)(c - 'A' + 10); + else return -1; + if (chunk_size > 0x0FFFFFFFu) return -1; + chunk_size = chunk_size * 16 + digit; + ++digits; + } + if (digits == 0 || read_pos >= encoded_len) return -1; + if (body[read_pos] == '\r') { + if (read_pos + 1 >= encoded_len || body[read_pos + 1] != '\n') return -1; + read_pos += 2; + } else { + ++read_pos; + } + + if (chunk_size == 0) { + // A zero chunk is followed by either an empty trailer line or a + // trailer header block. Do not accept a response cut at "0\r\n". + if (read_pos < encoded_len && body[read_pos] == '\n') return write_pos; + if (read_pos + 1 < encoded_len && body[read_pos] == '\r' && + body[read_pos + 1] == '\n') return write_pos; + if (find_header_block_end(body + read_pos, body + encoded_len)) + return write_pos; + return -1; + } + if (chunk_size > (unsigned)(encoded_len - read_pos)) return -1; + montauk::memmove(body + write_pos, body + read_pos, chunk_size); + write_pos += (int)chunk_size; + read_pos += (int)chunk_size; + if (read_pos >= encoded_len) return -1; + if (body[read_pos] == '\r') { + if (read_pos + 1 >= encoded_len || body[read_pos + 1] != '\n') return -1; + read_pos += 2; + } else if (body[read_pos] == '\n') { + ++read_pos; + } else { + return -1; + } + } +} + +// Parses and normalizes a response in place. Chunked bodies are decoded in +// the same buffer. Content-Length mismatches are reported as truncation. +inline int parse_response(char* buf, int len, Response* out) { + if (!out) return -1; + bool owns_raw = out->owns_raw; + *out = empty_response(); + out->raw = buf; + out->raw_len = len; + out->owns_raw = owns_raw; + if (!buf || len <= 0) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + + const char* start = find_final_response_start(buf, len); + const char* end = buf + len; + if (!start) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + int code = parse_status_code(start, end); + if (code < 0) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + + const char* status_end = start; + while (status_end < end && *status_end != '\n') ++status_end; + if (status_end >= end) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + const char* headers = status_end + 1; + const char* body = find_header_block_end(headers, end); + if (!body) { + out->error = Error::INVALID_RESPONSE; + return -1; + } + + const char* headers_end = body; + if (headers_end - headers >= 4 && headers_end[-4] == '\r') + headers_end -= 4; + else + headers_end -= 2; + + out->status = code; + out->headers = headers; + out->headers_len = (int)(headers_end - headers); + out->body = body; + out->body_len = (int)(end - body); + + if (header_has_token(out, "Transfer-Encoding", "chunked")) { + int decoded = decode_chunked_body((char*)out->body, out->body_len); + if (decoded < 0) { + out->error = Error::TRUNCATED_RESPONSE; + return code; + } + out->body_len = decoded; + ((char*)out->body)[decoded] = '\0'; + } else { + char value[32]; + if (get_header(out, "Content-Length", value, sizeof(value))) { + int expected = parse_decimal(value); + if (expected < 0) { + out->error = Error::INVALID_RESPONSE; + return code; + } + if (out->body_len < expected) { + out->error = Error::TRUNCATED_RESPONSE; + return code; + } + out->body_len = expected; + } + } + return code; +} + +inline void free_response(Response* resp) { + if (!resp) return; + if (resp->raw && resp->owns_raw) montauk::mfree(resp->raw); + *resp = empty_response(); +} + +inline bool contains_crlf(const char* value) { + if (!value) return false; + for (; *value; ++value) + if (*value == '\r' || *value == '\n') return true; + return false; +} + +inline bool contains_header_terminator(const char* value) { + if (!value) return false; + for (int i = 0; value[i]; ++i) { + if (value[i] == '\n' && value[i + 1] == '\n') return true; + if (value[i] == '\r' && value[i + 1] == '\n' && + value[i + 2] == '\r' && value[i + 3] == '\n') return true; + } + return false; +} + +struct RequestWriter { + char* p; + char* end; + bool overflow; + + void text(const char* value) { + if (!value) return; + while (*value) { + if (p >= end) { overflow = true; return; } + *p++ = *value++; + } + } + void number(int value) { + char digits[16]; + int n = 0; + if (value == 0) digits[n++] = '0'; + while (value > 0 && n < (int)sizeof(digits)) { + digits[n++] = (char)('0' + value % 10); + value /= 10; + } + while (n > 0) { + if (p >= end) { overflow = true; return; } + *p++ = digits[--n]; + } + } +}; inline int build_request(char* buf, int buf_size, const char* method, const char* host, const char* path, const char* content_type, const char* body_data, int body_len, const char* extra_headers) { - char* p = buf; - char* end = buf + buf_size - 1; + if (!buf || buf_size <= 0 || !method || !*method || !host || !*host || + !path || path[0] != '/' || body_len < 0 || + (body_len > 0 && !body_data) || contains_crlf(method) || + contains_crlf(host) || contains_crlf(path) || + (content_type && contains_crlf(content_type)) || + contains_header_terminator(extra_headers)) return -1; - auto append = [&](const char* s) { - while (*s && p < end) *p++ = *s++; - }; - auto append_int = [&](int n) { - char tmp[16]; int ti = 0; - if (n == 0) { if (p < end) *p++ = '0'; return; } - while (n > 0) { tmp[ti++] = '0' + (n % 10); n /= 10; } - for (int j = ti - 1; j >= 0 && p < end; j--) *p++ = tmp[j]; - }; - - // Request line - append(method); append(" "); append(path); append(" HTTP/1.1\r\n"); - - // Host - append("Host: "); append(host); append("\r\n"); - - // Content headers (for POST/PUT/PATCH) - if (body_data && body_len > 0) { + RequestWriter w = {buf, buf + buf_size, false}; + w.text(method); w.text(" "); w.text(path); w.text(" HTTP/1.1\r\n"); + w.text("Host: "); w.text(host); w.text("\r\n"); + if (body_data || body_len > 0) { if (content_type) { - append("Content-Type: "); append(content_type); append("\r\n"); + w.text("Content-Type: "); w.text(content_type); w.text("\r\n"); } - append("Content-Length: "); append_int(body_len); append("\r\n"); + w.text("Content-Length: "); w.number(body_len); w.text("\r\n"); } - - // Extra headers (caller-supplied, must include \r\n terminators) - if (extra_headers) append(extra_headers); - - append("Connection: close\r\n"); - append("\r\n"); - - int header_len = (int)(p - buf); - - // Append body - if (body_data && body_len > 0 && header_len + body_len < buf_size) { - montauk::memcpy(p, body_data, body_len); - p += body_len; + if (extra_headers) { + w.text(extra_headers); + int n = montauk::slen(extra_headers); + if (n > 0 && extra_headers[n - 1] != '\n') w.text("\r\n"); } - - return (int)(p - buf); + w.text("Connection: close\r\n\r\n"); + if (!w.overflow && body_len > 0) { + if (w.end - w.p < body_len) w.overflow = true; + else { + montauk::memcpy(w.p, body_data, body_len); + w.p += body_len; + } + } + return w.overflow ? -1 : (int)(w.p - buf); } -// ---------------------------------------------------------------------------- -// Public API -// ---------------------------------------------------------------------------- +inline bool plain_send_all(int fd, const char* data, int len, + uint64_t timeout_ms, tls::AbortCheckFn abort_check) { + int sent = 0; + uint64_t deadline = montauk::get_milliseconds() + timeout_ms; + while (sent < len) { + if (abort_check && abort_check()) return false; + int n = montauk::send(fd, data + sent, (uint32_t)(len - sent)); + if (n < 0) return false; + if (n > 0) { + sent += n; + deadline = montauk::get_milliseconds() + timeout_ms; + continue; + } + uint64_t now = montauk::get_milliseconds(); + if (now >= deadline) return false; + uint32_t signals = montauk::wait_handle( + fd, montauk::abi::IPC_SIGNAL_WRITABLE | + montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now); + if (signals == (uint32_t)-1 || + (signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED)) return false; + } + return true; +} + +inline int plain_receive(int fd, char* buf, int capacity, uint64_t timeout_ms, + tls::AbortCheckFn abort_check, Error* error) { + int total = 0; + uint64_t deadline = montauk::get_milliseconds() + timeout_ms; + while (total < capacity) { + if (abort_check && abort_check()) { + *error = Error::RECEIVE_FAILED; + return -1; + } + int n = montauk::recv(fd, buf + total, (uint32_t)(capacity - total)); + if (n > 0) { + total += n; + deadline = montauk::get_milliseconds() + timeout_ms; + continue; + } + if (n < 0) return total; + uint64_t now = montauk::get_milliseconds(); + if (now >= deadline) { + *error = Error::RECEIVE_FAILED; + return total > 0 ? total : -1; + } + uint32_t signals = montauk::wait_handle( + fd, montauk::abi::IPC_SIGNAL_READABLE | + montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now); + if (signals == (uint32_t)-1) { + *error = Error::RECEIVE_FAILED; + return total > 0 ? total : -1; + } + if ((signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED) && + !(signals & montauk::abi::IPC_SIGNAL_READABLE)) return total; + } + *error = Error::RESPONSE_TOO_LARGE; + return total; +} + +// Generic request using a caller-owned response buffer. +inline Response request_into(const char* method, const char* host, const char* path, + const char* content_type, const char* body_data, + int body_len, const tls::TrustAnchors* tas, + char* response_buffer, int response_buffer_size, + const RequestOptions& options = RequestOptions()) { + Response resp = empty_response(); + if (!response_buffer || response_buffer_size < 2 || !method || !host || !path || + body_len < 0 || (body_len > 0 && !body_data) || + (options.secure && (!tas || tas->count == 0))) { + resp.error = Error::INVALID_ARGUMENT; + return resp; + } + + uint64_t request_size64 = 160u + (uint64_t)montauk::slen(method) + + (uint64_t)montauk::slen(host) + (uint64_t)montauk::slen(path) + + (uint64_t)(content_type ? montauk::slen(content_type) : 0) + + (uint64_t)(options.extra_headers ? montauk::slen(options.extra_headers) : 0) + + (uint64_t)body_len; + if (request_size64 > 8u * 1024u * 1024u) { + resp.error = Error::REQUEST_TOO_LARGE; + return resp; + } + int request_size = (int)request_size64; + char* request_data = (char*)montauk::malloc(request_size); + if (!request_data) { + resp.error = Error::NO_MEMORY; + return resp; + } + int request_len = build_request(request_data, request_size, method, + options.host_header ? options.host_header : host, path, + content_type, body_data, body_len, + options.extra_headers); + if (request_len < 0) { + montauk::mfree(request_data); + resp.error = Error::REQUEST_TOO_LARGE; + return resp; + } + + uint32_t ip = options.resolved_ip ? options.resolved_ip : montauk::resolve(host); + if (!ip) { + montauk::mfree(request_data); + resp.error = Error::DNS_FAILED; + return resp; + } + uint16_t port = options.port ? options.port : (options.secure ? 443 : 80); + int received = -1; + Error transport_error = Error::NONE; + + if (options.secure) { + received = tls::https_fetch(host, ip, port, request_data, request_len, *tas, + response_buffer, response_buffer_size, + options.abort_check); + if (received < 0) transport_error = Error::TLS_FAILED; + else if (received >= response_buffer_size - 1) + transport_error = Error::RESPONSE_TOO_LARGE; + } else { + int fd = montauk::socket(montauk::abi::SOCK_TCP); + if (fd < 0) { + transport_error = Error::SOCKET_FAILED; + } else if (montauk::connect(fd, ip, port) < 0) { + transport_error = Error::CONNECT_FAILED; + montauk::closesocket(fd); + } else { + if (!plain_send_all(fd, request_data, request_len, options.timeout_ms, + options.abort_check)) { + transport_error = Error::SEND_FAILED; + } else { + received = plain_receive(fd, response_buffer, response_buffer_size - 1, + options.timeout_ms, options.abort_check, + &transport_error); + } + montauk::closesocket(fd); + } + } + montauk::mfree(request_data); + + if (received <= 0) { + resp.error = transport_error == Error::NONE ? Error::RECEIVE_FAILED : transport_error; + return resp; + } + response_buffer[received] = '\0'; + resp.owns_raw = false; + parse_response(response_buffer, received, &resp); + if (resp.error == Error::NONE && transport_error != Error::NONE) + resp.error = transport_error; + return resp; +} + +// Generic request with a library-owned response buffer. +inline Response request(const char* method, const char* host, const char* path, + const char* content_type, const char* body_data, int body_len, + const tls::TrustAnchors* tas, + const RequestOptions& options = RequestOptions()) { + if (options.response_buffer_size < 2) + return empty_response(Error::INVALID_ARGUMENT); + char* buffer = (char*)montauk::malloc(options.response_buffer_size); + if (!buffer) return empty_response(Error::NO_MEMORY); + Response resp = request_into(method, host, path, content_type, body_data, body_len, + tas, buffer, options.response_buffer_size, options); + if (!resp.raw) { + montauk::mfree(buffer); + } else { + resp.owns_raw = true; + } + return resp; +} -// GET request over HTTPS. Returns parsed response. Caller must free_response(). inline Response get(const char* host, const char* path, - const tls::TrustAnchors& tas, - int resp_buf_size = 32768, + const tls::TrustAnchors& tas, int response_buffer_size = 32768, const char* extra_headers = nullptr, tls::AbortCheckFn abort_check = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - char req[1024]; - int reqLen = build_request(req, sizeof(req), "GET", host, path, - nullptr, nullptr, 0, extra_headers); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) return resp; - - int n = tls::https_fetch(host, ip, 443, req, reqLen, tas, - buf, resp_buf_size - 1, abort_check); - if (n <= 0) { montauk::mfree(buf); return resp; } - buf[n] = 0; - - parse_response(buf, n, &resp); - return resp; + RequestOptions options; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + options.abort_check = abort_check; + return request("GET", host, path, nullptr, nullptr, 0, &tas, options); } -// POST request over HTTPS. Returns parsed response. Caller must free_response(). -inline Response post(const char* host, const char* path, - const char* content_type, +inline Response post(const char* host, const char* path, const char* content_type, const char* body_data, int body_len, - const tls::TrustAnchors& tas, - int resp_buf_size = 32768, + const tls::TrustAnchors& tas, int response_buffer_size = 32768, const char* extra_headers = nullptr, tls::AbortCheckFn abort_check = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - int req_size = 1024 + body_len; - char* req = (char*)montauk::malloc(req_size); - if (!req) return resp; - - int reqLen = build_request(req, req_size, "POST", host, path, - content_type, body_data, body_len, - extra_headers); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) { montauk::mfree(req); return resp; } - - int n = tls::https_fetch(host, ip, 443, req, reqLen, tas, - buf, resp_buf_size - 1, abort_check); - montauk::mfree(req); - - if (n <= 0) { montauk::mfree(buf); return resp; } - buf[n] = 0; - - parse_response(buf, n, &resp); - return resp; + RequestOptions options; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + options.abort_check = abort_check; + return request("POST", host, path, content_type, body_data, body_len, &tas, options); } -// Generic request over HTTPS (PUT, PATCH, DELETE, etc.). -inline Response request(const char* method, - const char* host, const char* path, - const char* content_type, - const char* body_data, int body_len, - const tls::TrustAnchors& tas, - int resp_buf_size = 32768, +inline Response request(const char* method, const char* host, const char* path, + const char* content_type, const char* body_data, int body_len, + const tls::TrustAnchors& tas, int response_buffer_size = 32768, const char* extra_headers = nullptr, tls::AbortCheckFn abort_check = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - int req_size = 1024 + (body_len > 0 ? body_len : 0); - char* req = (char*)montauk::malloc(req_size); - if (!req) return resp; - - int reqLen = build_request(req, req_size, method, host, path, - content_type, body_data, body_len, - extra_headers); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) { montauk::mfree(req); return resp; } - - int n = tls::https_fetch(host, ip, 443, req, reqLen, tas, - buf, resp_buf_size - 1, abort_check); - montauk::mfree(req); - - if (n <= 0) { montauk::mfree(buf); return resp; } - buf[n] = 0; - - parse_response(buf, n, &resp); - return resp; + RequestOptions options; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + options.abort_check = abort_check; + return request(method, host, path, content_type, body_data, body_len, &tas, options); } -// Plain HTTP (no TLS) GET over port 80. inline Response get_plain(const char* host, const char* path, - int resp_buf_size = 32768, + int response_buffer_size = 32768, const char* extra_headers = nullptr) { - Response resp = {}; - resp.status = -1; - - uint32_t ip = montauk::resolve(host); - if (!ip) return resp; - - char req[1024]; - int reqLen = build_request(req, sizeof(req), "GET", host, path, - nullptr, nullptr, 0, extra_headers); - - int sock = montauk::socket(montauk::abi::SOCK_TCP); - if (sock < 0) return resp; - if (montauk::connect(sock, ip, 80) < 0) { montauk::closesocket(sock); return resp; } - - montauk::send(sock, req, reqLen); - - char* buf = (char*)montauk::malloc(resp_buf_size); - if (!buf) { montauk::closesocket(sock); return resp; } - - int total = 0; - while (total < resp_buf_size - 1) { - int n = montauk::recv(sock, buf + total, resp_buf_size - 1 - total); - if (n <= 0) break; - total += n; - } - montauk::closesocket(sock); - - if (total <= 0) { montauk::mfree(buf); return resp; } - buf[total] = 0; - - parse_response(buf, total, &resp); - return resp; + RequestOptions options; + options.secure = false; + options.response_buffer_size = response_buffer_size; + options.extra_headers = extra_headers; + return request("GET", host, path, nullptr, nullptr, 0, nullptr, options); } } // namespace http diff --git a/template/sysroot/include/tls/tls.hpp b/template/sysroot/include/tls/tls.hpp index 9555a49..64fff35 100644 --- a/template/sysroot/include/tls/tls.hpp +++ b/template/sysroot/include/tls/tls.hpp @@ -23,6 +23,7 @@ struct TrustAnchors { }; TrustAnchors load_trust_anchors(); +void free_trust_anchors(TrustAnchors* tas); void get_bearssl_time(uint32_t* days, uint32_t* seconds); int tls_send_all(int fd, const unsigned char* data, size_t len); int tls_recv_some(int fd, unsigned char* buf, size_t maxlen); diff --git a/template/sysroot/lib/libtls.a b/template/sysroot/lib/libtls.a index c1431679f4fff34ca516f45bc7901805339f3506..3143169c76bb9af35eaec5a309a71f0498f5a490 100644 GIT binary patch literal 113598 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 literal 111626 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 mGdM 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 + +static bool bytes_equal(const char* a, const char* b, int len) { + for (int i = 0; i < len; ++i) + if (a[i] != b[i]) return false; + return true; +} + +static int test_content_length() { + char raw[] = + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 5\r\n\r\n" + "helloignored"; + http::Response response = {}; + if (http::parse_response(raw, sizeof(raw) - 1, &response) != 200) return 1; + if (response.error != http::Error::NONE || response.body_len != 5) return 2; + if (!bytes_equal(response.body, "hello", 5)) return 3; + char type[32]; + if (!http::get_header(&response, "content-type", type, sizeof(type))) return 4; + if (!bytes_equal(type, "text/plain", 10) || type[10] != '\0') return 5; + return 0; +} + +static int test_chunked() { + char raw[] = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: Chunked\r\n\r\n" + "4\r\nWiki\r\n5;extension=yes\r\npedia\r\n0\r\nX-Trailer: yes\r\n\r\n"; + http::Response response = {}; + if (http::parse_response(raw, sizeof(raw) - 1, &response) != 200) return 1; + if (response.error != http::Error::NONE || response.body_len != 9) return 2; + if (!bytes_equal(response.body, "Wikipedia", 9)) return 3; + return 0; +} + +static int test_informational() { + char raw[] = + "HTTP/1.1 100 Continue\r\nHeader: value\r\n\r\n" + "HTTP/1.1 201 Created\r\nContent-Length: 2\r\n\r\nok"; + http::Response response = {}; + if (http::parse_response(raw, sizeof(raw) - 1, &response) != 201) return 1; + if (response.status != 201 || response.body_len != 2) return 2; + if (!bytes_equal(response.body, "ok", 2)) return 3; + return 0; +} + +static int test_rejections() { + char truncated[] = "HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nshort"; + http::Response response = {}; + if (http::parse_response(truncated, sizeof(truncated) - 1, &response) != 200) return 1; + if (response.error != http::Error::TRUNCATED_RESPONSE) return 2; + + char malformed[] = "not http\r\n\r\n"; + response = {}; + if (http::parse_response(malformed, sizeof(malformed) - 1, &response) >= 0) return 3; + if (response.error != http::Error::INVALID_RESPONSE) return 4; + + char request[128]; + if (http::build_request(request, sizeof(request), "GET", "safe\r\nInjected: yes", + "/", nullptr, nullptr, 0, nullptr) >= 0) return 5; + if (http::build_request(request, 16, "GET", "example.com", "/", + nullptr, nullptr, 0, nullptr) >= 0) return 6; + char bad_chunk[] = + "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n"; + response = {}; + http::parse_response(bad_chunk, sizeof(bad_chunk) - 1, &response); + if (response.error != http::Error::TRUNCATED_RESPONSE) return 7; + if (http::build_request(request, sizeof(request), "GET", "example.com", "/", + nullptr, nullptr, 0, "Safe: yes\r\n\r\nGET /evil") >= 0) + return 8; + return 0; +} + +int main() { + int result = test_content_length(); + if (result) return 10 + result; + result = test_chunked(); + if (result) return 20 + result; + result = test_informational(); + if (result) return 30 + result; + result = test_rejections(); + if (result) return 40 + result; + return 0; +}