feat: common TLS library, weather app, UI scaling fix in Window Server, and more

This commit is contained in:
2026-02-21 13:05:28 +01:00
parent 9d5e7eac8d
commit e0bfc97f0f
32 changed files with 1440 additions and 1048 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ LDFLAGS := \
# ---- C++ source files ----
SRCS := main.cpp font_data.cpp stb_truetype_impl.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_procmgr.cpp app_mandelbrot.cpp app_devexplorer.cpp app_settings.cpp app_doom.cpp
SRCS := main.cpp font_data.cpp stb_truetype_impl.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_weather.cpp app_procmgr.cpp app_mandelbrot.cpp app_devexplorer.cpp app_settings.cpp app_doom.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
# ---- Target ----
+3
View File
@@ -458,18 +458,21 @@ static void settings_on_mouse(Window* win, MouseEvent& ev) {
if (mx >= bx && mx < bx + sbw && cy >= y && cy < y + btn_h) {
s.ui_scale = 0;
apply_ui_scale(0);
zenith::win_setscale(0);
return;
}
// Default
if (mx >= bx + sbw + 8 && mx < bx + sbw * 2 + 8 && cy >= y && cy < y + btn_h) {
s.ui_scale = 1;
apply_ui_scale(1);
zenith::win_setscale(1);
return;
}
// Large
if (mx >= bx + (sbw + 8) * 2 && mx < bx + sbw * 3 + 16 && cy >= y && cy < y + btn_h) {
s.ui_scale = 2;
apply_ui_scale(2);
zenith::win_setscale(2);
return;
}
}
+13
View File
@@ -0,0 +1,13 @@
/*
* app_weather.cpp
* ZenithOS Desktop - Weather app launcher
* Spawns weather.elf as a standalone Window Server process
* Copyright (c) 2026 Daniel Hammer
*/
#include "apps_common.hpp"
void open_weather(DesktopState* ds) {
(void)ds;
zenith::spawn("0:/os/weather.elf");
}
+1
View File
@@ -167,6 +167,7 @@ void open_texteditor(DesktopState* ds);
void open_texteditor_with_file(DesktopState* ds, const char* path);
void open_klog(DesktopState* ds);
void open_wiki(DesktopState* ds);
void open_weather(DesktopState* ds);
void open_procmgr(DesktopState* ds);
void open_mandelbrot(DesktopState* ds);
void open_devexplorer(DesktopState* ds);
+9 -3
View File
@@ -61,6 +61,8 @@ void gui::desktop_init(DesktopState* ds) {
ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor);
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
ds->icon_weather = svg_load("0:/icons/weather-widget.svg", 20, 20, defColor);
ds->icon_doom = svg_load("0:/icons/doom.svg", 20, 20, defColor);
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor);
@@ -76,6 +78,7 @@ void gui::desktop_init(DesktopState* ds) {
ds->settings.show_shadows = true;
ds->settings.clock_24h = true;
ds->settings.ui_scale = 1;
zenith::win_setscale(1);
ds->ctx_menu_open = false;
ds->ctx_menu_x = 0;
@@ -421,7 +424,7 @@ struct MenuRow {
int app_id; // -1 for category headers / dividers
};
static constexpr int MENU_ROW_COUNT = 18;
static constexpr int MENU_ROW_COUNT = 19;
static const MenuRow menu_rows[MENU_ROW_COUNT] = {
{ true, "Applications", -1 },
{ false, "Terminal", 0 },
@@ -430,6 +433,7 @@ static const MenuRow menu_rows[MENU_ROW_COUNT] = {
{ false, "Calculator", 3 },
{ true, "Internet", -1 },
{ false, "Wikipedia", 9 },
{ false, "Weather", 13 },
{ true, "System", -1 },
{ false, "System Info", 2 },
{ false, "Kernel Log", 5 },
@@ -470,7 +474,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
draw_rect(fb, menu_x, menu_y, MENU_W, menu_h, colors::BORDER);
// Icon lookup by app_id
SvgIcon* icons[13] = {
SvgIcon* icons[14] = {
&ds->icon_terminal, // 0
&ds->icon_filemanager, // 1
&ds->icon_sysinfo, // 2
@@ -484,6 +488,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
&ds->icon_doom, // 10
&ds->icon_settings, // 11
&ds->icon_reboot, // 12
&ds->icon_weather, // 13
};
int mx = ds->mouse.x;
@@ -519,7 +524,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
// Icon
int icon_x = item_rect.x + 8;
int icon_y = item_rect.y + (row_h - 20) / 2;
if (row.app_id >= 0 && row.app_id < 13) {
if (row.app_id >= 0 && row.app_id < 14) {
SvgIcon* icon = icons[row.app_id];
if (icon && icon->pixels) {
fb.blit_alpha(icon_x, icon_y, icon->width, icon->height, icon->pixels);
@@ -1099,6 +1104,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
case 10: open_doom(ds); break;
case 11: open_settings(ds); break;
case 12: open_reboot_dialog(ds); break;
case 13: open_weather(ds); break;
}
ds->app_menu_open = false;
}
+2 -1
View File
@@ -16,6 +16,7 @@ endif
# ---- Paths ----
BEARSSL := ../../lib/bearssl
TLS_LIB := ../../lib/tls
LIBC_LIB := ../../lib/libc
LIBC_INC := ../../include/libc
PROG_INC := ../../include
@@ -67,7 +68,7 @@ LDFLAGS := \
# ---- Libraries ----
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
# ---- Target ----
+32 -426
View File
@@ -8,9 +8,9 @@
#include <zenith/syscall.h>
#include <zenith/string.h>
#include <tls/tls.hpp>
extern "C" {
#include <bearssl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -156,366 +156,15 @@ static void parse_status_text(const char* buf, int len, char* out, int outMax) {
out[j] = '\0';
}
// ---- Trust anchor loading ----
// ---- Keyboard abort check for TLS ----
struct TrustAnchors {
br_x509_trust_anchor* anchors;
size_t count;
size_t capacity;
};
// Accumulate DER-decoded certificate data
struct DerAccum {
unsigned char* data;
size_t len;
size_t cap;
};
static void der_append(void* ctx, const void* buf, size_t len) {
DerAccum* a = (DerAccum*)ctx;
if (a->len + len > a->cap) {
size_t newcap = a->cap * 2;
if (newcap < a->len + len) newcap = a->len + len + 4096;
unsigned char* nb = (unsigned char*)malloc(newcap);
if (!nb) return;
if (a->data) {
memcpy(nb, a->data, a->len);
free(a->data);
}
a->data = nb;
a->cap = newcap;
}
memcpy(a->data + a->len, buf, len);
a->len += len;
}
// Accumulate DN data from X.509 decoder
struct DnAccum {
unsigned char* data;
size_t len;
size_t cap;
};
static void dn_append(void* ctx, const void* buf, size_t len) {
DnAccum* a = (DnAccum*)ctx;
if (a->len + len > a->cap) {
size_t newcap = a->cap * 2;
if (newcap < a->len + len) newcap = a->len + len + 256;
unsigned char* nb = (unsigned char*)malloc(newcap);
if (!nb) return;
if (a->data) {
memcpy(nb, a->data, a->len);
free(a->data);
}
a->data = nb;
a->cap = newcap;
}
memcpy(a->data + a->len, buf, len);
a->len += len;
}
static void ta_add(TrustAnchors* tas, const br_x509_trust_anchor* ta) {
if (tas->count >= tas->capacity) {
size_t newcap = tas->capacity == 0 ? 64 : tas->capacity * 2;
br_x509_trust_anchor* na = (br_x509_trust_anchor*)malloc(
newcap * sizeof(br_x509_trust_anchor));
if (!na) return;
if (tas->anchors) {
memcpy(na, tas->anchors, tas->count * sizeof(br_x509_trust_anchor));
free(tas->anchors);
}
tas->anchors = na;
tas->capacity = newcap;
}
tas->anchors[tas->count++] = *ta;
}
// Process a single DER certificate into a trust anchor
static bool process_cert_der(TrustAnchors* tas, const unsigned char* der, size_t der_len) {
br_x509_decoder_context dc;
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_trust_anchor ta;
memset(&ta, 0, sizeof(ta));
// Copy DN
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;
// Deep-copy public key data
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);
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);
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);
break;
}
default:
if (dn.data) free(dn.data);
return false;
}
ta_add(tas, &ta);
return true;
}
static TrustAnchors load_trust_anchors(bool verbose) {
TrustAnchors tas = {nullptr, 0, 0};
int fh = zenith::open("0:/etc/ca-certificates.crt");
if (fh < 0) {
printf("Warning: could not open CA certificate bundle\n");
return tas;
}
uint64_t fsize = zenith::getsize(fh);
if (fsize == 0 || fsize > 512 * 1024) {
zenith::close(fh);
printf("Warning: CA cert file invalid size\n");
return tas;
}
unsigned char* pem = (unsigned char*)malloc(fsize + 1);
if (!pem) {
zenith::close(fh);
printf("Warning: out of memory loading CA certs\n");
return tas;
}
zenith::read(fh, pem, 0, fsize);
zenith::close(fh);
pem[fsize] = 0;
// Parse PEM -> DER certificates -> trust anchors
br_pem_decoder_context pc;
br_pem_decoder_init(&pc);
DerAccum der = {nullptr, 0, 0};
bool inCert = false;
size_t offset = 0;
while (offset < fsize) {
size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset);
offset += pushed;
int event = br_pem_decoder_event(&pc);
if (event == BR_PEM_BEGIN_OBJ) {
const char* name = br_pem_decoder_name(&pc);
inCert = (strcmp(name, "CERTIFICATE") == 0);
if (inCert) {
der.len = 0;
br_pem_decoder_setdest(&pc, der_append, &der);
} else {
br_pem_decoder_setdest(&pc, nullptr, nullptr);
}
} else if (event == BR_PEM_END_OBJ) {
if (inCert && der.len > 0) {
process_cert_der(&tas, der.data, der.len);
}
inCert = false;
} else if (event == BR_PEM_ERROR) {
break;
}
}
if (der.data) free(der.data);
free(pem);
if (verbose) {
char msg[64];
snprintf(msg, sizeof(msg), "Loaded %u trust anchors\n", (unsigned)tas.count);
zenith::print(msg);
}
return tas;
}
// ---- Time conversion for certificate validation ----
// Returns days since January 1, 0 AD (Gregorian) and seconds within the day.
// This is the format BearSSL's br_x509_minimal_set_time() expects.
static void get_bearssl_time(uint32_t* days, uint32_t* seconds) {
Zenith::DateTime dt;
zenith::gettime(&dt);
int y = dt.Year;
int m = dt.Month;
int d = dt.Day;
// Days from year 0 to start of year y (Gregorian proleptic calendar)
uint32_t total_days = 365 * (uint32_t)y
+ (uint32_t)(y / 4)
- (uint32_t)(y / 100)
+ (uint32_t)(y / 400);
// Add days for completed months in this year
const int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
for (int mo = 1; mo < m && mo <= 12; mo++) {
total_days += mdays[mo];
}
// Leap day for this year if we're past February
bool leap = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
if (leap && m > 2) total_days++;
total_days += d - 1;
*days = total_days;
*seconds = (uint32_t)(dt.Hour * 3600 + dt.Minute * 60 + dt.Second);
}
// ---- TLS I/O loop ----
static int tls_send_all(int fd, const unsigned char* data, size_t len) {
size_t sent = 0;
uint64_t deadline = zenith::get_milliseconds() + 15000;
while (sent < len) {
int r = zenith::send(fd, data + sent, (uint32_t)(len - sent));
if (r > 0) {
sent += r;
deadline = zenith::get_milliseconds() + 15000;
} else if (r < 0) {
return -1;
} else {
if (zenith::get_milliseconds() >= deadline) return -1;
zenith::sleep_ms(1);
}
}
return (int)sent;
}
static int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) {
uint64_t deadline = zenith::get_milliseconds() + 15000;
while (true) {
int r = zenith::recv(fd, buf, (uint32_t)maxlen);
if (r > 0) return r;
if (r < 0) return -1;
if (zenith::get_milliseconds() >= deadline) return -1;
zenith::sleep_ms(1);
}
}
// Run BearSSL I/O loop until handshake + app data exchange is done
// Returns: number of response bytes in respBuf, or -1 on error
static int tls_exchange(int fd, br_ssl_engine_context* eng,
const char* request, int reqLen,
char* respBuf, int respMax, bool verbose) {
bool requestSent = false;
int respLen = 0;
uint64_t deadline = zenith::get_milliseconds() + 30000;
while (true) {
unsigned state = br_ssl_engine_current_state(eng);
if (state & BR_SSL_CLOSED) {
int err = br_ssl_engine_last_error(eng);
if (err != BR_ERR_OK && err != BR_ERR_IO) {
char msg[64];
snprintf(msg, sizeof(msg), "TLS error: %d\n", err);
zenith::print(msg);
if (respLen == 0) return -1;
}
return respLen;
}
// Check for keyboard abort
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
br_ssl_engine_close(eng);
return respLen > 0 ? respLen : -1;
}
}
// Send record data to network
if (state & BR_SSL_SENDREC) {
size_t len;
unsigned char* buf = br_ssl_engine_sendrec_buf(eng, &len);
int sent = tls_send_all(fd, buf, len);
if (sent < 0) {
br_ssl_engine_close(eng);
return respLen > 0 ? respLen : -1;
}
br_ssl_engine_sendrec_ack(eng, len);
deadline = zenith::get_milliseconds() + 30000;
continue;
}
// Read application data from TLS
if (state & BR_SSL_RECVAPP) {
size_t len;
unsigned char* buf = br_ssl_engine_recvapp_buf(eng, &len);
size_t toCopy = len;
if (respLen + (int)toCopy > respMax - 1)
toCopy = respMax - 1 - respLen;
if (toCopy > 0) {
memcpy(respBuf + respLen, buf, toCopy);
respLen += toCopy;
}
br_ssl_engine_recvapp_ack(eng, len);
deadline = zenith::get_milliseconds() + 30000;
continue;
}
// Send application data (HTTP request) into TLS
if ((state & BR_SSL_SENDAPP) && !requestSent) {
size_t len;
unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len);
size_t toWrite = (size_t)reqLen;
if (toWrite > len) toWrite = len;
memcpy(buf, request, toWrite);
br_ssl_engine_sendapp_ack(eng, toWrite);
br_ssl_engine_flush(eng, 0);
requestSent = true;
deadline = zenith::get_milliseconds() + 30000;
continue;
}
// Receive record data from network
if (state & BR_SSL_RECVREC) {
size_t len;
unsigned char* buf = br_ssl_engine_recvrec_buf(eng, &len);
int got = tls_recv_some(fd, buf, len);
if (got < 0) {
br_ssl_engine_close(eng);
return respLen > 0 ? respLen : -1;
}
br_ssl_engine_recvrec_ack(eng, got);
deadline = zenith::get_milliseconds() + 30000;
continue;
}
// Nothing actionable — wait
if (zenith::get_milliseconds() >= deadline) {
return respLen > 0 ? respLen : -1;
}
zenith::sleep_ms(1);
static bool check_keyboard_abort() {
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') return true;
}
return false;
}
// ---- Plain HTTP exchange (no TLS) ----
@@ -706,19 +355,6 @@ extern "C" void _start() {
zenith::print(msg);
}
// Create and connect socket
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
if (zenith::connect(fd, serverIp, port) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
zenith::exit(1);
}
// Build HTTP request
char request[1024];
int reqLen = snprintf(request, sizeof(request),
@@ -740,7 +376,6 @@ extern "C" void _start() {
char* respBuf = (char*)malloc(RESP_MAX);
if (!respBuf) {
zenith::print("Error: out of memory\n");
zenith::closesocket(fd);
zenith::exit(1);
}
@@ -748,35 +383,21 @@ extern "C" void _start() {
if (useHttps) {
// ---- TLS handshake and exchange ----
// Load trust anchors
TrustAnchors tas = load_trust_anchors(verbose);
tls::TrustAnchors tas = tls::load_trust_anchors();
if (verbose) {
char msg[64];
snprintf(msg, sizeof(msg), "Loaded %u trust anchors\n", (unsigned)tas.count);
zenith::print(msg);
}
if (tas.count == 0) {
zenith::print("Error: no trust anchors loaded\n");
free(respBuf);
zenith::closesocket(fd);
zenith::exit(1);
}
// Initialize BearSSL client
// Allocate contexts on heap to avoid stack overflow
br_ssl_client_context* cc = (br_ssl_client_context*)malloc(sizeof(br_ssl_client_context));
br_x509_minimal_context* xc = (br_x509_minimal_context*)malloc(sizeof(br_x509_minimal_context));
if (!cc || !xc) {
zenith::print("Error: out of memory for TLS context\n");
free(respBuf);
zenith::closesocket(fd);
zenith::exit(1);
}
br_ssl_client_init_full(cc, xc, tas.anchors, tas.count);
// Set time for certificate validation
uint32_t days, secs;
get_bearssl_time(&days, &secs);
br_x509_minimal_set_time(xc, days, secs);
if (verbose) {
uint32_t days, secs;
tls::get_bearssl_time(&days, &secs);
Zenith::DateTime dt;
zenith::gettime(&dt);
char tmsg[128];
@@ -785,54 +406,39 @@ extern "C" void _start() {
(unsigned)dt.Hour, (unsigned)dt.Minute, (unsigned)dt.Second,
(unsigned)days, (unsigned)secs);
zenith::print(tmsg);
}
// Seed the PRNG with RDRAND entropy
unsigned char seed[32];
zenith::getrandom(seed, sizeof(seed));
br_ssl_engine_set_buffer(&cc->eng, malloc(BR_SSL_BUFSIZE_BIDI),
BR_SSL_BUFSIZE_BIDI, 1);
// Inject entropy
br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed));
// Reset client with server name for SNI
if (!br_ssl_client_reset(cc, hostStr, 0)) {
int err = br_ssl_engine_last_error(&cc->eng);
char msg[64];
snprintf(msg, sizeof(msg), "Error: TLS reset failed (err=%d)\n", err);
zenith::print(msg);
free(respBuf);
zenith::closesocket(fd);
zenith::exit(1);
}
if (verbose) {
zenith::print("TLS handshake...\n");
}
// Run TLS I/O loop
respLen = tls_exchange(fd, &cc->eng, request, reqLen,
respBuf, RESP_MAX, verbose);
respLen = tls::https_fetch(hostStr, serverIp, port,
request, reqLen, tas,
respBuf, RESP_MAX, check_keyboard_abort);
if (verbose && respLen > 0) {
zenith::print("TLS connection established\n");
}
// Cleanup (we don't bother freeing everything since we're exiting)
} else {
// ---- Plain HTTP ----
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
if (zenith::connect(fd, serverIp, port) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
zenith::exit(1);
}
respLen = plain_http_exchange(fd, request, reqLen, respBuf, RESP_MAX);
zenith::closesocket(fd);
if (respLen == -2) {
zenith::print("\nAborted.\n");
zenith::closesocket(fd);
zenith::exit(0);
}
}
zenith::closesocket(fd);
if (respLen <= 0) {
zenith::print("Error: no response received\n");
zenith::exit(1);
+94
View File
@@ -0,0 +1,94 @@
# Makefile for weather (standalone Weather GUI client) on ZenithOS
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
# ---- Toolchain ----
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
# ---- Paths ----
BEARSSL := ../../lib/bearssl
TLS_LIB := ../../lib/tls
LIBC_LIB := ../../lib/libc
LIBC_INC := ../../include/libc
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
# ---- Compiler flags ----
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-unused-function \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-msse \
-msse2 \
-mno-red-zone \
-mcmodel=small \
-I $(PROG_INC) \
-isystem $(LIBC_INC) \
-I $(BEARSSL)/inc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
# ---- Linker flags ----
LDFLAGS := \
-nostdlib \
-static \
-Wl,--build-id=none \
-Wl,--gc-sections \
-Wl,-m,elf_x86_64 \
-z max-page-size=0x1000 \
-T $(LINK_LD)
# ---- Libraries ----
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
# ---- Source files ----
SRCS := main.cpp stb_truetype_impl.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
# ---- Target ----
TARGET := $(BINDIR)/os/weather.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LIBS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET)
+575
View File
@@ -0,0 +1,575 @@
/*
* main.cpp
* ZenithOS Weather app - standalone Window Server process
* Fetches current weather from wttr.in via HTTPS (BearSSL)
* Displays temperature, description, feels like, and location
* Copyright (c) 2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
#include <zenith/heap.h>
#include <gui/gui.hpp>
#include <gui/svg.hpp>
#include <gui/truetype.hpp>
#include <tls/tls.hpp>
extern "C" {
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
}
using namespace gui;
using namespace gui::colors;
// ============================================================================
// Constants
// ============================================================================
static constexpr int INIT_W = 380;
static constexpr int INIT_H = 280;
static constexpr int HEADER_H = 160;
static constexpr int FOOTER_H = 50;
static constexpr int ICON_SIZE = 80;
static constexpr int ICON_X = 28;
static constexpr int ICON_Y = 40;
static constexpr int INFO_X = ICON_X + ICON_SIZE + 20; // 128
static constexpr int TEMP_Y = 40;
static int TEMP_SIZE = 40;
static constexpr int DESC_Y = 92;
static int DESC_SIZE = 17;
static constexpr int FEELS_Y = 116;
static int LABEL_SIZE = 15;
static constexpr int RESP_MAX = 65536;
static const char WTTR_HOST[] = "wttr.in";
// ============================================================================
// App state
// ============================================================================
enum class AppPhase { IDLE, LOADING, DONE, ERR };
static AppPhase g_phase = AppPhase::IDLE;
static char g_temp[32] = {};
static char g_desc[128] = {};
static char g_feels[48] = {};
static char g_location[128] = {};
static char g_status[256] = {};
static int g_win_w = INIT_W;
static int g_win_h = INIT_H;
static char* g_resp_buf = nullptr;
// Fonts
static TrueTypeFont* g_font = nullptr; // Roboto Medium
static TrueTypeFont* g_font_bold = nullptr; // Roboto Bold
// Weather icon (loaded per-fetch based on weather code)
static SvgIcon g_icon = {nullptr, 0, 0};
static char g_icon_name[64] = {};
// TLS state (lazy-init on first fetch)
static bool g_tls_ready = false;
static uint32_t g_server_ip = 0;
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
// ============================================================================
// Pixel buffer helpers
// ============================================================================
static void px_fill(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, Color c) {
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
int x1 = x + w > bw ? bw : x + w;
int y1 = y + h > bh ? bh : y + h;
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
px[row * bw + col] = v;
}
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int len, Color c) {
if (y < 0 || y >= bh) return;
uint32_t v = c.to_pixel();
int x1 = x + len > bw ? bw : x + len;
for (int col = x < 0 ? 0 : x; col < x1; col++)
px[y * bw + col] = v;
}
// Integer square root (Newton's method).
static int isqrt(int n) {
if (n <= 0) return 0;
int x = n, y = (x + 1) / 2;
while (y < x) { x = y; y = (x + n / x) / 2; }
return x;
}
// Fill a rounded rectangle directly onto the pixel buffer.
static void px_fill_rounded(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, int r, Color c) {
if (r <= 0) { px_fill(px, bw, bh, x, y, w, h, c); return; }
if (r > w / 2) r = w / 2;
if (r > h / 2) r = h / 2;
uint32_t v = c.to_pixel();
// Corner arc centers
int cx_l = x + r;
int cx_r = x + w - 1 - r;
for (int row = y; row < y + h; row++) {
if (row < 0 || row >= bh) continue;
int left, right;
if (row < y + r) {
int dy = y + r - row; // 1..r
int dx = isqrt(r * r - dy * dy);
left = cx_l - dx;
right = cx_r + dx + 1;
} else if (row >= y + h - r) {
int dy = row - (y + h - 1 - r); // 1..r
int dx = isqrt(r * r - dy * dy);
left = cx_l - dx;
right = cx_r + dx + 1;
} else {
left = x;
right = x + w;
}
if (left < 0) left = 0;
if (right > bw) right = bw;
for (int col = left; col < right; col++)
px[row * bw + col] = v;
}
}
// Alpha-composite an SvgIcon onto the pixel buffer.
static void draw_icon(uint32_t* px, int bw, int bh,
int dx, int dy, const SvgIcon& icon) {
if (!icon.pixels) return;
for (int row = 0; row < icon.height; row++) {
int ty = dy + row;
if (ty < 0 || ty >= bh) continue;
for (int col = 0; col < icon.width; col++) {
int tx = dx + col;
if (tx < 0 || tx >= bw) continue;
uint32_t s = icon.pixels[row * icon.width + col];
uint8_t sa = (s >> 24) & 0xFF;
if (sa == 0) continue;
if (sa == 255) { px[ty * bw + tx] = s; continue; }
uint8_t sr = (s >> 16) & 0xFF, sg = (s >> 8) & 0xFF, sb = s & 0xFF;
uint32_t d = px[ty * bw + tx];
uint8_t dr = (d >> 16) & 0xFF, dg = (d >> 8) & 0xFF, db = d & 0xFF;
uint32_t inv = 255 - sa;
px[ty * bw + tx] = 0xFF000000u
| (((sr * sa + dr * inv) / 255u) << 16)
| (((sg * sa + dg * inv) / 255u) << 8)
| ((sb * sa + db * inv) / 255u);
}
}
}
// ============================================================================
// 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
// ============================================================================
// Extract value of a simple JSON string field: "key":"value"
static int extract_json_string(const char* buf, int len, const char* key,
char* out, int maxOut) {
int klen = (int)strlen(key);
for (int i = 0; i < len - klen - 3; i++) {
if (buf[i] != '"') continue;
if (memcmp(buf + i + 1, key, klen) != 0) continue;
if (buf[i + 1 + klen] != '"') continue;
if (buf[i + 2 + klen] != ':') continue;
int p = i + 3 + klen;
while (p < len && (buf[p]==' ' || buf[p]=='\t')) p++;
if (p >= len || buf[p] != '"') continue;
p++;
int j = 0;
while (p < len && j < maxOut - 1 && buf[p] != '"') {
if (buf[p] == '\\' && p + 1 < len) { p++; out[j++] = buf[p]; }
else out[j++] = buf[p];
p++;
}
out[j] = '\0';
return j;
}
out[0] = '\0';
return 0;
}
// Find the first occurrence of needle in buf[0..len-1]. Returns offset or -1.
static int find_substr(const char* buf, int len, const char* needle) {
int nlen = (int)strlen(needle);
if (nlen > len) return -1;
for (int i = 0; i <= len - nlen; i++) {
if (memcmp(buf + i, needle, nlen) == 0) return i;
}
return -1;
}
// Extract the "value" string within a named JSON array-of-objects field.
// e.g., "weatherDesc":[{"value":"Partly cloudy"}] → "Partly cloudy"
// Works by finding section_key first, then the first "value" after it.
static int extract_array_value(const char* buf, int len, const char* section_key,
char* out, int maxOut) {
int pos = find_substr(buf, len, section_key);
if (pos < 0) { out[0] = '\0'; return 0; }
return extract_json_string(buf + pos, len - pos, "value", out, maxOut);
}
// Parse a signed integer from a decimal string.
static int parse_int(const char* s) {
int sign = 1, val = 0, i = 0;
if (s[0] == '-') { sign = -1; i = 1; }
while (s[i] >= '0' && s[i] <= '9') val = val * 10 + (s[i++] - '0');
return sign * val;
}
// ============================================================================
// Weather code → icon filename
// Maps wttr.in WMO-style weather codes to Flat-Remix panel icon names.
// ============================================================================
static const char* weather_code_to_icon(int code) {
switch (code) {
case 113: return "weather-clear.svg";
case 116: return "weather-few-clouds.svg";
case 119: return "weather-clouds.svg";
case 122: return "weather-overcast.svg";
case 143: return "weather-mist.svg";
case 248: case 260: return "weather-fog.svg";
case 176: case 263: case 266: case 353: return "weather-showers-scattered.svg";
case 293: case 296: case 299: case 302:
case 305: case 308: case 356: case 359: return "weather-showers.svg";
case 179: case 362: case 365: case 368: return "weather-snow-scattered.svg";
case 323: case 326: case 329: case 332:
case 335: case 338: case 371: case 374: return "weather-snow.svg";
case 227: case 230: return "weather-snow.svg";
case 182: case 311: case 314: case 317: case 320: return "weather-snow-rain.svg";
case 185: case 281: case 284: return "weather-freezing-rain.svg";
case 350: case 377: return "weather-hail.svg";
case 200: case 386: case 389: case 392: case 395: return "weather-storm.svg";
default: return "weather-none-available.svg";
}
}
// Load the weather icon for the given icon filename (caches the last result).
static void load_weather_icon(const char* icon_name) {
if (strcmp(g_icon_name, icon_name) == 0 && g_icon.pixels) return;
if (g_icon.pixels) svg_free(g_icon);
static char path[80];
snprintf(path, sizeof(path), "0:/icons/%s", icon_name);
// ColorScheme-Text paths (main shape) use this fill_color; accent classes
// (ColorScheme-Highlight=blue, ColorScheme-NeutralText=yellow, etc.) use their
// CSS-defined colors as parsed from the SVG's <style> block by svg_load.
static constexpr Color ICON_FG = Color::from_rgb(0x5C, 0x61, 0x6C); // dark neutral
g_icon = svg_load(path, ICON_SIZE, ICON_SIZE, ICON_FG);
int i = 0;
while (icon_name[i] && i < 63) { g_icon_name[i] = icon_name[i]; i++; }
g_icon_name[i] = '\0';
}
// ============================================================================
// UI scale
// ============================================================================
static void apply_scale(int scale) {
switch (scale) {
case 0: TEMP_SIZE=32; DESC_SIZE=14; LABEL_SIZE=12; break;
case 2: TEMP_SIZE=50; DESC_SIZE=21; LABEL_SIZE=19; break;
default: TEMP_SIZE=40; DESC_SIZE=17; LABEL_SIZE=15; break;
}
}
// ============================================================================
// Network fetch (blocking — called from the event loop)
// ============================================================================
static void do_fetch() {
// Lazy init: resolve DNS and load CA certificates once
if (!g_tls_ready) {
g_server_ip = zenith::resolve(WTTR_HOST);
if (g_server_ip == 0) {
snprintf(g_status, sizeof(g_status),
"Error: could not resolve %s", WTTR_HOST);
g_phase = AppPhase::ERR; return;
}
g_tas = tls::load_trust_anchors();
if (g_tas.count == 0) {
snprintf(g_status, sizeof(g_status), "Error: no CA certificates loaded");
g_phase = AppPhase::ERR; return;
}
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"
"User-Agent: ZenithOS/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");
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");
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;
// Extract core weather fields
static char temp_raw[16], feels_raw[16], code_raw[8];
extract_json_string(body, bodyLen, "temp_C", temp_raw, sizeof(temp_raw));
extract_json_string(body, bodyLen, "FeelsLikeC", feels_raw, sizeof(feels_raw));
extract_json_string(body, bodyLen, "weatherCode", code_raw, sizeof(code_raw));
extract_array_value(body, bodyLen, "\"weatherDesc\"", g_desc, sizeof(g_desc));
static char area[64], country[64];
extract_array_value(body, bodyLen, "\"areaName\"", area, sizeof(area));
extract_array_value(body, bodyLen, "\"country\"", country, sizeof(country));
// Degree sign is U+00B0 = 0xB0 in Latin-1 (single byte, within the 256-entry glyph cache)
snprintf(g_temp, sizeof(g_temp), "%s\xb0""C", temp_raw);
snprintf(g_feels, sizeof(g_feels), "Feels like: %s\xb0""C", feels_raw);
if (area[0] && country[0])
snprintf(g_location, sizeof(g_location), "%s, %s", area, country);
else if (area[0])
snprintf(g_location, sizeof(g_location), "%s", area);
else
snprintf(g_location, sizeof(g_location), "Unknown location");
// Load matching weather icon
int code = parse_int(code_raw);
load_weather_icon(weather_code_to_icon(code));
g_phase = AppPhase::DONE;
}
// ============================================================================
// Theme colors
// ============================================================================
static constexpr Color CONTENT_BG = Color::from_rgb(0xFF, 0xFF, 0xFF); // white
static constexpr Color FOOTER_BG = Color::from_rgb(0xF5, 0xF5, 0xF5); // light gray
static constexpr Color DIVIDER = Color::from_rgb(0xCC, 0xCC, 0xCC); // subtle border
static constexpr Color DARK_TEXT = Color::from_rgb(0x33, 0x33, 0x33);
static constexpr Color MID_TEXT = Color::from_rgb(0x88, 0x88, 0x88);
static constexpr Color HINT_TEXT = Color::from_rgb(0x99, 0x99, 0x99);
static constexpr Color ERR_TEXT = Color::from_rgb(0xCC, 0x22, 0x22);
static constexpr Color BTN_BG = Color::from_rgb(0x36, 0x7B, 0xF0); // accent blue
static constexpr Color WHITE_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF);
// ============================================================================
// Rendering
// ============================================================================
static constexpr int BTN_W = 110;
static constexpr int BTN_H = 28;
static constexpr int BTN_RADIUS = 6;
static void render(uint32_t* pixels) {
// ── Background ───────────────────────────────────────────────────────────
px_fill(pixels, g_win_w, g_win_h, 0, 0,
g_win_w, g_win_h - FOOTER_H, CONTENT_BG);
px_fill(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H,
g_win_w, FOOTER_H, FOOTER_BG);
// Divider between content and location strip
px_hline(pixels, g_win_w, g_win_h, 0, HEADER_H, g_win_w, DIVIDER);
// Divider above footer
px_hline(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H, g_win_w, DIVIDER);
if (!g_font) return;
// ── Main content area (y=0..HEADER_H) ────────────────────────────────────
if (g_phase == AppPhase::LOADING) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, HEADER_H / 2 - 9,
"Fetching weather data...", HINT_TEXT, 18);
} else if (g_phase == AppPhase::ERR) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, 20, g_status, ERR_TEXT, 15);
} else if (g_phase == AppPhase::IDLE) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, HEADER_H / 2 - 9,
"Click Refresh to check weather.", HINT_TEXT, 18);
} else { // DONE
// Weather icon
draw_icon(pixels, g_win_w, g_win_h, ICON_X, ICON_Y, g_icon);
// Temperature (large bold)
TrueTypeFont* temp_font = g_font_bold ? g_font_bold : g_font;
temp_font->draw_to_buffer(pixels, g_win_w, g_win_h,
INFO_X, TEMP_Y, g_temp, DARK_TEXT, TEMP_SIZE);
// Weather description
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
INFO_X, DESC_Y, g_desc, DARK_TEXT, DESC_SIZE);
// Feels like
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
INFO_X, FEELS_Y, g_feels, MID_TEXT, LABEL_SIZE);
}
// ── Location strip (y=HEADER_H..g_win_h-FOOTER_H) ────────────────────────
if (g_phase == AppPhase::DONE) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, HEADER_H + 14, g_location, DARK_TEXT, LABEL_SIZE);
}
// ── Refresh button (rounded, in footer) ───────────────────────────────────
int btn_x = (g_win_w - BTN_W) / 2;
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
px_fill_rounded(pixels, g_win_w, g_win_h,
btn_x, btn_y, BTN_W, BTN_H, BTN_RADIUS, BTN_BG);
if (g_phase == AppPhase::LOADING) {
const char* lbl = "Loading...";
int sw = g_font->measure_text(lbl, 14);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 14) / 2,
lbl, WHITE_TEXT, 14);
} else {
const char* lbl = "Refresh";
int sw = g_font->measure_text(lbl, 15);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 15) / 2,
lbl, WHITE_TEXT, 15);
}
}
// ============================================================================
// Entry point
// ============================================================================
extern "C" void _start() {
// Allocate response buffer from heap
g_resp_buf = (char*)malloc(RESP_MAX + 1);
if (!g_resp_buf) zenith::exit(1);
// Load fonts
auto load_font = [](const char* path) -> TrueTypeFont* {
TrueTypeFont* f = (TrueTypeFont*)zenith::malloc(sizeof(TrueTypeFont));
if (!f) return nullptr;
zenith::memset(f, 0, sizeof(TrueTypeFont));
if (!f->init(path)) { zenith::mfree(f); return nullptr; }
return f;
};
g_font = load_font("0:/fonts/Roboto-Medium.ttf");
g_font_bold = load_font("0:/fonts/Roboto-Bold.ttf");
if (!g_font) zenith::exit(1);
apply_scale(zenith::win_getscale());
// Create window
Zenith::WinCreateResult wres;
if (zenith::win_create("Weather", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
zenith::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
// Initial fetch on startup
g_phase = AppPhase::LOADING;
render(pixels);
zenith::win_present(win_id);
do_fetch();
// Event loop
while (true) {
Zenith::WinEvent ev;
int r = zenith::win_poll(win_id, &ev);
if (r < 0) break;
if (r == 0) {
zenith::sleep_ms(16);
render(pixels);
zenith::win_present(win_id);
continue;
}
if (ev.type == 3) break; // close
if (ev.type == 4) {
apply_scale(ev.scale.scale);
}
if (ev.type == 1) {
// Mouse — check for Refresh button click
bool just_clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
if (just_clicked && g_phase != AppPhase::LOADING) {
int btn_x = (g_win_w - BTN_W) / 2;
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
int mx = ev.mouse.x, my = ev.mouse.y;
if (mx >= btn_x && mx < btn_x + BTN_W &&
my >= btn_y && my < btn_y + BTN_H) {
g_phase = AppPhase::LOADING;
render(pixels);
zenith::win_present(win_id);
do_fetch();
}
}
}
render(pixels);
zenith::win_present(win_id);
}
if (g_icon.pixels) svg_free(g_icon);
zenith::win_destroy(win_id);
zenith::exit(0);
}
@@ -0,0 +1,35 @@
/*
* stb_truetype_impl.cpp
* Single compilation unit for stb_truetype in ZenithOS freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#include <cstdint>
#include <cstddef>
#include <zenith/heap.h>
#include <zenith/string.h>
#include <gui/stb_math.h>
// Override all stb_truetype dependencies before including the implementation
#define STBTT_ifloor(x) ((int) stb_floor(x))
#define STBTT_iceil(x) ((int) stb_ceil(x))
#define STBTT_sqrt(x) stb_sqrt(x)
#define STBTT_pow(x,y) stb_pow(x,y)
#define STBTT_fmod(x,y) stb_fmod(x,y)
#define STBTT_cos(x) stb_cos(x)
#define STBTT_acos(x) stb_acos(x)
#define STBTT_fabs(x) stb_fabs(x)
#define STBTT_malloc(x,u) ((void)(u), zenith::malloc(x))
#define STBTT_free(x,u) ((void)(u), zenith::mfree(x))
#define STBTT_memcpy(d,s,n) zenith::memcpy(d,s,n)
#define STBTT_memset(d,v,n) zenith::memset(d,v,n)
#define STBTT_strlen(x) zenith::slen(x)
#define STBTT_assert(x) ((void)(x))
#define STB_TRUETYPE_IMPLEMENTATION
#include <gui/stb_truetype.h>
+2 -1
View File
@@ -16,6 +16,7 @@ endif
# ---- Paths ----
BEARSSL := ../../lib/bearssl
TLS_LIB := ../../lib/tls
LIBC_LIB := ../../lib/libc
LIBC_INC := ../../include/libc
PROG_INC := ../../include
@@ -67,7 +68,7 @@ LDFLAGS := \
# ---- Libraries ----
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
# ---- Target ----
+14 -343
View File
@@ -10,9 +10,9 @@
#include <zenith/syscall.h>
#include <zenith/string.h>
#include <tls/tls.hpp>
extern "C" {
#include <bearssl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -37,14 +37,8 @@ struct WikiLine {
// ---- Global state (loaded once, reused across fetches) ----
struct TrustAnchors {
br_x509_trust_anchor* anchors;
size_t count;
size_t capacity;
};
static uint32_t g_serverIp = 0;
static TrustAnchors g_tas = {nullptr, 0, 0};
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
// ---- Screen buffer for flicker-free rendering ----
@@ -75,340 +69,20 @@ static void sb_cursor_to(int row, int col) {
sb_putc('H');
}
// ---- Trust anchor loading ----
// ---- Keyboard abort check for TLS ----
struct DerAccum {
unsigned char* data;
size_t len;
size_t cap;
};
static void der_append(void* ctx, const void* buf, size_t len) {
DerAccum* a = (DerAccum*)ctx;
if (a->len + len > a->cap) {
size_t newcap = a->cap * 2;
if (newcap < a->len + len) newcap = a->len + len + 4096;
unsigned char* nb = (unsigned char*)malloc(newcap);
if (!nb) return;
if (a->data) {
memcpy(nb, a->data, a->len);
free(a->data);
}
a->data = nb;
a->cap = newcap;
static bool check_keyboard_abort() {
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') return true;
}
memcpy(a->data + a->len, buf, len);
a->len += len;
return false;
}
struct DnAccum {
unsigned char* data;
size_t len;
size_t cap;
};
static void dn_append(void* ctx, const void* buf, size_t len) {
DnAccum* a = (DnAccum*)ctx;
if (a->len + len > a->cap) {
size_t newcap = a->cap * 2;
if (newcap < a->len + len) newcap = a->len + len + 256;
unsigned char* nb = (unsigned char*)malloc(newcap);
if (!nb) return;
if (a->data) {
memcpy(nb, a->data, a->len);
free(a->data);
}
a->data = nb;
a->cap = newcap;
}
memcpy(a->data + a->len, buf, len);
a->len += len;
}
static void ta_add(TrustAnchors* tas, const br_x509_trust_anchor* ta) {
if (tas->count >= tas->capacity) {
size_t newcap = tas->capacity == 0 ? 64 : tas->capacity * 2;
br_x509_trust_anchor* na = (br_x509_trust_anchor*)malloc(
newcap * sizeof(br_x509_trust_anchor));
if (!na) return;
if (tas->anchors) {
memcpy(na, tas->anchors, tas->count * sizeof(br_x509_trust_anchor));
free(tas->anchors);
}
tas->anchors = na;
tas->capacity = newcap;
}
tas->anchors[tas->count++] = *ta;
}
static bool process_cert_der(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_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;
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);
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);
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);
break;
}
default:
if (dn.data) free(dn.data);
return false;
}
ta_add(tas, &ta);
return true;
}
static TrustAnchors load_trust_anchors() {
TrustAnchors tas = {nullptr, 0, 0};
int fh = zenith::open("0:/etc/ca-certificates.crt");
if (fh < 0) return tas;
uint64_t fsize = zenith::getsize(fh);
if (fsize == 0 || fsize > 512 * 1024) {
zenith::close(fh);
return tas;
}
unsigned char* pem = (unsigned char*)malloc(fsize + 1);
if (!pem) { zenith::close(fh); return tas; }
zenith::read(fh, pem, 0, fsize);
zenith::close(fh);
pem[fsize] = 0;
static br_pem_decoder_context pc; // keep off stack
br_pem_decoder_init(&pc);
DerAccum der = {nullptr, 0, 0};
bool inCert = false;
size_t offset = 0;
while (offset < fsize) {
size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset);
offset += pushed;
int event = br_pem_decoder_event(&pc);
if (event == BR_PEM_BEGIN_OBJ) {
const char* name = br_pem_decoder_name(&pc);
inCert = (strcmp(name, "CERTIFICATE") == 0);
if (inCert) {
der.len = 0;
br_pem_decoder_setdest(&pc, der_append, &der);
} else {
br_pem_decoder_setdest(&pc, nullptr, nullptr);
}
} else if (event == BR_PEM_END_OBJ) {
if (inCert && der.len > 0)
process_cert_der(&tas, der.data, der.len);
inCert = false;
} else if (event == BR_PEM_ERROR) {
break;
}
}
if (der.data) free(der.data);
free(pem);
return tas;
}
// ---- Time conversion for certificate validation ----
static void get_bearssl_time(uint32_t* days, uint32_t* seconds) {
Zenith::DateTime dt;
zenith::gettime(&dt);
int y = dt.Year;
int m = dt.Month;
int d = dt.Day;
uint32_t total_days = 365 * (uint32_t)y
+ (uint32_t)(y / 4)
- (uint32_t)(y / 100)
+ (uint32_t)(y / 400);
const int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
for (int mo = 1; mo < m && mo <= 12; mo++)
total_days += mdays[mo];
bool leap = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
if (leap && m > 2) total_days++;
total_days += d - 1;
*days = total_days;
*seconds = (uint32_t)(dt.Hour * 3600 + dt.Minute * 60 + dt.Second);
}
// ---- TLS I/O ----
static int tls_send_all(int fd, const unsigned char* data, size_t len) {
size_t sent = 0;
uint64_t deadline = zenith::get_milliseconds() + 15000;
while (sent < len) {
int r = zenith::send(fd, data + sent, (uint32_t)(len - sent));
if (r > 0) { sent += r; deadline = zenith::get_milliseconds() + 15000; }
else if (r < 0) return -1;
else { if (zenith::get_milliseconds() >= deadline) return -1; zenith::sleep_ms(1); }
}
return (int)sent;
}
static int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) {
uint64_t deadline = zenith::get_milliseconds() + 15000;
while (true) {
int r = zenith::recv(fd, buf, (uint32_t)maxlen);
if (r > 0) return r;
if (r < 0) return -1;
if (zenith::get_milliseconds() >= deadline) return -1;
zenith::sleep_ms(1);
}
}
static int tls_exchange(int fd, br_ssl_engine_context* eng,
const char* request, int reqLen,
char* respBuf, int respMax) {
bool requestSent = false;
int respLen = 0;
uint64_t deadline = zenith::get_milliseconds() + 30000;
while (true) {
unsigned state = br_ssl_engine_current_state(eng);
if (state & BR_SSL_CLOSED) {
int err = br_ssl_engine_last_error(eng);
if (err != BR_ERR_OK && err != BR_ERR_IO) {
if (respLen == 0) return -1;
}
return respLen;
}
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
br_ssl_engine_close(eng);
return respLen > 0 ? respLen : -1;
}
}
if (state & BR_SSL_SENDREC) {
size_t len;
unsigned char* buf = br_ssl_engine_sendrec_buf(eng, &len);
int sent = tls_send_all(fd, buf, len);
if (sent < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
br_ssl_engine_sendrec_ack(eng, len);
deadline = zenith::get_milliseconds() + 30000;
continue;
}
if (state & BR_SSL_RECVAPP) {
size_t len;
unsigned char* buf = br_ssl_engine_recvapp_buf(eng, &len);
size_t toCopy = len;
if (respLen + (int)toCopy > respMax - 1)
toCopy = respMax - 1 - respLen;
if (toCopy > 0) { memcpy(respBuf + respLen, buf, toCopy); respLen += toCopy; }
br_ssl_engine_recvapp_ack(eng, len);
deadline = zenith::get_milliseconds() + 30000;
continue;
}
if ((state & BR_SSL_SENDAPP) && !requestSent) {
size_t len;
unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len);
size_t toWrite = (size_t)reqLen;
if (toWrite > len) toWrite = len;
memcpy(buf, request, toWrite);
br_ssl_engine_sendapp_ack(eng, toWrite);
br_ssl_engine_flush(eng, 0);
requestSent = true;
deadline = zenith::get_milliseconds() + 30000;
continue;
}
if (state & BR_SSL_RECVREC) {
size_t len;
unsigned char* buf = br_ssl_engine_recvrec_buf(eng, &len);
int got = tls_recv_some(fd, buf, len);
if (got < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
br_ssl_engine_recvrec_ack(eng, got);
deadline = zenith::get_milliseconds() + 30000;
continue;
}
if (zenith::get_milliseconds() >= deadline)
return respLen > 0 ? respLen : -1;
zenith::sleep_ms(1);
}
}
// ---- HTTPS fetch wrapper (reusable for multiple requests) ----
// ---- HTTPS fetch wrapper ----
static int wiki_fetch(const char* path, char* respBuf, int respMax) {
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) return -1;
if (zenith::connect(fd, g_serverIp, 443) < 0) {
zenith::closesocket(fd);
return -1;
}
br_ssl_client_context* cc = (br_ssl_client_context*)malloc(sizeof(br_ssl_client_context));
br_x509_minimal_context* xc = (br_x509_minimal_context*)malloc(sizeof(br_x509_minimal_context));
void* iobuf = malloc(BR_SSL_BUFSIZE_BIDI);
if (!cc || !xc || !iobuf) {
zenith::closesocket(fd);
return -1;
}
br_ssl_client_init_full(cc, xc, g_tas.anchors, g_tas.count);
uint32_t days, secs;
get_bearssl_time(&days, &secs);
br_x509_minimal_set_time(xc, days, secs);
unsigned char seed[32];
zenith::getrandom(seed, sizeof(seed));
br_ssl_engine_set_buffer(&cc->eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1);
br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed));
if (!br_ssl_client_reset(cc, WIKI_HOST, 0)) {
zenith::closesocket(fd);
free(cc); free(xc); free(iobuf);
return -1;
}
static char request[2560]; // keep off stack
int reqLen = snprintf(request, sizeof(request),
"GET %s HTTP/1.0\r\n"
@@ -418,12 +92,9 @@ static int wiki_fetch(const char* path, char* respBuf, int respMax) {
"Connection: close\r\n"
"\r\n",
path, WIKI_HOST);
int respLen = tls_exchange(fd, &cc->eng, request, reqLen, respBuf, respMax);
zenith::closesocket(fd);
free(cc); free(xc); free(iobuf);
return respLen;
return tls::https_fetch(WIKI_HOST, g_serverIp, 443,
request, reqLen, g_tas,
respBuf, respMax, check_keyboard_abort);
}
// ---- HTTP response parsing ----
@@ -1030,7 +701,7 @@ extern "C" void _start() {
zenith::exit(1);
}
g_tas = load_trust_anchors();
g_tas = tls::load_trust_anchors();
if (g_tas.count == 0) {
if (mode == MODE_DUMP) { zenith::print("\x01"); zenith::sleep_ms(100); zenith::exit(1); }
zenith::print("\033[1;31mError:\033[0m no CA certificates loaded\n");
Binary file not shown.
+2 -1
View File
@@ -16,6 +16,7 @@ endif
# ---- Paths ----
BEARSSL := ../../lib/bearssl
TLS_LIB := ../../lib/tls
LIBC_LIB := ../../lib/libc
LIBC_INC := ../../include/libc
PROG_INC := ../../include
@@ -66,7 +67,7 @@ LDFLAGS := \
# ---- Libraries ----
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
# ---- Source files ----
+27 -255
View File
@@ -10,9 +10,9 @@
#include <zenith/heap.h>
#include <gui/gui.hpp>
#include <gui/truetype.hpp>
#include <tls/tls.hpp>
extern "C" {
#include <bearssl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -27,11 +27,11 @@ using namespace gui::colors;
static constexpr int INIT_W = 820;
static constexpr int INIT_H = 580;
static constexpr int TOOLBAR_H = 42;
static int TOOLBAR_H = 42;
static constexpr int SCROLLBAR_W = 14;
static constexpr int FONT_SIZE = 18;
static constexpr int TITLE_SIZE = 32;
static constexpr int SECTION_SIZE = 24;
static int FONT_SIZE = 18;
static int TITLE_SIZE = 32;
static int SECTION_SIZE = 24;
static constexpr int TEXT_PAD = 16;
static constexpr int RESP_MAX = 131072;
static constexpr int MAX_LINES = 2000;
@@ -79,13 +79,7 @@ static TrueTypeFont* g_font_serif = nullptr; // NotoSerif SemiBold (headings)
// TLS state (lazy-init on first search)
static bool g_tls_ready = false;
static uint32_t g_server_ip = 0;
struct TrustAnchors {
br_x509_trust_anchor* anchors;
size_t count;
size_t capacity;
};
static TrustAnchors g_tas = {nullptr, 0, 0};
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
// ============================================================================
// Pixel buffer helpers
@@ -124,252 +118,23 @@ static void px_rect_outline(uint32_t* px, int bw, int x, int y, int w, int h, Co
}
// ============================================================================
// Trust anchor loading
// UI scale
// ============================================================================
struct DerAccum { unsigned char* data; size_t len, cap; };
struct DnAccum { unsigned char* data; size_t len, cap; };
static void der_append(void* ctx, const void* buf, size_t len) {
DerAccum* a = (DerAccum*)ctx;
if (a->len + len > a->cap) {
size_t nc = a->cap * 2;
if (nc < a->len + len) nc = a->len + len + 4096;
unsigned char* nb = (unsigned char*)malloc(nc);
if (!nb) return;
if (a->data) { memcpy(nb, a->data, a->len); free(a->data); }
a->data = nb; a->cap = nc;
static void apply_scale(int scale) {
switch (scale) {
case 0: FONT_SIZE=14; TITLE_SIZE=26; SECTION_SIZE=20; TOOLBAR_H=34; break;
case 2: FONT_SIZE=22; TITLE_SIZE=40; SECTION_SIZE=30; TOOLBAR_H=52; break;
default: FONT_SIZE=18; TITLE_SIZE=32; SECTION_SIZE=24; TOOLBAR_H=42; break;
}
memcpy(a->data + a->len, buf, len);
a->len += len;
}
static void dn_append(void* ctx, const void* buf, size_t len) {
DnAccum* a = (DnAccum*)ctx;
if (a->len + len > a->cap) {
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 (a->data) { memcpy(nb, a->data, a->len); free(a->data); }
a->data = nb; a->cap = nc;
}
memcpy(a->data + a->len, buf, len);
a->len += len;
}
static void ta_add(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 (tas->anchors) { memcpy(na, tas->anchors, tas->count * sizeof(*na)); free(tas->anchors); }
tas->anchors = na; tas->capacity = nc;
}
tas->anchors[tas->count++] = *ta;
}
static bool process_cert_der(TrustAnchors* tas, const unsigned char* der, size_t der_len) {
static br_x509_decoder_context dc;
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_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;
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);
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);
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);
break;
default:
if (dn.data) free(dn.data);
return false;
}
ta_add(tas, &ta);
return true;
}
static TrustAnchors load_trust_anchors() {
TrustAnchors tas = {nullptr, 0, 0};
int fh = zenith::open("0:/etc/ca-certificates.crt");
if (fh < 0) return tas;
uint64_t fsize = zenith::getsize(fh);
if (fsize == 0 || fsize > 512 * 1024) { zenith::close(fh); return tas; }
unsigned char* pem = (unsigned char*)malloc(fsize + 1);
if (!pem) { zenith::close(fh); return tas; }
zenith::read(fh, pem, 0, fsize);
zenith::close(fh);
pem[fsize] = 0;
static br_pem_decoder_context pc;
br_pem_decoder_init(&pc);
DerAccum der = {nullptr, 0, 0};
bool inCert = false;
size_t offset = 0;
while (offset < fsize) {
size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset);
offset += pushed;
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;
} 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.data) free(der.data);
free(pem);
return tas;
if (g_font) g_line_h = g_font->get_line_height(FONT_SIZE) + 4;
}
// ============================================================================
// BearSSL time
// HTTPS fetch wrapper
// ============================================================================
static void get_bearssl_time(uint32_t* days, uint32_t* seconds) {
Zenith::DateTime dt;
zenith::gettime(&dt);
int y = dt.Year, m = dt.Month, d = dt.Day;
uint32_t total = 365u * (uint32_t)y
+ (uint32_t)(y/4) - (uint32_t)(y/100) + (uint32_t)(y/400);
const int md[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
for (int mo = 1; mo < m && mo <= 12; mo++) total += md[mo];
if (y%4==0 && (y%100!=0 || y%400==0) && m > 2) total++;
total += d - 1;
*days = total;
*seconds = (uint32_t)(dt.Hour*3600 + dt.Minute*60 + dt.Second);
}
// ============================================================================
// TLS I/O
// ============================================================================
static int tls_send_all(int fd, const unsigned char* data, size_t len) {
size_t sent = 0;
uint64_t deadline = zenith::get_milliseconds() + 15000;
while (sent < len) {
int r = zenith::send(fd, data + sent, (uint32_t)(len - sent));
if (r > 0) { sent += r; deadline = zenith::get_milliseconds() + 15000; }
else if (r < 0) return -1;
else { if (zenith::get_milliseconds() >= deadline) return -1; zenith::sleep_ms(1); }
}
return (int)sent;
}
static int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) {
uint64_t deadline = zenith::get_milliseconds() + 15000;
while (true) {
int r = zenith::recv(fd, buf, (uint32_t)maxlen);
if (r > 0) return r;
if (r < 0) return -1;
if (zenith::get_milliseconds() >= deadline) return -1;
zenith::sleep_ms(1);
}
}
static int tls_exchange(int fd, br_ssl_engine_context* eng,
const char* request, int reqLen,
char* respBuf, int respMax) {
bool requestSent = false;
int respLen = 0;
uint64_t deadline = zenith::get_milliseconds() + 30000;
while (true) {
unsigned state = br_ssl_engine_current_state(eng);
if (state & BR_SSL_CLOSED) {
int err = br_ssl_engine_last_error(eng);
if (err != BR_ERR_OK && err != BR_ERR_IO && respLen == 0) return -1;
return respLen;
}
if (state & BR_SSL_SENDREC) {
size_t len; unsigned char* buf = br_ssl_engine_sendrec_buf(eng, &len);
int sent = tls_send_all(fd, buf, len);
if (sent < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
br_ssl_engine_sendrec_ack(eng, len);
deadline = zenith::get_milliseconds() + 30000; continue;
}
if (state & BR_SSL_RECVAPP) {
size_t len; unsigned char* buf = br_ssl_engine_recvapp_buf(eng, &len);
size_t toCopy = len;
if (respLen + (int)toCopy > respMax - 1) toCopy = respMax - 1 - respLen;
if (toCopy > 0) { memcpy(respBuf + respLen, buf, toCopy); respLen += toCopy; }
br_ssl_engine_recvapp_ack(eng, len);
deadline = zenith::get_milliseconds() + 30000; continue;
}
if ((state & BR_SSL_SENDAPP) && !requestSent) {
size_t len; unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len);
size_t toWrite = (size_t)reqLen;
if (toWrite > len) toWrite = len;
memcpy(buf, request, toWrite);
br_ssl_engine_sendapp_ack(eng, toWrite);
br_ssl_engine_flush(eng, 0);
requestSent = true;
deadline = zenith::get_milliseconds() + 30000; continue;
}
if (state & BR_SSL_RECVREC) {
size_t len; unsigned char* buf = br_ssl_engine_recvrec_buf(eng, &len);
int got = tls_recv_some(fd, buf, len);
if (got < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
br_ssl_engine_recvrec_ack(eng, got);
deadline = zenith::get_milliseconds() + 30000; continue;
}
if (zenith::get_milliseconds() >= deadline) return respLen > 0 ? respLen : -1;
zenith::sleep_ms(1);
}
}
static int wiki_fetch(const char* path, char* respBuf, int respMax) {
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) return -1;
if (zenith::connect(fd, g_server_ip, 443) < 0) { zenith::closesocket(fd); return -1; }
br_ssl_client_context* cc = (br_ssl_client_context*)malloc(sizeof(*cc));
br_x509_minimal_context* xc = (br_x509_minimal_context*)malloc(sizeof(*xc));
void* iobuf = malloc(BR_SSL_BUFSIZE_BIDI);
if (!cc || !xc || !iobuf) {
if (cc) free(cc); if (xc) free(xc); if (iobuf) free(iobuf);
zenith::closesocket(fd); return -1;
}
br_ssl_client_init_full(cc, xc, g_tas.anchors, g_tas.count);
uint32_t days, secs;
get_bearssl_time(&days, &secs);
br_x509_minimal_set_time(xc, days, secs);
unsigned char seed[32];
zenith::getrandom(seed, sizeof(seed));
br_ssl_engine_set_buffer(&cc->eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1);
br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed));
if (!br_ssl_client_reset(cc, WIKI_HOST, 0)) {
zenith::closesocket(fd); free(cc); free(xc); free(iobuf); return -1;
}
static char request[2560];
int reqLen = snprintf(request, sizeof(request),
"GET %s HTTP/1.0\r\n"
@@ -379,11 +144,9 @@ static int wiki_fetch(const char* path, char* respBuf, int respMax) {
"Connection: close\r\n"
"\r\n",
path, WIKI_HOST);
int respLen = tls_exchange(fd, &cc->eng, request, reqLen, respBuf, respMax);
zenith::closesocket(fd);
free(cc); free(xc); free(iobuf);
return respLen;
return tls::https_fetch(WIKI_HOST, g_server_ip, 443,
request, reqLen, g_tas,
respBuf, respMax);
}
// ============================================================================
@@ -624,7 +387,7 @@ static void do_search(const char* query) {
"Error: could not resolve en.wikipedia.org");
g_phase = AppPhase::ERR; return;
}
g_tas = load_trust_anchors();
g_tas = tls::load_trust_anchors();
if (g_tas.count == 0) {
snprintf(g_status, sizeof(g_status), "Error: no CA certificates loaded");
g_phase = AppPhase::ERR; return;
@@ -793,6 +556,8 @@ extern "C" void _start() {
g_line_h = g_font->get_line_height(FONT_SIZE) + 4;
apply_scale(zenith::win_getscale());
// Create window
Zenith::WinCreateResult wres;
if (zenith::win_create("Wikipedia", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
@@ -823,6 +588,13 @@ extern "C" void _start() {
// ---- Handle event ----
if (ev.type == 3) break; // close
if (ev.type == 4) {
apply_scale(ev.scale.scale);
if (g_phase == AppPhase::DONE && g_line_count > 0) {
build_display_lines(g_title, g_extract_buf, g_extract_len);
}
}
if (ev.type == 2) {
int new_w = ev.resize.w;
int new_h = ev.resize.h;