mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-26 17:12:35 +00:00
Compare commits
26
Commits
v0.0.1-alpha
...
main
No files matched your search
@@ -9,3 +9,7 @@ build/
|
||||
.DS_Store
|
||||
.system_generated/
|
||||
.config
|
||||
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -4,6 +4,14 @@
|
||||
# Features: Linux Kconfig TUI (make configure / make menuconfig), dynamic Kbuild
|
||||
# =============================================================================
|
||||
|
||||
# Kernel version, Linux-style. NAME is the release codename; keep these in sync
|
||||
# with include/init/version.h.
|
||||
VERSION = 1
|
||||
PATCHLEVEL = 0
|
||||
SUBLEVEL = 0
|
||||
EXTRAVERSION = -stable
|
||||
NAME = Titan
|
||||
|
||||
-include .config
|
||||
|
||||
# Architecture Selection (Default: x86_64)
|
||||
@@ -125,13 +133,15 @@ CORE_SRCS = kernel/main.c kernel/task.c kernel/sync.c kernel/timer.c kernel/prin
|
||||
kernel/panic.c kernel/trace.c kernel/sched.c kernel/cron.c kernel/syscall.c \
|
||||
kernel/syslog.c kernel/module.c kernel/kobject.c kernel/metrics.c kernel/namespace.c \
|
||||
kernel/signal.c kernel/workqueue.c kernel/tsc.c kernel/vt_art.c \
|
||||
kernel/sub/sub_runtime.c kernel/sub/sub_vm.c \
|
||||
kernel/wait.c kernel/futex.c kernel/rcu.c kernel/ktest.c \
|
||||
kernel/sub/sub_runtime.c kernel/sub/sub_vm.c kernel/nt/ob.c kernel/nt/reg.c kernel/nt/io.c \
|
||||
kernel/wait.c kernel/futex.c kernel/rcu.c kernel/ktest.c kernel/time/ktime.c \
|
||||
kernel/elf.c kernel/exec.c \
|
||||
lib/string.c lib/vsprintf.c lib/bitmap.c lib/rbtree.c lib/kfifo.c lib/hashtable.c lib/font8x8.c \
|
||||
lib/inflate.c lib/image.c lib/jpeg.c \
|
||||
init/cmdline.c init/service.c init/bootlogo.c init/logo_data.c usr/initramfs.c block/block.c block/elevator.c \
|
||||
ipc/sem.c ipc/shm.c ipc/shm_posix.c ipc/pipe.c ipc/ipc.c ipc/msg.c \
|
||||
crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c sound/beep.c
|
||||
crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c \
|
||||
crypto/x25519.c crypto/chacha20poly1305.c crypto/hkdf.c sound/beep.c
|
||||
|
||||
# If no .config exists, default all core options to y
|
||||
ifeq ($(wildcard .config),)
|
||||
@@ -227,7 +237,7 @@ endif
|
||||
|
||||
# Network Subsystem
|
||||
ifeq ($(CONFIG_NET_STACK), y)
|
||||
CONFIG_SRCS-y += net/core/net.c net/core/socket.c net/ipv4/tcp.c net/ipv4/udp.c net/ipv4/dhcp.c net/ipv4/dns.c net/ipv4/dns_cache.c
|
||||
CONFIG_SRCS-y += net/core/net.c net/core/socket.c net/ipv4/tcp.c net/ipv4/udp.c net/ipv4/dhcp.c net/ipv4/dns.c net/ipv4/dns_cache.c net/tls.c net/http_client.c
|
||||
endif
|
||||
ifeq ($(CONFIG_NET_FILTER), y)
|
||||
CONFIG_SRCS-y += net/filter.c
|
||||
@@ -277,7 +287,8 @@ endif
|
||||
# Common Input/Display/Power/GPU/Networking Drivers
|
||||
CONFIG_SRCS-y += drivers/char/tty.c drivers/char/keyboard.c drivers/char/serial.c drivers/char/vga.c \
|
||||
drivers/input/mouse.c drivers/power/acpi.c drivers/power/cpufreq.c drivers/rtc/rtc.c drivers/sound/speaker.c \
|
||||
drivers/net/e1000e.c drivers/virtio/virtio_gpu.c drivers/virtio/virtio_input.c
|
||||
drivers/net/e1000e.c drivers/virtio/virtio_gpu.c drivers/virtio/virtio_input.c \
|
||||
drivers/timer/hpet.c drivers/char/lpt.c drivers/char/nvram.c
|
||||
|
||||
# Userland & GUI Desktop Subsystem
|
||||
ifeq ($(CONFIG_USERLAND_LAZYBOX), y)
|
||||
@@ -285,7 +296,8 @@ ifeq ($(CONFIG_USERLAND_LAZYBOX), y)
|
||||
endif
|
||||
|
||||
CONFIG_SRCS-y += gui/gui.c gui/gui_gfx.c gui/gui_cursor.c gui/gui_wm.c gui/gui_desktop.c gui/gui_apps.c \
|
||||
gui/gui_widgets.c gui/gui_icons.c gui/gui_dialog.c gui/gui_apps_ext.c gui/gui_terminal.c
|
||||
gui/gui_widgets.c gui/gui_icons.c gui/gui_dialog.c gui/gui_apps_ext.c gui/gui_terminal.c \
|
||||
gui/gui_app_sorry.c gui/gui_app_netmon.c gui/gui_app_web.c
|
||||
|
||||
# Deduplicate
|
||||
CONFIG_SRCS-y := $(sort $(CONFIG_SRCS-y))
|
||||
@@ -330,7 +342,7 @@ DEPS = $(C_OBJS:.o=.d) $(CPP_OBJS:.o=.d) $(S_OBJS:.o=.d)
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
.PHONY: all clean run run-fullscreen debug info configure menuconfig config tui nconfig defconfig x86_64_defconfig aarch64_defconfig armv8_defconfig armv8i_defconfig armv81_defconfig arm32_defconfig qemu help
|
||||
.PHONY: all clean run run-headless run-fullscreen run-vnc run-server debug info configure menuconfig config tui nconfig defconfig x86_64_defconfig aarch64_defconfig armv8_defconfig armv8i_defconfig armv81_defconfig arm32_defconfig qemu help
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
@@ -448,24 +460,29 @@ $(IMAGE): $(BUILD_DIR)/boot/boot.bin $(BUILD_DIR)/boot/stage2.bin $(BUILD_DIR)/k
|
||||
# -----------------------------------------------------------------------------
|
||||
# Execution & Emulation Targets
|
||||
# -----------------------------------------------------------------------------
|
||||
run qemu: $(TARGET)
|
||||
$(QEMU_CMD)
|
||||
|
||||
# -vga std gives the Bochs VBE adapter the kernel drives. vgamem_mb is raised
|
||||
# because a 1280x720 32-bit framebuffer needs more than the 8 MB default.
|
||||
QEMU_VGA = -device VGA,vgamem_mb=32
|
||||
QEMU_VGA = -device VGA,vgamem_mb=64
|
||||
|
||||
run-gui: $(TARGET)
|
||||
# `make run` boots straight into the SUB-OS Graphical Desktop Environment. The
|
||||
# guest window scales with zoom-to-fit; pass `nogui`/`text` on the kernel
|
||||
# command line for the emergency TTY. Use `make run-headless` for serial only.
|
||||
run qemu: $(TARGET)
|
||||
ifeq ($(ARCH), x86_64)
|
||||
qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \
|
||||
qemu-system-x86_64 -drive format=raw,file=$(IMAGE) -m 512M \
|
||||
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
|
||||
-device e1000,netdev=net0 $(QEMU_VGA) -display gtk,zoom-to-fit=on -serial stdio
|
||||
else ifneq ($(filter $(ARCH), aarch64 arm64),)
|
||||
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio
|
||||
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 256M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio
|
||||
else
|
||||
qemu-system-arm -M virt -cpu cortex-a15 -m 128M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio
|
||||
qemu-system-arm -M virt -cpu cortex-a15 -m 256M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio
|
||||
endif
|
||||
|
||||
# Headless: no graphical window, kernel console on the serial line only. Boot
|
||||
# with `nogui` on the kernel command line to reach the TTY in this mode.
|
||||
run-headless: $(TARGET)
|
||||
$(QEMU_CMD)
|
||||
|
||||
# Fullscreen without distortion: zoom-to-fit scales the guest while preserving
|
||||
# its aspect ratio, letterboxing rather than stretching on a wider panel.
|
||||
run-fullscreen: $(TARGET)
|
||||
@@ -521,7 +538,10 @@ help:
|
||||
@echo " make aarch64_defconfig- Load AArch64 default configuration"
|
||||
@echo " make armv8i_defconfig - Load ARMv8i (32-bit ARM) default configuration"
|
||||
@echo " make all [ARCH=...] - Compile kernel image for selected architecture"
|
||||
@echo " make qemu [ARCH=...] - Boot the compiled kernel in QEMU emulator"
|
||||
@echo " make run [ARCH=...] - Boot into the SUB-OS Graphical Desktop in QEMU"
|
||||
@echo " make run-headless - Boot in QEMU with serial console only (no window)"
|
||||
@echo " make run-fullscreen - Boot the desktop in a full-screen QEMU window"
|
||||
@echo " make run-vnc - Boot the desktop and expose it over VNC :0"
|
||||
@echo " make clean - Remove all compiled objects and disk images"
|
||||
@echo " make info - Display build configuration and source metrics"
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<div align="center">
|
||||
|
||||
<img src="assets/logo_SUB_OS.png" alt="SUB-OS logo" width="240" height="240" />
|
||||
|
||||
# SUB-OS: Modular Monolithic Linux-Compatible Operating System
|
||||
|
||||
```text
|
||||
_____ _ _ ____ ____ _____
|
||||
/ ____| | | | _ \ / __ \ / ____|
|
||||
| (___ | | | | |_) |______| | | | (___
|
||||
\___ \| | | | _ <|______| | | |\___ \
|
||||
____) | |__| | |_) | | |__| |____) |
|
||||
|_____/ \____/|____/ \____/|_____/
|
||||
```
|
||||
</div>
|
||||
|
||||
[]()
|
||||
[]()
|
||||
@@ -15,8 +12,8 @@
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[](https://github.com/subhobhai943/SUB-OS/releases/tag/v0.0.1-beta)
|
||||
[]()
|
||||
[](https://github.com/subhobhai943/SUB-OS/releases/tag/v1.0.0)
|
||||
[]()
|
||||
[]()
|
||||
[](LICENSE)
|
||||
@@ -272,7 +269,27 @@ make run-vnc # Headless: VNC server on localhost:5900
|
||||
## 📦 Official Release
|
||||
|
||||
Download precompiled kernel binaries and bootable disk images from GitHub:
|
||||
👉 **[SUB-OS v0.0.1-beta Release](https://github.com/subhobhai943/SUB-OS/releases/tag/v0.0.1-beta)**
|
||||
👉 **[SUB-OS v1.0.0 — First Stable Release](https://github.com/subhobhai943/SUB-OS/releases/tag/v1.0.0)**
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
| Document | What's inside |
|
||||
|---|---|
|
||||
| 📝 **[Contributing Guide](docs/CONTRIBUTING.md)** | How to build, code style, kernel gotchas, commit & PR conventions |
|
||||
| 🔒 **[Security Policy](docs/SECURITY.md)** | Supported versions, how to report a vulnerability, known caveats |
|
||||
| 🗺️ **[Roadmap](docs/ROADMAP.md)** | What shipped in v1.0.0 and what's planned next |
|
||||
| 🤖 **[Autonomous Dev Roadmap](docs/AUTODEV_ROADMAP.md)** | Fine-grained per-session engineering backlog |
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please read the **[Contributing Guide](docs/CONTRIBUTING.md)**
|
||||
before opening a pull request, and see the **[Roadmap](docs/ROADMAP.md)** for
|
||||
where help is most valuable. Found a security issue? Follow the
|
||||
**[Security Policy](docs/SECURITY.md)** — do not open a public issue.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* ChaCha20-Poly1305 AEAD, RFC 8439.
|
||||
*
|
||||
* The record-protection cipher for the TLS 1.3 client. ChaCha20 is a pure
|
||||
* add-rotate-xor stream cipher (no tables, so no data-dependent timing), and
|
||||
* Poly1305 is its one-time authenticator; together they form the AEAD that
|
||||
* encrypts and authenticates every TLS record. All integer arithmetic, so it
|
||||
* is safe in the freestanding kernel.
|
||||
*
|
||||
* Poly1305 here is the 32-bit "donna" formulation (public domain), which needs
|
||||
* only 64-bit intermediate products.
|
||||
*/
|
||||
#include <crypto/crypto.h>
|
||||
#include <lib/string.h>
|
||||
|
||||
// ===========================================================================
|
||||
// ChaCha20
|
||||
// ===========================================================================
|
||||
static inline uint32_t rotl32(uint32_t x, int n) {
|
||||
return (x << n) | (x >> (32 - n));
|
||||
}
|
||||
|
||||
static inline uint32_t load32_le(const uint8_t* p) {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static inline void store32_le(uint8_t* p, uint32_t v) {
|
||||
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
#define QR(a, b, c, d) \
|
||||
a += b; d ^= a; d = rotl32(d, 16); \
|
||||
c += d; b ^= c; b = rotl32(b, 12); \
|
||||
a += b; d ^= a; d = rotl32(d, 8); \
|
||||
c += d; b ^= c; b = rotl32(b, 7)
|
||||
|
||||
static void chacha20_block(const uint32_t in[16], uint8_t out[64]) {
|
||||
uint32_t x[16];
|
||||
for (int i = 0; i < 16; i++) x[i] = in[i];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
QR(x[0], x[4], x[8], x[12]);
|
||||
QR(x[1], x[5], x[9], x[13]);
|
||||
QR(x[2], x[6], x[10], x[14]);
|
||||
QR(x[3], x[7], x[11], x[15]);
|
||||
QR(x[0], x[5], x[10], x[15]);
|
||||
QR(x[1], x[6], x[11], x[12]);
|
||||
QR(x[2], x[7], x[8], x[13]);
|
||||
QR(x[3], x[4], x[9], x[14]);
|
||||
}
|
||||
for (int i = 0; i < 16; i++) store32_le(out + 4 * i, x[i] + in[i]);
|
||||
}
|
||||
|
||||
static void chacha20_init(uint32_t state[16], const uint8_t key[32],
|
||||
uint32_t counter, const uint8_t nonce[12]) {
|
||||
state[0] = 0x61707865; state[1] = 0x3320646e;
|
||||
state[2] = 0x79622d32; state[3] = 0x6b206574;
|
||||
for (int i = 0; i < 8; i++) state[4 + i] = load32_le(key + 4 * i);
|
||||
state[12] = counter;
|
||||
state[13] = load32_le(nonce + 0);
|
||||
state[14] = load32_le(nonce + 4);
|
||||
state[15] = load32_le(nonce + 8);
|
||||
}
|
||||
|
||||
// Encrypt/decrypt src into dst (they may alias) starting at block counter.
|
||||
void chacha20_xor(uint8_t* dst, const uint8_t* src, size_t len,
|
||||
const uint8_t key[32], uint32_t counter, const uint8_t nonce[12]) {
|
||||
uint32_t state[16];
|
||||
uint8_t block[64];
|
||||
chacha20_init(state, key, counter, nonce);
|
||||
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
chacha20_block(state, block);
|
||||
state[12]++; // next block counter
|
||||
size_t n = len - off < 64 ? len - off : 64;
|
||||
for (size_t i = 0; i < n; i++) dst[off + i] = src[off + i] ^ block[i];
|
||||
off += n;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Poly1305 (32-bit donna)
|
||||
// ===========================================================================
|
||||
typedef struct {
|
||||
uint32_t r[5];
|
||||
uint32_t h[5];
|
||||
uint32_t pad[4];
|
||||
size_t leftover;
|
||||
uint8_t buffer[16];
|
||||
uint8_t final;
|
||||
} poly1305_ctx;
|
||||
|
||||
static void poly1305_init(poly1305_ctx* st, const uint8_t key[32]) {
|
||||
st->r[0] = (load32_le(&key[0])) & 0x3ffffff;
|
||||
st->r[1] = (load32_le(&key[3]) >> 2) & 0x3ffff03;
|
||||
st->r[2] = (load32_le(&key[6]) >> 4) & 0x3ffc0ff;
|
||||
st->r[3] = (load32_le(&key[9]) >> 6) & 0x3f03fff;
|
||||
st->r[4] = (load32_le(&key[12]) >> 8) & 0x00fffff;
|
||||
|
||||
st->h[0] = st->h[1] = st->h[2] = st->h[3] = st->h[4] = 0;
|
||||
st->pad[0] = load32_le(&key[16]);
|
||||
st->pad[1] = load32_le(&key[20]);
|
||||
st->pad[2] = load32_le(&key[24]);
|
||||
st->pad[3] = load32_le(&key[28]);
|
||||
st->leftover = 0;
|
||||
st->final = 0;
|
||||
}
|
||||
|
||||
static void poly1305_blocks(poly1305_ctx* st, const uint8_t* m, size_t bytes) {
|
||||
const uint32_t hibit = st->final ? 0 : (1UL << 24);
|
||||
uint32_t r0 = st->r[0], r1 = st->r[1], r2 = st->r[2], r3 = st->r[3], r4 = st->r[4];
|
||||
uint32_t s1 = r1 * 5, s2 = r2 * 5, s3 = r3 * 5, s4 = r4 * 5;
|
||||
uint32_t h0 = st->h[0], h1 = st->h[1], h2 = st->h[2], h3 = st->h[3], h4 = st->h[4];
|
||||
|
||||
while (bytes >= 16) {
|
||||
uint64_t d0, d1, d2, d3, d4;
|
||||
uint32_t c;
|
||||
|
||||
h0 += (load32_le(m + 0)) & 0x3ffffff;
|
||||
h1 += (load32_le(m + 3) >> 2) & 0x3ffffff;
|
||||
h2 += (load32_le(m + 6) >> 4) & 0x3ffffff;
|
||||
h3 += (load32_le(m + 9) >> 6) & 0x3ffffff;
|
||||
h4 += (load32_le(m + 12) >> 8) | hibit;
|
||||
|
||||
d0 = (uint64_t)h0 * r0 + (uint64_t)h1 * s4 + (uint64_t)h2 * s3 + (uint64_t)h3 * s2 + (uint64_t)h4 * s1;
|
||||
d1 = (uint64_t)h0 * r1 + (uint64_t)h1 * r0 + (uint64_t)h2 * s4 + (uint64_t)h3 * s3 + (uint64_t)h4 * s2;
|
||||
d2 = (uint64_t)h0 * r2 + (uint64_t)h1 * r1 + (uint64_t)h2 * r0 + (uint64_t)h3 * s4 + (uint64_t)h4 * s3;
|
||||
d3 = (uint64_t)h0 * r3 + (uint64_t)h1 * r2 + (uint64_t)h2 * r1 + (uint64_t)h3 * r0 + (uint64_t)h4 * s4;
|
||||
d4 = (uint64_t)h0 * r4 + (uint64_t)h1 * r3 + (uint64_t)h2 * r2 + (uint64_t)h3 * r1 + (uint64_t)h4 * r0;
|
||||
|
||||
c = (uint32_t)(d0 >> 26); h0 = (uint32_t)d0 & 0x3ffffff;
|
||||
d1 += c; c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & 0x3ffffff;
|
||||
d2 += c; c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & 0x3ffffff;
|
||||
d3 += c; c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & 0x3ffffff;
|
||||
d4 += c; c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & 0x3ffffff;
|
||||
h0 += c * 5; c = (h0 >> 26); h0 &= 0x3ffffff;
|
||||
h1 += c;
|
||||
|
||||
m += 16;
|
||||
bytes -= 16;
|
||||
}
|
||||
|
||||
st->h[0] = h0; st->h[1] = h1; st->h[2] = h2; st->h[3] = h3; st->h[4] = h4;
|
||||
}
|
||||
|
||||
static void poly1305_update(poly1305_ctx* st, const uint8_t* m, size_t bytes) {
|
||||
if (st->leftover) {
|
||||
size_t want = 16 - st->leftover;
|
||||
if (want > bytes) want = bytes;
|
||||
for (size_t i = 0; i < want; i++) st->buffer[st->leftover + i] = m[i];
|
||||
bytes -= want;
|
||||
m += want;
|
||||
st->leftover += want;
|
||||
if (st->leftover < 16) return;
|
||||
poly1305_blocks(st, st->buffer, 16);
|
||||
st->leftover = 0;
|
||||
}
|
||||
if (bytes >= 16) {
|
||||
size_t want = bytes & ~(size_t)15;
|
||||
poly1305_blocks(st, m, want);
|
||||
m += want;
|
||||
bytes -= want;
|
||||
}
|
||||
for (size_t i = 0; i < bytes; i++) st->buffer[st->leftover + i] = m[i];
|
||||
st->leftover += bytes;
|
||||
}
|
||||
|
||||
static void poly1305_finish(poly1305_ctx* st, uint8_t mac[16]) {
|
||||
if (st->leftover) {
|
||||
size_t i = st->leftover;
|
||||
st->buffer[i++] = 1;
|
||||
for (; i < 16; i++) st->buffer[i] = 0;
|
||||
st->final = 1;
|
||||
poly1305_blocks(st, st->buffer, 16);
|
||||
}
|
||||
|
||||
uint32_t h0 = st->h[0], h1 = st->h[1], h2 = st->h[2], h3 = st->h[3], h4 = st->h[4];
|
||||
uint32_t c;
|
||||
|
||||
c = h1 >> 26; h1 &= 0x3ffffff;
|
||||
h2 += c; c = h2 >> 26; h2 &= 0x3ffffff;
|
||||
h3 += c; c = h3 >> 26; h3 &= 0x3ffffff;
|
||||
h4 += c; c = h4 >> 26; h4 &= 0x3ffffff;
|
||||
h0 += c * 5; c = h0 >> 26; h0 &= 0x3ffffff;
|
||||
h1 += c;
|
||||
|
||||
uint32_t g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff;
|
||||
uint32_t g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff;
|
||||
uint32_t g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff;
|
||||
uint32_t g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff;
|
||||
uint32_t g4 = h4 + c - (1UL << 26);
|
||||
|
||||
uint32_t mask = (g4 >> 31) - 1; // 0 if g>=p (use g), 0xffffffff if g<p (use h)
|
||||
g0 &= mask; g1 &= mask; g2 &= mask; g3 &= mask; g4 &= mask;
|
||||
mask = ~mask;
|
||||
h0 = (h0 & mask) | g0;
|
||||
h1 = (h1 & mask) | g1;
|
||||
h2 = (h2 & mask) | g2;
|
||||
h3 = (h3 & mask) | g3;
|
||||
h4 = (h4 & mask) | g4;
|
||||
|
||||
// Serialise h into 128-bit little-endian, add pad.
|
||||
uint64_t f;
|
||||
h0 = (h0) | (h1 << 26);
|
||||
h1 = (h1 >> 6) | (h2 << 20);
|
||||
h2 = (h2 >> 12) | (h3 << 14);
|
||||
h3 = (h3 >> 18) | (h4 << 8);
|
||||
|
||||
f = (uint64_t)h0 + st->pad[0]; h0 = (uint32_t)f;
|
||||
f = (uint64_t)h1 + st->pad[1] + (f >> 32); h1 = (uint32_t)f;
|
||||
f = (uint64_t)h2 + st->pad[2] + (f >> 32); h2 = (uint32_t)f;
|
||||
f = (uint64_t)h3 + st->pad[3] + (f >> 32); h3 = (uint32_t)f;
|
||||
|
||||
store32_le(mac + 0, h0);
|
||||
store32_le(mac + 4, h1);
|
||||
store32_le(mac + 8, h2);
|
||||
store32_le(mac + 12, h3);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// AEAD construction (RFC 8439 section 2.8)
|
||||
// ===========================================================================
|
||||
static void poly1305_key_gen(uint8_t out_key[32], const uint8_t key[32],
|
||||
const uint8_t nonce[12]) {
|
||||
uint32_t state[16];
|
||||
uint8_t block[64];
|
||||
chacha20_init(state, key, 0, nonce);
|
||||
chacha20_block(state, block);
|
||||
memcpy(out_key, block, 32);
|
||||
}
|
||||
|
||||
static const uint8_t poly_zeros[16] = { 0 };
|
||||
|
||||
static void aead_mac(uint8_t tag[16], const uint8_t poly_key[32],
|
||||
const uint8_t* aad, size_t aad_len,
|
||||
const uint8_t* ct, size_t ct_len) {
|
||||
poly1305_ctx st;
|
||||
poly1305_init(&st, poly_key);
|
||||
|
||||
poly1305_update(&st, aad, aad_len);
|
||||
if (aad_len % 16) poly1305_update(&st, poly_zeros, 16 - (aad_len % 16));
|
||||
|
||||
poly1305_update(&st, ct, ct_len);
|
||||
if (ct_len % 16) poly1305_update(&st, poly_zeros, 16 - (ct_len % 16));
|
||||
|
||||
uint8_t lengths[16];
|
||||
for (int i = 0; i < 8; i++) lengths[i] = (uint8_t)(aad_len >> (8 * i));
|
||||
for (int i = 0; i < 8; i++) lengths[8 + i] = (uint8_t)(ct_len >> (8 * i));
|
||||
poly1305_update(&st, lengths, 16);
|
||||
|
||||
poly1305_finish(&st, tag);
|
||||
}
|
||||
|
||||
// Encrypt plaintext -> ciphertext (same length) and produce a 16-byte tag.
|
||||
void chacha20poly1305_encrypt(uint8_t* ct, uint8_t tag[16],
|
||||
const uint8_t key[32], const uint8_t nonce[12],
|
||||
const uint8_t* aad, size_t aad_len,
|
||||
const uint8_t* pt, size_t pt_len) {
|
||||
uint8_t poly_key[32];
|
||||
poly1305_key_gen(poly_key, key, nonce);
|
||||
chacha20_xor(ct, pt, pt_len, key, 1, nonce); // counter starts at 1
|
||||
aead_mac(tag, poly_key, aad, aad_len, ct, pt_len);
|
||||
}
|
||||
|
||||
// Verify tag and decrypt ciphertext -> plaintext. Returns true on success.
|
||||
bool chacha20poly1305_decrypt(uint8_t* pt, const uint8_t key[32],
|
||||
const uint8_t nonce[12],
|
||||
const uint8_t* aad, size_t aad_len,
|
||||
const uint8_t* ct, size_t ct_len,
|
||||
const uint8_t tag[16]) {
|
||||
uint8_t poly_key[32];
|
||||
poly1305_key_gen(poly_key, key, nonce);
|
||||
|
||||
uint8_t expect[16];
|
||||
aead_mac(expect, poly_key, aad, aad_len, ct, ct_len);
|
||||
|
||||
uint8_t diff = 0;
|
||||
for (int i = 0; i < 16; i++) diff |= expect[i] ^ tag[i];
|
||||
if (diff) return false; // authentication failed
|
||||
|
||||
chacha20_xor(pt, ct, ct_len, key, 1, nonce);
|
||||
return true;
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* HMAC-SHA256 (RFC 2104), HKDF (RFC 5869), and the TLS 1.3 HKDF-Expand-Label
|
||||
* / Derive-Secret helpers (RFC 8446 section 7.1).
|
||||
*
|
||||
* These sit on top of the existing SHA-256 and drive the TLS 1.3 key schedule:
|
||||
* every traffic key, IV and Finished key is produced by an Expand-Label over a
|
||||
* secret and the running transcript hash.
|
||||
*/
|
||||
#include <crypto/crypto.h>
|
||||
#include <lib/string.h>
|
||||
|
||||
#define SHA256_BLOCK 64
|
||||
#define SHA256_LEN 32
|
||||
|
||||
void hmac_sha256(const uint8_t* key, size_t key_len,
|
||||
const uint8_t* msg, size_t msg_len, uint8_t out[32]) {
|
||||
uint8_t k[SHA256_BLOCK];
|
||||
uint8_t ipad[SHA256_BLOCK], opad[SHA256_BLOCK];
|
||||
|
||||
if (key_len > SHA256_BLOCK) {
|
||||
sha256(key, key_len, k);
|
||||
memset(k + SHA256_LEN, 0, SHA256_BLOCK - SHA256_LEN);
|
||||
} else {
|
||||
memcpy(k, key, key_len);
|
||||
memset(k + key_len, 0, SHA256_BLOCK - key_len);
|
||||
}
|
||||
|
||||
for (int i = 0; i < SHA256_BLOCK; i++) {
|
||||
ipad[i] = k[i] ^ 0x36;
|
||||
opad[i] = k[i] ^ 0x5c;
|
||||
}
|
||||
|
||||
uint8_t inner[SHA256_LEN];
|
||||
sha256_ctx_t c;
|
||||
sha256_init(&c);
|
||||
sha256_update(&c, ipad, SHA256_BLOCK);
|
||||
sha256_update(&c, msg, msg_len);
|
||||
sha256_final(inner, &c);
|
||||
|
||||
sha256_init(&c);
|
||||
sha256_update(&c, opad, SHA256_BLOCK);
|
||||
sha256_update(&c, inner, SHA256_LEN);
|
||||
sha256_final(out, &c);
|
||||
}
|
||||
|
||||
void hkdf_extract(const uint8_t* salt, size_t salt_len,
|
||||
const uint8_t* ikm, size_t ikm_len, uint8_t prk[32]) {
|
||||
static const uint8_t zero[SHA256_LEN] = { 0 };
|
||||
if (!salt || salt_len == 0) { salt = zero; salt_len = SHA256_LEN; }
|
||||
hmac_sha256(salt, salt_len, ikm, ikm_len, prk);
|
||||
}
|
||||
|
||||
void hkdf_expand(const uint8_t prk[32], const uint8_t* info, size_t info_len,
|
||||
uint8_t* out, size_t out_len) {
|
||||
uint8_t t[SHA256_LEN];
|
||||
size_t t_len = 0;
|
||||
size_t done = 0;
|
||||
uint8_t counter = 1;
|
||||
|
||||
while (done < out_len) {
|
||||
// T(n) = HMAC(PRK, T(n-1) | info | counter)
|
||||
sha256_ctx_t hc; (void)hc;
|
||||
uint8_t block[SHA256_LEN + 512 + 1]; // t | info | counter
|
||||
size_t bl = 0;
|
||||
if (t_len) { memcpy(block, t, t_len); bl += t_len; }
|
||||
if (info_len > 512) info_len = 512;
|
||||
memcpy(block + bl, info, info_len); bl += info_len;
|
||||
block[bl++] = counter;
|
||||
|
||||
hmac_sha256(prk, SHA256_LEN, block, bl, t);
|
||||
t_len = SHA256_LEN;
|
||||
|
||||
size_t n = out_len - done < SHA256_LEN ? out_len - done : SHA256_LEN;
|
||||
memcpy(out + done, t, n);
|
||||
done += n;
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
// TLS 1.3 HKDF-Expand-Label:
|
||||
// struct { uint16 length; opaque label<7..255>; opaque context<0..255>; }
|
||||
// label is prefixed with "tls13 ".
|
||||
void tls13_expand_label(const uint8_t secret[32], const char* label,
|
||||
const uint8_t* context, size_t context_len,
|
||||
uint8_t* out, size_t out_len) {
|
||||
uint8_t info[512];
|
||||
size_t p = 0;
|
||||
|
||||
info[p++] = (uint8_t)(out_len >> 8);
|
||||
info[p++] = (uint8_t)(out_len & 0xff);
|
||||
|
||||
char full_label[64] = "tls13 ";
|
||||
size_t ll = strlen(full_label);
|
||||
size_t rest = strlen(label);
|
||||
if (ll + rest > sizeof(full_label) - 1) rest = sizeof(full_label) - 1 - ll;
|
||||
memcpy(full_label + ll, label, rest);
|
||||
size_t label_len = ll + rest;
|
||||
|
||||
info[p++] = (uint8_t)label_len;
|
||||
memcpy(info + p, full_label, label_len); p += label_len;
|
||||
|
||||
info[p++] = (uint8_t)context_len;
|
||||
if (context_len) { memcpy(info + p, context, context_len); p += context_len; }
|
||||
|
||||
hkdf_expand(secret, info, p, out, out_len);
|
||||
}
|
||||
|
||||
// Derive-Secret(secret, label, messages) = Expand-Label(secret, label,
|
||||
// Hash(messages), Hash.length). `transcript_hash` is that Hash(messages).
|
||||
void tls13_derive_secret(const uint8_t secret[32], const char* label,
|
||||
const uint8_t transcript_hash[32], uint8_t out[32]) {
|
||||
tls13_expand_label(secret, label, transcript_hash, SHA256_LEN, out, SHA256_LEN);
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* X25519 (Curve25519 ECDH), RFC 7748.
|
||||
*
|
||||
* This is the compact TweetNaCl field arithmetic (public domain), which uses
|
||||
* only 64-bit integer operations -- no floating point, no wide multiply beyond
|
||||
* what the compiler provides -- so it is safe in the freestanding kernel. It is
|
||||
* the key exchange for the TLS 1.3 client: x25519_base() turns a random private
|
||||
* scalar into a public key to send, and x25519() combines our private scalar
|
||||
* with the peer's public key to produce the shared secret.
|
||||
*/
|
||||
#include <crypto/crypto.h>
|
||||
#include <lib/string.h>
|
||||
|
||||
typedef int64_t i64;
|
||||
typedef uint8_t u8;
|
||||
typedef i64 gf[16];
|
||||
|
||||
static const gf _121665 = { 0xDB41, 1 };
|
||||
|
||||
static void car25519(gf o) {
|
||||
int i;
|
||||
i64 c;
|
||||
for (i = 0; i < 16; i++) {
|
||||
o[i] += (1LL << 16);
|
||||
c = o[i] >> 16;
|
||||
o[(i + 1) * (i < 15)] += c - 1 + 37 * (c - 1) * (i == 15);
|
||||
o[i] -= c << 16;
|
||||
}
|
||||
}
|
||||
|
||||
static void sel25519(gf p, gf q, int b) {
|
||||
i64 t, i, c = ~(b - 1);
|
||||
for (i = 0; i < 16; i++) {
|
||||
t = c & (p[i] ^ q[i]);
|
||||
p[i] ^= t;
|
||||
q[i] ^= t;
|
||||
}
|
||||
}
|
||||
|
||||
static void pack25519(u8* o, const gf n) {
|
||||
int i, j, b;
|
||||
gf m, t;
|
||||
for (i = 0; i < 16; i++) t[i] = n[i];
|
||||
car25519(t);
|
||||
car25519(t);
|
||||
car25519(t);
|
||||
for (j = 0; j < 2; j++) {
|
||||
m[0] = t[0] - 0xffed;
|
||||
for (i = 1; i < 15; i++) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
|
||||
m[i - 1] &= 0xffff;
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
|
||||
b = (m[15] >> 16) & 1;
|
||||
m[14] &= 0xffff;
|
||||
sel25519(t, m, 1 - b);
|
||||
}
|
||||
for (i = 0; i < 16; i++) {
|
||||
o[2 * i] = t[i] & 0xff;
|
||||
o[2 * i + 1] = t[i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void unpack25519(gf o, const u8* n) {
|
||||
int i;
|
||||
for (i = 0; i < 16; i++) o[i] = n[2 * i] + ((i64)n[2 * i + 1] << 8);
|
||||
o[15] &= 0x7fff;
|
||||
}
|
||||
|
||||
static void A(gf o, const gf a, const gf b) {
|
||||
int i;
|
||||
for (i = 0; i < 16; i++) o[i] = a[i] + b[i];
|
||||
}
|
||||
|
||||
static void Z(gf o, const gf a, const gf b) {
|
||||
int i;
|
||||
for (i = 0; i < 16; i++) o[i] = a[i] - b[i];
|
||||
}
|
||||
|
||||
static void M(gf o, const gf a, const gf b) {
|
||||
i64 i, j, t[31];
|
||||
for (i = 0; i < 31; i++) t[i] = 0;
|
||||
for (i = 0; i < 16; i++)
|
||||
for (j = 0; j < 16; j++) t[i + j] += a[i] * b[j];
|
||||
for (i = 0; i < 15; i++) t[i] += 38 * t[i + 16];
|
||||
for (i = 0; i < 16; i++) o[i] = t[i];
|
||||
car25519(o);
|
||||
car25519(o);
|
||||
}
|
||||
|
||||
static void S(gf o, const gf a) { M(o, a, a); }
|
||||
|
||||
static void inv25519(gf o, const gf i) {
|
||||
gf c;
|
||||
int a;
|
||||
for (a = 0; a < 16; a++) c[a] = i[a];
|
||||
for (a = 253; a >= 0; a--) {
|
||||
S(c, c);
|
||||
if (a != 2 && a != 4) M(c, c, i);
|
||||
}
|
||||
for (a = 0; a < 16; a++) o[a] = c[a];
|
||||
}
|
||||
|
||||
static int crypto_scalarmult(u8* q, const u8* n, const u8* p) {
|
||||
u8 z[32];
|
||||
i64 x[80], r, i;
|
||||
gf a, b, c, d, e, f;
|
||||
for (i = 0; i < 31; i++) z[i] = n[i];
|
||||
z[31] = (n[31] & 127) | 64;
|
||||
z[0] &= 248;
|
||||
unpack25519(x, p);
|
||||
for (i = 0; i < 16; i++) {
|
||||
b[i] = x[i];
|
||||
d[i] = a[i] = c[i] = 0;
|
||||
}
|
||||
a[0] = d[0] = 1;
|
||||
for (i = 254; i >= 0; --i) {
|
||||
r = (z[i >> 3] >> (i & 7)) & 1;
|
||||
sel25519(a, b, r);
|
||||
sel25519(c, d, r);
|
||||
A(e, a, c);
|
||||
Z(a, a, c);
|
||||
A(c, b, d);
|
||||
Z(b, b, d);
|
||||
S(d, e);
|
||||
S(f, a);
|
||||
M(a, c, a);
|
||||
M(c, b, e);
|
||||
A(e, a, c);
|
||||
Z(a, a, c);
|
||||
S(b, a);
|
||||
Z(c, d, f);
|
||||
M(a, c, _121665);
|
||||
A(a, a, d);
|
||||
M(c, c, a);
|
||||
M(a, d, f);
|
||||
M(d, b, x);
|
||||
S(b, e);
|
||||
sel25519(a, b, r);
|
||||
sel25519(c, d, r);
|
||||
}
|
||||
for (i = 0; i < 16; i++) {
|
||||
x[i + 16] = a[i];
|
||||
x[i + 32] = c[i];
|
||||
x[i + 48] = b[i];
|
||||
x[i + 64] = d[i];
|
||||
}
|
||||
inv25519(x + 32, x + 32);
|
||||
M(x + 16, x + 16, x + 32);
|
||||
pack25519(q, x + 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The Curve25519 base point, u = 9.
|
||||
static const u8 x25519_basepoint[32] = { 9 };
|
||||
|
||||
void x25519_base(uint8_t out_pub[32], const uint8_t priv[32]) {
|
||||
crypto_scalarmult(out_pub, priv, x25519_basepoint);
|
||||
}
|
||||
|
||||
void x25519(uint8_t out_shared[32], const uint8_t priv[32], const uint8_t peer_pub[32]) {
|
||||
crypto_scalarmult(out_shared, priv, peer_pub);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<div align="center">
|
||||
|
||||
<img src="../assets/logo_SUB_OS.png" alt="SUB-OS logo" width="140" height="140" />
|
||||
|
||||
# Contributing to SUB-OS
|
||||
|
||||
</div>
|
||||
|
||||
Thanks for your interest in **SUB-OS** — a multi-architecture, modular monolithic,
|
||||
Linux-compatible operating system written from scratch in C, C++17, Assembly,
|
||||
freestanding Rust (`no_std`), and the native SUB Language. This guide covers how
|
||||
to build, the conventions we follow, and how to get a change merged.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Ground rules](#ground-rules)
|
||||
- [Development environment](#development-environment)
|
||||
- [Building](#building)
|
||||
- [Running & verifying](#running--verifying)
|
||||
- [Coding style](#coding-style)
|
||||
- [Kernel gotchas](#kernel-gotchas)
|
||||
- [Commit & PR conventions](#commit--pr-conventions)
|
||||
- [Reporting bugs](#reporting-bugs)
|
||||
|
||||
---
|
||||
|
||||
## Ground rules
|
||||
|
||||
- **Verify before you claim.** Nothing is "done" until it builds *and* boots.
|
||||
Every crypto primitive and codec is tested against a reference vector (RFC or
|
||||
a PIL-generated fixture) **before** it enters the kernel. Hold new code to the
|
||||
same bar.
|
||||
- **No libc, no POSIX host.** The kernel is freestanding. No floating point in
|
||||
kernel space (`-mno-sse`); all math is integer.
|
||||
- **Small, reviewable changes.** One feature or fix per pull request, with a
|
||||
clear description of what you verified and how.
|
||||
- **Be kind.** Assume good faith, keep discussion technical, and respect
|
||||
reviewers' time.
|
||||
|
||||
---
|
||||
|
||||
## Development environment
|
||||
|
||||
The project targets `x86_64`, `aarch64`, and `armv8i` (AArch32). On a Debian/
|
||||
Ubuntu host, install the toolchains and emulators:
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install build-essential g++ nasm qemu-system-x86 qemu-system-arm \
|
||||
gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf \
|
||||
rustc cargo dialog python3
|
||||
```
|
||||
|
||||
For the freestanding Rust layer:
|
||||
|
||||
```bash
|
||||
rustup target add x86_64-unknown-none
|
||||
```
|
||||
|
||||
> The x86_64 kernel is normally built with an `x86_64-elf-` cross toolchain. A
|
||||
> stock `x86_64-linux-gnu-` GCC also works — pass it via `CROSS_COMPILE=`.
|
||||
|
||||
---
|
||||
|
||||
## Building
|
||||
|
||||
SUB-OS uses a Linux-style Kconfig + Kbuild flow. The version/codename lives at
|
||||
the top of the `Makefile` (`VERSION`/`PATCHLEVEL`/`SUBLEVEL`/`EXTRAVERSION`/
|
||||
`NAME`) and in [`include/init/version.h`](../include/init/version.h) — keep the
|
||||
two in sync when you bump a release.
|
||||
|
||||
```bash
|
||||
# Configure (interactive) or pick an architecture preset:
|
||||
make configure # or: make menuconfig
|
||||
make x86_64_defconfig # x86_64 (Intel / AMD)
|
||||
make aarch64_defconfig # 64-bit ARM (Cortex-A57)
|
||||
make armv8i_defconfig # 32-bit ARM (Cortex-A15)
|
||||
|
||||
# Build:
|
||||
make # builds the configured/default ARCH
|
||||
make ARCH=aarch64 # or target one explicitly
|
||||
```
|
||||
|
||||
> **Always `make clean` when switching `ARCH`.** Object files from another
|
||||
> architecture link into a *"file in wrong format"* error otherwise.
|
||||
|
||||
---
|
||||
|
||||
## Running & verifying
|
||||
|
||||
```bash
|
||||
make run # x86_64 disk image in QEMU
|
||||
make run-gui # windowed graphical desktop
|
||||
make run-fullscreen # fullscreen desktop
|
||||
make run-vnc # headless: VNC on localhost:5900
|
||||
make ARCH=aarch64 run # ARM targets
|
||||
```
|
||||
|
||||
Default login is **`SUB` / `SUB`**. Pass `nogui`, `text`, `emergency`, or
|
||||
`single` on the kernel command line to boot to the TTY instead of the desktop.
|
||||
|
||||
**Headless verification** (what CI and reviewers use): boot with
|
||||
`-serial file:serial.log -monitor unix:mon.sock,server,nowait -display none`,
|
||||
drive the login over the monitor `sendkey` interface, and capture the screen
|
||||
with `screendump`. For a code path with no UI, add a temporary CLI applet under
|
||||
`userland/lazybox/` that exercises it over serial, verify, then revert the
|
||||
temporary hook.
|
||||
|
||||
When you submit a change, state plainly **what you ran and what you saw** — build
|
||||
output, boot log excerpt, screenshot, or reference-vector comparison.
|
||||
|
||||
---
|
||||
|
||||
## Coding style
|
||||
|
||||
- **C:** freestanding C11. Match the surrounding file — 4-space indent, K&R
|
||||
braces, `snake_case` for functions and variables, `SCREAMING_SNAKE` for
|
||||
macros/constants. Keep comment density and idiom consistent with the module
|
||||
you're editing.
|
||||
- **Assembly:** per-arch under `arch/<arch>/`. `.S` files run through `cpp`, so
|
||||
**no nested `/* ... */` comments**.
|
||||
- **Rust:** `no_std`, no allocator assumptions beyond what the kernel provides.
|
||||
- **C++:** freestanding C++17, no exceptions/RTTI, no host STL.
|
||||
- **Headers:** public interfaces in `include/`, with an include guard.
|
||||
- **No new external dependencies** in kernel space.
|
||||
|
||||
---
|
||||
|
||||
## Kernel gotchas
|
||||
|
||||
These bite newcomers — internalize them before touching the relevant subsystem:
|
||||
|
||||
- **`printk`/`vsprintf` are minimal.** Supported conversions are
|
||||
`c s d i u o x X p %` only — **no** length modifiers (`l`, `ll`) and **no**
|
||||
field width. Cast to `int` / `unsigned` / `void*` at the call site.
|
||||
- **`list_del()` NULLs both links**, so `list_empty()` is not a reliable "is
|
||||
this node still queued?" test after a delete — inspect the links directly.
|
||||
- **Preemption is armed after boot.** A context switch must never run while a
|
||||
plain spinlock is held: wrap such sections with
|
||||
`sched_preempt_disable/enable` (as `printk` does). `weave_lock` sections use
|
||||
`arch_irq_save/restore` so the timer tick cannot fire inside them.
|
||||
- **Dead tasks/stacks are not reclaimed yet.** Prefer one long-lived worker
|
||||
thread over spawning a task per request.
|
||||
- **Network checksums are byte-wise on purpose** — word-at-a-time reads get
|
||||
reordered around the caller's `hdr->checksum = 0` store under `-O2`.
|
||||
- **Ethernet frames are padded to 60 bytes** — trim payloads to the IP
|
||||
`total_length`, never the frame length.
|
||||
|
||||
---
|
||||
|
||||
## Commit & PR conventions
|
||||
|
||||
- **Commit messages** follow Conventional-Commits-style prefixes:
|
||||
`feat(scope): …`, `fix(scope): …`, `build: …`, `docs: …`, `release(vX.Y.Z): …`.
|
||||
Write the subject in the imperative and add a body explaining *what changed
|
||||
and what you verified*.
|
||||
- **Branch** off `main` for your work; open a pull request against `main`.
|
||||
- **One logical change per PR.** Rebase/squash noisy WIP commits before review.
|
||||
- **CI must pass** (build + kernel verification) before merge.
|
||||
- In the PR description, include: what the change does, which architectures you
|
||||
built, and how you verified boot behaviour.
|
||||
|
||||
---
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
Open a GitHub issue with:
|
||||
|
||||
1. **Architecture** and how you built (`defconfig`, `ARCH`, toolchain).
|
||||
2. **Exact command** that reproduces it.
|
||||
3. **What you expected** vs. **what happened** — paste the serial log and, for
|
||||
the desktop, attach a `screendump`.
|
||||
4. The commit SHA (`git rev-parse --short HEAD`).
|
||||
|
||||
For **security-sensitive** reports, do **not** open a public issue — follow the
|
||||
[Security Policy](SECURITY.md) instead.
|
||||
|
||||
---
|
||||
|
||||
Happy hacking. 🐉
|
||||
@@ -0,0 +1,87 @@
|
||||
<div align="center">
|
||||
|
||||
<img src="../assets/logo_SUB_OS.png" alt="SUB-OS logo" width="140" height="140" />
|
||||
|
||||
# SUB-OS Roadmap
|
||||
|
||||
</div>
|
||||
|
||||
Where SUB-OS is, and where it's going. This is a living document — priorities
|
||||
shift as the kernel grows. For the fine-grained, per-session engineering backlog
|
||||
see [`AUTODEV_ROADMAP.md`](AUTODEV_ROADMAP.md).
|
||||
|
||||
**Current release:** [`v1.0.0` — Titan](https://github.com/subhobhai943/SUB-OS/releases/tag/v1.0.0) (stable)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Shipped in v1.0.0 (Titan)
|
||||
|
||||
- **Weave scheduler** — genuine per-architecture context switching with
|
||||
behavioural priority lanes; cooperative **and** preemptive (timer-driven).
|
||||
- **Concurrency core** — wait queues, completions, futexes, tiny RCU.
|
||||
- **Memory management** — buddy PMM, slab allocator, per-process page tables,
|
||||
page cache.
|
||||
- **VFS** — ext2, FAT32, ramfs, procfs, sysfs, devfs.
|
||||
- **Networking** — Ethernet/ARP/IPv4/UDP, full TCP (active + passive open),
|
||||
DHCP, DNS, NetFilter; `sshd` and `httpd`.
|
||||
- **TLS 1.3 client** — X25519, ChaCha20-Poly1305, HKDF key schedule (browses
|
||||
HTTPS; see the [Security Policy](SECURITY.md) for the cert-verification caveat).
|
||||
- **Image & compression codecs** — DEFLATE/zlib, PNG, BMP, baseline JPEG.
|
||||
- **Graphical desktop (SUB-WT)** — 60 FPS compositor, window manager, Terminal,
|
||||
Files, System & Network Monitor, a real web browser with inline images, and
|
||||
more.
|
||||
- **Multi-arch** — `x86_64`, `aarch64`, `armv8i`.
|
||||
- **Linux-style build** — Kconfig TUI, Kbuild, per-arch defconfigs.
|
||||
|
||||
---
|
||||
|
||||
## 🔜 Near term (1.1.x)
|
||||
|
||||
- **Thread reaping / zombie cleanup.** Exited kernel threads currently leak their
|
||||
16 KB stack + `task_t`. Add a reaper (a dead list drained by the idle thread).
|
||||
- **Boot-test the ARM context switch.** Wire `weave_selftest()` into the
|
||||
aarch64/armv8i boot paths and verify the AArch64/AArch32 `sub_ctx_switch`
|
||||
frames under `qemu-system-aarch64` / `-arm`.
|
||||
- **`fork`/`exec`/`wait` for ring-3 userland.** Build on the ELF64 loader and
|
||||
per-process page tables so `sh` can spawn and reap real children.
|
||||
- **TLS certificate verification.** Close the biggest documented security gap by
|
||||
validating the server chain against a bundled trust store.
|
||||
|
||||
---
|
||||
|
||||
## 🌗 Mid term (1.x)
|
||||
|
||||
- **Capability-based IPC ("sub-ports").** Typed, capability-addressed message
|
||||
endpoints distinct from the SysV msg/pipe/shm set already in `ipc/`.
|
||||
- **SMP bring-up (x86_64).** AP boot trampoline, per-CPU run lanes, and per-CPU
|
||||
Weave lanes with work-stealing.
|
||||
- **Desktop maturity.** Compositor damage tracking, a richer file manager, a
|
||||
text editor, and a settings panel over the toolkit widgets.
|
||||
- **Storage & FS.** Write-back caching improvements, journaling groundwork, and
|
||||
broader ext2 write coverage.
|
||||
- **Exploit mitigations.** Enforce W^X on all mappings, add kernel stack
|
||||
canaries, and begin ASLR groundwork.
|
||||
|
||||
---
|
||||
|
||||
## 🌒 Long term (exploratory)
|
||||
|
||||
- **Self-hosting toolchain path** — grow the userland toward building parts of
|
||||
SUB-OS on SUB-OS.
|
||||
- **A native SUB-Language userland** — first-class apps written in `.sb`.
|
||||
- **Real hardware bring-up** — beyond QEMU, on at least one aarch64 board.
|
||||
- **Networking depth** — IPv6, a TLS *server*, and a hardened, fuzzed stack.
|
||||
- **Audio/graphics acceleration** — virtio-gpu 3D paths and a mixer.
|
||||
|
||||
---
|
||||
|
||||
## How priorities are chosen
|
||||
|
||||
Each development pass picks the single highest-value item achievable and
|
||||
verifiable in one session, implements it, verifies it under QEMU, and refines
|
||||
the backlog. Correctness and boot-tested verification always gate a feature
|
||||
before it's called done. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the
|
||||
verification discipline.
|
||||
|
||||
> Have an idea or want to pick something up? Open an issue or a discussion —
|
||||
> contributions against any of the items above are very welcome.
|
||||
@@ -0,0 +1,95 @@
|
||||
<div align="center">
|
||||
|
||||
<img src="../assets/logo_SUB_OS.png" alt="SUB-OS logo" width="140" height="140" />
|
||||
|
||||
# Security Policy
|
||||
|
||||
</div>
|
||||
|
||||
SUB-OS is an from-scratch, research/hobby operating system. It is built to be
|
||||
correct and educational, **not** to be deployed as a hardened production system.
|
||||
This document explains what is supported, how to report a vulnerability, and the
|
||||
security caveats you should know before running it anywhere that matters.
|
||||
|
||||
---
|
||||
|
||||
## Supported versions
|
||||
|
||||
| Version | Codename | Status | Security fixes |
|
||||
|---------|----------|--------|----------------|
|
||||
| `1.0.0` (`1.0.x`) | Titan | ✅ Current stable | Yes |
|
||||
| `0.2.x` / `0.0.x` (alpha/beta) | — | ⚠️ Superseded | No |
|
||||
|
||||
Only the current stable line receives security fixes. Please upgrade to the
|
||||
latest [release](https://github.com/subhobhai943/SUB-OS/releases/latest) before
|
||||
reporting an issue.
|
||||
|
||||
---
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
**Please do not open a public GitHub issue for a security vulnerability.**
|
||||
|
||||
Instead, report it privately through GitHub's coordinated-disclosure channel:
|
||||
|
||||
1. Go to the repository's **Security** tab →
|
||||
**[Report a vulnerability](https://github.com/subhobhai943/SUB-OS/security/advisories/new)**.
|
||||
2. This opens a **private security advisory** visible only to you and the
|
||||
maintainers.
|
||||
3. Include:
|
||||
- Affected version / commit SHA and architecture.
|
||||
- A description of the class of problem and its impact.
|
||||
- Minimal steps or a proof-of-concept that reproduces it.
|
||||
- Any suggested remediation, if you have one.
|
||||
|
||||
> Please describe the **class** of problem and its impact rather than publishing
|
||||
> a weaponized, step-by-step exploit.
|
||||
|
||||
### What to expect
|
||||
|
||||
- **Acknowledgement:** we aim to confirm receipt within **7 days**.
|
||||
- **Assessment:** an initial severity assessment and triage within **14 days**.
|
||||
- **Fix & disclosure:** we coordinate a fix and a disclosure timeline with you,
|
||||
and credit you in the advisory and release notes (unless you prefer to remain
|
||||
anonymous).
|
||||
|
||||
---
|
||||
|
||||
## Known security caveats
|
||||
|
||||
SUB-OS deliberately trades several hardening properties for simplicity and
|
||||
readability at this stage. These are **known and documented**, not bugs:
|
||||
|
||||
- **TLS has no certificate verification.** The TLS 1.3 client performs the key
|
||||
exchange and AEAD correctly but **does not validate the server certificate
|
||||
chain**. Connections are encrypted but **not authenticated** — they are
|
||||
trivially MITM-able. Do not use it to protect real secrets.
|
||||
- **Default credentials are `SUB` / `SUB`.** Every image ships with the same
|
||||
well-known login. Treat any running instance as unauthenticated.
|
||||
- **No exploit mitigations yet.** There is no ASLR, no stack canaries in kernel
|
||||
space, and W^X is not enforced on every mapping. Some segments load RWX.
|
||||
- **Single-user, cooperative trust model.** Isolation between the ring-3 shell
|
||||
and the kernel is minimal; the userland is trusted.
|
||||
- **Networking is best-effort.** The stack is a from-scratch implementation and
|
||||
has not been fuzzed or audited against hostile packets.
|
||||
|
||||
Run SUB-OS in a **VM or emulator (QEMU)**, on isolated networks, and never with
|
||||
real credentials or sensitive data.
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
In scope for a security report:
|
||||
|
||||
- Memory-safety faults reachable from the network stack, filesystem parsers, or
|
||||
the image/crypto codecs.
|
||||
- Authentication or privilege-boundary bypasses beyond the documented caveats.
|
||||
- Cryptographic implementation errors (e.g. a broken AEAD or key schedule).
|
||||
|
||||
Out of scope (already documented above): the default credentials, the absence
|
||||
of TLS certificate verification, and the lack of exploit mitigations.
|
||||
|
||||
---
|
||||
|
||||
Thank you for helping keep SUB-OS and its users safe. 🐉
|
||||
@@ -0,0 +1,80 @@
|
||||
// IBM PC parallel port (LPT1) printer driver for SUB-OS.
|
||||
//
|
||||
// Implements the classic Centronics handshake: place a byte on the data lines,
|
||||
// wait for the peripheral to be ready (nBUSY high), then pulse the STROBE line.
|
||||
|
||||
#include <drivers/lpt.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#include <arch/x86_64/io.h>
|
||||
|
||||
#define LPT1_DATA 0x378
|
||||
#define LPT1_STATUS 0x379 // bit7 nBUSY (1 = ready), bit6 nACK, bit5 PAPER, bit4 SELECT
|
||||
#define LPT1_CONTROL 0x37A // bit0 STROBE, bit1 AUTOLF, bit2 nINIT, bit3 SELECT
|
||||
|
||||
#define LPT_STATUS_BUSY 0x80
|
||||
#define LPT_CTRL_STROBE 0x01
|
||||
#define LPT_CTRL_INIT 0x04
|
||||
#define LPT_CTRL_SELECT 0x08
|
||||
|
||||
static bool g_present = false;
|
||||
static uint64_t g_bytes_written = 0;
|
||||
|
||||
void lpt_init(void) {
|
||||
uint8_t status = inb(LPT1_STATUS);
|
||||
// A floating (unimplemented) port typically reads back all-ones.
|
||||
if (status == 0xFF) {
|
||||
g_present = false;
|
||||
printk(KERN_INFO "LPT: no parallel port at 0x%03X\n", LPT1_DATA);
|
||||
return;
|
||||
}
|
||||
// Bring the port out of reset and select the printer.
|
||||
outb(LPT1_CONTROL, LPT_CTRL_INIT | LPT_CTRL_SELECT);
|
||||
g_present = true;
|
||||
printk(ANSI_BRIGHT_GREEN "LPT: " ANSI_RESET
|
||||
"Parallel port LPT1 online at 0x%03X (status 0x%02X)\n", LPT1_DATA, status);
|
||||
}
|
||||
|
||||
bool lpt_present(void) { return g_present; }
|
||||
uint8_t lpt_status(void) { return g_present ? inb(LPT1_STATUS) : 0xFF; }
|
||||
|
||||
int lpt_putc(char c) {
|
||||
if (!g_present) return -1;
|
||||
|
||||
// Wait (bounded) for the peripheral to raise nBUSY.
|
||||
int spins = 100000;
|
||||
while (spins-- > 0 && !(inb(LPT1_STATUS) & LPT_STATUS_BUSY)) {
|
||||
/* busy-wait */
|
||||
}
|
||||
|
||||
outb(LPT1_DATA, (uint8_t)c);
|
||||
|
||||
// Pulse STROBE low->high to latch the byte.
|
||||
uint8_t ctrl = inb(LPT1_CONTROL);
|
||||
outb(LPT1_CONTROL, ctrl | LPT_CTRL_STROBE);
|
||||
outb(LPT1_CONTROL, ctrl & ~LPT_CTRL_STROBE);
|
||||
|
||||
g_bytes_written++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lpt_write(const char* buf, size_t len) {
|
||||
if (!g_present || !buf) return -1;
|
||||
int n = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (lpt_putc(buf[i]) != 0) break;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
#else // Parallel port is an x86 platform device.
|
||||
|
||||
void lpt_init(void) { }
|
||||
bool lpt_present(void) { return false; }
|
||||
int lpt_putc(char c) { (void)c; return -1; }
|
||||
int lpt_write(const char* buf, size_t len) { (void)buf; (void)len; return -1; }
|
||||
uint8_t lpt_status(void) { return 0xFF; }
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
// CMOS / RTC NVRAM driver for SUB-OS.
|
||||
//
|
||||
// Reads and writes the MC146818 CMOS register file behind ports 0x70/0x71.
|
||||
// The NMI-disable bit (0x80) of the index port is preserved on every access so
|
||||
// this driver never inadvertently re-enables NMIs.
|
||||
|
||||
#include <drivers/nvram.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#include <arch/x86_64/io.h>
|
||||
|
||||
#define CMOS_INDEX 0x70
|
||||
#define CMOS_DATA 0x71
|
||||
#define CMOS_NMI_DISABLE 0x80
|
||||
|
||||
uint8_t nvram_read(uint8_t index) {
|
||||
uint8_t prev = inb(CMOS_INDEX) & CMOS_NMI_DISABLE;
|
||||
outb(CMOS_INDEX, prev | (index & 0x7F));
|
||||
return inb(CMOS_DATA);
|
||||
}
|
||||
|
||||
void nvram_write(uint8_t index, uint8_t value) {
|
||||
uint8_t prev = inb(CMOS_INDEX) & CMOS_NMI_DISABLE;
|
||||
outb(CMOS_INDEX, prev | (index & 0x7F));
|
||||
outb(CMOS_DATA, value);
|
||||
}
|
||||
|
||||
uint8_t nvram_checksum(void) {
|
||||
uint16_t sum = 0;
|
||||
for (int i = NVRAM_USER_START; i < NVRAM_SIZE; i++) {
|
||||
sum += nvram_read((uint8_t)i);
|
||||
}
|
||||
return (uint8_t)(sum & 0xFF);
|
||||
}
|
||||
|
||||
void nvram_dump(void) {
|
||||
printk(ANSI_BRIGHT_CYAN "CMOS NVRAM (128 bytes):\n" ANSI_RESET);
|
||||
for (int row = 0; row < NVRAM_SIZE; row += 16) {
|
||||
printk(" %02X: ", row);
|
||||
for (int col = 0; col < 16; col++) {
|
||||
printk("%02X ", nvram_read((uint8_t)(row + col)));
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void nvram_init(void) {
|
||||
// Byte 0x0F is the BIOS shutdown status; reading it proves the port works.
|
||||
uint8_t shutdown_status = nvram_read(0x0F);
|
||||
printk(ANSI_BRIGHT_GREEN "NVRAM: " ANSI_RESET
|
||||
"CMOS scratch RAM ready (%d bytes, checksum 0x%02X, shutdown-status 0x%02X)\n",
|
||||
NVRAM_SIZE, nvram_checksum(), shutdown_status);
|
||||
}
|
||||
|
||||
#else // CMOS/RTC NVRAM is an x86 platform device.
|
||||
|
||||
uint8_t nvram_read(uint8_t index) { (void)index; return 0; }
|
||||
void nvram_write(uint8_t index, uint8_t v){ (void)index; (void)v; }
|
||||
uint8_t nvram_checksum(void) { return 0; }
|
||||
void nvram_dump(void) { }
|
||||
void nvram_init(void) { }
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
// High Precision Event Timer (HPET) driver for SUB-OS.
|
||||
//
|
||||
// Reads the HPET block memory-mapped at the standard base 0xFED00000 (as
|
||||
// exposed by QEMU). The general-capabilities register carries the counter
|
||||
// period in femtoseconds, from which the tick frequency and a nanosecond
|
||||
// timestamp are derived. The kernel identity-maps low physical memory, so the
|
||||
// MMIO block is reachable directly through its physical address.
|
||||
|
||||
#include <drivers/hpet.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
#define HPET_BASE_PHYS 0xFED00000ULL
|
||||
#define HPET_REG_CAP_ID 0x000 // General Capabilities and ID (64-bit)
|
||||
#define HPET_REG_CONFIG 0x010 // General Configuration (64-bit)
|
||||
#define HPET_REG_COUNTER 0x0F0 // Main Counter Value (64-bit)
|
||||
|
||||
#define HPET_CFG_ENABLE 0x1ULL // General Config bit 0: overall enable
|
||||
|
||||
static volatile uint64_t* g_hpet = (volatile uint64_t*)HPET_BASE_PHYS;
|
||||
static bool g_available = false;
|
||||
static uint64_t g_period_fs = 0; // counter period in femtoseconds
|
||||
static uint64_t g_freq_hz = 0;
|
||||
static uint32_t g_num_timers = 0;
|
||||
static uint64_t g_base_counter = 0; // counter value captured at init
|
||||
|
||||
static inline uint64_t hpet_rd(uint32_t off) {
|
||||
return g_hpet[off / sizeof(uint64_t)];
|
||||
}
|
||||
static inline void hpet_wr(uint32_t off, uint64_t val) {
|
||||
g_hpet[off / sizeof(uint64_t)] = val;
|
||||
}
|
||||
|
||||
void hpet_init(void) {
|
||||
uint64_t caps = hpet_rd(HPET_REG_CAP_ID);
|
||||
|
||||
// Period lives in the top 32 bits (femtoseconds per tick). A sane HPET has
|
||||
// a non-zero period below 100 ns (100,000,000 fs); anything else means the
|
||||
// block is absent or unmapped, so we decline rather than divide by zero.
|
||||
uint64_t period = caps >> 32;
|
||||
if (period == 0 || period > 100000000ULL) {
|
||||
g_available = false;
|
||||
printk(KERN_INFO "HPET: not present (capabilities=0x%llx)\n",
|
||||
(unsigned long long)caps);
|
||||
return;
|
||||
}
|
||||
|
||||
g_period_fs = period;
|
||||
g_num_timers = (uint32_t)(((caps >> 8) & 0x1F) + 1);
|
||||
g_freq_hz = 1000000000000000ULL / g_period_fs; // 1e15 fs per second
|
||||
|
||||
// Enable the main counter.
|
||||
hpet_wr(HPET_REG_CONFIG, hpet_rd(HPET_REG_CONFIG) | HPET_CFG_ENABLE);
|
||||
g_base_counter = hpet_rd(HPET_REG_COUNTER);
|
||||
g_available = true;
|
||||
|
||||
printk(ANSI_BRIGHT_GREEN "HPET: " ANSI_RESET
|
||||
"High Precision Event Timer online (%llu Hz, %u comparators, period %llu fs)\n",
|
||||
(unsigned long long)g_freq_hz, g_num_timers,
|
||||
(unsigned long long)g_period_fs);
|
||||
}
|
||||
|
||||
bool hpet_available(void) { return g_available; }
|
||||
|
||||
uint64_t hpet_read_counter(void) {
|
||||
if (!g_available) return 0;
|
||||
return hpet_rd(HPET_REG_COUNTER);
|
||||
}
|
||||
|
||||
uint64_t hpet_get_frequency_hz(void) { return g_freq_hz; }
|
||||
|
||||
uint64_t hpet_get_ns(void) {
|
||||
if (!g_available) return 0;
|
||||
uint64_t ticks = hpet_rd(HPET_REG_COUNTER) - g_base_counter;
|
||||
// ns = ticks * period_fs / 1e6 ; group to avoid overflow on large counts.
|
||||
return (ticks / 1000000ULL) * g_period_fs + ((ticks % 1000000ULL) * g_period_fs) / 1000000ULL;
|
||||
}
|
||||
|
||||
uint32_t hpet_num_timers(void) { return g_num_timers; }
|
||||
|
||||
#else // Non-x86 targets: HPET is an x86 platform device.
|
||||
|
||||
void hpet_init(void) { }
|
||||
bool hpet_available(void) { return false; }
|
||||
uint64_t hpet_read_counter(void) { return 0; }
|
||||
uint64_t hpet_get_frequency_hz(void) { return 0; }
|
||||
uint64_t hpet_get_ns(void) { return 0; }
|
||||
uint32_t hpet_num_timers(void) { return 0; }
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* SUB-OS Network Monitor.
|
||||
*
|
||||
* A live view of the networking stack: the interface's configuration, a
|
||||
* rolling chart of receive and transmit rates taken from the NIC's own
|
||||
* counters, and the TCP engine's connection table with every connection's
|
||||
* real state and direction -- the same data `netstat` prints, refreshed in
|
||||
* place.
|
||||
*
|
||||
* Rates are differences between successive samples of the driver counters,
|
||||
* taken on a fixed ~2 Hz cadence rather than once per repaint, so the chart
|
||||
* reads in packets per half-second regardless of how often the compositor
|
||||
* happens to redraw the window.
|
||||
*/
|
||||
|
||||
#include <gui/gui_apps.h>
|
||||
#include <gui/gui_widgets.h>
|
||||
#include <gui/gui_gfx.h>
|
||||
#include <gui/gui_theme.h>
|
||||
#include <gui/gui_icons.h>
|
||||
#include <kernel/ktime.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <net/net.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/socket.h>
|
||||
#include <net/http_client.h>
|
||||
#include <drivers/e1000.h>
|
||||
|
||||
#define NETMON_HISTORY 64
|
||||
#define NETMON_PERIOD_MS 500 // sampling cadence for the rate chart
|
||||
|
||||
#define NETMON_RX_COLOR GUI_THEME_SUCCESS
|
||||
#define NETMON_TX_COLOR GUI_THEME_PRIMARY
|
||||
|
||||
typedef struct {
|
||||
uint16_t rx[NETMON_HISTORY]; // packets received per sample interval
|
||||
uint16_t tx[NETMON_HISTORY];
|
||||
int head; // next slot to write
|
||||
int count;
|
||||
|
||||
uint64_t last_sample_ms;
|
||||
uint64_t prev_rx_pkts;
|
||||
uint64_t prev_tx_pkts;
|
||||
bool primed; // a baseline reading has been taken
|
||||
|
||||
int scroll; // first connection row shown
|
||||
|
||||
// Internet reachability probe (async, via the HTTP worker).
|
||||
http_fetch_t* probe;
|
||||
int inet_state; // 0 unknown, 1 checking, 2 online, 3 offline
|
||||
char inet_detail[72];
|
||||
bool auto_checked;
|
||||
} netmon_data_t;
|
||||
|
||||
#define INET_UNKNOWN 0
|
||||
#define INET_CHECKING 1
|
||||
#define INET_ONLINE 2
|
||||
#define INET_OFFLINE 3
|
||||
|
||||
#define NETMON_PROBE_HOST "example.com/" // a plain-HTTP host to reach for
|
||||
|
||||
// Advance the reachability probe: collect a finished result, or note that one
|
||||
// is still in flight. Runs every paint; the fetch itself is on the worker.
|
||||
static void netmon_poll_probe(netmon_data_t* nd) {
|
||||
if (!nd->probe) return;
|
||||
|
||||
int st = nd->probe->state;
|
||||
if (st == HTTP_STATE_RUNNING) { nd->inet_state = INET_CHECKING; return; }
|
||||
|
||||
if (st == HTTP_STATE_DONE && nd->probe->status_code > 0) {
|
||||
char ipx[16];
|
||||
ip_to_str(nd->probe->ip, ipx);
|
||||
nd->inet_state = INET_ONLINE;
|
||||
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "%s HTTP %d %u ms",
|
||||
ipx, nd->probe->status_code, nd->probe->elapsed_ms);
|
||||
} else {
|
||||
nd->inet_state = INET_OFFLINE;
|
||||
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "%s",
|
||||
st == HTTP_STATE_ERROR ? nd->probe->err : "no response");
|
||||
}
|
||||
|
||||
http_fetch_release(nd->probe);
|
||||
nd->probe = NULL;
|
||||
}
|
||||
|
||||
static void netmon_start_probe(netmon_data_t* nd) {
|
||||
if (nd->probe) return; // one in flight already
|
||||
nd->probe = http_fetch_start(NETMON_PROBE_HOST, 4096);
|
||||
if (nd->probe) {
|
||||
nd->inet_state = INET_CHECKING;
|
||||
} else {
|
||||
// The single worker slot is busy (e.g. the Web app is fetching).
|
||||
nd->inet_state = INET_UNKNOWN;
|
||||
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "worker busy, try again");
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Sampling
|
||||
// ===========================================================================
|
||||
static void netmon_sample(netmon_data_t* nd, uint64_t now_ms) {
|
||||
uint64_t rx = e1000_get_rx_packets();
|
||||
uint64_t tx = e1000_get_tx_packets();
|
||||
|
||||
// The first reading only establishes a baseline: without it the whole
|
||||
// counter total since boot would be charted as one enormous first spike.
|
||||
if (!nd->primed) {
|
||||
nd->prev_rx_pkts = rx;
|
||||
nd->prev_tx_pkts = tx;
|
||||
nd->primed = true;
|
||||
nd->last_sample_ms = now_ms;
|
||||
return;
|
||||
}
|
||||
|
||||
if (now_ms - nd->last_sample_ms < NETMON_PERIOD_MS) return;
|
||||
nd->last_sample_ms = now_ms;
|
||||
|
||||
uint64_t drx = (rx >= nd->prev_rx_pkts) ? rx - nd->prev_rx_pkts : 0;
|
||||
uint64_t dtx = (tx >= nd->prev_tx_pkts) ? tx - nd->prev_tx_pkts : 0;
|
||||
nd->prev_rx_pkts = rx;
|
||||
nd->prev_tx_pkts = tx;
|
||||
|
||||
nd->rx[nd->head] = (drx > 0xFFFF) ? 0xFFFF : (uint16_t)drx;
|
||||
nd->tx[nd->head] = (dtx > 0xFFFF) ? 0xFFFF : (uint16_t)dtx;
|
||||
nd->head = (nd->head + 1) % NETMON_HISTORY;
|
||||
if (nd->count < NETMON_HISTORY) nd->count++;
|
||||
}
|
||||
|
||||
// Oldest-first read of a ring, so the chart scrolls left with time.
|
||||
static uint16_t ring_at(const uint16_t* ring, int head, int count, int i) {
|
||||
int start = (head + NETMON_HISTORY - count) % NETMON_HISTORY;
|
||||
return ring[(start + i) % NETMON_HISTORY];
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Rate chart
|
||||
// ===========================================================================
|
||||
static void netmon_draw_chart(netmon_data_t* nd, int ox, int oy, int x, int y, int w, int h) {
|
||||
gui_gfx_fill_rect(ox + x, oy + y, w, h, GUI_THEME_BG_DARK);
|
||||
gui_gfx_draw_rect(ox + x, oy + y, w, h, GUI_THEME_BORDER);
|
||||
|
||||
for (int gy = y + h / 4; gy < y + h; gy += h / 4) {
|
||||
gui_gfx_draw_line(ox + x + 1, oy + gy, ox + x + w - 2, oy + gy, 0xFF1E293B);
|
||||
}
|
||||
|
||||
// Scale to the tallest sample in view, with a floor so an idle link does
|
||||
// not magnify single stray packets into a full-height sawtooth.
|
||||
uint16_t peak = 4;
|
||||
for (int i = 0; i < nd->count; i++) {
|
||||
uint16_t r = ring_at(nd->rx, nd->head, nd->count, i);
|
||||
uint16_t t = ring_at(nd->tx, nd->head, nd->count, i);
|
||||
if (r > peak) peak = r;
|
||||
if (t > peak) peak = t;
|
||||
}
|
||||
|
||||
if (nd->count >= 2) {
|
||||
int step = (w - 8) / (NETMON_HISTORY - 1);
|
||||
if (step < 1) step = 1;
|
||||
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
const uint16_t* ring = pass ? nd->tx : nd->rx;
|
||||
uint32_t col = pass ? NETMON_TX_COLOR : NETMON_RX_COLOR;
|
||||
|
||||
int prev_x = 0, prev_y = 0;
|
||||
for (int i = 0; i < nd->count; i++) {
|
||||
uint16_t v = ring_at(ring, nd->head, nd->count, i);
|
||||
int px = x + 4 + i * step;
|
||||
int py = y + h - 4 - ((int)v * (h - 10)) / peak;
|
||||
if (px > x + w - 3) break;
|
||||
|
||||
if (i > 0) {
|
||||
gui_gfx_draw_line(ox + prev_x, oy + prev_y, ox + px, oy + py, col);
|
||||
}
|
||||
prev_x = px;
|
||||
prev_y = py;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gui_gfx_draw_string(ox + x + 8, oy + y + h / 2 - 4,
|
||||
"sampling...", GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "peak %u pkt/%ums", peak, NETMON_PERIOD_MS);
|
||||
gui_gfx_draw_string(ox + x + 6, oy + y + 5, buf, GUI_THEME_TEXT_DIM);
|
||||
|
||||
// Legend, right-aligned inside the plot.
|
||||
int lx = x + w - 88;
|
||||
gui_gfx_fill_rect(ox + lx, oy + y + 7, 8, 2, NETMON_RX_COLOR);
|
||||
gui_gfx_draw_string(ox + lx + 12, oy + y + 5, "rx", NETMON_RX_COLOR);
|
||||
gui_gfx_fill_rect(ox + lx + 34, oy + y + 7, 8, 2, NETMON_TX_COLOR);
|
||||
gui_gfx_draw_string(ox + lx + 46, oy + y + 5, "tx", NETMON_TX_COLOR);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Paint
|
||||
// ===========================================================================
|
||||
static void netmon_paint(gui_window_t* win) {
|
||||
netmon_data_t* nd = (netmon_data_t*)win->user_data;
|
||||
if (!nd) return;
|
||||
|
||||
netmon_sample(nd, ktime_ms());
|
||||
|
||||
gui_widget_begin(win);
|
||||
int w = gui_widget_client_width();
|
||||
int h = gui_widget_client_height();
|
||||
int ox = win->x + 1;
|
||||
int oy = win->y + GUI_TITLEBAR_HEIGHT + 1;
|
||||
if (w < 200 || h < 140) { gui_widget_end(); return; }
|
||||
|
||||
char buf[96];
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
bool link = e1000_is_link_up();
|
||||
|
||||
// --- interface header -------------------------------------------------
|
||||
char ip[16], mask[16], gw[16];
|
||||
if (nif) {
|
||||
ip_to_str(nif->ip, ip);
|
||||
ip_to_str(nif->subnet, mask);
|
||||
ip_to_str(nif->gateway, gw);
|
||||
} else {
|
||||
strcpy(ip, "0.0.0.0"); strcpy(mask, "0.0.0.0"); strcpy(gw, "0.0.0.0");
|
||||
}
|
||||
|
||||
gui_gfx_draw_string_16_shadow(ox + 10, oy + 8, nif ? nif->name : "eth0",
|
||||
GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
gui_badge(58, 10, link ? "UP" : "DOWN",
|
||||
link ? GUI_THEME_SUCCESS : GUI_THEME_DANGER, GUI_COLOR_BLACK);
|
||||
|
||||
uint8_t mac[6];
|
||||
e1000_get_mac(mac);
|
||||
snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
gui_label_aligned(0, 12, w - 12, buf, GUI_THEME_TEXT_DIM, GUI_ALIGN_RIGHT);
|
||||
|
||||
snprintf(buf, sizeof(buf), "inet %s mask %s gw %s", ip, mask, gw);
|
||||
gui_label(10, 30, buf, GUI_THEME_TEXT_MUTED);
|
||||
|
||||
// --- rate chart -------------------------------------------------------
|
||||
int chart_y = 46;
|
||||
int chart_h = (h < 300) ? 70 : 96;
|
||||
netmon_draw_chart(nd, ox, oy, 10, chart_y, w - 20, chart_h);
|
||||
|
||||
// --- counters ---------------------------------------------------------
|
||||
int row = chart_y + chart_h + 8;
|
||||
snprintf(buf, sizeof(buf), "RX %llu pkts / %llu B",
|
||||
(unsigned long long)e1000_get_rx_packets(),
|
||||
(unsigned long long)e1000_get_rx_bytes());
|
||||
gui_label(10, row, buf, NETMON_RX_COLOR);
|
||||
|
||||
snprintf(buf, sizeof(buf), "TX %llu pkts / %llu B",
|
||||
(unsigned long long)e1000_get_tx_packets(),
|
||||
(unsigned long long)e1000_get_tx_bytes());
|
||||
gui_label(10, row + 12, buf, NETMON_TX_COLOR);
|
||||
|
||||
uint64_t urx, utx, udrop;
|
||||
udp_get_stats(&urx, &utx, &udrop);
|
||||
snprintf(buf, sizeof(buf), "udp %llu in %llu out %llu dropped",
|
||||
(unsigned long long)urx, (unsigned long long)utx,
|
||||
(unsigned long long)udrop);
|
||||
gui_label_aligned(0, row, w - 12, buf, GUI_THEME_TEXT_MUTED, GUI_ALIGN_RIGHT);
|
||||
|
||||
snprintf(buf, sizeof(buf), "tcp %u connection(s) %d listener(s)",
|
||||
(unsigned)tcp_get_connections_count(), tcp_get_listener_count());
|
||||
gui_label_aligned(0, row + 12, w - 12, buf, GUI_THEME_TEXT_MUTED, GUI_ALIGN_RIGHT);
|
||||
|
||||
// --- internet reachability -------------------------------------------
|
||||
// Kick off one probe automatically the first time the window paints, then
|
||||
// let the user re-check on demand. The fetch runs on the worker thread, so
|
||||
// this stays responsive while a check is in flight.
|
||||
if (!nd->auto_checked) { nd->auto_checked = true; netmon_start_probe(nd); }
|
||||
netmon_poll_probe(nd);
|
||||
|
||||
int irow = row + 28;
|
||||
static const char* const inet_labels[] = { "UNKNOWN", "checking...", "ONLINE", "OFFLINE" };
|
||||
uint32_t inet_col = (nd->inet_state == INET_ONLINE) ? GUI_THEME_SUCCESS
|
||||
: (nd->inet_state == INET_OFFLINE) ? GUI_THEME_DANGER
|
||||
: (nd->inet_state == INET_CHECKING)? GUI_THEME_WARNING
|
||||
: GUI_THEME_TEXT_DIM;
|
||||
gui_label(10, irow, "Internet:", GUI_THEME_TEXT_MUTED);
|
||||
gui_badge(84, irow - 1, inet_labels[nd->inet_state],
|
||||
inet_col, GUI_COLOR_BLACK);
|
||||
if (nd->inet_detail[0]) {
|
||||
gui_label(150, irow, nd->inet_detail, GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
if (gui_button(5, w - 82, irow - 3, 72, 18,
|
||||
nd->probe ? "checking" : "Check")) {
|
||||
netmon_start_probe(nd);
|
||||
}
|
||||
|
||||
// --- connection table -------------------------------------------------
|
||||
int tbl_y = row + 46;
|
||||
gui_separator(10, tbl_y - 4, w - 20);
|
||||
gui_label_bold(10, tbl_y, "PROTO LOCAL", GUI_THEME_TEXT_DIM);
|
||||
gui_label(w / 2 + 6, tbl_y, "REMOTE", GUI_THEME_TEXT_DIM);
|
||||
gui_label_aligned(0, tbl_y, w - 12, "STATE", GUI_THEME_TEXT_DIM, GUI_ALIGN_RIGHT);
|
||||
|
||||
// Reserve the bottom strip for the scroll controls whether or not they are
|
||||
// needed, so the last row never ends up underneath them.
|
||||
int line_h = 12;
|
||||
int list_top = tbl_y + 14;
|
||||
int rows_fit = (h - 26 - list_top) / line_h;
|
||||
if (rows_fit < 1) rows_fit = 1;
|
||||
|
||||
// Build the display list: listeners first, then live connections. It is
|
||||
// rebuilt every frame because the engine's table is the only source of
|
||||
// truth and entries come and go on their own.
|
||||
int shown = 0;
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < tcp_get_listener_count(); i++) {
|
||||
uint16_t lp = tcp_get_listener_port(i);
|
||||
if (!lp) continue;
|
||||
if (index++ < nd->scroll) continue;
|
||||
if (shown >= rows_fit) break;
|
||||
|
||||
int ly = list_top + shown * line_h;
|
||||
snprintf(buf, sizeof(buf), "tcp 0.0.0.0:%u", lp);
|
||||
gui_label(10, ly, buf, GUI_THEME_TEXT_MAIN);
|
||||
gui_label(w / 2 + 6, ly, "*:*", GUI_THEME_TEXT_DIM);
|
||||
snprintf(buf, sizeof(buf), "LISTEN (%d)", tcp_get_listener_backlog(i));
|
||||
gui_label_aligned(0, ly, w - 12, buf, GUI_THEME_WARNING, GUI_ALIGN_RIGHT);
|
||||
shown++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tcp_conn_table_size(); i++) {
|
||||
const tcp_conn_t* c = tcp_get_connection(i);
|
||||
if (!c) continue;
|
||||
if (index++ < nd->scroll) continue;
|
||||
if (shown >= rows_fit) break;
|
||||
|
||||
int ly = list_top + shown * line_h;
|
||||
char lip[16], rip[16];
|
||||
ip_to_str(c->local_ip, lip);
|
||||
ip_to_str(c->remote_ip, rip);
|
||||
|
||||
snprintf(buf, sizeof(buf), "tcp %s:%u", lip, c->local_port);
|
||||
gui_label(10, ly, buf, GUI_THEME_TEXT_MAIN);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s:%u", rip, c->remote_port);
|
||||
gui_label(w / 2 + 6, ly, buf, GUI_THEME_TEXT_MUTED);
|
||||
|
||||
// Established is the state worth spotting at a glance; the transient
|
||||
// handshake and teardown states are dimmer.
|
||||
uint32_t col = (c->state == TCP_STATE_ESTABLISHED) ? GUI_THEME_SUCCESS
|
||||
: (c->state == TCP_STATE_CLOSED) ? GUI_THEME_TEXT_DIM
|
||||
: GUI_THEME_WARNING;
|
||||
snprintf(buf, sizeof(buf), "%s %s", tcp_state_name(c->state),
|
||||
c->role == TCP_ROLE_CLIENT ? "out" :
|
||||
c->role == TCP_ROLE_ACCEPTED ? "in" : "svc");
|
||||
gui_label_aligned(0, ly, w - 12, buf, col, GUI_ALIGN_RIGHT);
|
||||
shown++;
|
||||
}
|
||||
|
||||
if (shown == 0) {
|
||||
gui_label(10, list_top, "(no sockets open)", GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
|
||||
// Scroll only matters once the list outgrows the window.
|
||||
if (index > rows_fit) {
|
||||
snprintf(buf, sizeof(buf), "%d-%d of %d", nd->scroll + 1,
|
||||
nd->scroll + shown, index);
|
||||
gui_label(10, h - 20, buf, GUI_THEME_TEXT_DIM);
|
||||
|
||||
if (gui_button(1, w - 58, h - 24, 24, 18, "^") && nd->scroll > 0) nd->scroll--;
|
||||
if (gui_button(2, w - 32, h - 24, 24, 18, "v") && nd->scroll < index - rows_fit) {
|
||||
nd->scroll++;
|
||||
}
|
||||
} else {
|
||||
nd->scroll = 0;
|
||||
}
|
||||
|
||||
gui_widget_end();
|
||||
}
|
||||
|
||||
static void netmon_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
gui_widget_feed_event(win, ev);
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
|
||||
netmon_data_t* nd = (netmon_data_t*)win->user_data;
|
||||
if (nd->probe) http_fetch_release(nd->probe); // safe mid-fetch
|
||||
kfree(nd);
|
||||
win->user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void gui_app_netmon_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Network Monitor", x, y,
|
||||
(w > 0) ? w : 560, (h > 0) ? h : 400);
|
||||
if (!win) return;
|
||||
|
||||
netmon_data_t* nd = (netmon_data_t*)kzalloc(sizeof(netmon_data_t));
|
||||
if (!nd) {
|
||||
gui_wm_destroy_window(win->id);
|
||||
return;
|
||||
}
|
||||
|
||||
win->user_data = nd;
|
||||
win->paint = netmon_paint;
|
||||
win->handle_event = netmon_event;
|
||||
}
|
||||
@@ -0,0 +1,850 @@
|
||||
/*
|
||||
* SUB-OS "Sorry" -- an apology, rendered.
|
||||
*
|
||||
* The whole app is one idea: an apology should look like it took effort. So
|
||||
* nothing here is a static bitmap. The heart is the classic parametric heart
|
||||
* curve, scanline-filled every frame at whatever size the beat has it at; the
|
||||
* letter types itself out a character at a time; and a slow drift of small
|
||||
* hearts rises behind all of it.
|
||||
*
|
||||
* Two constraints shaped the implementation:
|
||||
*
|
||||
* 1. The kernel is built with -mno-sse and no floating point, so every
|
||||
* curve, gradient, easing and physics step below is integer arithmetic.
|
||||
* The heart outline is precomputed as a normalised integer polygon
|
||||
* (half-width == 1000) so no trigonometry is needed at runtime.
|
||||
*
|
||||
* 2. The compositor only repaints when the scene is dirty, and otherwise
|
||||
* ticks live content at ~10 Hz. That is too coarse for a heartbeat, so
|
||||
* the app renews gui_desktop_request_animation_frame() on every paint
|
||||
* and animates at the full frame rate for exactly as long as it is open.
|
||||
*/
|
||||
|
||||
#include <gui/gui_apps.h>
|
||||
#include <gui/gui_widgets.h>
|
||||
#include <gui/gui_desktop.h>
|
||||
#include <gui/gui_gfx.h>
|
||||
#include <gui/gui_theme.h>
|
||||
#include <kernel/ktime.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
|
||||
// ===========================================================================
|
||||
// Palette
|
||||
//
|
||||
// The desktop theme is a cold slate blue, which is the wrong register for
|
||||
// this entirely. The app paints its own warm rose over the client area.
|
||||
// ===========================================================================
|
||||
#define SORRY_BG_TOP 0xFF2B0E24 // deep plum
|
||||
#define SORRY_BG_BOT 0xFF120611 // near-black wine
|
||||
#define SORRY_ROSE 0xFFFF5C8A
|
||||
#define SORRY_ROSE_LIGHT 0xFFFFA8C5
|
||||
#define SORRY_ROSE_DEEP 0xFFB81D4E
|
||||
#define SORRY_ROSE_MUTED 0xFF8E4A63 // the waiting stage: same heart, dimmer
|
||||
#define SORRY_GOLD 0xFFF6C177
|
||||
#define SORRY_TEXT 0xFFF9EAF0
|
||||
#define SORRY_TEXT_SOFT 0xFFCBA4B5
|
||||
#define SORRY_TEXT_FAINT 0xFF8C6376
|
||||
#define SORRY_RULE 0xFF52243C
|
||||
|
||||
// ===========================================================================
|
||||
// Heart geometry
|
||||
//
|
||||
// x = 16sin^3(t), y = 13cos(t) - 5cos(2t) - 2cos(3t) - cos(4t), sampled at 96
|
||||
// points and normalised so the half-width is exactly 1000. In these units the
|
||||
// lobes top out at y = +745, the notch between them dips to y = +313, and the
|
||||
// bottom point reaches y = -1063 -- so a heart of half-width R occupies
|
||||
// 1808*R/1000 pixels vertically.
|
||||
// ===========================================================================
|
||||
#define SORRY_HEART_PTS 96
|
||||
#define HEART_TOP_Q 745 // lobe crown, in half-width units
|
||||
#define HEART_BOT_Q 1063 // bottom point
|
||||
#define HEART_SPAN_Q 1808 // total height == TOP + BOT
|
||||
|
||||
static const int16_t g_heart_pts[SORRY_HEART_PTS][2] = {
|
||||
{ 0, 313}, { 0, 318}, { 2, 334}, { 7, 360}, { 17, 395}, { 33, 436},
|
||||
{ 56, 482}, { 87, 530}, { 125, 579}, { 171, 625}, { 226, 666}, { 287, 700},
|
||||
{ 354, 725}, { 425, 741}, { 499, 745}, { 575, 738}, { 650, 719}, { 721, 688},
|
||||
{ 789, 647}, { 849, 597}, { 901, 538}, { 943, 472}, { 975, 402}, { 994, 327},
|
||||
{ 1000, 250}, { 994, 172}, { 975, 94}, { 943, 17}, { 901, -59}, { 849, -133},
|
||||
{ 789, -205}, { 721, -276}, { 650, -344}, { 575, -410}, { 499, -475}, { 425, -538},
|
||||
{ 354, -600}, { 287, -661}, { 226, -719}, { 171, -775}, { 125, -829}, { 87, -878},
|
||||
{ 56, -924}, { 33, -964}, { 17, -998}, { 7, -1026}, { 2, -1046}, { 0, -1058},
|
||||
{ 0, -1063}, { 0, -1058}, { -2, -1046}, { -7, -1026}, { -17, -998}, { -33, -964},
|
||||
{ -56, -924}, { -87, -878}, { -125, -829}, { -171, -775}, { -226, -719}, { -287, -661},
|
||||
{ -354, -600}, { -425, -538}, { -499, -475}, { -575, -410}, { -650, -344}, { -721, -276},
|
||||
{ -789, -205}, { -849, -133}, { -901, -59}, { -943, 17}, { -975, 94}, { -994, 172},
|
||||
{-1000, 250}, { -994, 327}, { -975, 402}, { -943, 472}, { -901, 538}, { -849, 597},
|
||||
{ -789, 647}, { -721, 688}, { -650, 719}, { -575, 738}, { -499, 745}, { -425, 741},
|
||||
{ -354, 725}, { -287, 700}, { -226, 666}, { -171, 625}, { -125, 579}, { -87, 530},
|
||||
{ -56, 482}, { -33, 436}, { -17, 395}, { -7, 360}, { -2, 334}, { 0, 318},
|
||||
};
|
||||
|
||||
// 127 * sin(2*pi*i/16); used for the drifting hearts' sideways sway.
|
||||
static const int8_t g_sin16[16] = {
|
||||
0, 49, 90, 117, 127, 117, 90, 49, 0, -49, -90, -117, -127, -117, -90, -49
|
||||
};
|
||||
|
||||
/* Scratch for the scaled outline. The compositor paints from a single thread,
|
||||
* and heart_scan never recurses, so one shared buffer is enough and keeps a
|
||||
* few hundred bytes off the kernel stack. */
|
||||
static int16_t s_hx[SORRY_HEART_PTS];
|
||||
static int16_t s_hy[SORRY_HEART_PTS];
|
||||
|
||||
typedef void (*heart_span_fn)(void* ctx, int x0, int x1, int y, int t);
|
||||
|
||||
/*
|
||||
* Scanline-fill the heart at (cx, cy) with half-width `radius`, handing each
|
||||
* horizontal run to `fn`. `t` is the run's vertical position through the shape
|
||||
* scaled to 0..255, which is what makes the top-to-bottom gradient possible
|
||||
* without the caller having to know the geometry.
|
||||
*
|
||||
* Even-odd crossing counting is what preserves the notch between the lobes: on
|
||||
* those rows the scanline crosses four edges, not two, and the middle pair is
|
||||
* correctly left unpainted.
|
||||
*/
|
||||
static void heart_scan(int cx, int cy, int radius, heart_span_fn fn, void* ctx) {
|
||||
if (radius < 2 || !fn) return;
|
||||
|
||||
int ymin = 1 << 30;
|
||||
int ymax = -(1 << 30);
|
||||
for (int i = 0; i < SORRY_HEART_PTS; i++) {
|
||||
s_hx[i] = (int16_t)(cx + ((int)g_heart_pts[i][0] * radius) / 1000);
|
||||
s_hy[i] = (int16_t)(cy - ((int)g_heart_pts[i][1] * radius) / 1000);
|
||||
if (s_hy[i] < ymin) ymin = s_hy[i];
|
||||
if (s_hy[i] > ymax) ymax = s_hy[i];
|
||||
}
|
||||
|
||||
int height = ymax - ymin;
|
||||
if (height <= 0) height = 1;
|
||||
|
||||
for (int y = ymin; y <= ymax; y++) {
|
||||
int xs[8];
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < SORRY_HEART_PTS && n < 8; i++) {
|
||||
int j = (i + 1) % SORRY_HEART_PTS;
|
||||
int y0 = s_hy[i];
|
||||
int y1 = s_hy[j];
|
||||
|
||||
// Half-open crossing test: the edge spans this scanline, and the
|
||||
// comparison also guarantees y1 != y0 below.
|
||||
if ((y0 > y) == (y1 > y)) continue;
|
||||
xs[n++] = s_hx[i] + ((y - y0) * (s_hx[j] - s_hx[i])) / (y1 - y0);
|
||||
}
|
||||
|
||||
for (int a = 1; a < n; a++) { // insertion sort; n is 2 or 4
|
||||
int v = xs[a];
|
||||
int b = a - 1;
|
||||
while (b >= 0 && xs[b] > v) { xs[b + 1] = xs[b]; b--; }
|
||||
xs[b + 1] = v;
|
||||
}
|
||||
|
||||
int t = ((y - ymin) * 255) / height;
|
||||
for (int a = 0; a + 1 < n; a += 2) fn(ctx, xs[a], xs[a + 1], y, t);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Coverage mask.
|
||||
*
|
||||
* The glow is three widening copies of the heart drawn behind the real one,
|
||||
* but only the sliver of each that escapes past the body is ever seen -- the
|
||||
* rest is painted over immediately. Blending those hidden pixels was most of
|
||||
* the app's frame cost, so the body's spans are recorded first and the glow
|
||||
* layers skip anything they cover. Two spans a row is exactly enough: a
|
||||
* scanline crosses the outline twice, or four times where it cuts the notch,
|
||||
* and recording both keeps the glow correctly visible inside the notch.
|
||||
*/
|
||||
#define SORRY_ROW_MAX 400
|
||||
static int16_t s_cov_l[SORRY_ROW_MAX][2];
|
||||
static int16_t s_cov_r[SORRY_ROW_MAX][2];
|
||||
static uint8_t s_cov_n[SORRY_ROW_MAX];
|
||||
static int s_cov_y0 = 0;
|
||||
static int s_cov_rows = 0;
|
||||
|
||||
static void heart_span_record(void* ctx, int x0, int x1, int y, int t) {
|
||||
(void)ctx; (void)t;
|
||||
|
||||
int r = y - s_cov_y0;
|
||||
if (r < 0 || r >= s_cov_rows || s_cov_n[r] >= 2) return;
|
||||
|
||||
s_cov_l[r][s_cov_n[r]] = (int16_t)x0;
|
||||
s_cov_r[r][s_cov_n[r]] = (int16_t)x1;
|
||||
s_cov_n[r]++;
|
||||
}
|
||||
|
||||
/* Record the coverage of a heart without drawing it. heart_scan emits its
|
||||
* spans left to right, which the subtraction below relies on. */
|
||||
static void heart_record(int cx, int cy, int radius, int clip_y0, int clip_y1) {
|
||||
int y0 = cy - (radius * HEART_TOP_Q) / 1000 - 1;
|
||||
int y1 = cy + (radius * HEART_BOT_Q) / 1000 + 1;
|
||||
if (y0 < clip_y0) y0 = clip_y0;
|
||||
if (y1 > clip_y1) y1 = clip_y1;
|
||||
|
||||
s_cov_y0 = y0;
|
||||
s_cov_rows = (y1 - y0 + 1);
|
||||
if (s_cov_rows < 0) s_cov_rows = 0;
|
||||
if (s_cov_rows > SORRY_ROW_MAX) s_cov_rows = SORRY_ROW_MAX;
|
||||
|
||||
for (int i = 0; i < s_cov_rows; i++) s_cov_n[i] = 0;
|
||||
heart_scan(cx, cy, radius, heart_span_record, NULL);
|
||||
}
|
||||
|
||||
/* How a single heart is painted: a vertical gradient, an overall opacity, and
|
||||
* an optional specular highlight on the upper-left lobe. The highlight is
|
||||
* applied inside the span callback so it is clipped to the shape for free. */
|
||||
typedef struct {
|
||||
uint32_t top;
|
||||
uint32_t bottom;
|
||||
uint8_t alpha;
|
||||
int hx, hy, hr; // highlight centre and radius; hr == 0 disables it
|
||||
bool mask; // skip pixels the recorded body already covers
|
||||
int clip_x0, clip_y0, clip_x1, clip_y1;
|
||||
} heart_paint_t;
|
||||
|
||||
static void heart_run(int x0, int x1, int y, uint32_t col, uint8_t alpha) {
|
||||
if (x1 < x0) return;
|
||||
if (alpha >= 255) gui_gfx_fill_rect(x0, y, x1 - x0 + 1, 1, col);
|
||||
else gui_gfx_fill_rect_blend(x0, y, x1 - x0 + 1, 1, col, alpha);
|
||||
}
|
||||
|
||||
static void heart_span_paint(void* ctx, int x0, int x1, int y, int t) {
|
||||
heart_paint_t* hp = (heart_paint_t*)ctx;
|
||||
|
||||
if (y < hp->clip_y0 || y > hp->clip_y1) return;
|
||||
if (x0 < hp->clip_x0) x0 = hp->clip_x0;
|
||||
if (x1 > hp->clip_x1) x1 = hp->clip_x1;
|
||||
if (x1 < x0) return;
|
||||
|
||||
uint32_t col = gui_color_alpha_blend(hp->top, hp->bottom, (uint8_t)t);
|
||||
|
||||
int r = hp->mask ? y - s_cov_y0 : -1;
|
||||
if (r >= 0 && r < s_cov_rows && s_cov_n[r] > 0) {
|
||||
// Paint only what the body leaves uncovered on this row.
|
||||
for (int i = 0; i < s_cov_n[r]; i++) {
|
||||
if (s_cov_r[r][i] < x0 || s_cov_l[r][i] > x1) continue;
|
||||
if (s_cov_l[r][i] > x0) heart_run(x0, s_cov_l[r][i] - 1, y, col, hp->alpha);
|
||||
x0 = s_cov_r[r][i] + 1;
|
||||
if (x0 > x1) return;
|
||||
}
|
||||
}
|
||||
heart_run(x0, x1, y, col, hp->alpha);
|
||||
|
||||
if (hp->hr <= 0) return;
|
||||
|
||||
int dy = y - hp->hy;
|
||||
int hr2 = hp->hr * hp->hr;
|
||||
if (dy * dy >= hr2) return;
|
||||
|
||||
int gx0 = hp->hx - hp->hr; if (gx0 < x0) gx0 = x0;
|
||||
int gx1 = hp->hx + hp->hr; if (gx1 > x1) gx1 = x1;
|
||||
|
||||
for (int x = gx0; x <= gx1; x++) {
|
||||
int dx = x - hp->hx;
|
||||
int d2 = dx * dx + dy * dy;
|
||||
if (d2 >= hr2) continue;
|
||||
// Quadratic falloff: brightest dead centre, nothing at the rim.
|
||||
gui_gfx_draw_pixel_blend(x, y, GUI_COLOR_WHITE,
|
||||
(uint8_t)((110 * (hr2 - d2)) / hr2));
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Heartbeat
|
||||
// ===========================================================================
|
||||
|
||||
/* One asymmetric thump: a quick rise to `peak`, a slower relax back to zero. */
|
||||
static int thump(int32_t p, int32_t rise, int32_t fall, int peak) {
|
||||
if (p < 0 || p >= rise + fall) return 0;
|
||||
if (p < rise) return (int)((p * peak) / rise);
|
||||
return peak - (int)(((p - rise) * peak) / fall);
|
||||
}
|
||||
|
||||
/* A real heartbeat is two beats, not one: a strong systolic thump followed by
|
||||
* a softer echo. Returns a scale percentage, 100 being the resting size. */
|
||||
static int heart_beat_pct(uint64_t ms, uint32_t period, int strength) {
|
||||
int32_t p = (int32_t)(ms % period);
|
||||
return 100 + thump(p, 90, 210, (strength * 15) / 100)
|
||||
+ thump(p - 300, 70, 220, (strength * 8) / 100);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// The letters
|
||||
//
|
||||
// The actual product. Everything above just carries these.
|
||||
// ===========================================================================
|
||||
typedef struct {
|
||||
const char* heading;
|
||||
const char* const* lines;
|
||||
int line_count;
|
||||
} sorry_letter_t;
|
||||
|
||||
static const char* const g_letter1[] = {
|
||||
"I have started this a hundred times",
|
||||
"and deleted every single one of them.",
|
||||
"",
|
||||
"So, plainly, with nothing behind it:",
|
||||
"",
|
||||
"I am sorry.",
|
||||
"",
|
||||
"Not the quick kind that just wants",
|
||||
"the quiet back. The kind that finally",
|
||||
"understands what it cost you.",
|
||||
};
|
||||
|
||||
static const char* const g_letter2[] = {
|
||||
"You handed me something soft to hold",
|
||||
"and I was careless with it.",
|
||||
"",
|
||||
"You waited. You left me room.",
|
||||
"And I filled that room with reasons",
|
||||
"instead of simply showing up.",
|
||||
"",
|
||||
"That part is mine. All of it.",
|
||||
"No 'but'. No small print. No excuse",
|
||||
"dressed up as an explanation.",
|
||||
};
|
||||
|
||||
static const char* const g_letter3[] = {
|
||||
"I am not asking you to be fine yet.",
|
||||
"I am not asking you to hurry.",
|
||||
"",
|
||||
"I only needed you to know that I see",
|
||||
"it, that I own it, and that I would",
|
||||
"rather become someone worth",
|
||||
"forgiving than someone who is merely",
|
||||
"good at apologising.",
|
||||
"",
|
||||
"Quietly. Consistently. Starting now.",
|
||||
};
|
||||
|
||||
static const sorry_letter_t g_letters[] = {
|
||||
{ "the part I kept rewriting", g_letter1, (int)(sizeof(g_letter1) / sizeof(g_letter1[0])) },
|
||||
{ "what I actually did", g_letter2, (int)(sizeof(g_letter2) / sizeof(g_letter2[0])) },
|
||||
{ "what happens from here", g_letter3, (int)(sizeof(g_letter3) / sizeof(g_letter3[0])) },
|
||||
};
|
||||
#define SORRY_LETTER_COUNT ((int)(sizeof(g_letters) / sizeof(g_letters[0])))
|
||||
|
||||
static const char* const g_forgiven[] = {
|
||||
"You did not owe me that.",
|
||||
"",
|
||||
"I know what it costs to hand someone",
|
||||
"back the benefit of the doubt after",
|
||||
"they have already spent it.",
|
||||
"",
|
||||
"I will be careful with it this time.",
|
||||
"Not just today. From here on.",
|
||||
};
|
||||
|
||||
static const char* const g_waiting[] = {
|
||||
"You do not have to be ready just",
|
||||
"because I happen to be sorry.",
|
||||
"",
|
||||
"So I will wait. No clock. No pressure.",
|
||||
"No sulking about it, either.",
|
||||
"",
|
||||
"I will be right here -- still sorry,",
|
||||
"still here -- for as long as it takes.",
|
||||
};
|
||||
|
||||
static const sorry_letter_t g_letter_forgiven = {
|
||||
"thank you", g_forgiven, (int)(sizeof(g_forgiven) / sizeof(g_forgiven[0]))
|
||||
};
|
||||
static const sorry_letter_t g_letter_waiting = {
|
||||
"that is completely fair", g_waiting, (int)(sizeof(g_waiting) / sizeof(g_waiting[0]))
|
||||
};
|
||||
|
||||
// ===========================================================================
|
||||
// App state
|
||||
// ===========================================================================
|
||||
typedef enum {
|
||||
SORRY_APOLOGY = 0, // reading the letters
|
||||
SORRY_FORGIVEN, // they said yes
|
||||
SORRY_WAITING // they said not yet, which is allowed
|
||||
} sorry_stage_t;
|
||||
|
||||
#define SORRY_PETALS 26
|
||||
#define SORRY_TYPE_CPS 52 // characters revealed per second
|
||||
#define SORRY_LINE_PAUSE 3 // characters' worth of pause at each line break
|
||||
|
||||
typedef struct {
|
||||
int32_t x, y; // position, 1/16 px
|
||||
int32_t vx, vy; // velocity, 1/16 px per 100 ms
|
||||
uint8_t size; // half-width in px
|
||||
uint8_t alpha;
|
||||
uint8_t phase; // sway phase offset
|
||||
uint8_t sway; // sway amplitude, px
|
||||
uint32_t color;
|
||||
} petal_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t open_ms; // when the window opened
|
||||
uint64_t stage_ms; // when the current stage began
|
||||
uint64_t now_ms;
|
||||
uint64_t last_ms; // previous frame, for dt
|
||||
sorry_stage_t stage;
|
||||
int letter; // which apology letter is on screen
|
||||
uint32_t typed; // characters revealed so far
|
||||
bool burst; // celebration burst in flight
|
||||
uint32_t rng;
|
||||
petal_t petals[SORRY_PETALS];
|
||||
} sorry_data_t;
|
||||
|
||||
static uint32_t sorry_rand(sorry_data_t* sd) {
|
||||
sd->rng = sd->rng * 1664525u + 1013904223u;
|
||||
return sd->rng >> 8;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Drifting hearts
|
||||
// ===========================================================================
|
||||
static void petal_spawn(sorry_data_t* sd, petal_t* p, int w, int h, bool anywhere) {
|
||||
static const uint32_t tints[] = {
|
||||
SORRY_ROSE, SORRY_ROSE_LIGHT, SORRY_ROSE_DEEP, SORRY_GOLD
|
||||
};
|
||||
|
||||
p->x = (int32_t)(sorry_rand(sd) % (uint32_t)(w > 8 ? w : 8)) * 16;
|
||||
p->y = anywhere ? (int32_t)(sorry_rand(sd) % (uint32_t)(h > 8 ? h : 8)) * 16
|
||||
: (int32_t)(h + 6 + (int)(sorry_rand(sd) % 40)) * 16;
|
||||
p->vx = 0;
|
||||
p->vy = -(int32_t)(18 + sorry_rand(sd) % 34);
|
||||
p->size = (uint8_t)(3 + sorry_rand(sd) % 5);
|
||||
p->alpha = (uint8_t)(38 + sorry_rand(sd) % 60);
|
||||
p->phase = (uint8_t)(sorry_rand(sd) & 15);
|
||||
p->sway = (uint8_t)(3 + sorry_rand(sd) % 7);
|
||||
p->color = tints[sorry_rand(sd) % (sizeof(tints) / sizeof(tints[0]))];
|
||||
}
|
||||
|
||||
/* Forgiveness gets a burst: every drifting heart is relaunched from the middle
|
||||
* of the heart, outward and upward, and then allowed to fall back. */
|
||||
static void petals_burst(sorry_data_t* sd, int cx, int cy) {
|
||||
for (int i = 0; i < SORRY_PETALS; i++) {
|
||||
petal_t* p = &sd->petals[i];
|
||||
int a = (int)(sorry_rand(sd) & 15);
|
||||
|
||||
p->x = cx * 16;
|
||||
p->y = cy * 16;
|
||||
p->vx = (g_sin16[a] * (int32_t)(24 + sorry_rand(sd) % 46)) / 127;
|
||||
p->vy = (g_sin16[(a + 4) & 15] * (int32_t)(24 + sorry_rand(sd) % 46)) / 127 - 30;
|
||||
p->size = (uint8_t)(3 + sorry_rand(sd) % 6);
|
||||
p->alpha = (uint8_t)(90 + sorry_rand(sd) % 90);
|
||||
p->sway = 0;
|
||||
}
|
||||
sd->burst = true;
|
||||
}
|
||||
|
||||
static void petals_step(sorry_data_t* sd, int w, int h, uint32_t dt_ms) {
|
||||
if (dt_ms > 120) dt_ms = 120; // never fast-forward across a stall
|
||||
|
||||
bool all_gone = true;
|
||||
|
||||
for (int i = 0; i < SORRY_PETALS; i++) {
|
||||
petal_t* p = &sd->petals[i];
|
||||
|
||||
p->x += (p->vx * (int32_t)dt_ms) / 100;
|
||||
p->y += (p->vy * (int32_t)dt_ms) / 100;
|
||||
|
||||
if (sd->burst) {
|
||||
p->vy += (int32_t)dt_ms / 3; // gravity
|
||||
p->vx -= (p->vx * (int32_t)dt_ms) / 900; // air drag
|
||||
if (p->y < (h + 40) * 16) all_gone = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Off the top: send it back to the bottom to rise again.
|
||||
if (p->y < -((int32_t)p->size + 8) * 16) petal_spawn(sd, p, w, h, false);
|
||||
}
|
||||
|
||||
// Once the burst has fallen out of sight, resume the gentle drift.
|
||||
if (sd->burst && all_gone) {
|
||||
sd->burst = false;
|
||||
for (int i = 0; i < SORRY_PETALS; i++) petal_spawn(sd, &sd->petals[i], w, h, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void petals_draw(sorry_data_t* sd, int ox, int oy, int w, int h) {
|
||||
for (int i = 0; i < SORRY_PETALS; i++) {
|
||||
petal_t* p = &sd->petals[i];
|
||||
|
||||
int px = p->x / 16;
|
||||
int py = p->y / 16;
|
||||
|
||||
if (p->sway) {
|
||||
int phase = (int)(((sd->now_ms / 90) + p->phase) & 15);
|
||||
px += (g_sin16[phase] * p->sway) / 127;
|
||||
}
|
||||
|
||||
if (py < -20 || py > h + 20 || px < -20 || px > w + 20) continue;
|
||||
|
||||
heart_paint_t hp = {
|
||||
.top = p->color, .bottom = p->color, .alpha = p->alpha, .hr = 0,
|
||||
.clip_x0 = ox, .clip_y0 = oy, .clip_x1 = ox + w - 1, .clip_y1 = oy + h - 1
|
||||
};
|
||||
heart_scan(ox + px, oy + py, p->size, heart_span_paint, &hp);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Text helpers
|
||||
// ===========================================================================
|
||||
|
||||
/* Draw at most `max_chars` characters, so a narrow window truncates cleanly
|
||||
* instead of spilling text across the window border. */
|
||||
static void draw_clipped(int x, int y, const char* s, int max_chars, uint32_t col) {
|
||||
char buf[96];
|
||||
if (max_chars <= 0 || !s || !*s) return;
|
||||
if (max_chars > (int)sizeof(buf) - 1) max_chars = (int)sizeof(buf) - 1;
|
||||
|
||||
int n = 0;
|
||||
while (s[n] && n < max_chars) { buf[n] = s[n]; n++; }
|
||||
buf[n] = '\0';
|
||||
gui_gfx_draw_string(x, y, buf, col);
|
||||
}
|
||||
|
||||
static uint32_t letter_total_chars(const sorry_letter_t* L) {
|
||||
uint32_t total = 0;
|
||||
for (int i = 0; i < L->line_count; i++) {
|
||||
total += (uint32_t)strlen(L->lines[i]) + SORRY_LINE_PAUSE;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/*
|
||||
* Type the letter out. Returns true once the whole thing is on screen.
|
||||
*
|
||||
* `budget` characters are revealed in total, spent line by line, with a few
|
||||
* characters' worth of pause charged at each line break so the rhythm reads
|
||||
* like someone actually writing rather than a uniform ticker.
|
||||
*/
|
||||
static bool letter_draw(const sorry_letter_t* L, int x, int y, int line_h,
|
||||
int max_chars, uint32_t budget, bool caret) {
|
||||
char buf[96];
|
||||
int limit = max_chars > (int)sizeof(buf) - 1 ? (int)sizeof(buf) - 1 : max_chars;
|
||||
if (limit < 1) return true;
|
||||
|
||||
for (int i = 0; i < L->line_count; i++) {
|
||||
int len = (int)strlen(L->lines[i]);
|
||||
int row = y + i * line_h;
|
||||
|
||||
if (budget >= (uint32_t)len) {
|
||||
draw_clipped(x, row, L->lines[i], limit, SORRY_TEXT);
|
||||
budget -= (uint32_t)len;
|
||||
// Charge the line break, but never let it underflow the budget.
|
||||
budget = (budget > SORRY_LINE_PAUSE) ? budget - SORRY_LINE_PAUSE : 0;
|
||||
if (budget == 0 && i + 1 < L->line_count) return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
int n = (int)budget;
|
||||
if (n > limit) n = limit;
|
||||
for (int c = 0; c < n; c++) buf[c] = L->lines[i][c];
|
||||
buf[n] = '\0';
|
||||
gui_gfx_draw_string(x, row, buf, SORRY_TEXT);
|
||||
|
||||
if (caret) gui_gfx_fill_rect(x + n * 8, row + 1, 6, 8, SORRY_ROSE);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Paint
|
||||
// ===========================================================================
|
||||
static void sorry_paint(gui_window_t* win) {
|
||||
sorry_data_t* sd = (sorry_data_t*)win->user_data;
|
||||
if (!sd) return;
|
||||
|
||||
// Animate at the compositor's full rate rather than the 10 Hz heartbeat.
|
||||
// Renewed every frame, so it stops mattering the moment this app closes.
|
||||
gui_desktop_request_animation_frame();
|
||||
|
||||
gui_widget_begin(win);
|
||||
int w = gui_widget_client_width();
|
||||
int h = gui_widget_client_height();
|
||||
int ox = win->x + 1;
|
||||
int oy = win->y + GUI_TITLEBAR_HEIGHT + 1;
|
||||
if (w < 40 || h < 40) { gui_widget_end(); return; }
|
||||
|
||||
uint64_t now = ktime_ms();
|
||||
uint32_t dt = (now > sd->last_ms) ? (uint32_t)(now - sd->last_ms) : 0;
|
||||
sd->now_ms = now;
|
||||
sd->last_ms = now;
|
||||
|
||||
uint64_t in_stage = now - sd->stage_ms;
|
||||
|
||||
// --- background -------------------------------------------------------
|
||||
gui_gfx_draw_gradient_v(ox, oy, w, h, SORRY_BG_TOP, SORRY_BG_BOT);
|
||||
|
||||
petals_step(sd, w, h, dt);
|
||||
petals_draw(sd, ox, oy, w, h);
|
||||
|
||||
// --- layout -----------------------------------------------------------
|
||||
// Give the letter the ~46 columns it is written for and let the heart have
|
||||
// the rest, bounded so it stays sensible in both a small window and a
|
||||
// maximised one.
|
||||
int panel_w = w - 380;
|
||||
if (panel_w < 150) panel_w = 150;
|
||||
if (panel_w > 300) panel_w = 300;
|
||||
if (panel_w > w - 120) panel_w = w - 120;
|
||||
if (panel_w < 60) panel_w = 60;
|
||||
|
||||
int btn_row = h - 38;
|
||||
int top = 24;
|
||||
int caption_h = 36; // the two lines of text under the heart
|
||||
|
||||
// Fit to whichever of width or height runs out first, then take 100/120 of
|
||||
// that: the heart is sized at rest but drawn up to ~1.15x at the peak of a
|
||||
// beat, and the margin is what stops the lobes clipping the panel edge.
|
||||
int avail = btn_row - top - caption_h;
|
||||
int r_by_w = (panel_w - 16) / 2;
|
||||
int r_by_h = (avail * 1000) / HEART_SPAN_Q;
|
||||
int radius = ((r_by_w < r_by_h ? r_by_w : r_by_h) * 100) / 120;
|
||||
if (radius > 120) radius = 120;
|
||||
if (radius < 0) radius = 0;
|
||||
|
||||
int beat_period = (sd->stage == SORRY_WAITING) ? 1600 : 1150;
|
||||
int beat_strength = (sd->stage == SORRY_WAITING) ? 55
|
||||
: (sd->stage == SORRY_FORGIVEN) ? 130 : 100;
|
||||
int beat = heart_beat_pct(now, (uint32_t)beat_period, beat_strength);
|
||||
|
||||
// A short swell as the window opens, so the heart arrives rather than
|
||||
// simply being there.
|
||||
if (in_stage < 420 && sd->stage != SORRY_APOLOGY) {
|
||||
beat = beat + (int)((420 - in_stage) * 18 / 420);
|
||||
}
|
||||
|
||||
int r_beat = (radius * beat) / 100;
|
||||
int cx = panel_w / 2;
|
||||
|
||||
// Centre the heart-plus-caption block in the column rather than pinning it
|
||||
// to the top, so the left side stays balanced at any window height. All of
|
||||
// it is measured at the peak of a beat (r_peak), which is the extent that
|
||||
// actually has to fit.
|
||||
int r_peak = (radius * 120) / 100;
|
||||
int block_h = (r_peak * HEART_SPAN_Q) / 1000 + caption_h;
|
||||
int slack = (btn_row - top) - block_h;
|
||||
if (slack < 0) slack = 0;
|
||||
int cy = top + slack / 2 + (r_peak * HEART_TOP_Q) / 1000;
|
||||
|
||||
if (radius >= 14) {
|
||||
uint32_t top_col, bot_col;
|
||||
switch (sd->stage) {
|
||||
case SORRY_FORGIVEN: top_col = SORRY_ROSE_LIGHT; bot_col = SORRY_ROSE; break;
|
||||
case SORRY_WAITING: top_col = SORRY_ROSE_MUTED; bot_col = SORRY_ROSE_DEEP; break;
|
||||
default: top_col = SORRY_ROSE; bot_col = SORRY_ROSE_DEEP; break;
|
||||
}
|
||||
|
||||
// Record where the heart itself will land, so the glow behind it can
|
||||
// skip every pixel it is about to be covered by.
|
||||
heart_record(ox + cx, oy + cy, r_beat, oy, oy + h - 1);
|
||||
|
||||
heart_paint_t glow = {
|
||||
.top = top_col, .bottom = bot_col, .hr = 0, .mask = true,
|
||||
.clip_x0 = ox, .clip_y0 = oy, .clip_x1 = ox + w - 1, .clip_y1 = oy + h - 1
|
||||
};
|
||||
|
||||
// Widening, fading copies behind the heart stand in for a real blur.
|
||||
// They are drawn outermost first and each one covers the ones before
|
||||
// it, so the accumulated alpha falls off smoothly towards the rim --
|
||||
// six thin layers rather than three thick ones is the difference
|
||||
// between a halo and a set of contour rings.
|
||||
static const int glow_scale[6] = { 131, 125, 119, 113, 108, 104 };
|
||||
for (int i = 0; i < 6; i++) {
|
||||
glow.alpha = (uint8_t)(sd->stage == SORRY_WAITING ? 6 : 12);
|
||||
heart_scan(ox + cx, oy + cy, (r_beat * glow_scale[i]) / 100,
|
||||
heart_span_paint, &glow);
|
||||
}
|
||||
|
||||
heart_paint_t body = {
|
||||
.top = top_col, .bottom = bot_col, .alpha = 255,
|
||||
.hx = ox + cx - (r_beat * 40) / 100,
|
||||
.hy = oy + cy - (r_beat * 36) / 100,
|
||||
.hr = (r_beat * 26) / 100,
|
||||
.clip_x0 = ox, .clip_y0 = oy, .clip_x1 = ox + w - 1, .clip_y1 = oy + h - 1
|
||||
};
|
||||
heart_scan(ox + cx, oy + cy, r_beat, heart_span_paint, &body);
|
||||
}
|
||||
|
||||
// --- caption under the heart -----------------------------------------
|
||||
char buf[80];
|
||||
// Clear the tip at the *peak* of a beat, not at rest, or the heart dips
|
||||
// into the caption every time it thumps.
|
||||
int cap_y = oy + cy + (r_peak * HEART_BOT_Q) / 1000 + 10;
|
||||
|
||||
const char* caption =
|
||||
(sd->stage == SORRY_FORGIVEN) ? "and it is not going to stop" :
|
||||
(sd->stage == SORRY_WAITING) ? "patient, for as long as it takes" :
|
||||
"still beating, still sorry";
|
||||
|
||||
int cap_chars = (panel_w - 8) / 8;
|
||||
int cap_w = (int)strlen(caption) * 8;
|
||||
if (cap_w > panel_w - 8) cap_w = cap_chars * 8;
|
||||
if (cap_y < oy + h - 12) {
|
||||
draw_clipped(ox + cx - cap_w / 2, cap_y, caption, cap_chars, SORRY_TEXT_SOFT);
|
||||
}
|
||||
|
||||
// The beat count is derived from elapsed time, so it is a real count of
|
||||
// the beats that have actually been drawn since the window opened.
|
||||
uint64_t beats = (now - sd->open_ms) / (uint64_t)beat_period;
|
||||
snprintf(buf, sizeof(buf), "%llu beats so far", (unsigned long long)beats);
|
||||
int bw = (int)strlen(buf) * 8;
|
||||
if (cap_y + 14 < oy + h - 12) {
|
||||
draw_clipped(ox + cx - bw / 2, cap_y + 14, buf, cap_chars, SORRY_TEXT_FAINT);
|
||||
}
|
||||
|
||||
// --- the letter -------------------------------------------------------
|
||||
int lx = panel_w + 10;
|
||||
int lw = w - lx - 14;
|
||||
int lc = lw / 8;
|
||||
if (lc < 8) lc = 8;
|
||||
|
||||
const sorry_letter_t* L =
|
||||
(sd->stage == SORRY_FORGIVEN) ? &g_letter_forgiven :
|
||||
(sd->stage == SORRY_WAITING) ? &g_letter_waiting :
|
||||
&g_letters[sd->letter];
|
||||
|
||||
uint32_t total = letter_total_chars(L);
|
||||
uint32_t elapsed = (uint32_t)((in_stage * SORRY_TYPE_CPS) / 1000);
|
||||
if (sd->typed < elapsed) sd->typed = elapsed;
|
||||
if (sd->typed > total) sd->typed = total;
|
||||
|
||||
gui_gfx_draw_string_16_shadow(ox + lx, oy + 16, "I'm sorry.", SORRY_ROSE_LIGHT, 0xFF3A0F26);
|
||||
gui_gfx_fill_rect(ox + lx, oy + 38, lw, 1, SORRY_RULE);
|
||||
draw_clipped(ox + lx, oy + 46, L->heading, lc, SORRY_GOLD);
|
||||
|
||||
bool done = letter_draw(L, ox + lx, oy + 68, 15, lc, sd->typed,
|
||||
((now / 420) & 1) != 0);
|
||||
|
||||
int sig_y = oy + 68 + L->line_count * 15 + 10;
|
||||
if (done && sig_y < oy + btn_row - 26) {
|
||||
const char* sig = (sd->stage == SORRY_APOLOGY)
|
||||
? "-- and I mean every character of it."
|
||||
: "-- SUB";
|
||||
draw_clipped(ox + lx, sig_y, sig, lc, SORRY_TEXT_FAINT);
|
||||
}
|
||||
|
||||
// How long they have been kept waiting, counted honestly.
|
||||
if (sd->stage == SORRY_WAITING) {
|
||||
uint64_t secs = in_stage / 1000;
|
||||
snprintf(buf, sizeof(buf), "waiting patiently: %02llu:%02llu",
|
||||
(unsigned long long)(secs / 60), (unsigned long long)(secs % 60));
|
||||
draw_clipped(ox + lx, oy + btn_row - 22, buf, lc, SORRY_GOLD);
|
||||
}
|
||||
|
||||
// Which letter of the three is on screen.
|
||||
if (sd->stage == SORRY_APOLOGY) {
|
||||
for (int i = 0; i < SORRY_LETTER_COUNT; i++) {
|
||||
int dx = ox + lx + i * 14;
|
||||
int dy = oy + btn_row - 18;
|
||||
if (i == sd->letter) gui_gfx_fill_rect(dx, dy, 8, 3, SORRY_ROSE);
|
||||
else gui_gfx_fill_rect(dx, dy, 8, 3, SORRY_RULE);
|
||||
}
|
||||
}
|
||||
|
||||
// --- controls ---------------------------------------------------------
|
||||
int bx = lx;
|
||||
int bh = 26;
|
||||
|
||||
if (sd->stage == SORRY_APOLOGY) {
|
||||
if (gui_button_styled(1, bx, btn_row, 116, bh, "I forgive you",
|
||||
SORRY_ROSE_DEEP, SORRY_ROSE, 0xFF8E1440,
|
||||
SORRY_ROSE_LIGHT, SORRY_TEXT)) {
|
||||
sd->stage = SORRY_FORGIVEN;
|
||||
sd->stage_ms = now;
|
||||
sd->typed = 0;
|
||||
petals_burst(sd, cx, cy);
|
||||
}
|
||||
bx += 124;
|
||||
|
||||
if (gui_button_styled(2, bx, btn_row, 104, bh, "Tell me more",
|
||||
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
|
||||
SORRY_RULE, SORRY_TEXT_SOFT)) {
|
||||
sd->letter = (sd->letter + 1) % SORRY_LETTER_COUNT;
|
||||
sd->stage_ms = now;
|
||||
sd->typed = 0;
|
||||
}
|
||||
bx += 112;
|
||||
|
||||
if (gui_button_styled(3, bx, btn_row, 76, bh, "Not yet",
|
||||
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
|
||||
SORRY_RULE, SORRY_TEXT_SOFT)) {
|
||||
sd->stage = SORRY_WAITING;
|
||||
sd->stage_ms = now;
|
||||
sd->typed = 0;
|
||||
}
|
||||
} else if (sd->stage == SORRY_WAITING) {
|
||||
if (gui_button_styled(4, bx, btn_row, 116, bh, "I'm ready now",
|
||||
SORRY_ROSE_DEEP, SORRY_ROSE, 0xFF8E1440,
|
||||
SORRY_ROSE_LIGHT, SORRY_TEXT)) {
|
||||
sd->stage = SORRY_FORGIVEN;
|
||||
sd->stage_ms = now;
|
||||
sd->typed = 0;
|
||||
petals_burst(sd, cx, cy);
|
||||
}
|
||||
bx += 124;
|
||||
|
||||
if (gui_button_styled(5, bx, btn_row, 104, bh, "Say it again",
|
||||
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
|
||||
SORRY_RULE, SORRY_TEXT_SOFT)) {
|
||||
sd->stage = SORRY_APOLOGY;
|
||||
sd->stage_ms = now;
|
||||
sd->typed = 0;
|
||||
}
|
||||
} else {
|
||||
if (gui_button_styled(6, bx, btn_row, 116, bh, "Read it again",
|
||||
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
|
||||
SORRY_RULE, SORRY_TEXT_SOFT)) {
|
||||
sd->stage = SORRY_APOLOGY;
|
||||
sd->letter = 0;
|
||||
sd->stage_ms = now;
|
||||
sd->typed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Impatient readers can click the letter to finish the typing at once.
|
||||
// Placed last so the buttons above get first claim on the click.
|
||||
if (!done && gui_hitzone(9, lx, 46, lw, btn_row - 52)) sd->typed = total;
|
||||
|
||||
gui_widget_end();
|
||||
}
|
||||
|
||||
static void sorry_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
gui_widget_feed_event(win, ev);
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
|
||||
kfree(win->user_data);
|
||||
win->user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void gui_app_sorry_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("I'm Sorry", x, y,
|
||||
(w > 0) ? w : 660, (h > 0) ? h : 470);
|
||||
if (!win) return;
|
||||
|
||||
sorry_data_t* sd = (sorry_data_t*)kzalloc(sizeof(sorry_data_t));
|
||||
if (!sd) {
|
||||
gui_wm_destroy_window(win->id);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t now = ktime_ms();
|
||||
sd->open_ms = now;
|
||||
sd->stage_ms = now;
|
||||
sd->last_ms = now;
|
||||
sd->stage = SORRY_APOLOGY;
|
||||
sd->rng = (uint32_t)(now * 2654435761u) | 1u;
|
||||
|
||||
// Scatter the first drift across the window so it does not start empty.
|
||||
for (int i = 0; i < SORRY_PETALS; i++) {
|
||||
petal_spawn(sd, &sd->petals[i], win->width - 2,
|
||||
win->height - GUI_TITLEBAR_HEIGHT - 2, true);
|
||||
}
|
||||
|
||||
win->user_data = sd;
|
||||
win->paint = sorry_paint;
|
||||
win->handle_event = sorry_event;
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
/*
|
||||
* SUB-OS Web -- a minimal text web browser.
|
||||
*
|
||||
* It is the visible end of the kernel's TCP client: type a URL, and the page
|
||||
* is fetched over the stack SUB-OS built. The fetch itself is blocking, so it
|
||||
* runs on the kernel's async HTTP worker thread (net/http_client) rather than
|
||||
* in the compositor -- the desktop keeps painting at full frame rate while a
|
||||
* page loads. This window only ever polls a job's state and renders the
|
||||
* result, so it never stalls.
|
||||
*
|
||||
* Two views of the response: Raw shows the status line, headers and body
|
||||
* exactly as received; Reader strips the markup and reflows the body into
|
||||
* wrapped text. The rendered line index is rebuilt only when the content, the
|
||||
* view or the window width changes, so scrolling and idle repaints are cheap.
|
||||
*/
|
||||
|
||||
#include <gui/gui_apps.h>
|
||||
#include <gui/gui_widgets.h>
|
||||
#include <gui/gui_gfx.h>
|
||||
#include <gui/gui_theme.h>
|
||||
#include <kernel/ktime.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <net/http_client.h>
|
||||
#include <net/net.h>
|
||||
#include <lib/image.h>
|
||||
#include <drivers/keyboard.h>
|
||||
|
||||
#define WEB_FETCH_CAP 131072 // response bytes retained (fits images and long pages)
|
||||
#define WEB_MAX_LINES 2048 // rendered display lines retained
|
||||
#define WEB_MAX_LINKS 400 // hyperlinks tracked per page
|
||||
#define WEB_HREF_POOL 6144 // bytes of href strings per page
|
||||
#define WEB_HISTORY 16 // back-stack depth
|
||||
#define WEB_URL_FIELD 1
|
||||
|
||||
// A hyperlink: the [start,end) span it occupies in the processed text, and the
|
||||
// offset of its href string in the pool.
|
||||
typedef struct {
|
||||
int start;
|
||||
int end;
|
||||
int href;
|
||||
} web_link_t;
|
||||
|
||||
typedef struct {
|
||||
char url[256];
|
||||
http_fetch_t* job;
|
||||
bool reader; // strip markup and reflow
|
||||
int scroll; // first visible display line
|
||||
|
||||
// Rendered text and its line index, cached against the inputs that
|
||||
// determine them.
|
||||
char* text; // processed text being displayed
|
||||
int text_len;
|
||||
int offsets[WEB_MAX_LINES];
|
||||
int lengths[WEB_MAX_LINES];
|
||||
int nlines;
|
||||
|
||||
// Links found while stripping markup (reader view only).
|
||||
web_link_t links[WEB_MAX_LINKS];
|
||||
int nlinks;
|
||||
char hrefs[WEB_HREF_POOL];
|
||||
int href_used;
|
||||
|
||||
// The page's own address, used to resolve relative links.
|
||||
char base_host[128];
|
||||
uint16_t base_port;
|
||||
char base_path[256];
|
||||
|
||||
// Back stack of previously-visited URLs.
|
||||
char hist[WEB_HISTORY][256];
|
||||
int hist_len;
|
||||
|
||||
// When the response is an image, it is decoded once into here and shown
|
||||
// instead of text.
|
||||
bool has_image;
|
||||
image_t image;
|
||||
|
||||
const char* cache_src; // job->buf the cache was built from
|
||||
int cache_len;
|
||||
bool cache_reader;
|
||||
int cache_cpl; // columns the cache was wrapped to
|
||||
} web_data_t;
|
||||
|
||||
// ===========================================================================
|
||||
// HTML to text
|
||||
// ===========================================================================
|
||||
static bool tag_is_break(const char* tag) {
|
||||
// Block-level tags whose close (or self) should start a new line.
|
||||
static const char* const brk[] = {
|
||||
"br", "/p", "p", "/div", "/h1", "/h2", "/h3", "/h4", "/h5", "/h6",
|
||||
"/li", "/tr", "/ul", "/ol", "/table", "hr", "/title", "/head"
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(brk) / sizeof(brk[0]); i++) {
|
||||
const char* b = brk[i];
|
||||
size_t n = strlen(b);
|
||||
// Match the tag name up to a space, '>' or end, case-insensitively.
|
||||
size_t k = 0;
|
||||
while (k < n && tag[k]) {
|
||||
char a = tag[k], c = b[k];
|
||||
if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
|
||||
if (a != c) break;
|
||||
k++;
|
||||
}
|
||||
if (k == n) {
|
||||
char after = tag[k];
|
||||
if (after == '\0' || after == ' ' || after == '>' || after == '/') return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the handful of entities worth handling; returns the byte, or 0 if the
|
||||
// entity is unknown (in which case the caller keeps it verbatim).
|
||||
static char decode_entity(const char* e, int len) {
|
||||
if (len == 2 && strncmp(e, "lt", 2) == 0) return '<';
|
||||
if (len == 2 && strncmp(e, "gt", 2) == 0) return '>';
|
||||
if (len == 3 && strncmp(e, "amp", 3) == 0) return '&';
|
||||
if (len == 4 && strncmp(e, "nbsp", 4) == 0) return ' ';
|
||||
if (len == 4 && strncmp(e, "quot", 4) == 0) return '"';
|
||||
if (len == 4 && strncmp(e, "apos", 4) == 0) return '\'';
|
||||
if (len == 5 && strncmp(e, "mdash", 5) == 0) return '-';
|
||||
if (len == 5 && strncmp(e, "ndash", 5) == 0) return '-';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the href="..." value out of an anchor tag's body (the text between the
|
||||
// '<' and its '>') into the page's href pool, and return its pool offset, or -1
|
||||
// if there is no usable href or the pool is full.
|
||||
static int capture_href(web_data_t* wd, const char* tag, int taglen) {
|
||||
// Find "href" (case-insensitive) followed by '='.
|
||||
int h = -1;
|
||||
for (int i = 0; i + 4 <= taglen; i++) {
|
||||
char a = tag[i], b = tag[i + 1], c = tag[i + 2], d = tag[i + 3];
|
||||
if ((a | 32) == 'h' && (b | 32) == 'r' && (c | 32) == 'e' && (d | 32) == 'f') {
|
||||
int j = i + 4;
|
||||
while (j < taglen && (tag[j] == ' ' || tag[j] == '\t')) j++;
|
||||
if (j < taglen && tag[j] == '=') { h = j + 1; break; }
|
||||
}
|
||||
}
|
||||
if (h < 0) return -1;
|
||||
|
||||
while (h < taglen && (tag[h] == ' ' || tag[h] == '\t')) h++;
|
||||
char quote = 0;
|
||||
if (h < taglen && (tag[h] == '"' || tag[h] == '\'')) { quote = tag[h]; h++; }
|
||||
|
||||
if (wd->href_used >= WEB_HREF_POOL - 1) return -1;
|
||||
int off = wd->href_used;
|
||||
int o = off;
|
||||
while (h < taglen && o < WEB_HREF_POOL - 1) {
|
||||
char c = tag[h];
|
||||
if (quote ? (c == quote) : (c == ' ' || c == '\t' || c == '>')) break;
|
||||
wd->hrefs[o++] = c;
|
||||
h++;
|
||||
}
|
||||
wd->hrefs[o++] = '\0';
|
||||
if (o == off + 1) return -1; // empty href
|
||||
wd->href_used = o;
|
||||
return off;
|
||||
}
|
||||
|
||||
// Strip markup from `src` into wd->text, collapsing whitespace and turning
|
||||
// block tags into newlines. <script>/<style> bodies are dropped. Anchors are
|
||||
// recorded as links spanning the text they enclose. Returns the text length.
|
||||
static int html_to_text(web_data_t* wd, const char* src, int slen) {
|
||||
char* out = wd->text;
|
||||
int cap = WEB_FETCH_CAP;
|
||||
int o = 0;
|
||||
bool sp_pending = false; // a space owed but not yet emitted
|
||||
bool at_line_start = true;
|
||||
|
||||
bool link_open = false; // inside an <a>...</a>
|
||||
int link_start = 0;
|
||||
int link_href = -1;
|
||||
|
||||
wd->nlinks = 0;
|
||||
wd->href_used = 0;
|
||||
|
||||
for (int i = 0; i < slen && o < cap - 1; i++) {
|
||||
char ch = src[i];
|
||||
|
||||
if (ch == '<') {
|
||||
const char* tag = src + i + 1;
|
||||
|
||||
// Skip <script>...</script> and <style>...</style> bodies entirely.
|
||||
if (strncmp(tag, "script", 6) == 0 || strncmp(tag, "style", 5) == 0) {
|
||||
const char* close = (tag[0] == 's' && tag[1] == 'c') ? "</script" : "</style";
|
||||
int cl = (int)strlen(close);
|
||||
int j = i + 1;
|
||||
while (j < slen && strncmp(src + j, close, cl) != 0) j++;
|
||||
i = j;
|
||||
while (i < slen && src[i] != '>') i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool brk = tag_is_break(tag);
|
||||
bool a_open = (tag[0] | 32) == 'a' &&
|
||||
(tag[1] == ' ' || tag[1] == '\t' || tag[1] == '>');
|
||||
bool a_close = tag[0] == '/' && (tag[1] | 32) == 'a' &&
|
||||
(tag[2] == '>' || tag[2] == ' ');
|
||||
|
||||
int tstart = i + 1;
|
||||
while (i < slen && src[i] != '>') i++; // consume to tag close
|
||||
int tlen = i - tstart;
|
||||
|
||||
if (a_open && !link_open) {
|
||||
link_href = capture_href(wd, src + tstart, tlen);
|
||||
link_open = (link_href >= 0);
|
||||
link_start = o;
|
||||
} else if (a_close && link_open) {
|
||||
if (o > link_start && wd->nlinks < WEB_MAX_LINKS) {
|
||||
wd->links[wd->nlinks].start = link_start;
|
||||
wd->links[wd->nlinks].end = o;
|
||||
wd->links[wd->nlinks].href = link_href;
|
||||
wd->nlinks++;
|
||||
}
|
||||
link_open = false;
|
||||
}
|
||||
|
||||
if (brk && !at_line_start) {
|
||||
out[o++] = '\n';
|
||||
at_line_start = true;
|
||||
sp_pending = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '&') {
|
||||
int j = i + 1;
|
||||
while (j < slen && src[j] != ';' && j - i < 8) j++;
|
||||
char d = (j < slen && src[j] == ';') ? decode_entity(src + i + 1, j - i - 1) : 0;
|
||||
if (d) {
|
||||
if (sp_pending && !at_line_start) { out[o++] = ' '; sp_pending = false; }
|
||||
if (o < cap - 1) out[o++] = d;
|
||||
at_line_start = false;
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
// Unknown entity: fall through and treat '&' as an ordinary char.
|
||||
}
|
||||
|
||||
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
|
||||
if (!at_line_start) sp_pending = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sp_pending) { out[o++] = ' '; sp_pending = false; if (o >= cap - 1) break; }
|
||||
out[o++] = ch;
|
||||
at_line_start = false;
|
||||
}
|
||||
|
||||
out[o] = '\0';
|
||||
return o;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Line breaking
|
||||
// ===========================================================================
|
||||
static void web_build_lines(web_data_t* wd, int cpl) {
|
||||
wd->nlines = 0;
|
||||
if (cpl < 8) cpl = 8;
|
||||
|
||||
int n = wd->text_len;
|
||||
const char* t = wd->text;
|
||||
int i = 0;
|
||||
|
||||
while (i < n && wd->nlines < WEB_MAX_LINES) {
|
||||
int start = i;
|
||||
int last_space = -1;
|
||||
int col = 0;
|
||||
|
||||
while (i < n && t[i] != '\n' && col < cpl) {
|
||||
if (t[i] == ' ') last_space = i;
|
||||
i++;
|
||||
col++;
|
||||
}
|
||||
|
||||
int end;
|
||||
if (i < n && t[i] == '\n') {
|
||||
end = i;
|
||||
i++; // consume the newline
|
||||
} else if (col >= cpl && i < n) {
|
||||
if (last_space > start) { // wrap at the last space
|
||||
end = last_space;
|
||||
i = last_space + 1;
|
||||
} else {
|
||||
end = i; // a word longer than a line
|
||||
}
|
||||
} else {
|
||||
end = i; // reached the end of the text
|
||||
}
|
||||
|
||||
wd->offsets[wd->nlines] = start;
|
||||
wd->lengths[wd->nlines] = end - start;
|
||||
wd->nlines++;
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the processed text and line index if any input changed.
|
||||
static void web_refresh_cache(web_data_t* wd, int cpl) {
|
||||
http_fetch_t* j = wd->job;
|
||||
if (!j || j->state != HTTP_STATE_DONE) { wd->nlines = 0; return; }
|
||||
|
||||
if (wd->cache_src == j->buf && wd->cache_len == j->len &&
|
||||
wd->cache_reader == wd->reader && wd->cache_cpl == cpl) {
|
||||
return; // still valid
|
||||
}
|
||||
|
||||
// Remember the page's own address for resolving relative links.
|
||||
strncpy(wd->base_host, j->host, sizeof(wd->base_host) - 1);
|
||||
wd->base_host[sizeof(wd->base_host) - 1] = '\0';
|
||||
strncpy(wd->base_path, j->path, sizeof(wd->base_path) - 1);
|
||||
wd->base_path[sizeof(wd->base_path) - 1] = '\0';
|
||||
wd->base_port = j->port;
|
||||
|
||||
// If the body is an image, decode it once and show it instead of text.
|
||||
if (wd->has_image) image_free(&wd->image);
|
||||
wd->has_image = false;
|
||||
{
|
||||
const uint8_t* body = (const uint8_t*)j->buf;
|
||||
char* sep = strstr(j->buf, "\r\n\r\n");
|
||||
if (sep) body = (const uint8_t*)(sep + 4);
|
||||
int blen = j->len - (int)((const char*)body - j->buf);
|
||||
if (blen > 0 && image_sniff(body, blen)) {
|
||||
if (image_decode(body, blen, &wd->image) == 0) wd->has_image = true;
|
||||
}
|
||||
}
|
||||
if (wd->has_image) {
|
||||
wd->nlines = 0;
|
||||
wd->cache_src = j->buf; wd->cache_len = j->len;
|
||||
wd->cache_reader = wd->reader; wd->cache_cpl = cpl;
|
||||
return; // image branch: no text layout
|
||||
}
|
||||
|
||||
if (wd->reader) {
|
||||
// Reader view: skip the headers, then strip markup from the body,
|
||||
// recording anchors as links as it goes.
|
||||
const char* body = j->buf;
|
||||
char* sep = strstr(j->buf, "\r\n\r\n");
|
||||
if (sep) body = sep + 4;
|
||||
int blen = j->len - (int)(body - j->buf);
|
||||
if (blen < 0) blen = 0;
|
||||
wd->text_len = html_to_text(wd, body, blen);
|
||||
} else {
|
||||
// Raw view: the response verbatim, with tabs normalised. No links.
|
||||
int n = j->len < WEB_FETCH_CAP - 1 ? j->len : WEB_FETCH_CAP - 1;
|
||||
for (int k = 0; k < n; k++) {
|
||||
char c = j->buf[k];
|
||||
wd->text[k] = (c == '\t') ? ' ' : c;
|
||||
}
|
||||
wd->text[n] = '\0';
|
||||
wd->text_len = n;
|
||||
wd->nlinks = 0;
|
||||
}
|
||||
|
||||
web_build_lines(wd, cpl);
|
||||
|
||||
wd->cache_src = j->buf;
|
||||
wd->cache_len = j->len;
|
||||
wd->cache_reader = wd->reader;
|
||||
wd->cache_cpl = cpl;
|
||||
if (wd->scroll > wd->nlines - 1) wd->scroll = 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Fetching and navigation
|
||||
// ===========================================================================
|
||||
static void web_go(web_data_t* wd) {
|
||||
if (wd->url[0] == '\0') return;
|
||||
|
||||
// Drop any previous job (releasing a still-running one hands it to the
|
||||
// worker to free) and invalidate the render cache.
|
||||
if (wd->job) { http_fetch_release(wd->job); wd->job = NULL; }
|
||||
wd->cache_src = NULL;
|
||||
wd->scroll = 0;
|
||||
|
||||
wd->job = http_fetch_start(wd->url, WEB_FETCH_CAP);
|
||||
// A NULL result (worker busy or bad URL) is reflected in the status line
|
||||
// drawn from the live job state, so nothing more to do here.
|
||||
}
|
||||
|
||||
// Resolve a link's href against the current page into an absolute URL. Returns
|
||||
// false for links that are not navigable here (fragments, mailto:, javascript:,
|
||||
// empty). https:// is resolved through and left for the fetch layer to refuse
|
||||
// with a clear message rather than silently dropped.
|
||||
static bool web_resolve(web_data_t* wd, const char* href, char* out, size_t cap) {
|
||||
if (!href || href[0] == '\0' || href[0] == '#') return false;
|
||||
if (strncmp(href, "mailto:", 7) == 0 || strncmp(href, "javascript:", 11) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncmp(href, "http://", 7) == 0 || strncmp(href, "https://", 8) == 0) {
|
||||
strncpy(out, href, cap - 1);
|
||||
out[cap - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
// Port suffix only when it is not the default 80.
|
||||
char portbuf[8] = "";
|
||||
if (wd->base_port != 80) snprintf(portbuf, sizeof(portbuf), ":%u", wd->base_port);
|
||||
|
||||
if (href[0] == '/') {
|
||||
// Root-relative: keep host, replace path.
|
||||
snprintf(out, cap, "http://%s%s%s", wd->base_host, portbuf, href);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Document-relative: append to the current path's directory.
|
||||
char dir[256];
|
||||
strncpy(dir, wd->base_path, sizeof(dir) - 1);
|
||||
dir[sizeof(dir) - 1] = '\0';
|
||||
char* slash = strrchr(dir, '/');
|
||||
if (slash) slash[1] = '\0'; // keep the trailing slash
|
||||
else strcpy(dir, "/");
|
||||
snprintf(out, cap, "http://%s%s%s%s", wd->base_host, portbuf, dir, href);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Follow a link: remember where we are, then load the target.
|
||||
static void web_navigate(web_data_t* wd, const char* href) {
|
||||
char target[256];
|
||||
if (!web_resolve(wd, href, target, sizeof(target))) return;
|
||||
|
||||
// Push the current address onto the back stack (dropping the oldest if it
|
||||
// is full), unless we have not loaded anything yet.
|
||||
if (wd->url[0]) {
|
||||
if (wd->hist_len == WEB_HISTORY) {
|
||||
for (int i = 1; i < WEB_HISTORY; i++) {
|
||||
strcpy(wd->hist[i - 1], wd->hist[i]);
|
||||
}
|
||||
wd->hist_len--;
|
||||
}
|
||||
strncpy(wd->hist[wd->hist_len], wd->url, sizeof(wd->hist[0]) - 1);
|
||||
wd->hist[wd->hist_len][sizeof(wd->hist[0]) - 1] = '\0';
|
||||
wd->hist_len++;
|
||||
}
|
||||
|
||||
strncpy(wd->url, target, sizeof(wd->url) - 1);
|
||||
wd->url[sizeof(wd->url) - 1] = '\0';
|
||||
web_go(wd);
|
||||
}
|
||||
|
||||
static void web_back(web_data_t* wd) {
|
||||
if (wd->hist_len == 0) return;
|
||||
wd->hist_len--;
|
||||
strncpy(wd->url, wd->hist[wd->hist_len], sizeof(wd->url) - 1);
|
||||
wd->url[sizeof(wd->url) - 1] = '\0';
|
||||
web_go(wd);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Paint
|
||||
// ===========================================================================
|
||||
static void web_paint(gui_window_t* win) {
|
||||
web_data_t* wd = (web_data_t*)win->user_data;
|
||||
if (!wd) return;
|
||||
|
||||
gui_widget_begin(win);
|
||||
int w = gui_widget_client_width();
|
||||
int h = gui_widget_client_height();
|
||||
int ox = win->x + 1;
|
||||
int oy = win->y + GUI_TITLEBAR_HEIGHT + 1;
|
||||
if (w < 240 || h < 120) { gui_widget_end(); return; }
|
||||
|
||||
// --- toolbar ----------------------------------------------------------
|
||||
int back_w = 26, go_w = 34, mode_w = 62;
|
||||
int field_x = 10 + back_w + 4;
|
||||
int field_w = w - field_x - 4 - go_w - 4 - mode_w - 8;
|
||||
if (field_w < 60) field_w = 60;
|
||||
|
||||
// Back is enabled only when there is somewhere to go back to.
|
||||
bool can_back = wd->hist_len > 0;
|
||||
if (gui_button_colored(6, 10, 8, back_w, 20, "<",
|
||||
can_back ? GUI_THEME_PRIMARY : GUI_THEME_BORDER) && can_back) {
|
||||
web_back(wd);
|
||||
}
|
||||
|
||||
if (gui_textfield(WEB_URL_FIELD, field_x, 8, field_w, wd->url, sizeof(wd->url))) {
|
||||
/* editing; nothing to do until Go */
|
||||
}
|
||||
|
||||
// Enter in the focused URL field triggers the fetch: the field leaves
|
||||
// newline keys unconsumed, so they are still visible here.
|
||||
const gui_widget_input_t* in = gui_widget_get_input();
|
||||
if (in->focus_id == WEB_URL_FIELD && (in->key == '\n' || in->key == '\r')) {
|
||||
gui_widget_set_focus(0);
|
||||
web_go(wd);
|
||||
}
|
||||
|
||||
if (gui_button(2, field_x + field_w + 4, 8, go_w, 20, "Go")) web_go(wd);
|
||||
|
||||
if (gui_button_colored(3, field_x + field_w + 4 + go_w + 4, 8, mode_w, 20,
|
||||
wd->reader ? "Reader" : "Raw",
|
||||
wd->reader ? GUI_THEME_SUCCESS : GUI_THEME_ACCENT)) {
|
||||
wd->reader = !wd->reader;
|
||||
wd->cache_src = NULL; // force a re-render in the new view
|
||||
wd->scroll = 0;
|
||||
}
|
||||
|
||||
// --- status line ------------------------------------------------------
|
||||
int bar_y = 34;
|
||||
char status[128];
|
||||
uint32_t status_col = GUI_THEME_TEXT_MUTED;
|
||||
|
||||
if (!wd->job) {
|
||||
snprintf(status, sizeof(status), "Enter a URL and press Go (e.g. 10.0.2.2:8000/)");
|
||||
} else if (wd->job->state == HTTP_STATE_RUNNING) {
|
||||
// A small spinner off the 10 Hz repaint so a slow fetch looks alive.
|
||||
static const char frames[] = { '|', '/', '-', '\\' };
|
||||
char sp = frames[(ktime_ms() / 120) % 4];
|
||||
snprintf(status, sizeof(status), "%c fetching %s ...", sp, wd->job->host);
|
||||
status_col = GUI_THEME_WARNING;
|
||||
} else if (wd->job->state == HTTP_STATE_ERROR) {
|
||||
snprintf(status, sizeof(status), "error: %s", wd->job->err);
|
||||
status_col = GUI_THEME_DANGER;
|
||||
} else {
|
||||
char ips[16];
|
||||
ip_to_str(wd->job->ip, ips);
|
||||
snprintf(status, sizeof(status), "HTTP %d %d bytes %u ms %s:%u (%s)",
|
||||
wd->job->status_code, wd->job->len, wd->job->elapsed_ms,
|
||||
wd->job->host, wd->job->port, ips);
|
||||
status_col = (wd->job->status_code == 200) ? GUI_THEME_SUCCESS : GUI_THEME_TEXT_MAIN;
|
||||
}
|
||||
gui_label(10, bar_y, status, status_col);
|
||||
gui_separator(10, bar_y + 14, w - 20);
|
||||
|
||||
// --- page body --------------------------------------------------------
|
||||
int view_x = 10;
|
||||
int view_y = bar_y + 20;
|
||||
int view_h = h - view_y - 6;
|
||||
int line_h = 10;
|
||||
int cpl = (w - 20 - 12) / GUI_FONT_W; // leave room for the scrollbar
|
||||
if (cpl < 8) cpl = 8;
|
||||
int rows = view_h / line_h;
|
||||
if (rows < 1) rows = 1;
|
||||
|
||||
gui_gfx_fill_rect(ox + view_x, oy + view_y, w - 20, view_h, GUI_THEME_BG_DARK);
|
||||
gui_gfx_draw_rect(ox + view_x, oy + view_y, w - 20, view_h, GUI_THEME_BORDER);
|
||||
|
||||
web_refresh_cache(wd, cpl);
|
||||
|
||||
if (wd->has_image && wd->job && wd->job->state == HTTP_STATE_DONE) {
|
||||
image_t* im = &wd->image;
|
||||
|
||||
int avail_w = (w - 20) - 12; // panel minus scrollbar
|
||||
if (avail_w < 16) avail_w = 16;
|
||||
int sw = im->width, sh = im->height;
|
||||
if (sw > avail_w) { sh = (int)((long)sh * avail_w / sw); sw = avail_w; }
|
||||
if (sh < 1) sh = 1;
|
||||
|
||||
int page = view_h - 6;
|
||||
if (wd->scroll > sh - 1) wd->scroll = sh - 1;
|
||||
if (wd->scroll < 0) wd->scroll = 0;
|
||||
|
||||
int draw_x = ox + view_x + 6;
|
||||
int draw_y0 = oy + view_y + 4;
|
||||
|
||||
// Column map (screen column -> source column) so the inner loop has no
|
||||
// divide. Bounded by the panel width.
|
||||
static int xmap[1920];
|
||||
int cols = sw < (int)(sizeof(xmap) / sizeof(xmap[0])) ? sw
|
||||
: (int)(sizeof(xmap) / sizeof(xmap[0]));
|
||||
for (int sx = 0; sx < cols; sx++) xmap[sx] = (int)((long)sx * im->width / sw);
|
||||
|
||||
for (int r = 0; r < page; r++) {
|
||||
int scaled_y = wd->scroll + r;
|
||||
if (scaled_y >= sh) break;
|
||||
int iy = (int)((long)scaled_y * im->height / sh);
|
||||
if (iy >= im->height) break;
|
||||
const uint32_t* srow = im->pixels + (long)iy * im->width;
|
||||
int py = draw_y0 + r;
|
||||
for (int sx = 0; sx < cols; sx++) {
|
||||
uint32_t px = srow[xmap[sx]];
|
||||
uint8_t a = (uint8_t)(px >> 24);
|
||||
uint32_t rgb = 0xFF000000u | (px & 0x00FFFFFFu);
|
||||
uint32_t col = (a == 255) ? rgb
|
||||
: gui_color_alpha_blend(GUI_THEME_BG_DARK, rgb, a);
|
||||
gui_gfx_draw_pixel(draw_x + sx, py, col);
|
||||
}
|
||||
}
|
||||
|
||||
gui_scrollbar(4, w - 18, view_y, view_h, &wd->scroll, sh, page);
|
||||
|
||||
if (in->focus_id != WEB_URL_FIELD) {
|
||||
if (in->key == KEY_DOWN) wd->scroll += 16;
|
||||
else if (in->key == KEY_UP) wd->scroll -= 16;
|
||||
else if (in->key == KEY_PAGE_DOWN) wd->scroll += page;
|
||||
else if (in->key == KEY_PAGE_UP) wd->scroll -= page;
|
||||
}
|
||||
|
||||
char hint[48];
|
||||
snprintf(hint, sizeof(hint), "image %dx%d", im->width, im->height);
|
||||
gui_label_aligned(0, bar_y, w - 12, hint, GUI_THEME_ACCENT, GUI_ALIGN_RIGHT);
|
||||
} else if (wd->job && wd->job->state == HTTP_STATE_DONE && wd->nlines > 0) {
|
||||
if (wd->scroll > wd->nlines - 1) wd->scroll = wd->nlines - 1;
|
||||
if (wd->scroll < 0) wd->scroll = 0;
|
||||
|
||||
// A click on a link's text follows it. Map the click to a text offset,
|
||||
// then to the link whose span contains it. Done before drawing so a
|
||||
// navigation takes hold immediately.
|
||||
if (wd->reader && wd->nlinks && in->mouse_clicked) {
|
||||
int rx = in->mouse_x - (view_x + 6);
|
||||
int ry = in->mouse_y - (view_y + 5);
|
||||
if (rx >= 0 && ry >= 0 && in->mouse_x < view_x + (w - 20) - 4 &&
|
||||
in->mouse_y < view_y + view_h) {
|
||||
int idx = wd->scroll + ry / line_h;
|
||||
int col = rx / GUI_FONT_W;
|
||||
if (idx >= 0 && idx < wd->nlines && col < wd->lengths[idx]) {
|
||||
int off = wd->offsets[idx] + col;
|
||||
for (int li = 0; li < wd->nlinks; li++) {
|
||||
if (off >= wd->links[li].start && off < wd->links[li].end) {
|
||||
web_navigate(wd, wd->hrefs + wd->links[li].href);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char line[200];
|
||||
for (int r = 0; r < rows; r++) {
|
||||
int idx = wd->scroll + r;
|
||||
if (idx >= wd->nlines) break;
|
||||
|
||||
int loff = wd->offsets[idx];
|
||||
int len = wd->lengths[idx];
|
||||
if (len > (int)sizeof(line) - 1) len = (int)sizeof(line) - 1;
|
||||
memcpy(line, wd->text + loff, len);
|
||||
line[len] = '\0';
|
||||
|
||||
int sx = ox + view_x + 6;
|
||||
int sy = oy + view_y + 5 + r * line_h;
|
||||
|
||||
// In raw view the header block (up to the blank line) is dimmed so
|
||||
// the body stands out; reader view is uniform.
|
||||
uint32_t col = GUI_THEME_TEXT_MAIN;
|
||||
if (!wd->reader && len == 0) col = GUI_THEME_TEXT_DIM;
|
||||
gui_gfx_draw_string(sx, sy, line, col);
|
||||
|
||||
// Overlay any link spans on this line in the accent colour, with an
|
||||
// underline, so hyperlinks read as clickable.
|
||||
if (wd->reader) {
|
||||
for (int li = 0; li < wd->nlinks; li++) {
|
||||
int s = wd->links[li].start, e = wd->links[li].end;
|
||||
if (e <= loff || s >= loff + len) continue;
|
||||
int a = s > loff ? s : loff;
|
||||
int b = e < loff + len ? e : loff + len;
|
||||
int seglen = b - a;
|
||||
if (seglen <= 0 || seglen > (int)sizeof(line) - 1) continue;
|
||||
|
||||
char seg[200];
|
||||
memcpy(seg, wd->text + a, seglen);
|
||||
seg[seglen] = '\0';
|
||||
int segx = sx + (a - loff) * GUI_FONT_W;
|
||||
gui_gfx_draw_string(segx, sy, seg, GUI_THEME_ACCENT);
|
||||
gui_gfx_fill_rect(segx, sy + 9, seglen * GUI_FONT_W, 1, GUI_THEME_ACCENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollbar and keyboard paging.
|
||||
gui_scrollbar(4, w - 18, view_y, view_h, &wd->scroll, wd->nlines, rows);
|
||||
|
||||
if (in->focus_id != WEB_URL_FIELD) {
|
||||
if (in->key == KEY_DOWN) wd->scroll += 1;
|
||||
else if (in->key == KEY_UP) wd->scroll -= 1;
|
||||
else if (in->key == KEY_PAGE_DOWN) wd->scroll += rows;
|
||||
else if (in->key == KEY_PAGE_UP) wd->scroll -= rows;
|
||||
}
|
||||
|
||||
// A link count hint, so it is clear the page is navigable.
|
||||
if (wd->reader && wd->nlinks) {
|
||||
char hint[48];
|
||||
snprintf(hint, sizeof(hint), "%d link%s - click to follow",
|
||||
wd->nlinks, wd->nlinks == 1 ? "" : "s");
|
||||
gui_label_aligned(0, bar_y, w - 12, hint, GUI_THEME_ACCENT, GUI_ALIGN_RIGHT);
|
||||
}
|
||||
} else if (wd->job && wd->job->state == HTTP_STATE_RUNNING) {
|
||||
gui_gfx_draw_string(ox + view_x + 8, oy + view_y + 10,
|
||||
"Loading over TCP -- the desktop stays responsive.",
|
||||
GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
|
||||
gui_widget_end();
|
||||
}
|
||||
|
||||
static void web_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
gui_widget_feed_event(win, ev);
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
|
||||
web_data_t* wd = (web_data_t*)win->user_data;
|
||||
if (wd->job) http_fetch_release(wd->job); // safe mid-fetch
|
||||
if (wd->has_image) image_free(&wd->image);
|
||||
if (wd->text) kfree(wd->text);
|
||||
kfree(wd);
|
||||
win->user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void gui_app_web_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Web", x, y,
|
||||
(w > 0) ? w : 600, (h > 0) ? h : 420);
|
||||
if (!win) return;
|
||||
|
||||
web_data_t* wd = (web_data_t*)kzalloc(sizeof(web_data_t));
|
||||
if (!wd) { gui_wm_destroy_window(win->id); return; }
|
||||
|
||||
wd->text = (char*)kzalloc(WEB_FETCH_CAP);
|
||||
if (!wd->text) { kfree(wd); gui_wm_destroy_window(win->id); return; }
|
||||
|
||||
wd->reader = true;
|
||||
strcpy(wd->url, "https://example.com/"); // a real HTTPS page as the default
|
||||
|
||||
win->user_data = wd;
|
||||
win->paint = web_paint;
|
||||
win->handle_event = web_event;
|
||||
}
|
||||
+746
-18
@@ -8,8 +8,13 @@
|
||||
#include <mm/kmalloc.h>
|
||||
#include <mm/pmm.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/rust.h>
|
||||
#include <kernel/nt/ob.h>
|
||||
#include <kernel/nt/reg.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <kernel/ktime.h>
|
||||
|
||||
static int simple_atoi(const char* s) {
|
||||
if (!s) return 0;
|
||||
@@ -26,19 +31,41 @@ static int simple_atoi(const char* s) {
|
||||
// =================================================================
|
||||
// 1. System Performance Monitor App (High-Res 320x240)
|
||||
// =================================================================
|
||||
static uint8_t g_cpu_history[60] = {
|
||||
12, 15, 18, 22, 30, 25, 20, 35, 45, 50, 42, 38, 55, 60, 48, 52, 35, 30, 25, 20,
|
||||
28, 32, 45, 60, 55, 40, 30, 25, 20, 18, 22, 25, 15, 18, 22, 30, 35, 40, 38, 42,
|
||||
30, 25, 20, 18, 15, 22, 28, 35, 40, 48, 52, 45, 38, 30, 25, 20, 18, 15, 20, 24
|
||||
};
|
||||
/* Rolling trace of live heap utilisation (percent), newest sample on the right.
|
||||
* Seeded flat and advanced one sample per repaint from real allocator stats. */
|
||||
static uint8_t g_heap_history[60];
|
||||
static bool g_heap_history_seeded = false;
|
||||
|
||||
static void sysmon_sample_heap(uint8_t pct) {
|
||||
if (!g_heap_history_seeded) {
|
||||
for (int i = 0; i < 60; i++) g_heap_history[i] = pct;
|
||||
g_heap_history_seeded = true;
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 59; i++) g_heap_history[i] = g_heap_history[i + 1];
|
||||
g_heap_history[59] = pct;
|
||||
}
|
||||
|
||||
static void sysmon_paint(gui_window_t* win) {
|
||||
int cx = win->x + 12;
|
||||
int cy = win->y + GUI_TITLEBAR_HEIGHT + 10;
|
||||
|
||||
gui_gfx_draw_string_16_shadow(cx, cy, "CPU Load History (Real-Time)", GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
/* Pull live figures straight from the kernel allocators. */
|
||||
uint64_t heap_used = (uint64_t)heap_get_used_bytes();
|
||||
uint64_t heap_total = (uint64_t)heap_get_total_bytes();
|
||||
uint64_t heap_free = (uint64_t)heap_get_free_bytes();
|
||||
uint64_t heap_grows = (uint64_t)heap_get_grow_count();
|
||||
uint32_t heap_pct = heap_total ? (uint32_t)((heap_used * 100) / heap_total) : 0;
|
||||
if (heap_pct > 100) heap_pct = 100;
|
||||
|
||||
// CPU Graph Box
|
||||
uint64_t ram_free_mb = (pmm_get_free_pages() * PMM_PAGE_SIZE) / (1024 * 1024);
|
||||
uint64_t ram_total_mb = pmm_get_usable_memory() / (1024 * 1024);
|
||||
|
||||
sysmon_sample_heap((uint8_t)heap_pct);
|
||||
|
||||
gui_gfx_draw_string_16_shadow(cx, cy, "Heap Utilization History (Live)", GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
|
||||
// Heap Graph Box
|
||||
int graph_x = cx;
|
||||
int graph_y = cy + 20;
|
||||
int graph_w = win->width - 24;
|
||||
@@ -54,31 +81,37 @@ static void sysmon_paint(gui_window_t* win) {
|
||||
gui_gfx_draw_line(gx, graph_y + 1, gx, graph_y + graph_h - 2, 0xFF1E293B);
|
||||
}
|
||||
|
||||
// Plot Waveform
|
||||
// Plot Waveform from the live heap trace
|
||||
for (int i = 0; i < 59 && (i * 5 + 5) < graph_w; i++) {
|
||||
int y1 = graph_y + graph_h - 3 - (g_cpu_history[i] * (graph_h - 6)) / 100;
|
||||
int y2 = graph_y + graph_h - 3 - (g_cpu_history[i + 1] * (graph_h - 6)) / 100;
|
||||
int y1 = graph_y + graph_h - 3 - (g_heap_history[i] * (graph_h - 6)) / 100;
|
||||
int y2 = graph_y + graph_h - 3 - (g_heap_history[i + 1] * (graph_h - 6)) / 100;
|
||||
gui_gfx_draw_line(graph_x + 4 + i * 5, y1, graph_x + 4 + (i + 1) * 5, y2, GUI_THEME_SUCCESS);
|
||||
}
|
||||
|
||||
// Memory Usage Bar
|
||||
// Heap Usage Bar (real used / total)
|
||||
char buf[80];
|
||||
int mem_y = graph_y + graph_h + 12;
|
||||
gui_gfx_draw_string_16(cx, mem_y, "RAM Usage: 24 MB / 128 MB (18% Heap)", GUI_THEME_TEXT_MAIN);
|
||||
snprintf(buf, sizeof(buf), "Heap: %llu / %llu KB (%u%%)",
|
||||
(unsigned long long)(heap_used / 1024),
|
||||
(unsigned long long)(heap_total / 1024), heap_pct);
|
||||
gui_gfx_draw_string_16(cx, mem_y, buf, GUI_THEME_TEXT_MAIN);
|
||||
|
||||
int bar_y = mem_y + 20;
|
||||
int bar_w = win->width - 24;
|
||||
gui_gfx_fill_rect(cx, bar_y, bar_w, 12, GUI_THEME_BG_DARK);
|
||||
gui_gfx_fill_rect(cx, bar_y, (bar_w * 24) / 128, 12, GUI_THEME_ACCENT);
|
||||
gui_gfx_fill_rect(cx, bar_y, (int)((uint64_t)bar_w * heap_pct / 100), 12,
|
||||
(heap_pct >= 90) ? GUI_THEME_DANGER : GUI_THEME_ACCENT);
|
||||
gui_gfx_draw_rect(cx, bar_y, bar_w, 12, GUI_THEME_BORDER);
|
||||
|
||||
// System Telemetry Metrics
|
||||
char buf[64];
|
||||
// System Telemetry Metrics (live)
|
||||
cpufreq_stats_t stats = cpufreq_get_stats();
|
||||
snprintf(buf, sizeof(buf), "Clock: %u.%03u GHz | Governor: ondemand",
|
||||
stats.current_khz / 1000000, (stats.current_khz % 1000000) / 1000);
|
||||
snprintf(buf, sizeof(buf), "Clock: %u.%03u GHz | Heap free: %llu KB | Grows: %llu",
|
||||
stats.current_khz / 1000000, (stats.current_khz % 1000000) / 1000,
|
||||
(unsigned long long)(heap_free / 1024), (unsigned long long)heap_grows);
|
||||
gui_gfx_draw_string(cx, bar_y + 20, buf, GUI_THEME_TEXT_MUTED);
|
||||
|
||||
snprintf(buf, sizeof(buf), "Core Tasks: 6 Active | VFS Cache: 98.4%% Hit", 0);
|
||||
snprintf(buf, sizeof(buf), "System RAM: %llu MB free / %llu MB usable",
|
||||
(unsigned long long)ram_free_mb, (unsigned long long)ram_total_mb);
|
||||
gui_gfx_draw_string(cx, bar_y + 32, buf, GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
|
||||
@@ -326,3 +359,698 @@ void gui_app_about_launch(int x, int y, int w, int h) {
|
||||
win->paint = about_paint;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 7. Conway's Game of Life (interactive cellular automaton)
|
||||
// =================================================================
|
||||
#define LIFE_COLS 56
|
||||
#define LIFE_ROWS 34
|
||||
#define LIFE_TOOLBAR_H 24
|
||||
#define LIFE_STATUS_H 22
|
||||
|
||||
typedef struct {
|
||||
uint8_t cur[LIFE_ROWS][LIFE_COLS];
|
||||
uint8_t nxt[LIFE_ROWS][LIFE_COLS];
|
||||
bool running;
|
||||
uint32_t generation;
|
||||
uint32_t frame;
|
||||
} life_data_t;
|
||||
|
||||
// Seed the grid from the Rust ChaCha20 CSPRNG (~28% of cells alive).
|
||||
static void life_randomize(life_data_t* ld) {
|
||||
// Static (not on the stack): the desktop is single-threaded, and this keeps
|
||||
// ~1.9 KB off the kernel stack that the GUI thread runs on.
|
||||
static uint8_t rnd[LIFE_ROWS * LIFE_COLS];
|
||||
if (rust_csprng_get_random(rnd, sizeof(rnd)) != 0) {
|
||||
// Deterministic fallback if the CSPRNG is unavailable.
|
||||
for (size_t i = 0; i < sizeof(rnd); i++) {
|
||||
rnd[i] = (uint8_t)((i * 2654435761u) >> 24);
|
||||
}
|
||||
}
|
||||
for (int r = 0; r < LIFE_ROWS; r++) {
|
||||
for (int c = 0; c < LIFE_COLS; c++) {
|
||||
ld->cur[r][c] = ((rnd[r * LIFE_COLS + c] & 7) < 2) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
ld->generation = 0;
|
||||
}
|
||||
|
||||
// One tick of the B3/S23 rule with dead borders.
|
||||
static void life_step(life_data_t* ld) {
|
||||
for (int r = 0; r < LIFE_ROWS; r++) {
|
||||
for (int c = 0; c < LIFE_COLS; c++) {
|
||||
int n = 0;
|
||||
for (int dr = -1; dr <= 1; dr++) {
|
||||
for (int dc = -1; dc <= 1; dc++) {
|
||||
if (dr == 0 && dc == 0) continue;
|
||||
int rr = r + dr, cc = c + dc;
|
||||
if (rr >= 0 && rr < LIFE_ROWS && cc >= 0 && cc < LIFE_COLS && ld->cur[rr][cc]) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t alive = ld->cur[r][c];
|
||||
ld->nxt[r][c] = ((alive && (n == 2 || n == 3)) || (!alive && n == 3)) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
memcpy(ld->cur, ld->nxt, sizeof(ld->cur));
|
||||
ld->generation++;
|
||||
}
|
||||
|
||||
// Grid cell size and top-left, derived from the current window geometry so the
|
||||
// board scales when the window is resized. Kept identical in paint and event.
|
||||
static void life_geometry(gui_window_t* win, int* cell, int* gw, int* gh, int* gx_rel, int* grid_top_rel) {
|
||||
int avail_w = win->width - 16;
|
||||
int avail_h = win->height - GUI_TITLEBAR_HEIGHT - LIFE_TOOLBAR_H - LIFE_STATUS_H;
|
||||
int cw = avail_w / LIFE_COLS;
|
||||
int ch = avail_h / LIFE_ROWS;
|
||||
int sz = (cw < ch) ? cw : ch;
|
||||
if (sz < 2) sz = 2;
|
||||
*cell = sz;
|
||||
*gw = sz * LIFE_COLS;
|
||||
*gh = sz * LIFE_ROWS;
|
||||
*gx_rel = (win->width - *gw) / 2;
|
||||
*grid_top_rel = LIFE_TOOLBAR_H;
|
||||
}
|
||||
|
||||
static const char* const LIFE_BTNS[4] = { "Play", "Step", "Random", "Clear" };
|
||||
#define LIFE_BTN_W 66
|
||||
#define LIFE_BTN_GAP 72
|
||||
|
||||
static void life_paint(gui_window_t* win) {
|
||||
life_data_t* ld = (life_data_t*)win->user_data;
|
||||
if (!ld) return;
|
||||
|
||||
int cx = win->x + 8;
|
||||
int top = win->y + GUI_TITLEBAR_HEIGHT;
|
||||
|
||||
// Toolbar
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int bx = cx + i * LIFE_BTN_GAP;
|
||||
int by = top + 3;
|
||||
gui_gfx_fill_rect(bx, by, LIFE_BTN_W, 18, GUI_THEME_BG_DARK);
|
||||
gui_gfx_draw_rect(bx, by, LIFE_BTN_W, 18, GUI_THEME_BORDER);
|
||||
const char* label = (i == 0 && ld->running) ? "Pause" : LIFE_BTNS[i];
|
||||
int tw = (int)strlen(label) * 8;
|
||||
gui_gfx_draw_string(bx + (LIFE_BTN_W - tw) / 2, by + 5, label, GUI_THEME_TEXT_MAIN);
|
||||
}
|
||||
|
||||
// Board
|
||||
int cell, gw, gh, gx_rel, grid_top_rel;
|
||||
life_geometry(win, &cell, &gw, &gh, &gx_rel, &grid_top_rel);
|
||||
int gx = win->x + gx_rel;
|
||||
int gy = top + grid_top_rel;
|
||||
|
||||
gui_gfx_fill_rect(gx, gy, gw, gh, 0xFF0B1220);
|
||||
int pop = 0;
|
||||
for (int r = 0; r < LIFE_ROWS; r++) {
|
||||
for (int c = 0; c < LIFE_COLS; c++) {
|
||||
if (ld->cur[r][c]) {
|
||||
pop++;
|
||||
gui_gfx_fill_rect(gx + c * cell, gy + r * cell,
|
||||
(cell > 2) ? cell - 1 : cell,
|
||||
(cell > 2) ? cell - 1 : cell, GUI_THEME_SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
gui_gfx_draw_rect(gx, gy, gw, gh, GUI_THEME_BORDER);
|
||||
|
||||
// Status line
|
||||
char buf[96];
|
||||
snprintf(buf, sizeof(buf), "Gen %u Pop %d %s -- click cells to toggle",
|
||||
ld->generation, pop, ld->running ? "RUNNING" : "paused");
|
||||
gui_gfx_draw_string(cx, gy + gh + 5, buf, GUI_THEME_TEXT_MUTED);
|
||||
|
||||
// Advance the automaton while running, throttled so it is watchable.
|
||||
if (ld->running) {
|
||||
ld->frame++;
|
||||
if ((ld->frame & 3) == 0) {
|
||||
life_step(ld);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void life_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
life_data_t* ld = (life_data_t*)win->user_data;
|
||||
if (!ld) return;
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE) {
|
||||
if (win->user_data) {
|
||||
kfree(win->user_data);
|
||||
win->user_data = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Act only on the press edge so buttons and cells do not re-fire while held.
|
||||
if (ev->type != GUI_EVENT_MOUSE_DOWN) return;
|
||||
|
||||
int rel_x = ev->rel_x;
|
||||
int rel_y = ev->rel_y;
|
||||
|
||||
// Toolbar row
|
||||
if (rel_y >= 3 && rel_y < LIFE_TOOLBAR_H) {
|
||||
int i = (rel_x - 8) / LIFE_BTN_GAP;
|
||||
int within = (rel_x - 8) - i * LIFE_BTN_GAP;
|
||||
if (i >= 0 && i < 4 && within >= 0 && within < LIFE_BTN_W) {
|
||||
switch (i) {
|
||||
case 0: ld->running = !ld->running; break;
|
||||
case 1: life_step(ld); break;
|
||||
case 2: life_randomize(ld); break;
|
||||
case 3: memset(ld->cur, 0, sizeof(ld->cur)); ld->generation = 0; break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Board: toggle the clicked cell
|
||||
int cell, gw, gh, gx_rel, grid_top_rel;
|
||||
life_geometry(win, &cell, &gw, &gh, &gx_rel, &grid_top_rel);
|
||||
int lx = rel_x - gx_rel;
|
||||
int ly = rel_y - grid_top_rel;
|
||||
if (lx >= 0 && ly >= 0 && lx < gw && ly < gh) {
|
||||
int c = lx / cell;
|
||||
int r = ly / cell;
|
||||
if (r >= 0 && r < LIFE_ROWS && c >= 0 && c < LIFE_COLS) {
|
||||
ld->cur[r][c] ^= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gui_app_life_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Game of Life", x, y, (w > 0) ? w : 480, (h > 0) ? h : 380);
|
||||
if (win) {
|
||||
life_data_t* ld = (life_data_t*)kzalloc(sizeof(life_data_t));
|
||||
if (ld) {
|
||||
life_randomize(ld);
|
||||
ld->running = true;
|
||||
}
|
||||
win->user_data = ld;
|
||||
win->paint = life_paint;
|
||||
win->handle_event = life_event;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 8. Rust Kernel Lab -- showcases the memory-safe Rust subsystem
|
||||
// =================================================================
|
||||
typedef struct {
|
||||
rust_crypto_bench_result_t bench; // measured once at launch
|
||||
bool have_bench;
|
||||
uint32_t frame;
|
||||
uint8_t entropy[8]; // live CSPRNG sample
|
||||
} rustlab_data_t;
|
||||
|
||||
static void rustlab_bar(int x, int y, int w, const char* label, uint32_t value,
|
||||
uint32_t max_value, uint32_t color) {
|
||||
char buf[48];
|
||||
gui_gfx_draw_string(x, y, label, GUI_THEME_TEXT_MAIN);
|
||||
int bx = x + 96;
|
||||
int bw = w - 96 - 64;
|
||||
if (bw < 20) bw = 20;
|
||||
gui_gfx_fill_rect(bx, y, bw, 10, GUI_THEME_BG_DARK);
|
||||
uint32_t denom = (max_value > 0) ? max_value : 1;
|
||||
int fill = (int)(((uint64_t)value * (uint64_t)bw) / denom);
|
||||
if (fill > bw) fill = bw;
|
||||
gui_gfx_fill_rect(bx, y, fill, 10, color);
|
||||
gui_gfx_draw_rect(bx, y, bw, 10, GUI_THEME_BORDER);
|
||||
snprintf(buf, sizeof(buf), "%u MB/s", value);
|
||||
gui_gfx_draw_string(bx + bw + 6, y, buf, GUI_THEME_TEXT_MUTED);
|
||||
}
|
||||
|
||||
static void rustlab_paint(gui_window_t* win) {
|
||||
rustlab_data_t* rd = (rustlab_data_t*)win->user_data;
|
||||
if (!rd) return;
|
||||
|
||||
int cx = win->x + 14;
|
||||
int cy = win->y + GUI_TITLEBAR_HEIGHT + 12;
|
||||
int w = win->width - 28;
|
||||
|
||||
gui_gfx_draw_string_16_shadow(cx, cy, "Rust Kernel Subsystem (no_std)", GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
cy += 26;
|
||||
|
||||
// Checksum engine (recomputed live -- it is cheap)
|
||||
static const char sample[] = "SUB-OS";
|
||||
uint32_t crc = rust_crc32c((const uint8_t*)sample, 6);
|
||||
uint32_t adl = rust_adler32((const uint8_t*)sample, 6);
|
||||
char buf[80];
|
||||
gui_gfx_draw_string(cx, cy, "Checksums of \"SUB-OS\":", GUI_THEME_SUCCESS);
|
||||
cy += 14;
|
||||
snprintf(buf, sizeof(buf), "CRC-32C 0x%08X Adler-32 0x%08X", crc, adl);
|
||||
gui_gfx_draw_string(cx + 8, cy, buf, GUI_THEME_TEXT_MUTED);
|
||||
cy += 22;
|
||||
|
||||
// Crypto benchmark bars (measured once at launch)
|
||||
gui_gfx_draw_string(cx, cy, "Crypto throughput (measured):", GUI_THEME_SUCCESS);
|
||||
cy += 16;
|
||||
if (rd->have_bench) {
|
||||
uint32_t mx = rd->bench.chacha20_mbs;
|
||||
if (rd->bench.sha3_256_mbs > mx) mx = rd->bench.sha3_256_mbs;
|
||||
if (rd->bench.aes128_mbs > mx) mx = rd->bench.aes128_mbs;
|
||||
rustlab_bar(cx + 4, cy, w, "ChaCha20", rd->bench.chacha20_mbs, mx, GUI_THEME_ACCENT);
|
||||
cy += 16;
|
||||
rustlab_bar(cx + 4, cy, w, "SHA3-256", rd->bench.sha3_256_mbs, mx, GUI_THEME_SUCCESS);
|
||||
cy += 16;
|
||||
rustlab_bar(cx + 4, cy, w, "AES-128", rd->bench.aes128_mbs, mx, GUI_THEME_WARNING);
|
||||
cy += 16;
|
||||
snprintf(buf, sizeof(buf), "Composite score: %u", rd->bench.score);
|
||||
gui_gfx_draw_string(cx + 4, cy, buf, GUI_THEME_TEXT_DIM);
|
||||
cy += 22;
|
||||
} else {
|
||||
gui_gfx_draw_string(cx + 4, cy, "benchmark unavailable", GUI_THEME_TEXT_DIM);
|
||||
cy += 22;
|
||||
}
|
||||
|
||||
// Live CSPRNG entropy, refreshed periodically
|
||||
if ((rd->frame++ % 20) == 0) {
|
||||
rust_csprng_get_random(rd->entropy, sizeof(rd->entropy));
|
||||
}
|
||||
gui_gfx_draw_string(cx, cy, "ChaCha20 CSPRNG stream:", GUI_THEME_SUCCESS);
|
||||
cy += 14;
|
||||
snprintf(buf, sizeof(buf), "%02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
rd->entropy[0], rd->entropy[1], rd->entropy[2], rd->entropy[3],
|
||||
rd->entropy[4], rd->entropy[5], rd->entropy[6], rd->entropy[7]);
|
||||
gui_gfx_draw_string(cx + 8, cy, buf, GUI_THEME_PRIMARY);
|
||||
}
|
||||
|
||||
static void rustlab_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
|
||||
kfree(win->user_data);
|
||||
win->user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void gui_app_rustlab_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Rust Lab", x, y, (w > 0) ? w : 420, (h > 0) ? h : 300);
|
||||
if (win) {
|
||||
rustlab_data_t* rd = (rustlab_data_t*)kzalloc(sizeof(rustlab_data_t));
|
||||
if (rd) {
|
||||
rd->have_bench = (rust_crypto_run_benchmark(&rd->bench) == 0);
|
||||
rust_csprng_get_random(rd->entropy, sizeof(rd->entropy));
|
||||
}
|
||||
win->user_data = rd;
|
||||
win->paint = rustlab_paint;
|
||||
win->handle_event = rustlab_event;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 9. NT Object Browser -- a WinObj-style view of the object namespace
|
||||
// =================================================================
|
||||
typedef struct { ob_object_t* obj; int depth; } objrow_t;
|
||||
#define OBJBROWSE_MAX_ROWS 96
|
||||
|
||||
typedef struct {
|
||||
int scroll;
|
||||
} objbrowse_data_t;
|
||||
|
||||
static int objbrowse_flatten(ob_object_t* obj, int depth, objrow_t* rows, int max, int count) {
|
||||
if (!obj || count >= max) return count;
|
||||
rows[count].obj = obj;
|
||||
rows[count].depth = depth;
|
||||
count++;
|
||||
if (obj->type == OB_TYPE_DIRECTORY) {
|
||||
int c = ob_dir_child_count(obj);
|
||||
for (int i = 0; i < c && count < max; i++) {
|
||||
count = objbrowse_flatten(ob_dir_child(obj, i), depth + 1, rows, max, count);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static uint32_t objbrowse_type_color(ob_type_t t) {
|
||||
switch (t) {
|
||||
case OB_TYPE_DIRECTORY: return GUI_THEME_PRIMARY;
|
||||
case OB_TYPE_EVENT: return GUI_THEME_SUCCESS;
|
||||
case OB_TYPE_SEMAPHORE: return GUI_THEME_WARNING;
|
||||
case OB_TYPE_MUTANT: return GUI_THEME_ACCENT;
|
||||
default: return GUI_THEME_TEXT_MUTED;
|
||||
}
|
||||
}
|
||||
|
||||
static void objbrowse_paint(gui_window_t* win) {
|
||||
objbrowse_data_t* bd = (objbrowse_data_t*)win->user_data;
|
||||
if (!bd) return;
|
||||
|
||||
int cx = win->x + 12;
|
||||
int top = win->y + GUI_TITLEBAR_HEIGHT;
|
||||
|
||||
// Header + live stats
|
||||
uint32_t objs = 0, handles = 0, per[OB_TYPE_MAX];
|
||||
ob_get_stats(&objs, &handles, per);
|
||||
gui_gfx_draw_string_16_shadow(cx, top + 8, "NT Object Namespace \\", GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
char hdr[80];
|
||||
snprintf(hdr, sizeof(hdr), "%u objects %u handles open (scroll: click top/bottom)", objs, handles);
|
||||
gui_gfx_draw_string(cx, top + 28, hdr, GUI_THEME_TEXT_MUTED);
|
||||
|
||||
// Flatten the live namespace
|
||||
objrow_t rows[OBJBROWSE_MAX_ROWS];
|
||||
int total = objbrowse_flatten(ob_root_directory(), 0, rows, OBJBROWSE_MAX_ROWS, 0);
|
||||
|
||||
int list_top = top + 44;
|
||||
int row_h = 13;
|
||||
int visible = (win->height - (list_top - win->y) - 10) / row_h;
|
||||
if (visible < 1) visible = 1;
|
||||
|
||||
if (bd->scroll > total - visible) bd->scroll = total - visible;
|
||||
if (bd->scroll < 0) bd->scroll = 0;
|
||||
|
||||
for (int i = 0; i < visible && (bd->scroll + i) < total; i++) {
|
||||
objrow_t* r = &rows[bd->scroll + i];
|
||||
ob_object_t* o = r->obj;
|
||||
int y = list_top + i * row_h;
|
||||
int x = cx + r->depth * 14;
|
||||
|
||||
char line[96];
|
||||
if (o->type == OB_TYPE_DIRECTORY) {
|
||||
snprintf(line, sizeof(line), "%s\\", o->name);
|
||||
} else {
|
||||
snprintf(line, sizeof(line), "%s", o->name);
|
||||
}
|
||||
gui_gfx_draw_string(x, y, line, objbrowse_type_color(o->type));
|
||||
|
||||
char meta[48];
|
||||
snprintf(meta, sizeof(meta), "%-9s r=%d h=%d", ob_type_name(o->type), o->ref_count, o->handle_count);
|
||||
gui_gfx_draw_string(win->x + win->width - 180, y, meta, GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
|
||||
// Scroll indicator
|
||||
if (total > visible) {
|
||||
char sc[32];
|
||||
snprintf(sc, sizeof(sc), "%d-%d / %d", bd->scroll + 1,
|
||||
(bd->scroll + visible < total) ? bd->scroll + visible : total, total);
|
||||
gui_gfx_draw_string(win->x + win->width - 90, top + 28, sc, GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
}
|
||||
|
||||
static void objbrowse_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
objbrowse_data_t* bd = (objbrowse_data_t*)win->user_data;
|
||||
if (!bd) return;
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE) {
|
||||
if (win->user_data) { kfree(win->user_data); win->user_data = NULL; }
|
||||
return;
|
||||
}
|
||||
if (ev->type != GUI_EVENT_MOUSE_DOWN) return;
|
||||
|
||||
// Top third scrolls up, bottom third scrolls down.
|
||||
if (ev->rel_y < win->height / 3) {
|
||||
bd->scroll -= 3;
|
||||
} else if (ev->rel_y > (win->height * 2) / 3) {
|
||||
bd->scroll += 3;
|
||||
}
|
||||
if (bd->scroll < 0) bd->scroll = 0;
|
||||
}
|
||||
|
||||
void gui_app_objbrowse_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Object Browser", x, y, (w > 0) ? w : 460, (h > 0) ? h : 340);
|
||||
if (win) {
|
||||
objbrowse_data_t* bd = (objbrowse_data_t*)kzalloc(sizeof(objbrowse_data_t));
|
||||
win->user_data = bd;
|
||||
win->paint = objbrowse_paint;
|
||||
win->handle_event = objbrowse_event;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 10. Registry Editor -- navigate the NT \Registry hive tree
|
||||
// =================================================================
|
||||
typedef struct {
|
||||
reg_key_t* current;
|
||||
int row_h;
|
||||
int list_top_rel; // list start, relative to window content top
|
||||
int sub_count; // subkeys shown this frame (for click mapping)
|
||||
bool has_up; // a ".." row is present
|
||||
} regedit_data_t;
|
||||
|
||||
static void regedit_build_path(reg_key_t* key, char* out, size_t len) {
|
||||
// Walk parents into a reversed segment stack, then join with backslashes.
|
||||
const char* segs[16];
|
||||
int n = 0;
|
||||
reg_key_t* k = key;
|
||||
while (k && n < 16) { segs[n++] = k->name; k = k->parent; }
|
||||
size_t pos = 0;
|
||||
out[0] = '\0';
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
int wrote = snprintf(out + pos, (pos < len) ? len - pos : 0, "\\%s", segs[i]);
|
||||
if (wrote > 0) pos += (size_t)wrote;
|
||||
if (pos >= len) break;
|
||||
}
|
||||
}
|
||||
|
||||
static void regedit_paint(gui_window_t* win) {
|
||||
regedit_data_t* rd = (regedit_data_t*)win->user_data;
|
||||
if (!rd) return;
|
||||
if (!rd->current) rd->current = reg_root();
|
||||
|
||||
int cx = win->x + 12;
|
||||
int top = win->y + GUI_TITLEBAR_HEIGHT;
|
||||
|
||||
char path[192];
|
||||
regedit_build_path(rd->current, path, sizeof(path));
|
||||
gui_gfx_draw_string_16_shadow(cx, top + 8, "Registry Editor", GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
gui_gfx_draw_string(cx, top + 28, path, GUI_THEME_TEXT_MUTED);
|
||||
|
||||
rd->row_h = 13;
|
||||
rd->list_top_rel = GUI_TITLEBAR_HEIGHT + 46;
|
||||
int y = top + 46;
|
||||
|
||||
rd->has_up = (rd->current->parent != NULL);
|
||||
if (rd->has_up) {
|
||||
gui_gfx_draw_string(cx, y, "..", GUI_THEME_ACCENT);
|
||||
y += rd->row_h;
|
||||
}
|
||||
|
||||
// Subkeys (navigable)
|
||||
int kc = reg_enum_key_count(rd->current);
|
||||
rd->sub_count = kc;
|
||||
for (int i = 0; i < kc; i++) {
|
||||
reg_key_t* sub = reg_enum_key(rd->current, i);
|
||||
char line[64];
|
||||
snprintf(line, sizeof(line), "[%s]", sub->name);
|
||||
gui_gfx_draw_string(cx, y, line, GUI_THEME_SUCCESS);
|
||||
y += rd->row_h;
|
||||
}
|
||||
|
||||
// Values pane
|
||||
y += 4;
|
||||
gui_gfx_draw_line(cx, y, win->x + win->width - 12, y, GUI_THEME_BORDER);
|
||||
y += 6;
|
||||
int vc = reg_enum_value_count(rd->current);
|
||||
if (vc == 0) {
|
||||
gui_gfx_draw_string(cx, y, "(no values)", GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
for (int i = 0; i < vc; i++) {
|
||||
reg_value_t* v = reg_enum_value(rd->current, i);
|
||||
char rendered[REG_STR_MAX + 8];
|
||||
reg_value_to_string(v, rendered, sizeof(rendered));
|
||||
char line[160];
|
||||
snprintf(line, sizeof(line), "%-18s %-10s %s", v->name, reg_type_name(v->type), rendered);
|
||||
gui_gfx_draw_string(cx, y, line, GUI_THEME_TEXT_MAIN);
|
||||
y += rd->row_h;
|
||||
if (y > win->y + win->height - 12) break;
|
||||
}
|
||||
}
|
||||
|
||||
static void regedit_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
regedit_data_t* rd = (regedit_data_t*)win->user_data;
|
||||
if (!rd) return;
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE) {
|
||||
if (win->user_data) { kfree(win->user_data); win->user_data = NULL; }
|
||||
return;
|
||||
}
|
||||
if (ev->type != GUI_EVENT_MOUSE_DOWN) return;
|
||||
if (!rd->current) return;
|
||||
|
||||
int rel = ev->rel_y - rd->list_top_rel;
|
||||
if (rel < 0) return;
|
||||
int row = rel / (rd->row_h > 0 ? rd->row_h : 13);
|
||||
|
||||
int idx = 0;
|
||||
if (rd->has_up) {
|
||||
if (row == 0) { rd->current = rd->current->parent; return; }
|
||||
idx = row - 1;
|
||||
} else {
|
||||
idx = row;
|
||||
}
|
||||
if (idx >= 0 && idx < rd->sub_count) {
|
||||
reg_key_t* sub = reg_enum_key(rd->current, idx);
|
||||
if (sub) rd->current = sub;
|
||||
}
|
||||
}
|
||||
|
||||
void gui_app_regedit_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Registry Editor", x, y, (w > 0) ? w : 480, (h > 0) ? h : 360);
|
||||
if (win) {
|
||||
regedit_data_t* rd = (regedit_data_t*)kzalloc(sizeof(regedit_data_t));
|
||||
if (rd) rd->current = reg_root();
|
||||
win->user_data = rd;
|
||||
win->paint = regedit_paint;
|
||||
win->handle_event = regedit_event;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 12. Analytics -- live charts backed by the C++ Analytics Engine
|
||||
// =================================================================
|
||||
typedef struct {
|
||||
uint64_t last_sample_ns; // throttle: pull a new sample ~2 Hz
|
||||
int selected; // -1 = grid overview, else single-channel focus
|
||||
} analytics_data_t;
|
||||
|
||||
#define ANALYTICS_SAMPLE_INTERVAL_NS 500000000ULL // 0.5s
|
||||
|
||||
static uint32_t analytics_channel_color(int ch) {
|
||||
switch (ch) {
|
||||
case 0: return GUI_THEME_PRIMARY; // CPU
|
||||
case 1: return GUI_THEME_ACCENT; // Memory
|
||||
case 2: return GUI_THEME_WARNING; // Heap
|
||||
case 3: return GUI_THEME_SUCCESS; // Network
|
||||
default: return GUI_THEME_TEXT_MUTED;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw one channel's line graph inside the rectangle (gx,gy,gw,gh).
|
||||
static void analytics_draw_graph(int ch, int gx, int gy, int gw, int gh, bool detailed) {
|
||||
uint32_t color = analytics_channel_color(ch);
|
||||
|
||||
// Panel background + border
|
||||
gui_gfx_fill_rect(gx, gy, gw, gh, GUI_THEME_BG_DARK);
|
||||
gui_gfx_draw_rect(gx, gy, gw, gh, GUI_THEME_BORDER);
|
||||
|
||||
// Header: name + last value
|
||||
uint32_t mn, mx, avg, last, sd;
|
||||
cpp_analytics_get_stats(ch, &mn, &mx, &avg, &last, &sd);
|
||||
uint32_t scale = cpp_analytics_channel_scale(ch);
|
||||
if (scale == 0) scale = 1;
|
||||
|
||||
char hdr[48];
|
||||
snprintf(hdr, sizeof(hdr), "%s", cpp_analytics_channel_name(ch));
|
||||
gui_gfx_draw_string(gx + 6, gy + 5, hdr, color);
|
||||
char val[24];
|
||||
snprintf(val, sizeof(val), "%u%s", last, cpp_analytics_channel_unit(ch));
|
||||
gui_gfx_draw_string(gx + gw - 8 * (int)strlen(val) - 6, gy + 5, val, GUI_THEME_TEXT_MAIN);
|
||||
|
||||
// Plot area
|
||||
int px = gx + 6;
|
||||
int py = gy + 20;
|
||||
int pw = gw - 12;
|
||||
int ph = gh - (detailed ? 40 : 34);
|
||||
if (ph < 8) ph = 8;
|
||||
|
||||
// Horizontal gridlines (25/50/75%)
|
||||
for (int g = 1; g < 4; g++) {
|
||||
int gyl = py + (ph * g) / 4;
|
||||
gui_gfx_draw_line(px, gyl, px + pw, gyl, GUI_THEME_BG_ELEVATED);
|
||||
}
|
||||
|
||||
// Fetch the series and draw it as a connected line with a soft fill.
|
||||
uint32_t series[CPP_ANALYTICS_HISTORY];
|
||||
int n = cpp_analytics_get_series(ch, series, CPP_ANALYTICS_HISTORY);
|
||||
if (n >= 1) {
|
||||
int prev_x = 0, prev_y = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int x = px + (n > 1 ? (pw * i) / (n - 1) : pw);
|
||||
uint32_t v = series[i];
|
||||
if (v > scale) v = scale;
|
||||
int y = py + ph - (int)(((uint64_t)v * ph) / scale);
|
||||
// vertical fill to baseline
|
||||
gui_gfx_draw_line(x, y, x, py + ph, (color & 0x00FFFFFF) | 0x30000000);
|
||||
if (i > 0) gui_gfx_draw_line(prev_x, prev_y, x, y, color);
|
||||
prev_x = x; prev_y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer stats
|
||||
char foot[64];
|
||||
if (detailed) {
|
||||
snprintf(foot, sizeof(foot), "min %u avg %u max %u sd %u", mn, avg, mx, sd);
|
||||
} else {
|
||||
snprintf(foot, sizeof(foot), "min %u avg %u max %u", mn, avg, mx);
|
||||
}
|
||||
gui_gfx_draw_string(gx + 6, gy + gh - 12, foot, GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
|
||||
static void analytics_paint(gui_window_t* win) {
|
||||
analytics_data_t* ad = (analytics_data_t*)win->user_data;
|
||||
if (!ad) return;
|
||||
|
||||
// Throttled live sampling driven by the compositor's repaint cadence.
|
||||
uint64_t now = ktime_ns();
|
||||
if (now - ad->last_sample_ns >= ANALYTICS_SAMPLE_INTERVAL_NS) {
|
||||
cpp_analytics_sample();
|
||||
ad->last_sample_ns = now;
|
||||
}
|
||||
|
||||
int cx = win->x + 10;
|
||||
int top = win->y + GUI_TITLEBAR_HEIGHT + 4;
|
||||
|
||||
char title[72];
|
||||
snprintf(title, sizeof(title), "C++ Analytics Engine %llu samples",
|
||||
(unsigned long long)cpp_analytics_sample_count());
|
||||
gui_gfx_draw_string_16_shadow(cx, top, title, GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
gui_gfx_draw_string(cx, top + 20, "Templated time-series + Observer pattern (kernel/cpp)", GUI_THEME_TEXT_MUTED);
|
||||
|
||||
int grid_top = top + 38;
|
||||
int nch = cpp_analytics_channel_count();
|
||||
|
||||
if (ad->selected >= 0 && ad->selected < nch) {
|
||||
// Focused single-channel view.
|
||||
analytics_draw_graph(ad->selected, cx, grid_top,
|
||||
win->width - 20, win->height - (grid_top - win->y) - 10, true);
|
||||
gui_gfx_draw_string(cx + 6, win->y + win->height - 12,
|
||||
"(click to return to overview)", GUI_THEME_TEXT_DIM);
|
||||
} else {
|
||||
// 2x2 overview grid.
|
||||
int gw = (win->width - 20 - 8) / 2;
|
||||
int gh = (win->height - (grid_top - win->y) - 10 - 8) / 2;
|
||||
if (gw < 60) gw = 60;
|
||||
if (gh < 40) gh = 40;
|
||||
for (int i = 0; i < nch && i < 4; i++) {
|
||||
int col = i % 2, row = i / 2;
|
||||
analytics_draw_graph(i, cx + col * (gw + 8), grid_top + row * (gh + 8), gw, gh, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void analytics_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
analytics_data_t* ad = (analytics_data_t*)win->user_data;
|
||||
if (!ad) return;
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE) {
|
||||
if (win->user_data) { kfree(win->user_data); win->user_data = NULL; }
|
||||
return;
|
||||
}
|
||||
if (ev->type != GUI_EVENT_MOUSE_DOWN) return;
|
||||
|
||||
if (ad->selected >= 0) {
|
||||
ad->selected = -1; // any click leaves focus mode
|
||||
return;
|
||||
}
|
||||
// Overview: figure out which quadrant was clicked and focus it.
|
||||
int grid_top_rel = GUI_TITLEBAR_HEIGHT + 4 + 38;
|
||||
int gw = (win->width - 20 - 8) / 2;
|
||||
int gh = (win->height - grid_top_rel - 10 - 8) / 2;
|
||||
if (gw < 1 || gh < 1) return;
|
||||
int rx = ev->rel_x - 10;
|
||||
int ry = ev->rel_y - grid_top_rel;
|
||||
if (rx < 0 || ry < 0) return;
|
||||
int col = rx / (gw + 8);
|
||||
int row = ry / (gh + 8);
|
||||
if (col > 1) col = 1;
|
||||
if (row > 1) row = 1;
|
||||
int idx = row * 2 + col;
|
||||
if (idx >= 0 && idx < cpp_analytics_channel_count()) ad->selected = idx;
|
||||
}
|
||||
|
||||
void gui_app_analytics_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Analytics", x, y, (w > 0) ? w : 560, (h > 0) ? h : 400);
|
||||
if (win) {
|
||||
analytics_data_t* ad = (analytics_data_t*)kzalloc(sizeof(analytics_data_t));
|
||||
if (ad) { ad->selected = -1; ad->last_sample_ns = 0; }
|
||||
win->user_data = ad;
|
||||
win->paint = analytics_paint;
|
||||
win->handle_event = analytics_event;
|
||||
}
|
||||
}
|
||||
+52
-9
@@ -10,6 +10,7 @@
|
||||
#include <gui/gui_dialog.h>
|
||||
#include <gui/gui_gfx.h>
|
||||
#include <gui/gui_theme.h>
|
||||
#include <gui/gui_desktop.h>
|
||||
#include <kernel/ktest.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/task.h>
|
||||
@@ -54,6 +55,23 @@ typedef struct {
|
||||
|
||||
static const char* const settings_tabs[] = { "Appearance", "Input", "Power" };
|
||||
|
||||
// Map the Settings governor radio (0 powersave / 1 ondemand / 2 performance)
|
||||
// to and from the cpufreq_gov_type_t enum, whose order differs.
|
||||
static cpufreq_gov_type_t settings_gov_from_choice(int choice) {
|
||||
switch (choice) {
|
||||
case 0: return CPUFREQ_GOV_POWERSAVE;
|
||||
case 2: return CPUFREQ_GOV_PERFORMANCE;
|
||||
default: return CPUFREQ_GOV_ONDEMAND;
|
||||
}
|
||||
}
|
||||
static int settings_choice_from_gov(cpufreq_gov_type_t gov) {
|
||||
switch (gov) {
|
||||
case CPUFREQ_GOV_POWERSAVE: return 0;
|
||||
case CPUFREQ_GOV_PERFORMANCE: return 2;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void settings_paint(gui_window_t* win) {
|
||||
settings_data_t* sd = (settings_data_t*)win->user_data;
|
||||
if (!sd) return;
|
||||
@@ -69,7 +87,9 @@ static void settings_paint(gui_window_t* win) {
|
||||
if (sd->active_tab == 0) {
|
||||
gui_label_bold(10, y, "Desktop Appearance", GUI_THEME_PRIMARY);
|
||||
y += 24;
|
||||
gui_checkbox(10, 10, y, "Show wallpaper grid overlay", &sd->show_grid);
|
||||
if (gui_checkbox(10, 10, y, "Show wallpaper grid overlay", &sd->show_grid)) {
|
||||
gui_desktop_set_grid(sd->show_grid); // apply immediately
|
||||
}
|
||||
y += 22;
|
||||
gui_checkbox(11, 10, y, "Drop shadows under windows", &sd->window_shadows);
|
||||
y += 26;
|
||||
@@ -95,7 +115,9 @@ static void settings_paint(gui_window_t* win) {
|
||||
|
||||
gui_checkbox(21, 10, y, "Enable UI sound effects", &sd->sound_enabled);
|
||||
y += 22;
|
||||
gui_checkbox(22, 10, y, "Show seconds in taskbar clock", &sd->show_clock_seconds);
|
||||
if (gui_checkbox(22, 10, y, "Show seconds in taskbar clock", &sd->show_clock_seconds)) {
|
||||
gui_desktop_set_clock_seconds(sd->show_clock_seconds); // apply immediately
|
||||
}
|
||||
y += 30;
|
||||
|
||||
const mouse_state_t* ms = mouse_get_state();
|
||||
@@ -111,9 +133,13 @@ static void settings_paint(gui_window_t* win) {
|
||||
|
||||
gui_label(10, y, "CPUFreq governor", GUI_THEME_TEXT_MUTED);
|
||||
y += 16;
|
||||
gui_radio(30, 14, y, "powersave", &sd->governor_choice, 0); y += 18;
|
||||
gui_radio(31, 14, y, "ondemand", &sd->governor_choice, 1); y += 18;
|
||||
gui_radio(32, 14, y, "performance", &sd->governor_choice, 2); y += 26;
|
||||
bool gov_changed = false;
|
||||
gov_changed |= gui_radio(30, 14, y, "powersave", &sd->governor_choice, 0); y += 18;
|
||||
gov_changed |= gui_radio(31, 14, y, "ondemand", &sd->governor_choice, 1); y += 18;
|
||||
gov_changed |= gui_radio(32, 14, y, "performance", &sd->governor_choice, 2); y += 26;
|
||||
if (gov_changed) {
|
||||
cpufreq_set_governor(settings_gov_from_choice(sd->governor_choice));
|
||||
}
|
||||
|
||||
cpufreq_stats_t cf = cpufreq_get_stats();
|
||||
snprintf(buf, sizeof(buf), "Current clock: %u.%03u GHz",
|
||||
@@ -128,6 +154,10 @@ static void settings_paint(gui_window_t* win) {
|
||||
|
||||
int btn_y = gui_widget_client_height() - 32;
|
||||
if (gui_button(90, 10, btn_y, 96, 24, "Apply")) {
|
||||
// Push every preference to the live system, then confirm.
|
||||
gui_desktop_set_grid(sd->show_grid);
|
||||
gui_desktop_set_clock_seconds(sd->show_clock_seconds);
|
||||
cpufreq_set_governor(settings_gov_from_choice(sd->governor_choice));
|
||||
gui_dialog_show(GUI_DIALOG_INFO, "Settings",
|
||||
"Preferences applied to the current desktop session.");
|
||||
}
|
||||
@@ -140,11 +170,23 @@ static void settings_paint(gui_window_t* win) {
|
||||
sd->cursor_speed = 5;
|
||||
sd->brightness = 100;
|
||||
sd->governor_choice = 1;
|
||||
// Apply the restored defaults live as well.
|
||||
gui_desktop_set_grid(true);
|
||||
gui_desktop_set_clock_seconds(true);
|
||||
cpufreq_set_governor(CPUFREQ_GOV_ONDEMAND);
|
||||
}
|
||||
|
||||
gui_widget_end();
|
||||
}
|
||||
|
||||
static void settings_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
if (ev->type == GUI_EVENT_CLOSE) {
|
||||
if (win->user_data) { kfree(win->user_data); win->user_data = NULL; }
|
||||
return;
|
||||
}
|
||||
gui_widget_feed_event(win, ev);
|
||||
}
|
||||
|
||||
void gui_app_settings_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Settings", x, y,
|
||||
(w > 0) ? w : 380, (h > 0) ? h : 300);
|
||||
@@ -153,17 +195,18 @@ void gui_app_settings_launch(int x, int y, int w, int h) {
|
||||
settings_data_t* sd = (settings_data_t*)kzalloc(sizeof(settings_data_t));
|
||||
if (!sd) return;
|
||||
|
||||
sd->show_grid = true;
|
||||
// Seed the controls from the live system so the app reflects real state.
|
||||
sd->show_grid = gui_desktop_get_grid();
|
||||
sd->window_shadows = true;
|
||||
sd->sound_enabled = true;
|
||||
sd->show_clock_seconds = true;
|
||||
sd->show_clock_seconds = gui_desktop_get_clock_seconds();
|
||||
sd->cursor_speed = 5;
|
||||
sd->brightness = 100;
|
||||
sd->governor_choice = 1;
|
||||
sd->governor_choice = settings_choice_from_gov(cpufreq_get_stats().governor);
|
||||
|
||||
win->user_data = sd;
|
||||
win->paint = settings_paint;
|
||||
win->handle_event = app_forward_event;
|
||||
win->handle_event = settings_event;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
@@ -81,3 +81,51 @@ void gui_cursor_draw(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Save-under overlay: lets the compositor move the cursor without repainting
|
||||
// the whole scene. The block under the cursor (body + drop shadow) is stashed
|
||||
// before drawing and restored on the next frame.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define CUR_SAVE_W (CURSOR_W + 3)
|
||||
#define CUR_SAVE_H (CURSOR_H + 3)
|
||||
|
||||
static uint32_t g_save[CUR_SAVE_H * CUR_SAVE_W];
|
||||
static int g_save_x = -1, g_save_y = -1, g_save_w = 0, g_save_h = 0;
|
||||
|
||||
// Put back whatever was under the cursor last frame (erasing it).
|
||||
void gui_cursor_restore_under(void) {
|
||||
if (g_save_x < 0) return;
|
||||
uint32_t* bb = gui_gfx_get_backbuffer();
|
||||
if (!bb) { g_save_x = -1; return; }
|
||||
int W = gui_gfx_get_width();
|
||||
for (int j = 0; j < g_save_h; j++) {
|
||||
uint32_t* dst = &bb[(g_save_y + j) * W + g_save_x];
|
||||
const uint32_t* src = &g_save[j * g_save_w];
|
||||
for (int i = 0; i < g_save_w; i++) dst[i] = src[i];
|
||||
}
|
||||
g_save_x = -1;
|
||||
}
|
||||
|
||||
// Stash the block under the current cursor position, then draw the cursor.
|
||||
void gui_cursor_composite(void) {
|
||||
uint32_t* bb = gui_gfx_get_backbuffer();
|
||||
if (!bb) return;
|
||||
int W = gui_gfx_get_width();
|
||||
int H = gui_gfx_get_height();
|
||||
|
||||
int x = g_cur_x, y = g_cur_y;
|
||||
int w = CUR_SAVE_W, h = CUR_SAVE_H;
|
||||
if (x + w > W) w = W - x;
|
||||
if (y + h > H) h = H - y;
|
||||
if (w <= 0 || h <= 0) { g_save_x = -1; gui_cursor_draw(); return; }
|
||||
|
||||
for (int j = 0; j < h; j++) {
|
||||
const uint32_t* srow = &bb[(y + j) * W + x];
|
||||
uint32_t* d = &g_save[j * w];
|
||||
for (int i = 0; i < w; i++) d[i] = srow[i];
|
||||
}
|
||||
g_save_x = x; g_save_y = y; g_save_w = w; g_save_h = h;
|
||||
|
||||
gui_cursor_draw();
|
||||
}
|
||||
+147
-22
@@ -24,6 +24,8 @@
|
||||
#include <drivers/keyboard.h>
|
||||
#include <drivers/rtc.h>
|
||||
#include <drivers/cpufreq.h>
|
||||
#include <kernel/ktime.h>
|
||||
#include <kernel/sched.h>
|
||||
#include <mm/pmm.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <arch/arch.h>
|
||||
@@ -56,14 +58,25 @@ static const desktop_app_t g_apps[] = {
|
||||
{ "Editor", GUI_ICON_FILE, gui_app_editor_launch, 120, 50, 500, 330 },
|
||||
{ "Calculator", GUI_ICON_CALC, gui_app_calc_launch, 260, 100, 220, 240 },
|
||||
{ "Paint", GUI_ICON_PAINT, gui_app_paint_launch, 200, 60, 360, 260 },
|
||||
{ "Life", GUI_ICON_FLASK, gui_app_life_launch, 240, 70, 480, 380 },
|
||||
{ "Rust Lab", GUI_ICON_INFO, gui_app_rustlab_launch, 300, 80, 420, 300 },
|
||||
{ "Objects", GUI_ICON_TASKS, gui_app_objbrowse_launch,280, 70, 460, 340 },
|
||||
{ "RegEdit", GUI_ICON_SETTINGS, gui_app_regedit_launch, 300, 90, 480, 360 },
|
||||
{ "Clock", GUI_ICON_CLOCK, gui_app_clock_launch, 460, 120, 280, 260 },
|
||||
{ "Settings", GUI_ICON_SETTINGS, gui_app_settings_launch, 160, 60, 400, 320 },
|
||||
{ "Analytics", GUI_ICON_MONITOR, gui_app_analytics_launch,320, 70, 560, 400 },
|
||||
{ "Sorry", GUI_ICON_HEART, gui_app_sorry_launch, 260, 60, 660, 470 },
|
||||
{ "Network", GUI_ICON_NETWORK, gui_app_netmon_launch, 300, 80, 560, 400 },
|
||||
{ "Web", GUI_ICON_GLOBE, gui_app_web_launch, 280, 70, 600, 420 },
|
||||
{ "About", GUI_ICON_INFO, gui_app_about_launch, 240, 140, 330, 170 },
|
||||
};
|
||||
#define APP_COUNT ((int)(sizeof(g_apps) / sizeof(g_apps[0])))
|
||||
|
||||
// Which of the above appear on the desktop itself.
|
||||
static const int g_desktop_icons[] = { 0, 1, 2, 4, 5, 10 };
|
||||
// Indices into g_apps[]: Terminal, Files, Monitor, Kernel Log, KTest, Life,
|
||||
// Rust Lab, Settings, Analytics, Sorry. (Objects/RegEdit live in the start
|
||||
// menu.) Network sits alongside them.
|
||||
static const int g_desktop_icons[] = { 0, 1, 2, 4, 5, 9, 10, 14, 15, 16, 17, 18 };
|
||||
#define DESKTOP_ICON_COUNT ((int)(sizeof(g_desktop_icons) / sizeof(g_desktop_icons[0])))
|
||||
|
||||
static bool g_start_menu_open = false;
|
||||
@@ -72,9 +85,22 @@ static int g_context_x = 0, g_context_y = 0;
|
||||
static bool g_desktop_running = false;
|
||||
static bool g_prev_left = false, g_prev_right = false;
|
||||
static bool g_show_grid = true;
|
||||
static bool g_clock_seconds = true;
|
||||
static int g_selected_icon = -1;
|
||||
static int g_hover_menu_item = -1;
|
||||
static uint64_t g_frames = 0;
|
||||
static uint32_t g_fps = 0; // frames per second, sampled from ktime
|
||||
static uint64_t g_fps_last_ms = 0;
|
||||
static uint64_t g_fps_last_frames = 0;
|
||||
static uint64_t g_last_scene_ms = 0; // last full scene recomposite (for heartbeat)
|
||||
static int g_prev_mx = -1, g_prev_my = -1;
|
||||
static bool g_anim_request = false; // an app asked for the next frame back
|
||||
|
||||
// Target frame budget: 60 FPS == 16666 us. The compositor only repaints the
|
||||
// full scene when it is dirty; between times it just moves the cursor overlay,
|
||||
// so idle and pointer-only frames comfortably hit the cap.
|
||||
#define FRAME_BUDGET_US 16666
|
||||
#define SCENE_HEARTBEAT_MS 100 // ~10 Hz refresh for live content (clock, graphs)
|
||||
|
||||
/*
|
||||
* Cached wallpaper.
|
||||
@@ -171,28 +197,50 @@ void gui_desktop_init(void) {
|
||||
// Wallpaper and desktop icons
|
||||
// ===========================================================================
|
||||
|
||||
/* Paint the wallpaper straight into the back buffer. */
|
||||
/* Paint the wallpaper straight into the back buffer. A three-stop vertical
|
||||
* gradient gives the sky depth, a soft edge vignette frames it, and an optional
|
||||
* fine grid adds texture without banding. Painted once and cached. */
|
||||
static void desktop_paint_background(int sw, int wh) {
|
||||
gui_gfx_draw_gradient_v(0, 0, sw, wh, 0xFF0B132B, 0xFF1C2541);
|
||||
// Three-stop vertical gradient: deep space blue -> indigo -> a touch lighter.
|
||||
int mid = wh * 55 / 100;
|
||||
gui_gfx_draw_gradient_v(0, 0, sw, mid, 0xFF070B18, 0xFF141C38);
|
||||
gui_gfx_draw_gradient_v(0, mid, sw, wh - mid, 0xFF141C38, 0xFF1D2A52);
|
||||
|
||||
if (g_show_grid) {
|
||||
// Blend rather than stamp a fixed colour: the gradient's lower half is
|
||||
// lighter than any single grid tone, so a constant colour disappears
|
||||
// there and only shows near the top of the screen.
|
||||
for (int y = 0; y < wh; y += 40) {
|
||||
gui_gfx_fill_rect_blend(0, y, sw, 1, GUI_COLOR_BLACK, 40);
|
||||
// A finer, fainter grid than before reads as texture rather than lines.
|
||||
for (int y = 0; y < wh; y += 48) {
|
||||
gui_gfx_fill_rect_blend(0, y, sw, 1, 0xFF3B4E80, 22);
|
||||
}
|
||||
for (int x = 0; x < sw; x += 40) {
|
||||
gui_gfx_fill_rect_blend(x, 0, 1, wh, GUI_COLOR_BLACK, 40);
|
||||
for (int x = 0; x < sw; x += 48) {
|
||||
gui_gfx_fill_rect_blend(x, 0, 1, wh, 0xFF3B4E80, 22);
|
||||
}
|
||||
}
|
||||
|
||||
// Branding, offset to the right so it clears the icon column.
|
||||
int bx = sw / 2 + 40;
|
||||
int by = wh / 2 - 30;
|
||||
gui_gfx_draw_string_16_shadow(bx - 60, by, "SUB-OS DESKTOP", 0xFF38BDF8, 0xFF0284C7);
|
||||
gui_gfx_draw_string(bx - 76, by + 22, "Modular Monolithic Kernel v0.2.0-LTS", 0xFF64748B);
|
||||
gui_gfx_draw_string(bx - 76, by + 36, "F5 cycle F6 cascade F7 tile F8 max", 0xFF475569);
|
||||
// Soft vignette: concentric 1px frames darkening toward the edges. The
|
||||
// alpha falls off with depth, so the corners recede smoothly.
|
||||
int depth = (wh < sw ? wh : sw) / 6;
|
||||
for (int i = 0; i < depth; i++) {
|
||||
uint8_t a = (uint8_t)(((depth - i) * 42) / depth);
|
||||
if (a == 0) continue;
|
||||
gui_gfx_fill_rect_blend(i, i, sw - 2 * i, 1, GUI_COLOR_BLACK, a); // top
|
||||
gui_gfx_fill_rect_blend(i, wh - 1 - i, sw - 2 * i, 1, GUI_COLOR_BLACK, a); // bottom
|
||||
gui_gfx_fill_rect_blend(i, i, 1, wh - 2 * i, GUI_COLOR_BLACK, a); // left
|
||||
gui_gfx_fill_rect_blend(sw - 1 - i, i, 1, wh - 2 * i, GUI_COLOR_BLACK, a); // right
|
||||
}
|
||||
|
||||
// Centred brand mark with a subtle glow (drawn as a shadowed 16px title).
|
||||
const char* brand = "SUB-OS";
|
||||
int glyph_w = 16; // draw_string_16 advances ~16px per glyph
|
||||
int bw = (int)strlen(brand) * glyph_w;
|
||||
int bx = (sw - bw) / 2;
|
||||
int by = wh / 2 - 40;
|
||||
gui_gfx_draw_string_16_shadow(bx, by, brand, 0xFF7DD3FC, 0xFF0C4A6E);
|
||||
const char* sub = "Modular Monolithic Kernel - v0.2.0-LTS";
|
||||
int sw2 = (int)strlen(sub) * 8;
|
||||
gui_gfx_draw_string((sw - sw2) / 2, by + 24, sub, 0xFF64748B);
|
||||
const char* hint = "F5 cycle F6 cascade F7 tile F8 maximize";
|
||||
int hw = (int)strlen(hint) * 8;
|
||||
gui_gfx_draw_string((sw - hw) / 2, by + 40, hint, 0xFF3E4C6B);
|
||||
}
|
||||
|
||||
void gui_desktop_invalidate_background(void) {
|
||||
@@ -307,7 +355,7 @@ void gui_desktop_render_taskbar(void) {
|
||||
|
||||
// One button per window, in z-order
|
||||
int tab_x = 106;
|
||||
int tray_x = sw - 200;
|
||||
int tray_x = sw - 260; // widened tray: FPS pill + CPU trace + mem + clock
|
||||
for (int i = 0; i < gui_wm_get_window_count() && tab_x < tray_x - 106; i++) {
|
||||
gui_window_t* win = gui_wm_get_window_at_index(i);
|
||||
if (!win || !win->visible) continue;
|
||||
@@ -351,12 +399,29 @@ void gui_desktop_render_taskbar(void) {
|
||||
gui_gfx_draw_string(sw - 140, ty + 12, buf, GUI_COLOR_BLACK);
|
||||
(void)total;
|
||||
|
||||
// Live FPS pill, tinted by how smooth the compositor is running.
|
||||
char fps[16];
|
||||
snprintf(fps, sizeof(fps), "%u FPS", g_fps);
|
||||
uint32_t fps_col = (g_fps >= 45) ? GUI_THEME_SUCCESS
|
||||
: (g_fps >= 20) ? GUI_THEME_WARNING
|
||||
: GUI_THEME_DANGER;
|
||||
gui_gfx_draw_string(sw - 205, ty + 12, fps, fps_col);
|
||||
|
||||
rtc_time_t now;
|
||||
rtc_get_time(&now);
|
||||
if (g_clock_seconds) {
|
||||
snprintf(buf, sizeof(buf), "%02u:%02u:%02u", now.hour, now.minute, now.second);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%02u:%02u", now.hour, now.minute);
|
||||
}
|
||||
gui_gfx_draw_string_16_shadow(sw - 84, ty + 8, buf, GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
}
|
||||
|
||||
void gui_desktop_set_grid(bool on) { g_show_grid = on; }
|
||||
bool gui_desktop_get_grid(void) { return g_show_grid; }
|
||||
void gui_desktop_set_clock_seconds(bool on) { g_clock_seconds = on; }
|
||||
bool gui_desktop_get_clock_seconds(void) { return g_clock_seconds; }
|
||||
|
||||
// ===========================================================================
|
||||
// Start menu
|
||||
// ===========================================================================
|
||||
@@ -477,6 +542,10 @@ void gui_desktop_close_menus(void) {
|
||||
g_context_menu_open = false;
|
||||
}
|
||||
|
||||
void gui_desktop_request_animation_frame(void) {
|
||||
g_anim_request = true;
|
||||
}
|
||||
|
||||
void gui_desktop_request_exit(void) { g_desktop_running = false; }
|
||||
|
||||
static void exit_confirm_cb(gui_dialog_result_t result, void* ctx) {
|
||||
@@ -663,9 +732,12 @@ int gui_desktop_run(void) {
|
||||
fbcon_enable(false);
|
||||
gui_desktop_init();
|
||||
|
||||
// Open a default session so the desktop is not empty on first boot.
|
||||
launch_app(2); // System Monitor
|
||||
launch_app(0); // Terminal
|
||||
// Open a default session so the desktop is not empty on first boot. Placed
|
||||
// and sized for the HD work area rather than the old 800x600 defaults.
|
||||
int sw = gui_gfx_get_width();
|
||||
int wh = gui_desktop_workarea_height();
|
||||
gui_app_sysmon_launch(sw - 620, 60, 560, 360);
|
||||
gui_app_terminal_launch(120, 90, sw - 820, wh - 200);
|
||||
|
||||
// Discard anything the console left in the keyboard queue; a stale key
|
||||
// arriving on frame 0 would otherwise act as a desktop shortcut.
|
||||
@@ -677,6 +749,8 @@ int gui_desktop_run(void) {
|
||||
printk(KERN_INFO "GUI: Desktop session started (ESC returns to the kernel TTY)\n");
|
||||
|
||||
while (g_desktop_running) {
|
||||
uint64_t frame_start_us = ktime_us();
|
||||
|
||||
// --- 1. Sample input -----------------------------------------------
|
||||
const mouse_state_t* ms = mouse_get_state();
|
||||
int mx = ms ? ms->x : gui_gfx_get_width() / 2;
|
||||
@@ -732,9 +806,31 @@ int gui_desktop_run(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. Composite the scene, back to front --------------------------
|
||||
// --- 4. Decide what needs repainting --------------------------------
|
||||
// Advancing open animations mutates window geometry, so this both drives
|
||||
// the effect and reports whether it is still running.
|
||||
bool wm_animating = gui_wm_update();
|
||||
|
||||
uint64_t now_ms = ktime_ms();
|
||||
bool heartbeat = (now_ms - g_last_scene_ms) >= SCENE_HEARTBEAT_MS;
|
||||
bool interacting = left || right || key ||
|
||||
g_start_menu_open || g_context_menu_open ||
|
||||
gui_dialog_is_open();
|
||||
|
||||
// Claim any animation request left by the previous frame's painters.
|
||||
// Clearing it here means a request only survives while an app keeps
|
||||
// renewing it from its paint routine.
|
||||
bool animating = g_anim_request;
|
||||
g_anim_request = false;
|
||||
|
||||
// A full recomposite is needed when anything but the bare cursor moved;
|
||||
// otherwise we keep the retained scene and just slide the cursor overlay.
|
||||
bool scene_dirty = wm_animating || interacting || heartbeat || animating ||
|
||||
(g_frames == 0);
|
||||
|
||||
if ((g_frames & 0x1F) == 0) sample_cpu_history();
|
||||
|
||||
if (scene_dirty) {
|
||||
gui_desktop_render_background();
|
||||
gui_desktop_render_icons();
|
||||
gui_wm_render();
|
||||
@@ -742,11 +838,40 @@ int gui_desktop_run(void) {
|
||||
gui_desktop_render_start_menu();
|
||||
gui_desktop_render_context_menu();
|
||||
gui_dialog_render();
|
||||
gui_cursor_draw();
|
||||
g_last_scene_ms = now_ms;
|
||||
} else {
|
||||
// Retained scene: erase the previous cursor from it before redrawing.
|
||||
gui_cursor_restore_under();
|
||||
}
|
||||
|
||||
// The cursor is an overlay drawn on top of the (retained or fresh) scene,
|
||||
// so pointer motion never forces a full recomposite.
|
||||
gui_cursor_composite();
|
||||
|
||||
gui_gfx_present();
|
||||
g_frames++;
|
||||
g_pushed_pixels += gui_gfx_last_present_pixels();
|
||||
g_prev_mx = mx;
|
||||
g_prev_my = my;
|
||||
|
||||
// --- 5. Pace to the 60 FPS budget -----------------------------------
|
||||
// Spend the slack yielding to other kernel threads rather than
|
||||
// busy-spinning, so a background task -- the async HTTP worker behind
|
||||
// the Web app above all -- makes progress while the desktop idles
|
||||
// between frames. When nothing else is runnable each yield returns at
|
||||
// once and this simply spins out the budget, exactly as before.
|
||||
while (ktime_us() - frame_start_us < FRAME_BUDGET_US) {
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
// Sample the frame rate roughly twice a second from the monotonic clock.
|
||||
uint64_t fps_now_ms = ktime_ms();
|
||||
uint64_t dt = fps_now_ms - g_fps_last_ms;
|
||||
if (dt >= 500) {
|
||||
g_fps = (uint32_t)(((g_frames - g_fps_last_frames) * 1000ULL) / dt);
|
||||
g_fps_last_ms = fps_now_ms;
|
||||
g_fps_last_frames = g_frames;
|
||||
}
|
||||
|
||||
if ((g_frames & 0xFF) == 0) {
|
||||
uint64_t full = (uint64_t)gui_gfx_get_width() * (uint64_t)gui_gfx_get_height();
|
||||
|
||||
+59
-1
@@ -295,11 +295,69 @@ static const char* g_icons[GUI_ICON_COUNT][GUI_ICON_SIZE] = {
|
||||
"8811118811118800",
|
||||
"8888888888888888",
|
||||
"8888888888888888",
|
||||
},
|
||||
// GUI_ICON_HEART
|
||||
{
|
||||
"0000000000000000",
|
||||
"0009BB9009BB9000",
|
||||
"009BBBB99BBBB900",
|
||||
"09BBBBBBBBBBBB90",
|
||||
"09BB2BBBBBBBBB90",
|
||||
"09B22BBBBBBBBB90",
|
||||
"09BB2BBBBBBBBB90",
|
||||
"09BBBBBBBBBBBB90",
|
||||
"009BBBBBBBBBB900",
|
||||
"0009BBBBBBBB9000",
|
||||
"00009BBBBBB90000",
|
||||
"000009BBBB900000",
|
||||
"0000009BB9000000",
|
||||
"0000000990000000",
|
||||
"0000000000000000",
|
||||
"0000000000000000",
|
||||
},
|
||||
// GUI_ICON_NETWORK
|
||||
{
|
||||
"0000000000000000",
|
||||
"0000005555000000",
|
||||
"0000556666550000",
|
||||
"0005666556665000",
|
||||
"0056666556666500",
|
||||
"0555555555555550",
|
||||
"0566666556666650",
|
||||
"5666666556666665",
|
||||
"5666666556666665",
|
||||
"0566666556666650",
|
||||
"0555555555555550",
|
||||
"0056666556666500",
|
||||
"0005666556665000",
|
||||
"0000556666550000",
|
||||
"0000005555000000",
|
||||
"0000000000000000",
|
||||
},
|
||||
// GUI_ICON_GLOBE
|
||||
{
|
||||
"0000055555500000",
|
||||
"0003556555655300",
|
||||
"0035565556555300",
|
||||
"0355556555655530",
|
||||
"0355565556555530",
|
||||
"0355556555655530",
|
||||
"3555565556555553",
|
||||
"3666666666666663",
|
||||
"3555565556555553",
|
||||
"0355556555655530",
|
||||
"0355565556555530",
|
||||
"0355556555655530",
|
||||
"0035565556555300",
|
||||
"0003556555655300",
|
||||
"0000055555500000",
|
||||
"0000000000000000",
|
||||
},};
|
||||
|
||||
static const char* g_icon_names[GUI_ICON_COUNT] = {
|
||||
"Terminal", "Monitor", "Folder", "Calculator", "Paint", "Info",
|
||||
"Settings", "Tasks", "Log", "Tests", "Clock", "File", "Power", "Warning"
|
||||
"Settings", "Tasks", "Log", "Tests", "Clock", "File", "Power", "Warning",
|
||||
"Heart", "Network", "Globe"
|
||||
};
|
||||
|
||||
static uint8_t hex_index(char c) {
|
||||
|
||||
+26
-8
@@ -164,7 +164,20 @@ void gui_badge(int x, int y, const char* text, uint32_t bg, uint32_t fg) {
|
||||
// Interactive controls
|
||||
// ===========================================================================
|
||||
|
||||
bool gui_button_colored(int id, int x, int y, int w, int h, const char* label, uint32_t accent) {
|
||||
bool gui_hitzone(int id, int x, int y, int w, int h) {
|
||||
if (!hit_test(x, y, w, h)) return false;
|
||||
|
||||
g_input.hot_id = id;
|
||||
if (!g_input.mouse_clicked) return false;
|
||||
|
||||
g_input.mouse_clicked = false; // Claim the click
|
||||
g_input.active_id = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gui_button_styled(int id, int x, int y, int w, int h, const char* label,
|
||||
uint32_t face, uint32_t face_hover, uint32_t face_press,
|
||||
uint32_t border, uint32_t text) {
|
||||
bool hovered = hit_test(x, y, w, h);
|
||||
bool pressed = hovered && g_input.mouse_down;
|
||||
bool clicked = false;
|
||||
@@ -179,22 +192,27 @@ bool gui_button_colored(int id, int x, int y, int w, int h, const char* label, u
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t face = pressed ? GUI_THEME_BG_DARK
|
||||
: hovered ? GUI_THEME_BG_ELEVATED
|
||||
: GUI_THEME_BG_SURFACE;
|
||||
uint32_t border = hovered ? accent : GUI_THEME_BORDER;
|
||||
|
||||
gui_gfx_fill_rect(SX(x), SY(y), w, h, face);
|
||||
gui_gfx_fill_rect(SX(x), SY(y), w, h,
|
||||
pressed ? face_press : hovered ? face_hover : face);
|
||||
gui_gfx_draw_rect(SX(x), SY(y), w, h, border);
|
||||
|
||||
// A pressed button shifts its label a pixel to sell the depression.
|
||||
int tx = x + (w - text_width(label)) / 2 + (pressed ? 1 : 0);
|
||||
int ty = y + (h - GUI_FONT_H) / 2 + (pressed ? 1 : 0);
|
||||
gui_gfx_draw_string(SX(tx), SY(ty), label, hovered ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED);
|
||||
gui_gfx_draw_string(SX(tx), SY(ty), label, text);
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
bool gui_button_colored(int id, int x, int y, int w, int h, const char* label, uint32_t accent) {
|
||||
bool hovered = hit_test(x, y, w, h);
|
||||
|
||||
return gui_button_styled(id, x, y, w, h, label,
|
||||
GUI_THEME_BG_SURFACE, GUI_THEME_BG_ELEVATED, GUI_THEME_BG_DARK,
|
||||
hovered ? accent : GUI_THEME_BORDER,
|
||||
hovered ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED);
|
||||
}
|
||||
|
||||
bool gui_button(int id, int x, int y, int w, int h, const char* label) {
|
||||
return gui_button_colored(id, x, y, w, h, label, GUI_THEME_PRIMARY);
|
||||
}
|
||||
|
||||
+69
-3
@@ -10,6 +10,9 @@
|
||||
#include <drivers/keyboard.h>
|
||||
#include <lib/string.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <kernel/ktime.h>
|
||||
|
||||
#define WM_OPEN_ANIM_MS 160
|
||||
|
||||
#define BTN_SIZE 14
|
||||
#define BTN_GAP 4
|
||||
@@ -85,9 +88,63 @@ gui_window_t* gui_wm_create_window(const char* title, int x, int y, int w, int h
|
||||
|
||||
g_window_order[g_num_windows++] = win->id;
|
||||
gui_wm_focus_window(win->id);
|
||||
|
||||
// Kick off the grow-open animation: remember the final geometry and centre,
|
||||
// then start small. gui_wm_update() eases it back to full over ~160 ms.
|
||||
win->target_w = win->width;
|
||||
win->target_h = win->height;
|
||||
win->center_x = win->x + win->width / 2;
|
||||
win->center_y = win->y + win->height / 2;
|
||||
win->anim_open_ns = ktime_ns();
|
||||
win->width = win->target_w * 6 / 10;
|
||||
win->height = win->target_h * 6 / 10;
|
||||
win->x = win->center_x - win->width / 2;
|
||||
win->y = win->center_y - win->height / 2;
|
||||
if (win->x < 0) win->x = 0;
|
||||
if (win->y < 0) win->y = 0;
|
||||
return win;
|
||||
}
|
||||
|
||||
// Advance open animations. Returns true while any window is still animating.
|
||||
bool gui_wm_update(void) {
|
||||
bool animating = false;
|
||||
uint64_t now = ktime_ns();
|
||||
|
||||
for (int i = 0; i < g_num_windows; i++) {
|
||||
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
|
||||
if (!win || win->anim_open_ns == 0) continue;
|
||||
|
||||
uint64_t elapsed_ms = (now - win->anim_open_ns) / 1000000ULL;
|
||||
if (elapsed_ms >= WM_OPEN_ANIM_MS) {
|
||||
// Snap to the final geometry and stop animating.
|
||||
win->width = win->target_w;
|
||||
win->height = win->target_h;
|
||||
win->x = win->center_x - win->target_w / 2;
|
||||
win->y = win->center_y - win->target_h / 2;
|
||||
win->anim_open_ns = 0;
|
||||
} else {
|
||||
animating = true;
|
||||
int p = (int)(elapsed_ms * 1000 / WM_OPEN_ANIM_MS); // 0..1000
|
||||
int inv = 1000 - p;
|
||||
int ease = 1000 - (inv * inv / 1000) * inv / 1000; // cubic ease-out
|
||||
int scale = 600 + 400 * ease / 1000; // 600..1000 permille
|
||||
win->width = win->target_w * scale / 1000;
|
||||
win->height = win->target_h * scale / 1000;
|
||||
win->x = win->center_x - win->width / 2;
|
||||
win->y = win->center_y - win->height / 2;
|
||||
}
|
||||
|
||||
// Keep the animating window on screen.
|
||||
if (win->x < 0) win->x = 0;
|
||||
if (win->y < 0) win->y = 0;
|
||||
if (win->x + win->width > g_work_w) win->x = g_work_w - win->width;
|
||||
if (win->y + win->height > g_work_h) win->y = g_work_h - win->height;
|
||||
if (win->x < 0) win->x = 0;
|
||||
if (win->y < 0) win->y = 0;
|
||||
}
|
||||
return animating;
|
||||
}
|
||||
|
||||
static int order_index_of(int win_id) {
|
||||
for (int i = 0; i < g_num_windows; i++) {
|
||||
if (g_window_order[i] == win_id) return i;
|
||||
@@ -616,15 +673,24 @@ void gui_wm_render(void) {
|
||||
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
|
||||
if (!win || !win->visible || win->minimized) continue;
|
||||
|
||||
gui_gfx_draw_shadow(win->x, win->y, win->width, win->height, 6);
|
||||
// A larger, softer drop shadow reads as real depth on the HD desktop.
|
||||
gui_gfx_draw_shadow(win->x, win->y, win->width, win->height,
|
||||
win->active ? 12 : 7);
|
||||
|
||||
uint32_t border = win->active ? GUI_THEME_PRIMARY : GUI_THEME_BORDER;
|
||||
gui_gfx_draw_rect(win->x, win->y, win->width, win->height, border);
|
||||
|
||||
uint32_t top_bg = win->active ? 0xFF1E293B : 0xFF0F172A;
|
||||
uint32_t bot_bg = win->active ? 0xFF0F172A : 0xFF0A0E1A;
|
||||
// Brighter, more saturated titlebar when focused; muted when not.
|
||||
uint32_t top_bg = win->active ? 0xFF24345A : 0xFF141C2E;
|
||||
uint32_t bot_bg = win->active ? 0xFF141E38 : 0xFF0B1120;
|
||||
gui_gfx_draw_gradient_v(win->x + 1, win->y + 1, win->width - 2,
|
||||
GUI_TITLEBAR_HEIGHT - 1, top_bg, bot_bg);
|
||||
|
||||
// Accent underline on the active window's titlebar; plain divider otherwise.
|
||||
if (win->active) {
|
||||
gui_gfx_fill_rect(win->x + 1, win->y + GUI_TITLEBAR_HEIGHT - 1,
|
||||
win->width - 2, 2, GUI_THEME_PRIMARY);
|
||||
}
|
||||
gui_gfx_draw_line(win->x, win->y + GUI_TITLEBAR_HEIGHT,
|
||||
win->x + win->width - 1, win->y + GUI_TITLEBAR_HEIGHT, border);
|
||||
|
||||
|
||||
@@ -46,6 +46,36 @@ static inline void sha256(const void* data, size_t len, uint8_t hash[32]) {
|
||||
sha256_final(hash, &ctx);
|
||||
}
|
||||
|
||||
// X25519 (Curve25519 ECDH), RFC 7748.
|
||||
void x25519_base(uint8_t out_pub[32], const uint8_t priv[32]);
|
||||
void x25519(uint8_t out_shared[32], const uint8_t priv[32], const uint8_t peer_pub[32]);
|
||||
|
||||
// ChaCha20-Poly1305 AEAD, RFC 8439.
|
||||
void chacha20_xor(uint8_t* dst, const uint8_t* src, size_t len,
|
||||
const uint8_t key[32], uint32_t counter, const uint8_t nonce[12]);
|
||||
void chacha20poly1305_encrypt(uint8_t* ct, uint8_t tag[16],
|
||||
const uint8_t key[32], const uint8_t nonce[12],
|
||||
const uint8_t* aad, size_t aad_len,
|
||||
const uint8_t* pt, size_t pt_len);
|
||||
bool chacha20poly1305_decrypt(uint8_t* pt, const uint8_t key[32],
|
||||
const uint8_t nonce[12],
|
||||
const uint8_t* aad, size_t aad_len,
|
||||
const uint8_t* ct, size_t ct_len,
|
||||
const uint8_t tag[16]);
|
||||
|
||||
// HMAC-SHA256 / HKDF (RFC 2104, 5869) and the TLS 1.3 key-schedule helpers.
|
||||
void hmac_sha256(const uint8_t* key, size_t key_len,
|
||||
const uint8_t* msg, size_t msg_len, uint8_t out[32]);
|
||||
void hkdf_extract(const uint8_t* salt, size_t salt_len,
|
||||
const uint8_t* ikm, size_t ikm_len, uint8_t prk[32]);
|
||||
void hkdf_expand(const uint8_t prk[32], const uint8_t* info, size_t info_len,
|
||||
uint8_t* out, size_t out_len);
|
||||
void tls13_expand_label(const uint8_t secret[32], const char* label,
|
||||
const uint8_t* context, size_t context_len,
|
||||
uint8_t* out, size_t out_len);
|
||||
void tls13_derive_secret(const uint8_t secret[32], const char* label,
|
||||
const uint8_t transcript_hash[32], uint8_t out[32]);
|
||||
|
||||
// PRNG (Cryptographic Pseudo-Random Number Generator)
|
||||
void prng_seed(uint64_t seed1, uint64_t seed2);
|
||||
uint64_t prng_rand64(void);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef _DRIVERS_HPET_H
|
||||
#define _DRIVERS_HPET_H
|
||||
|
||||
// High Precision Event Timer (HPET) driver.
|
||||
//
|
||||
// The HPET is a memory-mapped, free-running up-counter defined by the
|
||||
// Intel/Microsoft HPET spec and emulated by QEMU at the standard base
|
||||
// 0xFED00000. It provides a monotonic high-resolution time source far finer
|
||||
// than the 100 Hz PIT tick.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void hpet_init(void);
|
||||
bool hpet_available(void);
|
||||
uint64_t hpet_read_counter(void); // raw main counter value
|
||||
uint64_t hpet_get_frequency_hz(void); // counter ticks per second
|
||||
uint64_t hpet_get_ns(void); // nanoseconds since HPET was enabled
|
||||
uint32_t hpet_num_timers(void); // comparator count reported by the block
|
||||
|
||||
#endif // _DRIVERS_HPET_H
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef _DRIVERS_LPT_H
|
||||
#define _DRIVERS_LPT_H
|
||||
|
||||
// IBM PC parallel port (LPT1) printer driver. Centronics handshake over the
|
||||
// standard I/O ports 0x378 (data) / 0x379 (status) / 0x37A (control).
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void lpt_init(void);
|
||||
bool lpt_present(void);
|
||||
int lpt_putc(char c); // 0 on success, -1 on timeout/absent
|
||||
int lpt_write(const char* buf, size_t len); // bytes written
|
||||
uint8_t lpt_status(void); // raw status register
|
||||
|
||||
#endif // _DRIVERS_LPT_H
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef _DRIVERS_NVRAM_H
|
||||
#define _DRIVERS_NVRAM_H
|
||||
|
||||
// CMOS / RTC NVRAM driver. The MC146818-compatible RTC exposes 128 indexed
|
||||
// bytes behind ports 0x70 (index) / 0x71 (data); bytes 0x0E..0x7F are the
|
||||
// battery-backed scratch RAM used by the BIOS for configuration.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define NVRAM_SIZE 128
|
||||
#define NVRAM_USER_START 0x0E // first non-RTC (scratch) byte
|
||||
|
||||
void nvram_init(void);
|
||||
uint8_t nvram_read(uint8_t index);
|
||||
void nvram_write(uint8_t index, uint8_t value);
|
||||
uint8_t nvram_checksum(void); // simple sum over the scratch region
|
||||
void nvram_dump(void); // hex dump of all 128 bytes to the console
|
||||
|
||||
#endif // _DRIVERS_NVRAM_H
|
||||
@@ -9,5 +9,13 @@ void gui_app_fileman_launch(int x, int y, int w, int h);
|
||||
void gui_app_calc_launch(int x, int y, int w, int h);
|
||||
void gui_app_paint_launch(int x, int y, int w, int h);
|
||||
void gui_app_about_launch(int x, int y, int w, int h);
|
||||
void gui_app_life_launch(int x, int y, int w, int h);
|
||||
void gui_app_rustlab_launch(int x, int y, int w, int h);
|
||||
void gui_app_objbrowse_launch(int x, int y, int w, int h);
|
||||
void gui_app_regedit_launch(int x, int y, int w, int h);
|
||||
void gui_app_analytics_launch(int x, int y, int w, int h);
|
||||
void gui_app_sorry_launch(int x, int y, int w, int h);
|
||||
void gui_app_netmon_launch(int x, int y, int w, int h);
|
||||
void gui_app_web_launch(int x, int y, int w, int h);
|
||||
|
||||
#endif // _GUI_APPS_H
|
||||
@@ -10,4 +10,10 @@ int gui_cursor_get_x(void);
|
||||
int gui_cursor_get_y(void);
|
||||
void gui_cursor_draw(void);
|
||||
|
||||
// Overlay compositing: restore the pixels under the previous cursor position,
|
||||
// then stash-and-draw the cursor at the current one. Lets the compositor move
|
||||
// the pointer at full frame rate without repainting the whole scene.
|
||||
void gui_cursor_restore_under(void);
|
||||
void gui_cursor_composite(void);
|
||||
|
||||
#endif // _GUI_CURSOR_H
|
||||
@@ -32,6 +32,21 @@ bool gui_desktop_handle_click(int mx, int my);
|
||||
// Height of the area windows may occupy (screen minus taskbar).
|
||||
int gui_desktop_workarea_height(void);
|
||||
|
||||
// An app that animates continuously calls this from its paint routine to ask
|
||||
// for a full-rate recomposite instead of the ~10 Hz live-content heartbeat.
|
||||
// The request covers only the next frame, so it has to be renewed on every
|
||||
// paint; it therefore lapses by itself when the app stops animating or its
|
||||
// window closes, and the compositor goes straight back to idling cheaply.
|
||||
void gui_desktop_request_animation_frame(void);
|
||||
|
||||
// Wallpaper grid overlay (also toggled from the desktop context menu).
|
||||
void gui_desktop_set_grid(bool on);
|
||||
bool gui_desktop_get_grid(void);
|
||||
|
||||
// Taskbar clock: show seconds (HH:MM:SS) instead of HH:MM.
|
||||
void gui_desktop_set_clock_seconds(bool on);
|
||||
bool gui_desktop_get_clock_seconds(void);
|
||||
|
||||
void gui_desktop_request_exit(void);
|
||||
int gui_desktop_run(void);
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ typedef enum {
|
||||
GUI_ICON_FILE,
|
||||
GUI_ICON_POWER,
|
||||
GUI_ICON_WARNING,
|
||||
GUI_ICON_HEART,
|
||||
GUI_ICON_NETWORK,
|
||||
GUI_ICON_GLOBE,
|
||||
GUI_ICON_COUNT
|
||||
} gui_icon_id_t;
|
||||
|
||||
|
||||
@@ -75,6 +75,18 @@ void gui_badge(int x, int y, const char* text, uint32_t bg, uint32_t fg);
|
||||
// within one window.
|
||||
bool gui_button(int id, int x, int y, int w, int h, const char* label);
|
||||
bool gui_button_colored(int id, int x, int y, int w, int h, const char* label, uint32_t accent);
|
||||
|
||||
// Fully styled variant, for apps that paint their own palette instead of
|
||||
// inheriting the desktop's slate theme. The three faces cover the idle,
|
||||
// hovered and pressed states.
|
||||
bool gui_button_styled(int id, int x, int y, int w, int h, const char* label,
|
||||
uint32_t face, uint32_t face_hover, uint32_t face_press,
|
||||
uint32_t border, uint32_t text);
|
||||
|
||||
// Claims a click inside a rectangle without drawing anything, so an app can
|
||||
// make a custom-drawn region clickable. Place it after the real controls: a
|
||||
// widget only ever sees a click no earlier widget already claimed.
|
||||
bool gui_hitzone(int id, int x, int y, int w, int h);
|
||||
bool gui_checkbox(int id, int x, int y, const char* label, bool* value);
|
||||
bool gui_radio(int id, int x, int y, const char* label, int* selected, int this_value);
|
||||
bool gui_slider(int id, int x, int y, int w, int* value, int min_val, int max_val);
|
||||
|
||||
@@ -70,6 +70,14 @@ typedef struct gui_window {
|
||||
|
||||
void* user_data;
|
||||
|
||||
// Open animation: the window grows from a fraction of its final size to
|
||||
// full over a short interval. anim_open_ns == 0 means no animation running.
|
||||
uint64_t anim_open_ns; // ktime_ns() when the open animation began
|
||||
int target_w; // final geometry the animation resolves to
|
||||
int target_h;
|
||||
int center_x; // fixed centre the growth expands around
|
||||
int center_y;
|
||||
|
||||
void (*paint)(struct gui_window* win);
|
||||
void (*handle_event)(struct gui_window* win, const gui_event_t* ev);
|
||||
} gui_window_t;
|
||||
@@ -80,6 +88,10 @@ void gui_wm_init(void);
|
||||
void gui_wm_set_workarea(int width, int height);
|
||||
|
||||
gui_window_t* gui_wm_create_window(const char* title, int x, int y, int w, int h);
|
||||
|
||||
// Advance any running open animations; returns true while at least one window
|
||||
// is still animating (so the compositor keeps redrawing during the effect).
|
||||
bool gui_wm_update(void);
|
||||
void gui_wm_destroy_window(int win_id);
|
||||
void gui_wm_focus_window(int win_id);
|
||||
gui_window_t* gui_wm_get_window(int win_id);
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
#ifndef _INIT_VERSION_H
|
||||
#define _INIT_VERSION_H
|
||||
|
||||
#define SUBOS_VERSION_MAJOR 0
|
||||
#define SUBOS_VERSION_MINOR 2
|
||||
#define SUBOS_VERSION_MAJOR 1
|
||||
#define SUBOS_VERSION_MINOR 0
|
||||
#define SUBOS_VERSION_PATCH 0
|
||||
#define SUBOS_VERSION_CODENAME "Titan"
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#define SUBOS_VERSION_STRING "0.2.0-lts (x86_64)"
|
||||
#define SUBOS_VERSION_STRING "1.0.0-stable (x86_64)"
|
||||
#define SUBOS_BUILD_TARGET "x86_64-elf-baremetal"
|
||||
#define SUBOS_COMPILER "x86_64-elf-gcc"
|
||||
#elif defined(__aarch64__)
|
||||
#define SUBOS_VERSION_STRING "0.2.0-lts (aarch64)"
|
||||
#define SUBOS_VERSION_STRING "1.0.0-stable (aarch64)"
|
||||
#define SUBOS_BUILD_TARGET "aarch64-linux-gnu-baremetal"
|
||||
#define SUBOS_COMPILER "aarch64-linux-gnu-gcc"
|
||||
#elif defined(__arm__) || defined(__armv8i__)
|
||||
#define SUBOS_VERSION_STRING "0.2.0-lts (armv8i)"
|
||||
#define SUBOS_VERSION_STRING "1.0.0-stable (armv8i)"
|
||||
#define SUBOS_BUILD_TARGET "arm-linux-gnueabihf-baremetal"
|
||||
#define SUBOS_COMPILER "arm-linux-gnueabihf-gcc"
|
||||
#else
|
||||
#define SUBOS_VERSION_STRING "0.2.0-lts (generic)"
|
||||
#define SUBOS_VERSION_STRING "1.0.0-stable (generic)"
|
||||
#define SUBOS_BUILD_TARGET "generic-elf-baremetal"
|
||||
#define SUBOS_COMPILER "gcc"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifndef _KERNEL_CPP_ANALYTICS_H
|
||||
#define _KERNEL_CPP_ANALYTICS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// The C++ analytics engine keeps a rolling time-series per channel and computes
|
||||
// online statistics (min/max/mean/variance via Welford) over it. Channels are:
|
||||
// 0 = CPU load % 1 = Memory used %
|
||||
// 2 = Kernel heap % 3 = Network packets/interval
|
||||
#define CPP_ANALYTICS_CHANNELS 4
|
||||
#define CPP_ANALYTICS_HISTORY 64
|
||||
|
||||
// Bring up the engine (registers channels + an example observer). Idempotent.
|
||||
void cpp_analytics_init(void);
|
||||
|
||||
// Pull one live sample from the kernel (metrics/pmm/heap/NIC) into every
|
||||
// channel. Call this periodically (the GUI Analytics app and the `cppstat`
|
||||
// applet both drive it). Returns the total number of samples taken so far.
|
||||
uint64_t cpp_analytics_sample(void);
|
||||
|
||||
// Copy up to `max` most-recent values of a channel into `out` (oldest first,
|
||||
// newest last). Returns the number written.
|
||||
int cpp_analytics_get_series(int channel, uint32_t* out, int max);
|
||||
|
||||
// Fetch the online statistics for a channel. Any out pointer may be NULL.
|
||||
void cpp_analytics_get_stats(int channel, uint32_t* out_min, uint32_t* out_max,
|
||||
uint32_t* out_avg, uint32_t* out_last,
|
||||
uint32_t* out_stddev);
|
||||
|
||||
// Human-readable channel name / unit suffix and the current scale ceiling used
|
||||
// for graphing (e.g. 100 for a percentage channel).
|
||||
const char* cpp_analytics_channel_name(int channel);
|
||||
const char* cpp_analytics_channel_unit(int channel);
|
||||
uint32_t cpp_analytics_channel_scale(int channel);
|
||||
|
||||
int cpp_analytics_channel_count(void);
|
||||
uint64_t cpp_analytics_sample_count(void);
|
||||
|
||||
// Print a textual dashboard (used by the `cppstat` lazybox applet).
|
||||
void cpp_analytics_dump(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _KERNEL_CPP_ANALYTICS_H
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef _KERNEL_KTIME_H
|
||||
#define _KERNEL_KTIME_H
|
||||
|
||||
// Unified monotonic high-resolution clock. Prefers the HPET (nanosecond grade)
|
||||
// and falls back to the 100 Hz timer tick when no HPET is present.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void ktime_init(void);
|
||||
uint64_t ktime_ns(void); // nanoseconds since ktime_init
|
||||
uint64_t ktime_us(void);
|
||||
uint64_t ktime_ms(void);
|
||||
const char* ktime_source(void); // "HPET" or "PIT"
|
||||
bool ktime_is_highres(void); // true when backed by the HPET
|
||||
|
||||
// Busy-wait for the given number of microseconds against the monotonic clock.
|
||||
// Precise when HPET-backed; used by the compositor's 60 FPS frame pacer.
|
||||
void ktime_delay_us(uint64_t us);
|
||||
|
||||
#endif // _KERNEL_KTIME_H
|
||||
@@ -0,0 +1,87 @@
|
||||
#ifndef _KERNEL_NT_IO_H
|
||||
#define _KERNEL_NT_IO_H
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SUB-OS NT-Inspired I/O Manager
|
||||
//
|
||||
// Models the Windows NT I/O subsystem: DRIVER_OBJECTs export a MajorFunction
|
||||
// dispatch table, DEVICE_OBJECTs are created by drivers and can be stacked
|
||||
// (attached) into a device stack, and I/O flows as IRPs (I/O Request Packets)
|
||||
// pushed down the stack with IoCallDriver / completed with IoCompleteRequest.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <kernel/nt/ob.h> // NTSTATUS, STATUS_* codes
|
||||
|
||||
typedef enum {
|
||||
IRP_MJ_CREATE = 0,
|
||||
IRP_MJ_CLOSE,
|
||||
IRP_MJ_READ,
|
||||
IRP_MJ_WRITE,
|
||||
IRP_MJ_DEVICE_CONTROL,
|
||||
IRP_MJ_MAXIMUM
|
||||
} irp_major_t;
|
||||
|
||||
#define IO_NAME_MAX 48
|
||||
#define IO_MAX_DRIVERS 16
|
||||
#define IO_MAX_DEVICES 32
|
||||
|
||||
typedef struct _DEVICE_OBJECT DEVICE_OBJECT;
|
||||
typedef struct _DRIVER_OBJECT DRIVER_OBJECT;
|
||||
typedef struct _IRP IRP;
|
||||
|
||||
typedef NTSTATUS (*driver_dispatch_t)(DEVICE_OBJECT* dev, IRP* irp);
|
||||
typedef NTSTATUS (*driver_entry_t)(DRIVER_OBJECT* drv);
|
||||
|
||||
struct _DRIVER_OBJECT {
|
||||
char name[IO_NAME_MAX]; // e.g. "\Driver\Null"
|
||||
driver_dispatch_t major_function[IRP_MJ_MAXIMUM];
|
||||
DEVICE_OBJECT* device_list; // devices this driver owns
|
||||
uint64_t irp_count;
|
||||
};
|
||||
|
||||
struct _DEVICE_OBJECT {
|
||||
char name[IO_NAME_MAX]; // e.g. "\Device\Null"
|
||||
DRIVER_OBJECT* driver;
|
||||
DEVICE_OBJECT* next_on_driver; // next in the driver's device_list
|
||||
DEVICE_OBJECT* attached_to; // lower device in the stack (or NULL)
|
||||
void* device_extension;
|
||||
uint64_t read_count;
|
||||
uint64_t write_count;
|
||||
uint64_t ioctl_count;
|
||||
};
|
||||
|
||||
struct _IRP {
|
||||
irp_major_t major_function;
|
||||
void* buffer;
|
||||
uint32_t length;
|
||||
uint32_t io_control_code;
|
||||
NTSTATUS status;
|
||||
uint32_t information; // bytes transferred on completion
|
||||
DEVICE_OBJECT* current_device;
|
||||
};
|
||||
|
||||
void io_init(void);
|
||||
const char* io_major_name(irp_major_t mj);
|
||||
|
||||
DRIVER_OBJECT* io_create_driver(const char* name, driver_entry_t entry);
|
||||
NTSTATUS io_create_device(DRIVER_OBJECT* drv, const char* name,
|
||||
size_t ext_size, DEVICE_OBJECT** out);
|
||||
DEVICE_OBJECT* io_attach_device(DEVICE_OBJECT* source, DEVICE_OBJECT* target);
|
||||
DEVICE_OBJECT* io_lookup_device(const char* name);
|
||||
|
||||
IRP* io_build_irp(irp_major_t mj, void* buffer, uint32_t length);
|
||||
NTSTATUS io_call_driver(DEVICE_OBJECT* dev, IRP* irp);
|
||||
void io_complete_request(IRP* irp, NTSTATUS status);
|
||||
void io_free_irp(IRP* irp);
|
||||
|
||||
// Enumeration / stats.
|
||||
int io_driver_count(void);
|
||||
DRIVER_OBJECT* io_driver_at(int index);
|
||||
int io_device_count(void);
|
||||
DEVICE_OBJECT* io_device_at(int index);
|
||||
void io_get_stats(uint32_t* out_drivers, uint32_t* out_devices, uint64_t* out_irps);
|
||||
|
||||
#endif // _KERNEL_NT_IO_H
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifndef _KERNEL_NT_OB_H
|
||||
#define _KERNEL_NT_OB_H
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SUB-OS NT-Inspired Object Manager
|
||||
//
|
||||
// A faithful-in-spirit port of the Windows NT Executive Object Manager (Ob):
|
||||
// every kernel resource is a typed, reference-counted object living in a
|
||||
// hierarchical named namespace ("\BaseNamedObjects\MyEvent"), reached through a
|
||||
// per-process handle table. Also carries NT dispatcher primitives (events,
|
||||
// semaphores, mutants) and a software IRQL model.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// NTSTATUS-style result codes (high bit set == error, as in NT).
|
||||
typedef int32_t NTSTATUS;
|
||||
#define STATUS_SUCCESS ((NTSTATUS)0x00000000)
|
||||
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001)
|
||||
#define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008)
|
||||
#define STATUS_INSUFFICIENT_RES ((NTSTATUS)0xC000009A)
|
||||
#define STATUS_OBJECT_NAME_EXISTS ((NTSTATUS)0xC0000035)
|
||||
#define STATUS_OBJECT_NOT_FOUND ((NTSTATUS)0xC0000225)
|
||||
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||
|
||||
typedef enum {
|
||||
OB_TYPE_DIRECTORY = 0,
|
||||
OB_TYPE_EVENT,
|
||||
OB_TYPE_SEMAPHORE,
|
||||
OB_TYPE_MUTANT,
|
||||
OB_TYPE_MAX
|
||||
} ob_type_t;
|
||||
|
||||
#define OB_NAME_MAX 48
|
||||
#define OB_DIR_MAX 32 // children per directory
|
||||
#define OB_HANDLE_MAX 256 // system handle table size
|
||||
|
||||
typedef struct ob_object ob_object_t;
|
||||
|
||||
struct ob_object {
|
||||
ob_type_t type;
|
||||
char name[OB_NAME_MAX];
|
||||
int32_t ref_count; // outstanding pointer references
|
||||
int32_t handle_count; // outstanding open handles
|
||||
bool permanent; // survives handle_count/ref_count reaching 0
|
||||
ob_object_t* parent; // containing directory (NULL for the root)
|
||||
|
||||
union {
|
||||
struct { ob_object_t* children[OB_DIR_MAX]; int count; } dir;
|
||||
struct { bool signaled; bool manual_reset; } event;
|
||||
struct { int32_t count; int32_t limit; } sem;
|
||||
struct { bool held; uint32_t owner; int32_t recursion; } mutant;
|
||||
} body;
|
||||
};
|
||||
|
||||
// A HANDLE indexes the system handle table; -1 is the invalid handle.
|
||||
typedef int32_t HANDLE;
|
||||
#define OB_INVALID_HANDLE ((HANDLE)-1)
|
||||
|
||||
// Lifecycle -------------------------------------------------------------------
|
||||
void ob_init(void);
|
||||
const char* ob_type_name(ob_type_t t);
|
||||
ob_object_t* ob_root_directory(void);
|
||||
|
||||
NTSTATUS ob_create_object(ob_type_t type, const char* name,
|
||||
ob_object_t* dir, ob_object_t** out);
|
||||
ob_object_t* ob_lookup_path(const char* path); // "\BaseNamedObjects\Name"
|
||||
void ob_reference_object(ob_object_t* obj);
|
||||
void ob_dereference_object(ob_object_t* obj);
|
||||
void ob_make_permanent(ob_object_t* obj);
|
||||
|
||||
// Handle table ----------------------------------------------------------------
|
||||
HANDLE ob_open_handle(ob_object_t* obj);
|
||||
ob_object_t* ob_reference_by_handle(HANDLE h);
|
||||
NTSTATUS ob_close_handle(HANDLE h);
|
||||
int ob_open_handle_count(void);
|
||||
|
||||
// Dispatcher primitives (non-blocking, poll-style) ----------------------------
|
||||
NTSTATUS ke_set_event(ob_object_t* evt);
|
||||
NTSTATUS ke_reset_event(ob_object_t* evt);
|
||||
bool ke_test_event(ob_object_t* evt); // true if signaled (auto-reset consumes)
|
||||
NTSTATUS ke_release_semaphore(ob_object_t* sem, int32_t count);
|
||||
bool ke_wait_semaphore(ob_object_t* sem); // true if a unit was acquired
|
||||
|
||||
// Enumeration (for the objdir tool) -------------------------------------------
|
||||
int ob_dir_child_count(ob_object_t* dir);
|
||||
ob_object_t* ob_dir_child(ob_object_t* dir, int index);
|
||||
void ob_get_stats(uint32_t* out_objects, uint32_t* out_handles,
|
||||
uint32_t per_type[OB_TYPE_MAX]);
|
||||
|
||||
// Software IRQL model ---------------------------------------------------------
|
||||
typedef enum {
|
||||
PASSIVE_LEVEL = 0,
|
||||
APC_LEVEL = 1,
|
||||
DISPATCH_LEVEL = 2,
|
||||
HIGH_LEVEL = 15
|
||||
} KIRQL;
|
||||
|
||||
KIRQL ke_get_current_irql(void);
|
||||
KIRQL ke_raise_irql(KIRQL new_irql); // returns previous IRQL
|
||||
void ke_lower_irql(KIRQL old_irql);
|
||||
|
||||
#endif // _KERNEL_NT_OB_H
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef _KERNEL_NT_REG_H
|
||||
#define _KERNEL_NT_REG_H
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SUB-OS NT-Inspired Configuration Registry
|
||||
//
|
||||
// A hive-and-key configuration store modelled on the Windows NT Configuration
|
||||
// Manager (Cm). Keys form a tree rooted at "\Registry"; each key holds typed
|
||||
// values (REG_SZ / REG_DWORD / REG_QWORD / REG_BINARY / REG_MULTI_SZ) whose
|
||||
// type codes match the Win32 constants. Backed by kmalloc, bounded fan-out.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
REG_NONE = 0,
|
||||
REG_SZ = 1, // null-terminated string
|
||||
REG_BINARY = 3, // raw bytes
|
||||
REG_DWORD = 4, // 32-bit
|
||||
REG_MULTI_SZ = 7, // list of strings, shown joined by '|'
|
||||
REG_QWORD = 11 // 64-bit
|
||||
} reg_type_t;
|
||||
|
||||
#define REG_NAME_MAX 48
|
||||
#define REG_STR_MAX 128
|
||||
#define REG_MAX_CHILDREN 24
|
||||
#define REG_MAX_VALUES 24
|
||||
|
||||
typedef struct reg_value {
|
||||
char name[REG_NAME_MAX];
|
||||
reg_type_t type;
|
||||
uint32_t data_len;
|
||||
union {
|
||||
uint32_t dword;
|
||||
uint64_t qword;
|
||||
char str[REG_STR_MAX];
|
||||
uint8_t bin[REG_STR_MAX];
|
||||
} data;
|
||||
} reg_value_t;
|
||||
|
||||
typedef struct reg_key {
|
||||
char name[REG_NAME_MAX];
|
||||
struct reg_key* parent;
|
||||
struct reg_key* children[REG_MAX_CHILDREN];
|
||||
int child_count;
|
||||
reg_value_t* values[REG_MAX_VALUES];
|
||||
int value_count;
|
||||
} reg_key_t;
|
||||
|
||||
void reg_init(void);
|
||||
reg_key_t* reg_root(void);
|
||||
const char* reg_type_name(reg_type_t t);
|
||||
|
||||
// Path form: "\Registry\Machine\Software\SUB-OS". create makes intermediates.
|
||||
reg_key_t* reg_create_key(const char* path);
|
||||
reg_key_t* reg_open_key(const char* path);
|
||||
|
||||
// Value setters/getters (operate on an already-open key).
|
||||
reg_value_t* reg_set_dword(reg_key_t* key, const char* name, uint32_t value);
|
||||
reg_value_t* reg_set_qword(reg_key_t* key, const char* name, uint64_t value);
|
||||
reg_value_t* reg_set_sz(reg_key_t* key, const char* name, const char* str);
|
||||
reg_value_t* reg_query_value(reg_key_t* key, const char* name);
|
||||
|
||||
// Enumeration.
|
||||
int reg_enum_key_count(reg_key_t* key);
|
||||
reg_key_t* reg_enum_key(reg_key_t* key, int index);
|
||||
int reg_enum_value_count(reg_key_t* key);
|
||||
reg_value_t* reg_enum_value(reg_key_t* key, int index);
|
||||
|
||||
// Render a value's data into a human-readable string (for reg query / GUI).
|
||||
void reg_value_to_string(const reg_value_t* v, char* out, size_t out_len);
|
||||
|
||||
void reg_get_stats(uint32_t* out_keys, uint32_t* out_values);
|
||||
|
||||
#endif // _KERNEL_NT_REG_H
|
||||
@@ -71,6 +71,15 @@ typedef struct {
|
||||
int rust_kernel_init(void);
|
||||
const char* rust_kernel_status(void);
|
||||
|
||||
// Checksums & Non-Cryptographic Hashing (Rust)
|
||||
uint32_t rust_crc32c(const uint8_t* data, size_t len);
|
||||
uint32_t rust_adler32(const uint8_t* data, size_t len);
|
||||
uint64_t rust_fnv1a64(const uint8_t* data, size_t len);
|
||||
|
||||
// Run-Length Encoding Codec (Rust). Return bytes written, or -1.
|
||||
int rust_rle_compress(const uint8_t* in_buf, size_t in_len, uint8_t* out_buf, size_t max_out);
|
||||
int rust_rle_decompress(const uint8_t* in_buf, size_t in_len, uint8_t* out_buf, size_t max_out);
|
||||
|
||||
// Cryptography
|
||||
int rust_chacha20_crypt(const uint8_t* key, const uint8_t* nonce, uint32_t counter, uint8_t* data, size_t len);
|
||||
int rust_csprng_get_random(uint8_t* buffer, size_t len);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef _LIB_IMAGE_H
|
||||
#define _LIB_IMAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Decoded image: a heap-allocated buffer of 0xAARRGGBB pixels, row-major, ready
|
||||
// to hand to gui_gfx_blit. Free with image_free.
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
uint32_t* pixels;
|
||||
} image_t;
|
||||
|
||||
// True if the bytes begin with a signature this decoder supports (PNG or BMP).
|
||||
bool image_sniff(const uint8_t* data, unsigned len);
|
||||
|
||||
// Decode PNG (8-bit grayscale/RGB/RGBA/palette, non-interlaced) or BMP
|
||||
// (24/32-bit uncompressed) into img. Returns 0 on success. On failure img is
|
||||
// left zeroed and nothing needs freeing.
|
||||
int image_decode(const uint8_t* data, unsigned len, image_t* img);
|
||||
|
||||
void image_free(image_t* img);
|
||||
|
||||
#endif // _LIB_IMAGE_H
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef _LIB_INFLATE_H
|
||||
#define _LIB_INFLATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// DEFLATE / zlib decompression (RFC 1950/1951). Both write into a
|
||||
// caller-provided buffer; *dest_len is the buffer capacity on entry and the
|
||||
// decompressed length on success. Return 0 on success, negative on error.
|
||||
int inflate_raw(uint8_t* dest, unsigned* dest_len,
|
||||
const uint8_t* src, unsigned src_len);
|
||||
int inflate_zlib(uint8_t* dest, unsigned* dest_len,
|
||||
const uint8_t* src, unsigned src_len);
|
||||
|
||||
#endif // _LIB_INFLATE_H
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef _LIB_JPEG_H
|
||||
#define _LIB_JPEG_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <lib/image.h>
|
||||
|
||||
// Baseline JPEG (SOF0) decoder. Fills img with a heap-allocated 0xAARRGGBB
|
||||
// buffer on success (return 0); progressive/arithmetic JPEGs are rejected.
|
||||
int jpeg_decode(const uint8_t* data, unsigned len, image_t* img);
|
||||
|
||||
#endif // _LIB_JPEG_H
|
||||
@@ -14,6 +14,7 @@ void kfree(void* ptr);
|
||||
size_t heap_get_used_bytes(void);
|
||||
size_t heap_get_free_bytes(void);
|
||||
size_t heap_get_total_bytes(void);
|
||||
size_t heap_get_grow_count(void); // Number of on-demand PMM growth events
|
||||
|
||||
static inline size_t kmalloc_get_used(void) { return heap_get_used_bytes(); }
|
||||
static inline size_t kmalloc_get_free(void) { return heap_get_free_bytes(); }
|
||||
|
||||
@@ -5,6 +5,23 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
void dhcp_init(void);
|
||||
|
||||
// Run a full DHCP handshake (DISCOVER/OFFER/REQUEST/ACK) and, on success, apply
|
||||
// the offered IP, mask, gateway and DNS to the primary interface. Returns 0 on
|
||||
// a granted lease, -1 on timeout (the static configuration is left in place).
|
||||
int dhcp_request_lease(void);
|
||||
|
||||
// Last-lease details for status reporting (valid after a successful lease).
|
||||
typedef struct {
|
||||
uint32_t ip;
|
||||
uint32_t mask;
|
||||
uint32_t gateway;
|
||||
uint32_t dns;
|
||||
uint32_t server;
|
||||
uint32_t lease_secs;
|
||||
bool bound;
|
||||
} dhcp_lease_t;
|
||||
|
||||
const dhcp_lease_t* dhcp_get_lease(void);
|
||||
|
||||
#endif // _NET_DHCP_H
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef _NET_HTTP_CLIENT_H
|
||||
#define _NET_HTTP_CLIENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Asynchronous HTTP/1.0 client.
|
||||
//
|
||||
// The TCP client calls (tcp_connect/tcp_send/tcp_recv) all busy-wait against
|
||||
// the timer tick, so calling them from an interactive context -- the GUI
|
||||
// compositor above all -- would freeze it for the length of the fetch. This
|
||||
// module runs every fetch on one long-lived kernel worker thread instead: the
|
||||
// requester posts a job and polls its state, and the compositor keeps painting
|
||||
// at full rate while the worker blocks.
|
||||
//
|
||||
// Ownership follows the state. While a job is HTTP_STATE_RUNNING the worker
|
||||
// owns it; once it is DONE or ERROR the requester owns it again. The requester
|
||||
// hands a job back with http_fetch_release, which frees it now if the fetch has
|
||||
// finished, or marks it abandoned so the worker frees it when it does -- so a
|
||||
// window can close mid-fetch without leaking or use-after-free.
|
||||
|
||||
typedef enum {
|
||||
HTTP_STATE_RUNNING = 0, // worker owns the job
|
||||
HTTP_STATE_DONE, // completed; buf holds the response
|
||||
HTTP_STATE_ERROR // failed; err holds a short reason
|
||||
} http_fetch_state_t;
|
||||
|
||||
typedef struct http_fetch {
|
||||
// Request, filled in by http_fetch_start from the parsed URL.
|
||||
char host[128];
|
||||
char path[256];
|
||||
uint16_t port;
|
||||
bool tls; // the URL asked for https, which we cannot speak
|
||||
|
||||
// Result. `state` is the only field the requester may read while the fetch
|
||||
// is in flight; everything else is valid once state != RUNNING.
|
||||
volatile int state; // http_fetch_state_t
|
||||
volatile bool abandoned; // requester released it mid-flight; worker frees
|
||||
|
||||
char* buf; // response bytes (status line, headers, body)
|
||||
int len; // bytes stored
|
||||
int cap; // buffer capacity
|
||||
|
||||
uint32_t ip; // resolved peer address (of the final hop)
|
||||
int status_code; // parsed HTTP status, 0 if not found
|
||||
uint32_t elapsed_ms; // wall time of the whole fetch
|
||||
int redirects; // number of 3xx hops followed
|
||||
char err[96]; // human-readable failure reason
|
||||
} http_fetch_t;
|
||||
|
||||
// Start the worker thread. Call once at boot, after the TCP stack is up.
|
||||
void http_client_init(void);
|
||||
|
||||
// Begin fetching `url` (forms: "host", "host/path", "host:port/path", with an
|
||||
// optional "http://" prefix). `cap` bounds the stored response. Returns a job
|
||||
// to poll, or NULL if the URL is unusable or a fetch is already in flight.
|
||||
http_fetch_t* http_fetch_start(const char* url, int cap);
|
||||
|
||||
// Give a job back. Frees it if the fetch has finished; otherwise marks it so
|
||||
// the worker frees it on completion. The pointer must not be used afterwards.
|
||||
void http_fetch_release(http_fetch_t* f);
|
||||
|
||||
// True while the worker thread is busy with a fetch.
|
||||
bool http_client_busy(void);
|
||||
|
||||
#endif // _NET_HTTP_CLIENT_H
|
||||
@@ -102,6 +102,24 @@ arp_entry_t* net_get_arp_table(int* count_out);
|
||||
|
||||
uint32_t ip_parse(const char* ip_str);
|
||||
void ip_to_str(uint32_t ip, char* buf);
|
||||
|
||||
// Yield the CPU once while blocked in a network wait loop (a handshake, a
|
||||
// receive, a ping reply). Inbound packets are delivered from the NIC interrupt
|
||||
// regardless of which task runs, so cooperatively handing the CPU to other
|
||||
// tasks -- the GUI compositor above all -- keeps the desktop live during a
|
||||
// fetch instead of freezing it for the duration. When the caller is the only
|
||||
// runnable task it returns at once, so a shell-context wait still just spins to
|
||||
// its timeout.
|
||||
void net_wait(void);
|
||||
|
||||
// One's-complement checksum, summed a byte at a time so the loads cannot be
|
||||
// reordered around a caller's store into the buffer being summed, and so no
|
||||
// alignment is assumed of a packed header. Words are read big-endian;
|
||||
// net_csum_finish returns the result already in network byte order, ready to
|
||||
// be assigned straight to a header's checksum field.
|
||||
uint32_t net_csum_add(uint32_t sum, const void* data, size_t len);
|
||||
uint16_t net_csum_finish(uint32_t sum);
|
||||
uint16_t net_checksum(const void* data, size_t len);
|
||||
void mac_to_str(const uint8_t* mac, char* buf);
|
||||
|
||||
#endif // _NET_NET_H
|
||||
@@ -23,6 +23,11 @@ typedef struct sockaddr_in {
|
||||
char sin_zero[8];
|
||||
} sockaddr_in_t;
|
||||
|
||||
// Values for socket_t.state.
|
||||
#define SOCK_STATE_OPEN 0 // created, no peer or port committed yet
|
||||
#define SOCK_STATE_CONNECTED 1 // peer address known (and, for a stream, open)
|
||||
#define SOCK_STATE_LISTEN 2 // stream socket accepting inbound connections
|
||||
|
||||
typedef struct socket {
|
||||
int domain;
|
||||
int type;
|
||||
@@ -39,8 +44,26 @@ void socket_subsystem_init(void);
|
||||
int sys_socket(int domain, int type, int protocol);
|
||||
int sys_bind(int sockfd, const struct sockaddr_in* addr, size_t addrlen);
|
||||
int sys_connect(int sockfd, const struct sockaddr_in* addr, size_t addrlen);
|
||||
|
||||
// Stream sockets only. sys_listen claims the bound port; sys_accept returns a
|
||||
// new socket descriptor for the next connection to complete its handshake, or
|
||||
// -1 if none arrives within the wait. `addr`, when given, receives the peer.
|
||||
int sys_listen(int sockfd, int backlog);
|
||||
int sys_accept(int sockfd, struct sockaddr_in* addr, size_t* addrlen);
|
||||
ssize_t sys_send(int sockfd, const void* buf, size_t len, int flags);
|
||||
ssize_t sys_recv(int sockfd, void* buf, size_t len, int flags);
|
||||
|
||||
// Address-carrying variants for connectionless (SOCK_DGRAM) use.
|
||||
ssize_t sys_sendto(int sockfd, const void* buf, size_t len, int flags,
|
||||
const struct sockaddr_in* dest, size_t addrlen);
|
||||
ssize_t sys_recvfrom(int sockfd, void* buf, size_t len, int flags,
|
||||
struct sockaddr_in* src, size_t* addrlen);
|
||||
|
||||
int sys_close_socket(int sockfd);
|
||||
|
||||
// Introspection for netstat / sockstat.
|
||||
int socket_get_count(void);
|
||||
const socket_t* socket_get(int idx);
|
||||
void socket_get_stats(uint64_t* tx_dgrams, uint64_t* rx_dgrams, uint64_t* drops);
|
||||
|
||||
#endif // _NET_SOCKET_H
|
||||
+96
-2
@@ -34,15 +34,42 @@ typedef struct {
|
||||
uint16_t urgent_pointer;
|
||||
} __attribute__((packed)) tcp_header_t;
|
||||
|
||||
// Which end opened the connection, and how it is serviced.
|
||||
//
|
||||
// SERVICE -- created by an inbound SYN to one of the built-in servers
|
||||
// (sshd, httpd), which are answered inline from the receive
|
||||
// path and never hand a stream back to a caller.
|
||||
// CLIENT -- opened by tcp_connect().
|
||||
// ACCEPTED -- opened by a peer against a tcp_listen() port and handed to
|
||||
// tcp_accept(). Carries a byte stream exactly like a CLIENT.
|
||||
#define TCP_ROLE_SERVICE 0
|
||||
#define TCP_ROLE_CLIENT 1
|
||||
#define TCP_ROLE_ACCEPTED 2
|
||||
|
||||
#define TCP_RX_BUF_SIZE 4096
|
||||
#define TCP_MSS 1460
|
||||
|
||||
typedef struct tcp_conn {
|
||||
uint32_t local_ip;
|
||||
uint32_t remote_ip;
|
||||
uint16_t local_port;
|
||||
uint16_t remote_port;
|
||||
uint8_t state;
|
||||
uint32_t seq_num;
|
||||
uint32_t ack_num;
|
||||
uint32_t seq_num; // next sequence number we will send
|
||||
uint32_t ack_num; // next sequence number we expect to receive
|
||||
bool in_use;
|
||||
|
||||
// --- client / stream state, unused by the server path ----------------
|
||||
uint8_t role;
|
||||
uint32_t iss; // initial send sequence number
|
||||
uint32_t snd_una; // oldest sequence number not yet acknowledged
|
||||
uint8_t* rx_buf; // receive ring, allocated on active open
|
||||
uint16_t rx_head; // read cursor
|
||||
uint16_t rx_count; // bytes available to the reader
|
||||
bool peer_fin; // peer sent FIN: no more data will arrive
|
||||
bool reset; // peer sent RST
|
||||
uint64_t timer; // tick of the last transmission awaiting an ACK
|
||||
uint64_t closed_at; // tick TIME_WAIT began
|
||||
} tcp_conn_t;
|
||||
|
||||
void tcp_init(void);
|
||||
@@ -51,7 +78,74 @@ void tcp_send_packet(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
uint32_t seq, uint32_t ack, uint8_t flags,
|
||||
const void* payload, uint16_t payload_len);
|
||||
|
||||
// --- Active open (client side) ------------------------------------------
|
||||
//
|
||||
// Every call below is bounded: a silent or unreachable peer produces a
|
||||
// timeout, never a hang. They busy-wait against the timer tick with the CPU
|
||||
// halted between checks, the same way net_ping and the DNS resolver do, so
|
||||
// they must be called from a context where inbound packets can still be
|
||||
// serviced by the NIC interrupt.
|
||||
|
||||
// Perform the three-way handshake with dst_ip:dst_port, retransmitting the
|
||||
// SYN until the handshake completes or timeout_ms elapses. Returns the
|
||||
// connection, or NULL if it could not be established.
|
||||
tcp_conn_t* tcp_connect(uint32_t dst_ip, uint16_t dst_port, uint32_t timeout_ms);
|
||||
|
||||
// Send len bytes, segmented to the MSS. Stop-and-wait: each segment is
|
||||
// retransmitted until acknowledged before the next is sent. Returns the number
|
||||
// of bytes acknowledged, or -1 if the connection is unusable.
|
||||
int tcp_send(tcp_conn_t* c, const void* data, uint16_t len);
|
||||
|
||||
// Read up to len bytes, waiting up to timeout_ms for data to arrive. Returns
|
||||
// the byte count, or -1 on reset. A return of 0 means no data became
|
||||
// available: use tcp_peer_closed() to tell a finished stream from an idle one.
|
||||
int tcp_recv(tcp_conn_t* c, void* buf, uint16_t len, uint32_t timeout_ms);
|
||||
|
||||
// Orderly shutdown, then release the connection. The handle must not be used
|
||||
// afterwards.
|
||||
void tcp_close(tcp_conn_t* c);
|
||||
|
||||
bool tcp_is_established(const tcp_conn_t* c);
|
||||
|
||||
// True once the peer's FIN has been received, so no further data can arrive.
|
||||
bool tcp_peer_closed(const tcp_conn_t* c);
|
||||
|
||||
// --- Passive open (server side) -----------------------------------------
|
||||
//
|
||||
// tcp_listen claims a port; inbound SYNs to it are completed by the receive
|
||||
// path and the finished connections queue up until tcp_accept collects them.
|
||||
// This is the general path: the built-in sshd and httpd predate it and are
|
||||
// still answered inline, so listening on their ports is not useful.
|
||||
|
||||
#define TCP_MAX_LISTENERS 8
|
||||
#define TCP_BACKLOG_MAX 4
|
||||
|
||||
// Returns 0 on success, -1 if the port is taken or the table is full.
|
||||
int tcp_listen(uint16_t port, int backlog);
|
||||
|
||||
// Collect the next established connection, waiting up to timeout_ms.
|
||||
// Returns NULL if none arrived. The caller owns it and must tcp_close it.
|
||||
tcp_conn_t* tcp_accept(uint16_t port, uint32_t timeout_ms);
|
||||
|
||||
// Stop listening. Connections already accepted are unaffected; any still
|
||||
// waiting in the backlog are reset.
|
||||
void tcp_unlisten(uint16_t port);
|
||||
|
||||
int tcp_get_listener_count(void);
|
||||
uint16_t tcp_get_listener_port(int idx);
|
||||
int tcp_get_listener_backlog(int idx);
|
||||
|
||||
size_t tcp_get_connections_count(void);
|
||||
const tcp_conn_t* tcp_get_connection(size_t index);
|
||||
const char* tcp_state_name(uint8_t state);
|
||||
|
||||
// Size of the connection table, so callers iterate the right range rather than
|
||||
// a hardcoded constant.
|
||||
int tcp_conn_table_size(void);
|
||||
|
||||
// Cumulative count of connections that have reached ESTABLISHED, and the
|
||||
// high-water mark of simultaneously-live table entries. Diagnostic.
|
||||
uint64_t tcp_get_total_opened(void);
|
||||
int tcp_get_peak_connections(void);
|
||||
|
||||
#endif // _NET_TCP_H
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef _NET_TLS_H
|
||||
#define _NET_TLS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Minimal TLS 1.3 client (RFC 8446), enough to fetch an HTTPS page.
|
||||
//
|
||||
// Scope, stated plainly:
|
||||
// - TLS 1.3 only, one cipher suite (TLS_CHACHA20_POLY1305_SHA256) and one
|
||||
// key exchange (X25519). These are universally supported by modern servers.
|
||||
// - The server certificate is NOT verified. The handshake completes and the
|
||||
// channel is encrypted, but a man-in-the-middle could impersonate the peer.
|
||||
// Real certificate-chain validation (X.509 parsing, signature checks, a CA
|
||||
// bundle, hostname matching) is a large separate piece not built here.
|
||||
//
|
||||
// The calls mirror the TCP client and block the same cooperative way, so they
|
||||
// run on the async HTTP worker thread, not the compositor.
|
||||
|
||||
typedef struct tls_conn tls_conn_t;
|
||||
|
||||
// Open a TLS connection to ip:port, using `host` for SNI. Returns NULL on any
|
||||
// failure (connect, handshake, unsupported server parameters).
|
||||
tls_conn_t* tls_connect(const char* host, uint32_t ip, uint16_t port,
|
||||
uint32_t timeout_ms);
|
||||
|
||||
// Send application data (the HTTP request). Returns bytes sent or -1.
|
||||
int tls_send(tls_conn_t* c, const void* data, int len);
|
||||
|
||||
// Receive up to len bytes of application data, waiting up to timeout_ms.
|
||||
// Returns the byte count, 0 at clean end of stream, or -1 on error.
|
||||
int tls_recv(tls_conn_t* c, void* buf, int len, uint32_t timeout_ms);
|
||||
|
||||
// Shut down and release the connection.
|
||||
void tls_close(tls_conn_t* c);
|
||||
|
||||
// A short reason for the last failure, for diagnostics.
|
||||
const char* tls_last_error(void);
|
||||
|
||||
#endif // _NET_TLS_H
|
||||
+30
-1
@@ -3,8 +3,37 @@
|
||||
|
||||
#include <net/net.h>
|
||||
|
||||
// A bound UDP port dispatches every matching datagram to this callback. It runs
|
||||
// in soft-IRQ / receive context, so handlers must be quick and non-blocking:
|
||||
// stash the datagram and set a completion flag, then process it from a thread.
|
||||
typedef void (*udp_handler_t)(uint32_t src_ip, uint16_t src_port,
|
||||
uint16_t dst_port, const uint8_t* data,
|
||||
uint16_t len, void* ctx);
|
||||
|
||||
void udp_init(void);
|
||||
|
||||
// Demultiplex an inbound IPv4 payload (protocol 17) to a bound port.
|
||||
void udp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip);
|
||||
void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, const void* data, uint16_t len);
|
||||
|
||||
// Emit a datagram to dst_ip:dst_port from src_port (checksummed).
|
||||
void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
const void* data, uint16_t len);
|
||||
|
||||
// Emit a datagram to the limited broadcast address (255.255.255.255) from
|
||||
// source IP 0.0.0.0 — used before the interface has an address (DHCP).
|
||||
void udp_send_broadcast(uint16_t src_port, uint16_t dst_port,
|
||||
const void* data, uint16_t len);
|
||||
|
||||
// Register/unregister a receive handler for a local port. udp_bind returns 0 on
|
||||
// success, -1 if the port is taken or the table is full.
|
||||
int udp_bind(uint16_t port, udp_handler_t handler, void* ctx);
|
||||
void udp_unbind(uint16_t port);
|
||||
|
||||
// Allocate an unused ephemeral port in the 49152-65535 range.
|
||||
uint16_t udp_alloc_ephemeral_port(void);
|
||||
|
||||
// Counters: datagrams received, transmitted, and dropped (no listener / bad).
|
||||
void udp_get_stats(uint64_t* rx, uint64_t* tx, uint64_t* drop);
|
||||
int udp_get_bindings(uint16_t* ports_out, int max);
|
||||
|
||||
#endif // _NET_UDP_H
|
||||
@@ -31,4 +31,9 @@ int auth_set_current_user(const char* username);
|
||||
size_t auth_get_user_count(void);
|
||||
const user_account_t* auth_get_user_by_index(size_t index);
|
||||
|
||||
/* Interactive boot-time login: prompts for a username and password on the
|
||||
* console and blocks until a valid pair is entered, then sets the current
|
||||
* user. Default account is SUB / SUB. */
|
||||
void console_login(void);
|
||||
|
||||
#endif // _SECURITY_AUTH_H
|
||||
+255
-255
@@ -1,5 +1,5 @@
|
||||
// Generated by scripts/mklogo.py -- do not edit by hand.
|
||||
// Source artwork: logo_SUB.png
|
||||
// Source artwork: logo_SUB_OS.png
|
||||
|
||||
#include <init/bootlogo.h>
|
||||
|
||||
@@ -12,10 +12,10 @@ const uint16_t boot_logo_transparent_mask = 0x7800;
|
||||
|
||||
// 16-entry ARGB palette; index 0 is the wallpaper background.
|
||||
const uint32_t boot_logo_palette[16] = {
|
||||
0xFFFEFDFD,
|
||||
0xFF77D4F6,
|
||||
0xFF1C94C6,
|
||||
0xFF232D32,
|
||||
0xFFFDFDFD,
|
||||
0xFF5ECDF7,
|
||||
0xFF1D8DBB,
|
||||
0xFF222B2F,
|
||||
0xFF211F1E,
|
||||
0xFF201F1F,
|
||||
0xFF201E1F,
|
||||
@@ -27,7 +27,7 @@ const uint32_t boot_logo_palette[16] = {
|
||||
0xFF0B132C,
|
||||
0xFF0B132B,
|
||||
0xFF0A132C,
|
||||
0xFF141016,
|
||||
0xFF141015,
|
||||
};
|
||||
|
||||
// 160x160 pixels, two 4-bit indices per byte, row major.
|
||||
@@ -84,28 +84,28 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xEB, 0x94, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x76,
|
||||
0x7A, 0x22, 0x3A, 0xA9, 0x57, 0x77, 0x77, 0x77, 0x9A, 0x22, 0x27, 0xA9, 0x57, 0x77, 0x77, 0x77,
|
||||
0x7A, 0x22, 0x3A, 0xA9, 0x57, 0x77, 0x77, 0x77, 0x9A, 0x21, 0x27, 0xA9, 0x57, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x49, 0xBE, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDC, 0xB5, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x58, 0xA2, 0x22, 0x3A, 0xA5, 0x57, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x3A, 0x89, 0x77, 0x77, 0x77,
|
||||
0x58, 0xA2, 0x12, 0x3A, 0xA5, 0x57, 0x77, 0x77, 0x75, 0xA2, 0x12, 0x3A, 0x89, 0x77, 0x77, 0x77,
|
||||
0x77, 0x86, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x5B, 0xCD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDE, 0xB9, 0x48, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x75, 0x8A, 0x22, 0x22, 0x3A, 0xA6, 0x95, 0x77, 0x75, 0x89, 0x22, 0x22, 0x9A, 0x75, 0x77, 0x77,
|
||||
0x75, 0x8A, 0x21, 0x22, 0x3A, 0xA6, 0x95, 0x77, 0x75, 0x89, 0x21, 0x22, 0x9A, 0x75, 0x77, 0x77,
|
||||
0x77, 0x58, 0x67, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x84, 0x9B, 0xED, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xC9, 0x48, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x55, 0x55, 0x55, 0x99,
|
||||
0x55, 0x58, 0xA2, 0x22, 0x22, 0x38, 0xAA, 0x55, 0x57, 0x5A, 0x32, 0x22, 0x23, 0x8A, 0x55, 0x77,
|
||||
0x5A, 0x33, 0xA6, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x84, 0x9C, 0xDD,
|
||||
0x5A, 0x23, 0xA6, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x84, 0x9C, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xEB, 0x94, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x8A, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0x79, 0x9A, 0x32, 0x22, 0x22, 0x23, 0xAA, 0xA5, 0x59, 0xA3, 0x22, 0x22, 0x23, 0xAA, 0x55,
|
||||
0x9A, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xBE,
|
||||
0x9A, 0x22, 0xF9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xBE,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEE,
|
||||
0xB4, 0x87, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x58, 0x32, 0x22, 0x22, 0x22, 0x22,
|
||||
@@ -113,155 +113,155 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x9A, 0x32, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x4B,
|
||||
0xEE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xB9,
|
||||
0x48, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x93, 0x33, 0x22, 0x22, 0x22,
|
||||
0x48, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x93, 0x33, 0x22, 0x12, 0x22,
|
||||
0x22, 0x22, 0x33, 0x8A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA, 0xA7, 0x22, 0x22, 0x22, 0x23,
|
||||
0xA8, 0xA2, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x84,
|
||||
0x9B, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x94,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x8A, 0xAA, 0xA9, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x23, 0x9F, 0x83, 0x22, 0x22, 0x22, 0x22, 0x23, 0x3A, 0xA2, 0x22, 0x22, 0x22,
|
||||
0x23, 0xF3, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x23, 0xF3, 0x12, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x49, 0xBE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB5, 0x87,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x59, 0x99, 0x99, 0x99, 0x99, 0x98, 0xF2, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x23, 0x9F, 0x83, 0x22, 0x22, 0x22, 0x22, 0x22, 0x9F, 0x22, 0x22, 0x22,
|
||||
0x22, 0x3F, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0x22, 0x22, 0x23, 0x9F, 0x83, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3F, 0x22, 0x22, 0x22,
|
||||
0x22, 0x3F, 0x21, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x48, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x57, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x32, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x7A, 0x83, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA2, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x7F, 0x83, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA2, 0x22, 0x22,
|
||||
0x22, 0x22, 0x32, 0x23, 0xA5, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x84, 0xBE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x75, 0x97, 0xAA, 0xA8, 0x33, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x82, 0x22, 0x22, 0x22, 0x22, 0xA3, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x3A, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x75, 0x97, 0xAA, 0xA8, 0x33, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x82, 0x22, 0x22, 0x22, 0x12, 0xA3, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x2A, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77,
|
||||
0x77, 0x77, 0x75, 0x5A, 0xA8, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0xA3, 0x33, 0x22, 0x39, 0x8A, 0x32, 0x22, 0x23, 0x22, 0x2F, 0x22, 0x22,
|
||||
0x77, 0x77, 0x75, 0x5A, 0xA8, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0xA3, 0x32, 0x22, 0x39, 0x8A, 0x32, 0x22, 0x23, 0x21, 0x2F, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x8A, 0x97, 0x77, 0x77, 0x78, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0x5A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x2F, 0x99, 0xA7, 0x39, 0x89, 0xA9, 0x22, 0x22, 0x83, 0x23, 0x82, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA9, 0x67, 0x77, 0x89, 0xAA, 0x57, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0x22, 0x21, 0x2F, 0x99, 0xF7, 0x39, 0x89, 0xA9, 0x22, 0x22, 0x83, 0x23, 0x82, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA9, 0x67, 0x77, 0x83, 0xAA, 0x57, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77,
|
||||
0x75, 0x5A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x23, 0x33, 0x37, 0x32, 0x23, 0x39, 0x8A, 0xAA, 0xAA, 0x83, 0x3A, 0xA9, 0x53, 0x22,
|
||||
0x22, 0x23, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x65, 0x75, 0x83, 0x33, 0xA9, 0x67, 0x77, 0x77, 0x77,
|
||||
0x22, 0x23, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x65, 0x75, 0x83, 0x23, 0xA9, 0x67, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77,
|
||||
0x58, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23,
|
||||
0x38, 0xFF, 0xFF, 0xFA, 0xAA, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA9, 0x32, 0x22,
|
||||
0x38, 0xFF, 0xFF, 0xFA, 0xAA, 0xA3, 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA9, 0x22, 0x22,
|
||||
0x22, 0x22, 0x32, 0x22, 0x22, 0x22, 0x22, 0xA7, 0x77, 0x5A, 0x32, 0x3A, 0x85, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x76,
|
||||
0x83, 0x22, 0x23, 0x33, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x5F, 0xFA,
|
||||
0xA9, 0x32, 0x22, 0x23, 0xA3, 0x3A, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA3, 0x32,
|
||||
0x22, 0x22, 0x3F, 0x32, 0x22, 0x22, 0x22, 0x2A, 0x57, 0x75, 0xA9, 0x22, 0xAA, 0x97, 0x77, 0x77,
|
||||
0xA3, 0x32, 0x22, 0x23, 0xA3, 0x3A, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x11, 0x22, 0x3A, 0xA3, 0x32,
|
||||
0x22, 0x22, 0x3F, 0x32, 0x22, 0x22, 0x21, 0x2A, 0x57, 0x75, 0xA9, 0x22, 0xAA, 0x97, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x76,
|
||||
0x83, 0x3A, 0xAA, 0xAA, 0xAA, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xFA, 0x33, 0x23,
|
||||
0x32, 0x22, 0x22, 0x28, 0x22, 0x23, 0x8A, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x93, 0x22, 0x22,
|
||||
0x32, 0x22, 0x21, 0x28, 0x22, 0x23, 0x8A, 0x32, 0x22, 0x22, 0x22, 0x22, 0x12, 0x93, 0x22, 0x22,
|
||||
0x22, 0x22, 0x2F, 0x33, 0x32, 0x22, 0x22, 0x23, 0xA5, 0x77, 0x58, 0xA2, 0x23, 0xA9, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x5A, 0xA5, 0x95, 0x59, 0x9A, 0x32, 0x22, 0x22, 0x22, 0x22, 0x23, 0x5A, 0xA3, 0x22, 0x22, 0x29,
|
||||
0x22, 0x22, 0x22, 0x52, 0x22, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x22, 0x23, 0x3A, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x23, 0x72, 0x23, 0x22, 0x22, 0x22, 0xA9, 0x77, 0x75, 0x5A, 0x32, 0x3A, 0x55, 0x77,
|
||||
0x5A, 0xA5, 0x95, 0x59, 0x9A, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x5A, 0xA3, 0x22, 0x21, 0x29,
|
||||
0x22, 0x22, 0x22, 0x52, 0x12, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x12, 0x23, 0x3A, 0x31, 0x22, 0x22,
|
||||
0x22, 0x22, 0x23, 0x72, 0x23, 0x21, 0x22, 0x22, 0xA9, 0x77, 0x75, 0x5F, 0x22, 0x3A, 0x55, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x75, 0x57, 0x75, 0x9A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAF, 0x33, 0x92, 0x22, 0x22, 0x32,
|
||||
0x22, 0x22, 0x2A, 0x32, 0x22, 0x23, 0x33, 0x89, 0xAA, 0x93, 0x32, 0x22, 0x3A, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x39, 0x39, 0xA2, 0x22, 0x23, 0xA9, 0x77, 0x77, 0x79, 0xF3, 0x22, 0xA7, 0x57,
|
||||
0x75, 0x57, 0x75, 0x9A, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAF, 0x33, 0x92, 0x12, 0x22, 0x32,
|
||||
0x12, 0x22, 0x2A, 0x22, 0x22, 0x23, 0x33, 0x89, 0xAA, 0x93, 0x22, 0x22, 0x3A, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x33, 0x39, 0xA2, 0x22, 0x22, 0xA9, 0x77, 0x77, 0x79, 0xF3, 0x22, 0xA7, 0x57,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDB, 0x58, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0x9A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAF, 0x32, 0x23, 0x22, 0x22, 0x23, 0x32,
|
||||
0x22, 0x33, 0xA5, 0x8A, 0xAA, 0xAA, 0xA8, 0x58, 0x8A, 0xA3, 0x32, 0x22, 0x22, 0xAF, 0x32, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x32, 0x22, 0xA7, 0x57, 0x75, 0x77, 0x9A, 0x32, 0x2A, 0x85,
|
||||
0x77, 0x75, 0x9A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xFF, 0x32, 0x23, 0x21, 0x22, 0x23, 0x32,
|
||||
0x22, 0x33, 0xA5, 0x8A, 0xAA, 0xAA, 0xA8, 0x58, 0x8A, 0xA3, 0x22, 0x21, 0x12, 0xAF, 0x32, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x32, 0x22, 0xA7, 0x57, 0x75, 0x77, 0x9A, 0x31, 0x2A, 0x85,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x85, 0xBD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x75, 0x5A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x23, 0x22, 0x22, 0x2A, 0x33,
|
||||
0x75, 0x5A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x32, 0x21, 0x23, 0x22, 0x22, 0x2A, 0x33,
|
||||
0x8A, 0xAA, 0x57, 0x75, 0x59, 0x59, 0xAA, 0xA7, 0x33, 0x22, 0x22, 0x22, 0x33, 0x73, 0x33, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x28, 0xA9, 0x58, 0x57, 0x79, 0xA3, 0x22, 0x88,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x28, 0xA9, 0x58, 0x57, 0x79, 0xA3, 0x12, 0x88,
|
||||
0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x5A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAF, 0xA3, 0x22, 0x22, 0x33, 0x22, 0x23, 0xA5, 0xAA,
|
||||
0x5A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0xAF, 0xA3, 0x22, 0x22, 0x33, 0x12, 0x23, 0xA5, 0xAA,
|
||||
0x65, 0x55, 0x77, 0x77, 0x77, 0x85, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3F, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x83, 0x87, 0x77, 0x9A, 0x32, 0x2A,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x83, 0x87, 0x77, 0x9A, 0x31, 0x2A,
|
||||
0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x9A,
|
||||
0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3F, 0x92, 0x32, 0x22, 0x22, 0x92, 0x23, 0xAA, 0x57, 0x55,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x68, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xA3, 0x2A, 0x57, 0x79, 0xA2, 0x22,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x68, 0x32, 0x22, 0x22, 0x22, 0x22, 0x11, 0x13, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xA3, 0x2A, 0x57, 0x79, 0xA2, 0x12,
|
||||
0xA8, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x98, 0xA3,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA3, 0x22, 0x32, 0x22, 0x23, 0xA9, 0xAA, 0x55, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xAA, 0xA3, 0x32, 0x22, 0x22, 0x22, 0x23, 0xF2, 0x22, 0x22,
|
||||
0x22, 0x22, 0x3A, 0xAA, 0xA3, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA5, 0x77, 0x5A, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xF3, 0x22, 0x32, 0x22, 0x13, 0xA9, 0xAA, 0x55, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xAA, 0xA3, 0x32, 0x22, 0x22, 0x22, 0x23, 0xF2, 0x12, 0x22,
|
||||
0x22, 0x22, 0x3A, 0xAA, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA5, 0x77, 0x5A, 0x21,
|
||||
0x2A, 0x75, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0xA9, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x23, 0xA9, 0x22, 0x22, 0x32, 0x22, 0x3A, 0x68, 0x55, 0x77, 0x77, 0x77,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0xA3, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x23, 0xA9, 0x22, 0x12, 0x32, 0x22, 0x2A, 0x68, 0x55, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x59, 0x6A, 0xAA, 0xAA, 0xAA, 0xAF, 0x33, 0x3A, 0x22, 0x22,
|
||||
0x22, 0x22, 0x2A, 0x99, 0x83, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xA5, 0x77, 0x65, 0xA2,
|
||||
0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x89, 0xCD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x22, 0x21, 0x2A, 0x99, 0x83, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0xA5, 0x77, 0x65, 0xA2,
|
||||
0x12, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x89, 0xCD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x75, 0x8A, 0x32, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x9A, 0x52, 0x22, 0x23, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0x22, 0x22, 0x9A, 0x52, 0x12, 0x23, 0x21, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x59, 0x95, 0x99, 0xA3, 0x22, 0x39, 0x93, 0x22,
|
||||
0x22, 0x22, 0x2A, 0x95, 0xA3, 0x22, 0x33, 0x22, 0x22, 0x22, 0x33, 0x22, 0x3A, 0x57, 0x75, 0x86,
|
||||
0x22, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x22, 0x22, 0x2A, 0x95, 0xA3, 0x12, 0x33, 0x22, 0x22, 0x22, 0x33, 0x22, 0x3A, 0x57, 0x75, 0x86,
|
||||
0x21, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x52, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x2A, 0xA5, 0x32, 0x22, 0x23, 0x33, 0xAA, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x8A, 0x32, 0x22, 0x22, 0x22, 0x22,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x8F, 0x32, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x2A, 0x55, 0x83, 0x38, 0x22, 0x33, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x57, 0x77, 0x5A,
|
||||
0x32, 0x23, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, 0x98, 0x77, 0x77, 0x77, 0x77, 0x79, 0xA3, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0xAA, 0x23, 0x32, 0x22, 0x2A, 0xAA, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x68, 0x92, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0xAA, 0x23, 0x22, 0x21, 0x2A, 0xAA, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x68, 0x92, 0x12, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x23, 0xAA, 0x8A, 0xA8, 0x22, 0x22, 0x33, 0x22, 0x22, 0x22, 0x38, 0x57, 0x77, 0x79,
|
||||
0xA3, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x89, 0xCD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x3F, 0x52, 0x23, 0x22, 0x22, 0x3A, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x23, 0x39, 0xAA, 0xAA, 0x93,
|
||||
0x22, 0x22, 0x22, 0xA3, 0x36, 0x55, 0xAF, 0x32, 0x23, 0x33, 0x22, 0x22, 0xA5, 0x77, 0x77, 0x77,
|
||||
0x9A, 0x22, 0x29, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x22, 0x22, 0x3F, 0x52, 0x13, 0x22, 0x22, 0x3A, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x23, 0x33, 0xAA, 0xAA, 0x33,
|
||||
0x22, 0x22, 0x22, 0xA3, 0x36, 0x55, 0xAF, 0x31, 0x23, 0x33, 0x22, 0x22, 0xA5, 0x77, 0x77, 0x77,
|
||||
0x9A, 0x21, 0x29, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB5, 0x87, 0x77, 0x77, 0x77, 0x79, 0xA3, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x23, 0xF9, 0x22, 0x23, 0x22, 0x25, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x23, 0xF9, 0x22, 0x23, 0x21, 0x25, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xAA, 0xAA, 0xA8, 0x55, 0x55, 0x8A,
|
||||
0xA3, 0x22, 0x22, 0x32, 0x3A, 0x57, 0x57, 0x33, 0xF2, 0x23, 0x32, 0x2A, 0x67, 0x77, 0x77, 0x77,
|
||||
0x75, 0xA2, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xA3, 0x22, 0x22, 0x32, 0x3A, 0x57, 0x57, 0x33, 0xF2, 0x13, 0x31, 0x2A, 0x67, 0x77, 0x77, 0x77,
|
||||
0x75, 0xA2, 0x12, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x3A, 0x92, 0x22, 0x23, 0x22, 0xA8, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x59, 0x95, 0x55, 0x77, 0x77, 0x55,
|
||||
0x5A, 0x82, 0x22, 0x29, 0x22, 0xA7, 0x75, 0xAA, 0x52, 0x23, 0x32, 0x88, 0x57, 0x77, 0x77, 0x77,
|
||||
0x75, 0xA3, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDB, 0x47, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x3A, 0xA5, 0x22, 0x22,
|
||||
0x23, 0xAA, 0x32, 0x22, 0x23, 0x3A, 0x75, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0xAA, 0x32, 0x22, 0x23, 0x3A, 0x75, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x79, 0x8A, 0x22, 0x23, 0x22, 0xF9, 0x75, 0x58, 0x33, 0xAA, 0x5A, 0x85, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x9A, 0x32, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x77, 0x9A, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x87, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x3A, 0xA9, 0xA2, 0x22, 0x22,
|
||||
0x2F, 0x33, 0x32, 0x22, 0x2A, 0xA5, 0x67, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x56, 0xA2, 0x22, 0x93, 0x23, 0xAA, 0x85, 0x8A, 0x55, 0x85, 0x57, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA2, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x77, 0x56, 0xA2, 0x12, 0x93, 0x23, 0xAA, 0x85, 0x8A, 0x55, 0x85, 0x57, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA2, 0x12, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x78, 0x33, 0xA9, 0x9A, 0x32, 0x22, 0x22,
|
||||
0xA7, 0x22, 0x32, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA3, 0x22, 0x23, 0x29, 0xA3, 0x98, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA3, 0x21, 0x23, 0x29, 0xA3, 0x98, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA3, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDE, 0x98, 0x77, 0x77, 0x77, 0x77, 0x89, 0x6A, 0x99, 0xA3, 0x22, 0x22, 0x2A,
|
||||
0xA2, 0x22, 0x32, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0xA2, 0x12, 0x32, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x5A, 0x22, 0x23, 0x92, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x9A, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x89, 0xED, 0xDD, 0xDD, 0xDD,
|
||||
0x77, 0x77, 0x9A, 0x21, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x89, 0xED, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x78, 0x75, 0x9A, 0x32, 0x22, 0x22, 0x3F,
|
||||
0x22, 0x22, 0x32, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0x31, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x9A, 0x22, 0x22, 0x33, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x58, 0x92, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD,
|
||||
0x77, 0x77, 0x58, 0x92, 0x12, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x79, 0xA3, 0x22, 0x22, 0x23, 0xF3,
|
||||
0x22, 0x22, 0x32, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
@@ -270,10 +270,10 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x22, 0x22, 0x2A, 0xA2,
|
||||
0x22, 0x22, 0xAA, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5A, 0x32, 0x22, 0x22, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x68, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD,
|
||||
0x77, 0x5A, 0x32, 0x22, 0x22, 0x21, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x68, 0x21, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x77, 0x79, 0xA3, 0x22, 0x22, 0x22, 0xA9, 0xA2,
|
||||
0x22, 0x23, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x78, 0x99, 0x92, 0x22, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x5A, 0x32, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD,
|
||||
@@ -282,13 +282,13 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0x83, 0x22, 0x22, 0x3A, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x82, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x86, 0xBD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x22, 0x2F, 0x32, 0x23,
|
||||
0xDD, 0xDD, 0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x22, 0x2F, 0x31, 0x23,
|
||||
0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x83, 0x33, 0x5A, 0xA5, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x79, 0xA3, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0x22, 0xA8, 0x22, 0x23,
|
||||
0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0xDD, 0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0x22, 0xA8, 0x21, 0x23,
|
||||
0x12, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x78, 0xA8, 0x85, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x76, 0x89, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9C, 0xDD, 0xDD,
|
||||
@@ -313,63 +313,63 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x77, 0x77, 0x58, 0xF3, 0x03, 0xA4, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0x84, 0x77, 0x75, 0xA3, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD,
|
||||
0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x79, 0xA2, 0x22, 0x22, 0x22, 0x23, 0xA2, 0x32, 0x22, 0x3A,
|
||||
0x57, 0x77, 0x77, 0x75, 0xF3, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x11, 0x11, 0x11, 0x15, 0x87, 0x45, 0xF3, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA8, 0x10, 0x0A, 0x45, 0xA1, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
|
||||
0x11, 0x11, 0x5A, 0x47, 0x76, 0x89, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD,
|
||||
0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0x22, 0x2F, 0x32, 0x33, 0x22, 0xA5,
|
||||
0x57, 0x77, 0x77, 0x75, 0xF3, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x87, 0x45, 0xF3, 0x2F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA8, 0x10, 0x0A, 0x45, 0xA2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x01, 0x3A, 0x47, 0x76, 0x89, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD,
|
||||
0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0x22, 0x2F, 0x31, 0x32, 0x12, 0xA5,
|
||||
0x77, 0x77, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x45, 0x8F, 0x20, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5F, 0x30, 0x00, 0x1F, 0x58, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1F, 0x57, 0x77, 0x9A, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9C, 0xDD,
|
||||
0xDD, 0xB5, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x23, 0x22, 0x22, 0x3A, 0x22, 0x23, 0x23, 0xA5,
|
||||
0xDD, 0xB5, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x21, 0x23, 0x22, 0x22, 0x3F, 0x21, 0x23, 0x23, 0xA5,
|
||||
0x77, 0x77, 0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x13, 0xF5, 0x5A, 0xA1, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x5A, 0xA1, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x45, 0xF3, 0x00, 0x00, 0x2F, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xF5, 0x77, 0x5A, 0x32, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5B, 0xDD,
|
||||
0xDE, 0xB4, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0xA6, 0x22, 0x22, 0xA3, 0x22, 0x23, 0x3A, 0x57,
|
||||
0x77, 0x74, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x8A, 0x55, 0xF3, 0x10, 0x00, 0xA4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0xDE, 0xB4, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0xA6, 0x22, 0x22, 0xF3, 0x22, 0x23, 0x3A, 0x57,
|
||||
0x77, 0x74, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x8A, 0x55, 0xF3, 0x00, 0x00, 0xA4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x8F, 0x20, 0x00, 0x00, 0x3F, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3F, 0x57, 0x68, 0x92, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xED,
|
||||
0xDE, 0x94, 0x77, 0x77, 0x77, 0x79, 0xA2, 0x2A, 0xA3, 0x22, 0x23, 0xF2, 0x22, 0x23, 0xA5, 0x77,
|
||||
0x77, 0x58, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1F, 0x85, 0x5F, 0x30, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5F,
|
||||
0x00, 0x00, 0x00, 0x2F, 0x85, 0x5F, 0x30, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5F,
|
||||
0x51, 0x00, 0x00, 0x00, 0x58, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xA5, 0x75, 0xA2, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED,
|
||||
0xDB, 0x98, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x55, 0xA2, 0x22, 0x25, 0xA2, 0x22, 0x23, 0xA5, 0x77,
|
||||
0x75, 0xA8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x03, 0xF5, 0x55, 0xF3, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF3,
|
||||
0x00, 0x00, 0x02, 0xF5, 0x55, 0xF3, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF3,
|
||||
0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0xF7, 0x45, 0xA3, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89, 0xBD,
|
||||
0x00, 0x00, 0x00, 0x00, 0xF7, 0x45, 0xA3, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89, 0xBD,
|
||||
0xDB, 0x47, 0x77, 0x77, 0x77, 0x5A, 0x33, 0xA8, 0x32, 0x22, 0x2A, 0xA2, 0x22, 0x23, 0xA5, 0x77,
|
||||
0x5A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0x55, 0xAF, 0x20, 0x00, 0x00, 0x03, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x30,
|
||||
0x00, 0x00, 0x00, 0x02, 0xF5, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x55, 0xA3, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2F, 0x55, 0xA3, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD,
|
||||
0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x38, 0x9A, 0x22, 0x22, 0x38, 0x33, 0x22, 0x28, 0x77, 0x74,
|
||||
0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0xF5, 0x5A, 0x51, 0x00, 0x00, 0x00, 0x05, 0x87, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00,
|
||||
0x00, 0x03, 0xF5, 0x5A, 0x51, 0x00, 0x00, 0x00, 0x05, 0x87, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xF5, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xA5, 0x58, 0x22, 0x25, 0x87, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE,
|
||||
0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x85, 0x85, 0x22, 0x22, 0xA3, 0x23, 0x22, 0x2A, 0x97, 0x75,
|
||||
0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x5F, 0x54, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00,
|
||||
0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x5F, 0x54, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xA5, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xA5, 0x9A, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E,
|
||||
0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x55, 0xA3, 0x22, 0x22, 0xF3, 0x23, 0x22, 0x3A, 0x57, 0x75,
|
||||
0xF1, 0x00, 0x00, 0x00, 0x01, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x00, 0x00, 0x00,
|
||||
0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x55, 0xA3, 0x22, 0x22, 0xF3, 0x13, 0x22, 0x3A, 0x57, 0x75,
|
||||
0xF1, 0x00, 0x00, 0x00, 0x01, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0xA5, 0x77, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x57, 0x77, 0x77, 0x77, 0x48, 0x50, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1F, 0x54, 0xA2, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0F, 0x54, 0xA2, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0A, 0x77, 0x5A, 0x32, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E,
|
||||
0xB9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x23, 0xF2, 0x22, 0x32, 0xA5, 0x77, 0x74,
|
||||
0xB9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x23, 0xF2, 0x12, 0x32, 0xA5, 0x77, 0x74,
|
||||
0xA0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x02,
|
||||
0xF8, 0x57, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x5F, 0x30, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1F, 0x57, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30,
|
||||
0x00, 0x00, 0x00, 0x2F, 0x57, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x5A, 0x32, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9B,
|
||||
0xB6, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x27, 0x92, 0x22, 0x39, 0x85, 0x77, 0x4A,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0x10, 0x00, 0x00, 0x3F,
|
||||
0x54, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x58, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0x10, 0x00, 0x00, 0x2F,
|
||||
0x54, 0x77, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0x58, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3F, 0x57, 0x75, 0x55, 0x55, 0x55, 0xF3, 0x37, 0x55, 0x55, 0x55, 0x55, 0xA0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2F, 0x57, 0x58, 0x32, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x6B,
|
||||
0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x22, 0x22, 0x2F, 0x32, 0x22, 0x2A, 0x97, 0x77, 0x5F,
|
||||
@@ -377,121 +377,121 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x77, 0x77, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x10, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x30, 0x3A, 0x47, 0x77, 0x77, 0x4A, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x76, 0x82, 0x22, 0x38, 0x57, 0x77, 0x77, 0x77, 0x77, 0x4B,
|
||||
0xB4, 0x77, 0x77, 0x77, 0x77, 0x75, 0x83, 0x22, 0x22, 0x3F, 0x22, 0x22, 0x2A, 0x97, 0x77, 0x5F,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x00, 0x00, 0x3F, 0x57,
|
||||
0xB4, 0x77, 0x77, 0x77, 0x77, 0x75, 0x83, 0x22, 0x22, 0x2F, 0x22, 0x22, 0x2A, 0x97, 0x77, 0x5F,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x00, 0x00, 0x3F, 0x57,
|
||||
0x77, 0x77, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x78, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xA4, 0x77, 0x77, 0x77, 0x45, 0xF3, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x5F, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x38, 0x47, 0x75, 0xA2, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x4B,
|
||||
0x94, 0x77, 0x77, 0x77, 0x77, 0x79, 0xA3, 0x22, 0x22, 0x3F, 0x22, 0x22, 0x2A, 0x97, 0x77, 0x5F,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x03, 0xF5, 0x77,
|
||||
0x94, 0x77, 0x77, 0x77, 0x77, 0x79, 0xA2, 0x22, 0x22, 0x3F, 0x22, 0x22, 0x2A, 0x97, 0x77, 0x5F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x03, 0xF5, 0x77,
|
||||
0x77, 0x74, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x74, 0xA3, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x75, 0x8F, 0x20, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x5F, 0x10,
|
||||
0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x79, 0xA2, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x49,
|
||||
0x94, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x22, 0x9A, 0x92, 0x22, 0x3A, 0x57, 0x74, 0xA3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x01, 0x8A, 0x57, 0x77,
|
||||
0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x75, 0x8F, 0x20, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x5F, 0x20,
|
||||
0x00, 0x00, 0x00, 0x00, 0xF5, 0x77, 0x79, 0xA2, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x49,
|
||||
0x94, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x22, 0x3A, 0x92, 0x22, 0x3A, 0x57, 0x74, 0xA3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x01, 0x8A, 0x57, 0x77,
|
||||
0x77, 0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA4, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x00,
|
||||
0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x5A, 0xA1, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x5F, 0x10,
|
||||
0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x79, 0xA3, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x49,
|
||||
0x98, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0xA3, 0x32, 0x22, 0x3A, 0x57, 0x75, 0xF3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x77, 0xA0, 0x1F, 0x85, 0x77, 0x77,
|
||||
0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x5A, 0xA1, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x5F, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x79, 0xA2, 0x22, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x49,
|
||||
0x98, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0xA3, 0x32, 0x12, 0x3A, 0x57, 0x75, 0xF3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x77, 0xA0, 0x1F, 0x85, 0x77, 0x77,
|
||||
0x77, 0x75, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00,
|
||||
0x00, 0x00, 0x03, 0xA4, 0x77, 0x75, 0xF5, 0x10, 0x00, 0x03, 0xA4, 0x77, 0x77, 0x74, 0xA3, 0x00,
|
||||
0x00, 0x00, 0x03, 0xA4, 0x77, 0x75, 0xF5, 0x00, 0x00, 0x03, 0xA4, 0x77, 0x77, 0x74, 0xA3, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x75, 0xA3, 0x22, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x98, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x22, 0x22, 0x22, 0xF3, 0x29, 0x22, 0x98, 0x67, 0x75, 0xF2,
|
||||
0x98, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x22, 0x22, 0x22, 0xF2, 0x29, 0x22, 0x98, 0x67, 0x75, 0xF2,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x4A, 0x33, 0xF4, 0x47, 0x77, 0x77,
|
||||
0x77, 0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0A, 0x47, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x75, 0xF3, 0x00,
|
||||
0x77, 0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0A, 0x47, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x0F, 0x57, 0x77, 0x77, 0x75, 0xF3, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xA4, 0x78, 0x85, 0xA3, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x98, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x22, 0x22, 0xF2, 0x23, 0x32, 0xA5, 0x77, 0x75, 0xF1,
|
||||
0x98, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x22, 0x22, 0xF2, 0x13, 0x32, 0xA5, 0x77, 0x75, 0xF1,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xA4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x8F, 0x54, 0x77, 0x77, 0x77,
|
||||
0x77, 0x48, 0x50, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x4A, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1F, 0x57, 0x75, 0xF3, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x75, 0xF2, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0A, 0x75, 0x89, 0x96, 0xA3, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x67, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x23, 0xF2, 0x22, 0x33, 0x85, 0x77, 0x48, 0x50,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x77, 0x77, 0x77, 0x77,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0x57, 0x4F, 0x30, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x75, 0xF1, 0x00,
|
||||
0x00, 0x00, 0x2F, 0x57, 0x4F, 0x20, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x75, 0xF1, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1F, 0x55, 0xA3, 0x3A, 0x83, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x76,
|
||||
0x57, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x23, 0xA2, 0x22, 0x2A, 0x97, 0x77, 0x5F, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00, 0x00,
|
||||
0x57, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x23, 0xA2, 0x22, 0x2F, 0x97, 0x77, 0x5F, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2F, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3A, 0x54, 0xA2, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x44, 0x55, 0x55, 0x57, 0x80, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3F, 0x55, 0xA3, 0x2A, 0x83, 0x22, 0x28, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x00, 0x00, 0x00, 0x2F, 0x55, 0xA3, 0x2A, 0x83, 0x22, 0x28, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x57, 0x77, 0x77, 0x77, 0x79, 0xA2, 0x22, 0x22, 0x23, 0x52, 0x22, 0x29, 0x85, 0x77, 0x5F, 0x20,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFA, 0x57, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x10, 0x00, 0x00,
|
||||
0x00, 0x00, 0x58, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x87, 0x58, 0xFF, 0xFF, 0xFF, 0x20, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3A, 0x46, 0x89, 0x23, 0xA3, 0x22, 0x25, 0x87, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x47, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x22, 0x29, 0x92, 0x22, 0x23, 0xA5, 0x77, 0x5F, 0x10,
|
||||
0x47, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x22, 0x29, 0x32, 0x22, 0x23, 0xA5, 0x77, 0x5F, 0x10,
|
||||
0x00, 0x00, 0x00, 0x00, 0x23, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xA5, 0x77, 0x77, 0x77,
|
||||
0x77, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x85, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0xF5, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x01, 0xF5, 0xA4, 0x33, 0x33, 0x31, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0xF5, 0x77, 0x7A, 0x23, 0xA9, 0x22, 0x25, 0x86, 0x77, 0x77, 0x77, 0x77, 0x74,
|
||||
0x00, 0x01, 0xF5, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x01, 0xF5, 0xA4, 0x33, 0x33, 0x31, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xF5, 0x77, 0x7A, 0x23, 0xA9, 0x22, 0x25, 0x86, 0x77, 0x77, 0x77, 0x77, 0x74,
|
||||
0x47, 0x77, 0x77, 0x77, 0x58, 0x92, 0x22, 0x22, 0x29, 0xA2, 0x22, 0x23, 0xA5, 0x77, 0x85, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77,
|
||||
0x74, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0xF5, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x02, 0xFA, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0xF5, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFA, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x2F, 0x47, 0x77, 0x5A, 0x22, 0xA9, 0x22, 0x25, 0x86, 0x77, 0x77, 0x77, 0x77, 0x74,
|
||||
0x47, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0x25, 0xA3, 0x22, 0x23, 0xA5, 0x77, 0x5F, 0x10,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77,
|
||||
0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x05, 0x84, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x00, 0x00,
|
||||
0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x05, 0x84, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0xF5, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0xF5, 0x77, 0x77, 0x5A, 0x22, 0xA8, 0x22, 0x27, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74,
|
||||
0x47, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x23, 0x22, 0x2A, 0x33, 0x32, 0x23, 0x85, 0x77, 0x4A, 0x70,
|
||||
0x47, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x23, 0x22, 0x2A, 0x33, 0x31, 0x23, 0x85, 0x77, 0x4A, 0x70,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x57, 0x77,
|
||||
0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00, 0x00,
|
||||
0x75, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0xA5, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3F, 0x57, 0x77, 0x77, 0x5A, 0x22, 0x5A, 0x22, 0x28, 0x67, 0x77, 0x77, 0x77, 0x77, 0x74,
|
||||
0x57, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x29, 0x22, 0x2A, 0x32, 0x82, 0x29, 0x85, 0x77, 0x75, 0xF3,
|
||||
0x00, 0x3F, 0x57, 0x77, 0x77, 0x5A, 0x22, 0x5A, 0x21, 0x28, 0x67, 0x77, 0x77, 0x77, 0x77, 0x74,
|
||||
0x57, 0x77, 0x77, 0x77, 0x5A, 0x31, 0x29, 0x22, 0x2A, 0x32, 0x82, 0x29, 0x85, 0x77, 0x75, 0xF3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xA5, 0x77,
|
||||
0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x57, 0x77, 0x77, 0x77, 0x74, 0xA0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x1F, 0x55, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xF5, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3A, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x57, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x33, 0x22, 0x2A, 0x32, 0x27, 0x23, 0x86, 0x77, 0x77, 0x5F,
|
||||
0x75, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x57, 0x77, 0x77, 0x77, 0x74, 0xA0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0F, 0x55, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xF5, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x21, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x57, 0x77, 0x77, 0x77, 0x5A, 0x31, 0x33, 0x22, 0x2A, 0x31, 0x27, 0x23, 0x86, 0x77, 0x77, 0x5F,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF7, 0x47,
|
||||
0x4A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x4A, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x1F, 0x55, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0A, 0x44, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x67, 0x77, 0x77, 0x77, 0x5A, 0x32, 0xA3, 0x22, 0x2A, 0x32, 0x22, 0xA9, 0x87, 0x77, 0x77, 0x75,
|
||||
0x00, 0x2F, 0x55, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0A, 0x44, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x21, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0x67, 0x77, 0x77, 0x77, 0x5A, 0x22, 0xA3, 0x22, 0x2A, 0x32, 0x22, 0xA9, 0x87, 0x77, 0x77, 0x75,
|
||||
0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x47,
|
||||
0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3F, 0x58, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0xF5, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x32, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x76,
|
||||
0x01, 0xF5, 0x77, 0x77, 0x77, 0x5A, 0x21, 0x3F, 0x21, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x76,
|
||||
0x98, 0x77, 0x77, 0x77, 0x5A, 0x22, 0xF3, 0x22, 0x2A, 0x32, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x74,
|
||||
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF5,
|
||||
0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x10, 0x00, 0x00, 0x00,
|
||||
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF5,
|
||||
0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3A, 0x5A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x2F, 0x57, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x32, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x00, 0x2F, 0x57, 0x77, 0x77, 0x5A, 0x21, 0x3F, 0x31, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x98, 0x77, 0x77, 0x77, 0x5A, 0x22, 0xF3, 0x22, 0x2A, 0x32, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77,
|
||||
0x5A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77,
|
||||
0x5F, 0x10, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x10, 0x00, 0x00, 0x00,
|
||||
0x5F, 0x10, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xA7, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0xF5, 0x77, 0x77, 0x68, 0x22, 0x2F, 0x32, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x98, 0x77, 0x77, 0x77, 0x5A, 0x33, 0xF3, 0x22, 0x28, 0x32, 0x22, 0x23, 0x85, 0x77, 0x77, 0x77,
|
||||
0x00, 0x03, 0xF5, 0x77, 0x77, 0x68, 0x21, 0x2F, 0x31, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x98, 0x77, 0x77, 0x77, 0x5A, 0x23, 0xF3, 0x22, 0x28, 0x32, 0x22, 0x23, 0x85, 0x77, 0x77, 0x77,
|
||||
0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF5,
|
||||
0x85, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x74, 0xA3, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0xF5, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3A, 0x57, 0x76, 0x89, 0x22, 0x2F, 0x32, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x00, 0x00, 0x3A, 0x57, 0x76, 0x89, 0x21, 0x2F, 0x32, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89,
|
||||
0x94, 0x77, 0x77, 0x77, 0x5A, 0x33, 0xF3, 0x22, 0x23, 0x82, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5A, 0x53, 0x33, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF5,
|
||||
0x77, 0x5A, 0x53, 0x33, 0x33, 0x55, 0x55, 0x55, 0x53, 0x33, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF5,
|
||||
0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA5, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xF5, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x03, 0x83, 0x33, 0x33, 0x33, 0x35, 0x20, 0x00, 0x00,
|
||||
0x02, 0xF5, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x02, 0x83, 0x33, 0x33, 0x33, 0x35, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1F, 0x57, 0x75, 0x83, 0x22, 0x2F, 0x22, 0x58, 0x67, 0x77, 0x77, 0x77, 0x77, 0x49,
|
||||
0x94, 0x77, 0x77, 0x77, 0x5A, 0x33, 0xA3, 0x22, 0x23, 0xA3, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0x8A, 0xAA, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5,
|
||||
0xF3, 0x00, 0x00, 0x00, 0x00, 0x08, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00, 0x00,
|
||||
0xF2, 0x00, 0x00, 0x00, 0x00, 0x08, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xA5, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0xAA, 0xAA, 0xAA, 0xAA, 0xF0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x2F, 0x57, 0x75, 0xA3, 0x22, 0x2F, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49,
|
||||
0xB4, 0x77, 0x77, 0x77, 0x78, 0x99, 0xA3, 0x22, 0x23, 0xAA, 0x32, 0x23, 0xA9, 0x77, 0x77, 0x77,
|
||||
0xB4, 0x77, 0x77, 0x77, 0x78, 0x99, 0xA3, 0x22, 0x23, 0xAA, 0x32, 0x22, 0xA9, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x05, 0x85,
|
||||
0xF1, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00, 0x00, 0x00,
|
||||
0x05, 0x85, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x04, 0x44, 0x44, 0x44, 0x44, 0x47, 0x50, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0x57, 0x75, 0xA3, 0x22, 0x3F, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B,
|
||||
0xB4, 0x77, 0x77, 0x77, 0x77, 0x85, 0xA3, 0x22, 0x23, 0xF2, 0x33, 0x22, 0xA9, 0x77, 0x77, 0x77,
|
||||
0xB4, 0x77, 0x77, 0x77, 0x77, 0x85, 0xA3, 0x22, 0x23, 0xF2, 0x33, 0x12, 0xA9, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57,
|
||||
0xA0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x57, 0x77, 0x77, 0x77, 0x48, 0x50, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1F, 0x55, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x5A, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x58, 0x47, 0x79, 0xA3, 0x22, 0x3F, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B,
|
||||
0x00, 0x00, 0x58, 0x47, 0x79, 0xA2, 0x22, 0x3F, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B,
|
||||
0xB6, 0x77, 0x77, 0x77, 0x77, 0x75, 0x83, 0x22, 0x22, 0xF2, 0x23, 0x32, 0xA5, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x5A,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x5A, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -499,55 +499,55 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x00, 0x01, 0xF5, 0x77, 0x79, 0xA2, 0x22, 0x3A, 0x23, 0x87, 0x77, 0x77, 0x77, 0x77, 0x77, 0x6B,
|
||||
0xB9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x85, 0x22, 0x22, 0xF3, 0x22, 0x38, 0x97, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0xA8, 0x77, 0x77, 0x77, 0x77, 0x77, 0x48, 0x50, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x5F,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x58, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x58, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3F, 0x58, 0x50, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x5F, 0x10, 0x00, 0x00,
|
||||
0x00, 0x02, 0xF5, 0x77, 0x75, 0xA2, 0x22, 0x39, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9B,
|
||||
0x00, 0x02, 0xF5, 0x77, 0x75, 0xA2, 0x22, 0x39, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9B,
|
||||
0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x22, 0xA3, 0x22, 0x23, 0xA5, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5F, 0x35, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x5F,
|
||||
0x10, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3A, 0x5A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x58, 0x47, 0x77, 0x77, 0x77, 0x4A, 0x00, 0x00, 0x00,
|
||||
0x3A, 0x5A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x38, 0x47, 0x77, 0x77, 0x77, 0x4A, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0xF5, 0x77, 0x68, 0x92, 0x22, 0x59, 0x38, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E,
|
||||
0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x22, 0x22, 0x99, 0x22, 0x22, 0x3A, 0x57, 0x77, 0x77,
|
||||
0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x22, 0x22, 0x93, 0x22, 0x22, 0x3A, 0x57, 0x77, 0x77,
|
||||
0x75, 0xF3, 0x1A, 0x47, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0xA5, 0x5A,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0xF5, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x74, 0xA3, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xF5, 0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0xF5, 0x77, 0x77, 0x77, 0x74, 0xA3, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0xA4, 0x77, 0x5A, 0x32, 0x22, 0xA5, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E,
|
||||
0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x3A, 0x22, 0x22, 0x2A, 0x97, 0x77, 0x77,
|
||||
0x5F, 0x30, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x01, 0xF5, 0xA3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x74, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x74, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xF5, 0x5F, 0x10, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x00, 0x00,
|
||||
0x00, 0x1A, 0x57, 0x77, 0x9A, 0x32, 0x22, 0xA9, 0x58, 0x87, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE,
|
||||
0xDB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x2F, 0x22, 0x22, 0x28, 0x67, 0x77, 0x75,
|
||||
0x00, 0x0A, 0x57, 0x77, 0x9A, 0x22, 0x22, 0xA9, 0x58, 0x87, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE,
|
||||
0xDB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x2F, 0x21, 0x22, 0x28, 0x67, 0x77, 0x75,
|
||||
0xF3, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x85, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0xF3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xA4, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
0xF5, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00, 0x00,
|
||||
0x00, 0x1F, 0x57, 0x77, 0x5A, 0x22, 0x23, 0xA9, 0xA3, 0x38, 0x67, 0x77, 0x77, 0x77, 0x74, 0xBD,
|
||||
0xF5, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00,
|
||||
0x00, 0x2F, 0x57, 0x77, 0x5A, 0x22, 0x23, 0xA9, 0xA3, 0x38, 0x67, 0x77, 0x77, 0x77, 0x74, 0xBD,
|
||||
0xDB, 0x98, 0x77, 0x77, 0x77, 0x77, 0x68, 0x92, 0x22, 0x2A, 0x32, 0x22, 0x23, 0xA5, 0x77, 0x5F,
|
||||
0x30, 0x00, 0x3A, 0x47, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA5, 0xF2,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
0xA5, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA4, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
0xA5, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA4, 0x77, 0x77, 0x77, 0x75, 0xF0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3F, 0x56, 0x76, 0x89, 0x22, 0x23, 0xA5, 0x52, 0x98, 0x77, 0x77, 0x77, 0x77, 0x89, 0xBD,
|
||||
0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x23, 0xA9, 0x22, 0x22, 0xA9, 0x75, 0xF3,
|
||||
0x00, 0x00, 0xA7, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x75, 0xF1,
|
||||
0x00, 0x00, 0xA7, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x75, 0xF1,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x0A,
|
||||
0x45, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x47, 0x77, 0x77, 0x77, 0x4A, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3A, 0x58, 0x85, 0xA3, 0x22, 0x28, 0x9A, 0x32, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED,
|
||||
0xDE, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x23, 0xA3, 0x93, 0x22, 0xA6, 0x5F, 0x30,
|
||||
0x00, 0x01, 0xF5, 0x55, 0x55, 0x55, 0x55, 0x55, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x58, 0x50,
|
||||
0xDE, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x23, 0xA3, 0x92, 0x22, 0xA6, 0x5F, 0x30,
|
||||
0x00, 0x01, 0xF5, 0x55, 0x55, 0x55, 0x55, 0x55, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x58, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3A, 0x55, 0x55, 0x55, 0x55, 0x54, 0x50, 0x00, 0x00, 0x00, 0x00, 0x1F,
|
||||
0x55, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x55, 0x55, 0x55, 0x55, 0x5F, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0xA5, 0x83, 0x95, 0xA2, 0x22, 0x2A, 0x57, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xED,
|
||||
0xDD, 0xB5, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0xF3, 0x23, 0x32, 0x54, 0xF3, 0x00,
|
||||
0x00, 0x03, 0xFA, 0xFF, 0xFF, 0xFF, 0xFA, 0xAA, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x5F, 0x30,
|
||||
0xDD, 0xB5, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0xF2, 0x23, 0x32, 0x54, 0xF3, 0x00,
|
||||
0x00, 0x03, 0xFA, 0xFF, 0xFF, 0xFF, 0xFA, 0xAA, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x5F, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0xAA, 0xAA, 0xAF, 0xFA, 0xAF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3F,
|
||||
0x54, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xAF, 0xFF, 0xFF, 0xFA, 0xAF, 0x10, 0x00, 0x00, 0x00,
|
||||
0x01, 0xF5, 0xA3, 0x3A, 0x92, 0x22, 0x3A, 0xA2, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x5B, 0xDD,
|
||||
0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x22, 0x39, 0x22, 0x3F, 0x5F, 0x30, 0x00,
|
||||
0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x5F, 0x20,
|
||||
0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x22, 0x39, 0x21, 0x3F, 0x5F, 0x30, 0x00,
|
||||
0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x20, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x5F, 0x20,
|
||||
0x00, 0x00, 0x00, 0x00, 0x13, 0x33, 0x33, 0x33, 0x33, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A,
|
||||
0x5A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x31, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xF9, 0xA2, 0x3F, 0x32, 0x22, 0x9A, 0x32, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x78, 0x9C, 0xDD,
|
||||
0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x22, 0x3F, 0x22, 0x35, 0xF3, 0x00, 0x00,
|
||||
0x03, 0xF9, 0xA2, 0x3F, 0x32, 0x22, 0x9A, 0x31, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x78, 0x9C, 0xDD,
|
||||
0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x22, 0x22, 0x2F, 0x22, 0x35, 0xF3, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x5F, 0x10,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4,
|
||||
0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -556,15 +556,15 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x85, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA4,
|
||||
0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 0x74, 0x82, 0x39, 0x22, 0x23, 0x92, 0x22, 0x58, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD,
|
||||
0x07, 0x74, 0x82, 0x39, 0x22, 0x23, 0x32, 0x22, 0x58, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD,
|
||||
0xDD, 0xDB, 0x98, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x23, 0xA4, 0xF3, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA5, 0x85, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x55,
|
||||
0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2F, 0x5A, 0x32, 0x33, 0x22, 0x23, 0x22, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x89, 0xBD, 0xDD,
|
||||
0x2F, 0x5A, 0x32, 0x32, 0x22, 0x23, 0x22, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x89, 0xBD, 0xDD,
|
||||
0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x29, 0x5F, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x5F, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0xA5, 0x5A,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xA5, 0x5A,
|
||||
0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xF5, 0x9A, 0x22, 0x32, 0x22, 0x22, 0x22, 0x29, 0x85, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD,
|
||||
0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x88, 0x22, 0x54, 0xF3, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -573,7 +573,7 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
|
||||
0x57, 0x5A, 0x22, 0x22, 0x22, 0x22, 0x22, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x33, 0x8F, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x4A, 0x57, 0x77, 0x77, 0x5F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x57, 0x77, 0x77, 0x5F,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0x5F, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5,
|
||||
0x75, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x98, 0x67, 0x77, 0x77, 0x77, 0x78, 0x9C, 0xDD, 0xDD,
|
||||
@@ -584,147 +584,147 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x79, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDB, 0x68, 0x77, 0x77, 0x77, 0x77, 0x78, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF8, 0x57, 0x77, 0x77, 0x77, 0x75,
|
||||
0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x54, 0x4F, 0x30, 0x00,
|
||||
0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x54, 0x4F, 0x30, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77,
|
||||
0x58, 0x32, 0x22, 0x22, 0x22, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x86, 0xBD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x75, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x44, 0xF2, 0x00, 0x00,
|
||||
0x5F, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x44, 0xF2, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77,
|
||||
0x9A, 0x22, 0x22, 0x23, 0x32, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD,
|
||||
0x9A, 0x22, 0x22, 0x12, 0x32, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x4A, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x5F, 0x30, 0x00, 0x00,
|
||||
0x75, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x55, 0x9A, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x88, 0x67,
|
||||
0x89, 0x22, 0x22, 0x2A, 0x22, 0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x78, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
|
||||
0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5F, 0x54, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x7A, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x58, 0xA5, 0x77, 0x77, 0x35, 0x55, 0x55,
|
||||
0x77, 0x7A, 0x55, 0x94, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x58, 0xA5, 0x5A, 0xAA, 0x4A, 0xA8, 0x55,
|
||||
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xAA, 0x58, 0x93, 0x89,
|
||||
0xA2, 0x22, 0x22, 0xA2, 0x22, 0x28, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x8A, 0xAA, 0xA8, 0x88, 0x88, 0xAA, 0xA8,
|
||||
0x88, 0x87, 0x8A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x74, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87, 0x57, 0x77, 0x77, 0xA8, 0x88, 0x88,
|
||||
0x77, 0x74, 0x85, 0xA3, 0x22, 0x22, 0x23, 0xA7, 0x88, 0x87, 0x58, 0xA3, 0x22, 0x22, 0x23, 0xA6,
|
||||
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x45, 0x5A, 0x33, 0xA7,
|
||||
0x92, 0x22, 0x23, 0x32, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDE, 0x98, 0x77, 0x77, 0x77, 0x77, 0x74, 0x44, 0x5A, 0xAA, 0xAA, 0x45, 0x8A,
|
||||
0xAA, 0xAA, 0x85, 0x44, 0x44, 0x44, 0x44, 0x44, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x47, 0x77, 0x77, 0x77, 0x74, 0x44, 0x44,
|
||||
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x77, 0x5A, 0x23, 0x8A,
|
||||
0x77, 0x77, 0x5A, 0x32, 0x11, 0x11, 0x22, 0x2A, 0x56, 0x45, 0x8A, 0x22, 0x11, 0x11, 0x22, 0x3A,
|
||||
0x54, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x77, 0x5A, 0x23, 0x8A,
|
||||
0x22, 0x22, 0x3F, 0x22, 0x23, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x89, 0xED, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x72, 0x22, 0x22, 0xAA, 0x32,
|
||||
0x22, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x28, 0xA3,
|
||||
0x77, 0x79, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA5, 0x75, 0xA2, 0x12, 0x22, 0x22, 0x22, 0x2A,
|
||||
0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x28, 0xA3,
|
||||
0x22, 0x22, 0xF3, 0x22, 0x2A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x2A, 0x32,
|
||||
0x22, 0x22, 0x28, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x2A, 0xA2,
|
||||
0x22, 0x2A, 0x32, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x12, 0x21, 0x2A, 0x32,
|
||||
0x12, 0x21, 0x28, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x5A, 0x32, 0x22, 0xAA, 0xAF, 0x32, 0x22, 0xA5, 0x5A, 0x32, 0x22, 0xFA, 0xAF, 0x21, 0x29,
|
||||
0x86, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x2A, 0xA2,
|
||||
0x22, 0x2A, 0x32, 0x12, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x22, 0x23, 0xF3,
|
||||
0x22, 0x22, 0x22, 0x88, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x3F, 0x32,
|
||||
0x22, 0x3A, 0x22, 0x2A, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x77, 0x58, 0x52, 0x22, 0x22, 0x3A,
|
||||
0x76, 0x89, 0x22, 0x2A, 0x89, 0x99, 0xA2, 0x22, 0x98, 0x9A, 0x32, 0x23, 0xF8, 0x58, 0x32, 0x38,
|
||||
0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x31, 0x3F, 0x22,
|
||||
0x22, 0x3A, 0x21, 0x2A, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x77, 0x58, 0x52, 0x22, 0x22, 0x3F,
|
||||
0x32, 0x22, 0x22, 0x25, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x79, 0xA3, 0x22, 0x3A, 0x97, 0x75, 0xA2, 0x12, 0x3A, 0x9A, 0x32, 0x22, 0x23, 0x9A, 0xFA, 0xA7,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0xA3, 0x22,
|
||||
0x23, 0xF2, 0x22, 0x88, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x23, 0xF2, 0x12, 0x88, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB5, 0x87, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x23,
|
||||
0xA8, 0x99, 0x33, 0x35, 0xA8, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0xA2, 0x22,
|
||||
0x3F, 0x32, 0x28, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xA8, 0x93, 0x33, 0x35, 0xA8, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x75, 0xA2, 0x22, 0x98, 0x77, 0x75, 0xA2, 0x12, 0x98, 0x58, 0x32, 0x12, 0x12, 0x22, 0x29, 0x85,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x12, 0xA2, 0x22,
|
||||
0x3F, 0x31, 0x28, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x22, 0x22,
|
||||
0x9A, 0x32, 0x22, 0x22, 0x23, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x29, 0x22, 0x22,
|
||||
0x68, 0x92, 0x12, 0xA5, 0x77, 0x74, 0x72, 0x22, 0xA5, 0x79, 0x83, 0x22, 0x22, 0x12, 0x12, 0x98,
|
||||
0x67, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x29, 0x22, 0x22,
|
||||
0xF3, 0x22, 0xA8, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x79, 0xA2, 0x22, 0x22,
|
||||
0x28, 0xA3, 0x22, 0x22, 0x22, 0x3A, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x67, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x22, 0x2F,
|
||||
0x28, 0xA2, 0x22, 0x22, 0x22, 0x3A, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x5A, 0x32, 0x12, 0xA9, 0x77, 0x5A, 0x32, 0x22, 0xA9, 0x5A, 0xAA, 0xAA, 0x93, 0x22, 0x22, 0x3A,
|
||||
0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x67, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x22, 0x2F,
|
||||
0x92, 0x2A, 0x58, 0x87, 0x77, 0x77, 0x77, 0x77, 0x77, 0x89, 0xCD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x76, 0x5A, 0x22, 0x22,
|
||||
0x22, 0xAF, 0x32, 0x22, 0x22, 0x23, 0xAA, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x87, 0x77, 0x57, 0xA2, 0x22, 0x22, 0x22, 0xA9,
|
||||
0x23, 0xA9, 0xA9, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x22, 0xAF, 0x32, 0x22, 0x22, 0x22, 0xAA, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x58, 0x32, 0x22, 0xA9, 0x55, 0x9F, 0x22, 0x23, 0x85, 0x83, 0x33, 0x99, 0x88, 0xF2, 0x22, 0x3A,
|
||||
0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x87, 0x77, 0x57, 0xA2, 0x22, 0x22, 0x22, 0xA9,
|
||||
0x23, 0xF9, 0xA3, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x58, 0x82, 0x22,
|
||||
0x22, 0x28, 0xA3, 0x22, 0x22, 0x22, 0x23, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x76, 0x5A, 0x35, 0x77, 0x9A, 0x22, 0x22, 0x22, 0x28, 0xA3,
|
||||
0x22, 0x28, 0xA3, 0x22, 0x22, 0x21, 0x23, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x74, 0x72, 0x22, 0x3F, 0xAA, 0xF3, 0x22, 0x2A, 0x94, 0xA2, 0x12, 0xAA, 0xAA, 0xF2, 0x22, 0x38,
|
||||
0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x76, 0x5A, 0x35, 0x77, 0x9A, 0x22, 0x22, 0x22, 0x28, 0xA3,
|
||||
0x3A, 0x9A, 0x33, 0x87, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA9, 0x22,
|
||||
0x22, 0x22, 0x7A, 0x32, 0x22, 0x22, 0x23, 0xAA, 0x65, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x57, 0xA3, 0x3A, 0x59, 0xA3, 0x22, 0x22, 0x22, 0x9A, 0x98,
|
||||
0xA9, 0xA3, 0x29, 0x85, 0x77, 0x77, 0x77, 0x77, 0x89, 0xCD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0x22, 0x22, 0x7A, 0x32, 0x22, 0x22, 0x23, 0xFA, 0x65, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x79, 0xA2, 0x12, 0x23, 0x33, 0x22, 0x12, 0xA4, 0x75, 0xA2, 0x22, 0x23, 0x33, 0x22, 0x12, 0xA9,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x57, 0xA2, 0x3A, 0x59, 0xA3, 0x22, 0x22, 0x22, 0x9A, 0x98,
|
||||
0xA9, 0xF3, 0x29, 0x85, 0x77, 0x77, 0x77, 0x77, 0x89, 0xCD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32,
|
||||
0x22, 0x22, 0x29, 0xA7, 0x53, 0x33, 0x33, 0x33, 0x8A, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0x23, 0xA7, 0x53, 0x33, 0x33, 0x33, 0x8A, 0x55, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x76, 0x89, 0x21, 0x22, 0x22, 0x12, 0x28, 0x85, 0x75, 0xA3, 0x22, 0x22, 0x22, 0x12, 0x2A, 0x86,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x79, 0xAA, 0x22, 0xA7, 0x9A, 0x32, 0x22, 0x22, 0x28, 0xA5, 0x89,
|
||||
0x7A, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3,
|
||||
0x22, 0x22, 0x22, 0x3A, 0xA2, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0x22, 0x3A, 0xA2, 0x12, 0x22, 0x22, 0x23, 0xAA, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x58, 0x92, 0x22, 0x22, 0x23, 0xA8, 0x57, 0x77, 0x5A, 0x32, 0x22, 0x22, 0x23, 0xA8, 0x57,
|
||||
0x77, 0x77, 0x77, 0x77, 0x75, 0x5A, 0x32, 0x29, 0xA9, 0xA3, 0x22, 0x22, 0x22, 0xA8, 0x56, 0x9A,
|
||||
0x82, 0x23, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5A,
|
||||
0x92, 0x22, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x23, 0xA7, 0x57, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x32, 0x22, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x23, 0xA7, 0x57, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x75, 0x8A, 0x83, 0x39, 0xAA, 0x55, 0x77, 0x77, 0x75, 0xAA, 0x93, 0x39, 0xAA, 0x45, 0x77,
|
||||
0x77, 0x77, 0x77, 0x75, 0x9A, 0xA3, 0x22, 0x3A, 0x9A, 0x32, 0x22, 0x22, 0x2A, 0x85, 0x98, 0xA3,
|
||||
0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75,
|
||||
0xA9, 0x22, 0x22, 0x22, 0x2A, 0xF3, 0x22, 0x22, 0x22, 0x22, 0x25, 0x87, 0x55, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x98, 0xA3, 0x22, 0x23, 0xA9, 0xA2, 0x22, 0x22, 0x23, 0xA5, 0x97, 0xA9, 0x22,
|
||||
0xA9, 0x22, 0x22, 0x22, 0x2A, 0xF3, 0x22, 0x22, 0x22, 0x12, 0x25, 0x87, 0x55, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x69, 0x48, 0xA8, 0x55, 0x77, 0x77, 0x77, 0x77, 0x55, 0x8A, 0xA8, 0x55, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x98, 0xA3, 0x22, 0x23, 0xA9, 0xA2, 0x22, 0x22, 0x23, 0xA5, 0x97, 0xA9, 0x21,
|
||||
0x29, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x78, 0x9B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDB, 0x58, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x5A, 0x82, 0x22, 0x22, 0x22, 0x3F, 0xA2, 0x22, 0x22, 0x23, 0x35, 0x57, 0xAA, 0x69, 0x57, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x55, 0xA3, 0x22, 0x22, 0x2A, 0x8A, 0x22, 0x22, 0x22, 0x3A, 0x98, 0xA8, 0x32, 0x22,
|
||||
0x77, 0x77, 0x77, 0x75, 0x56, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x65, 0x56, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x55, 0xA3, 0x22, 0x22, 0x2A, 0x8A, 0x22, 0x22, 0x22, 0x3A, 0x98, 0xA8, 0x32, 0x12,
|
||||
0x8A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x85, 0xBD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x75, 0x8A, 0x22, 0x22, 0x22, 0x23, 0xAF, 0x33, 0x33, 0x32, 0x22, 0x22, 0x23, 0x8A, 0xA7, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x76,
|
||||
0x55, 0x85, 0x8A, 0x32, 0x22, 0x22, 0xAA, 0x32, 0x22, 0x22, 0x29, 0xA8, 0xA9, 0x32, 0x22, 0x3A,
|
||||
0x55, 0x85, 0x8A, 0x22, 0x22, 0x22, 0xAA, 0x32, 0x22, 0x22, 0x29, 0xA8, 0xA9, 0x32, 0x12, 0x2A,
|
||||
0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x4B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x55, 0xA3, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x92, 0x22, 0x22, 0x22, 0x22, 0x22, 0x39, 0x55,
|
||||
0x77, 0x55, 0xA3, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x92, 0x11, 0x22, 0x22, 0x22, 0x22, 0x39, 0x55,
|
||||
0x59, 0x55, 0x67, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x65, 0x58,
|
||||
0xAA, 0x9A, 0x92, 0x22, 0x22, 0x2A, 0xA3, 0x22, 0x22, 0x23, 0xFA, 0x83, 0x22, 0x22, 0x23, 0xA5,
|
||||
0xAA, 0x9A, 0x92, 0x22, 0x22, 0x2A, 0xA3, 0x22, 0x22, 0x23, 0xFA, 0x83, 0x22, 0x21, 0x23, 0xA5,
|
||||
0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x9A, 0x32, 0x22, 0x22, 0x22, 0x29, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA,
|
||||
0x77, 0x77, 0x9A, 0x32, 0x22, 0x22, 0x22, 0x29, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x11, 0x23, 0xFA,
|
||||
0xAA, 0xAA, 0x85, 0x77, 0x76, 0x66, 0x77, 0x77, 0x77, 0x77, 0x65, 0x55, 0x57, 0x55, 0x8A, 0xA5,
|
||||
0x32, 0x39, 0x22, 0x22, 0x23, 0xF9, 0x22, 0x22, 0x22, 0x3A, 0x32, 0x22, 0x22, 0x22, 0x8A, 0x97,
|
||||
0x32, 0x39, 0x22, 0x22, 0x23, 0xF9, 0x22, 0x22, 0x22, 0x3A, 0x32, 0x22, 0x21, 0x22, 0x8A, 0x97,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x79, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x37, 0xFA, 0x32, 0x22, 0x22, 0x23, 0x33, 0x22,
|
||||
0x77, 0x77, 0x79, 0xAA, 0x22, 0x22, 0x22, 0x22, 0x37, 0xFA, 0x32, 0x22, 0x22, 0x23, 0x33, 0x22,
|
||||
0x22, 0x33, 0x7A, 0x55, 0x88, 0x88, 0x75, 0x57, 0x75, 0x55, 0x88, 0xAA, 0xA5, 0x8A, 0x93, 0x22,
|
||||
0x22, 0x92, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x75, 0x77,
|
||||
0x12, 0x92, 0x12, 0x23, 0xAF, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0x75, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x55, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x39, 0xFA, 0x33, 0x33, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x9A, 0x59, 0x99, 0x7A, 0xA9, 0x7A, 0xAA, 0x93, 0x33, 0x3A, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x9A, 0x59, 0x99, 0x7A, 0xA9, 0x7A, 0xAA, 0x93, 0x33, 0x3A, 0x32, 0x22, 0x21,
|
||||
0x25, 0x22, 0x23, 0xAF, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA9, 0x67, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x9A, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x23, 0xFF, 0xA3, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x23, 0x32, 0x22, 0x22, 0x22, 0x23, 0xA3, 0x22, 0x22, 0x22, 0x33, 0x22, 0x22, 0x22,
|
||||
0x82, 0x23, 0xFA, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA5, 0x57, 0x77, 0x77,
|
||||
0x82, 0x23, 0xFF, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA5, 0x57, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x75, 0x5A, 0x32, 0x22, 0x22, 0x22, 0x22, 0x23, 0x5A, 0xF3, 0x32, 0x22,
|
||||
0x22, 0x29, 0x32, 0x22, 0x22, 0x22, 0x22, 0x33, 0x22, 0x22, 0x22, 0x29, 0x32, 0x22, 0x22, 0x29,
|
||||
0x88, 0xF8, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x3A, 0xA5, 0x57, 0x77, 0x77, 0x77,
|
||||
0x22, 0x23, 0x32, 0x22, 0x22, 0x22, 0x12, 0x33, 0x22, 0x22, 0x22, 0x29, 0x32, 0x22, 0x22, 0x29,
|
||||
0x88, 0xF8, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xA5, 0x57, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x47, 0x77, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x79, 0x8A, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3A, 0xFF, 0x93,
|
||||
0x33, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x39, 0x22, 0x22, 0x22, 0x22, 0x92, 0x22, 0x22, 0x23, 0xAA,
|
||||
0x33, 0xA2, 0x21, 0x22, 0x22, 0x22, 0x39, 0x22, 0x22, 0x22, 0x22, 0x92, 0x12, 0x22, 0x23, 0xAA,
|
||||
0x83, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA, 0xA5, 0x57, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0x94, 0x77, 0x77,
|
||||
@@ -734,13 +734,13 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0x77, 0x77, 0x49, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x48, 0x77,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x33, 0x9A, 0xFF, 0xFF, 0xAA, 0xAA, 0x33, 0x33, 0x33, 0x3A, 0xFA, 0xAF, 0xA9, 0x32, 0x22, 0x22,
|
||||
0x23, 0x9A, 0xFF, 0xFF, 0xAA, 0xAA, 0x33, 0x33, 0x33, 0x3A, 0xFA, 0xAF, 0xA9, 0x32, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x22, 0x23, 0x38, 0xAA, 0xA5, 0x95, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x77, 0x84, 0xBE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xB5, 0x87,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0xAA, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x22, 0x33, 0x33, 0x99, 0xAA, 0xAA, 0x89, 0x33, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x22, 0x22, 0x33, 0x9A, 0xAA, 0xA6, 0x55, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x22, 0x22, 0x22, 0x33, 0x33, 0x39, 0xAA, 0xAA, 0x89, 0x33, 0x33, 0x22, 0x22, 0x22, 0x12, 0x22,
|
||||
0x22, 0x22, 0x33, 0x3A, 0xAA, 0xA6, 0x55, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0x94,
|
||||
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x59, 0xAA, 0x73, 0x22, 0x22, 0x22, 0x22,
|
||||
@@ -765,11 +765,11 @@ const uint8_t boot_logo_bitmap[12800] = {
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xC9, 0x48, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0x96,
|
||||
0xAA, 0xAA, 0x93, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x33,
|
||||
0x9A, 0xAA, 0xA8, 0x55, 0x67, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x84, 0x9C, 0xDD,
|
||||
0x3A, 0xAA, 0xA8, 0x55, 0x67, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x84, 0x9C, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDE, 0xB9, 0x48, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
|
||||
0x55, 0x95, 0x8A, 0xAA, 0xAA, 0xA5, 0x93, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5A, 0xAA, 0xAA,
|
||||
0x55, 0x95, 0x8A, 0xAA, 0xAA, 0xA5, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5A, 0xAA, 0xAA,
|
||||
0x85, 0x95, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x84, 0x9B, 0xED, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
|
||||
@@ -850,13 +850,13 @@ const char* const boot_logo_ascii[] = {
|
||||
"...=--- :%@%****-. +@%: :%@+.%@*=*#@*: :-: ..",
|
||||
"...:--- .:+****@@+ %@* . *@%.=@%***%%*..-:. ..",
|
||||
" ...=:-. .. -%%--@%: :%@* %%+ *%# .-....",
|
||||
" .. --:.-#=:-:#@#.%%#:::*@%.=@%=::=@@- --:...",
|
||||
" .. --:.-#=:-:#@#.%@#:::*@%.=@%=::=@@- --:...",
|
||||
" ...-=#@@@@@@%+..+%@@@@%+-=%@@@@@@#-.==-. .",
|
||||
" . .-=---::-. . :-:::. :----::::..---. .",
|
||||
" .. :--::. .... . :-::. ..",
|
||||
" .. .:----:... . .. ...:::--:.....",
|
||||
" . .-=---::-. . :=-=::.--:--::::..---. .",
|
||||
" .. :--::. ... -:.- :-- :-::. ..",
|
||||
" .. .:----:... .::. ......:::--:.....",
|
||||
" .. .:-------:::.::.:::-===-:....",
|
||||
" .. ..::------=-==--:.....",
|
||||
" .. ..::---=--====--:.....",
|
||||
" .. ............ .",
|
||||
NULL
|
||||
};
|
||||
@@ -0,0 +1,323 @@
|
||||
// C++ Kernel Analytics Engine for SUB-OS.
|
||||
//
|
||||
// A freestanding C++17 telemetry pipeline that demonstrates templates, virtual
|
||||
// dispatch (the Observer pattern) and RAII inside the kernel while doing real
|
||||
// work: it samples live system metrics (CPU, memory, heap, network) into rolling
|
||||
// per-channel time-series and computes windowed statistics (min / max / mean /
|
||||
// standard deviation) using pure integer arithmetic -- the kernel is built with
|
||||
// -mno-sse, so no floating point is available.
|
||||
//
|
||||
// The GUI "Analytics" app and the `cppstat` shell applet both render the series
|
||||
// this engine maintains.
|
||||
|
||||
#include "cpp_containers.hpp"
|
||||
#include <kernel/cpp_analytics.h>
|
||||
|
||||
extern "C" {
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/metrics.h>
|
||||
#include <mm/pmm.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
void cpp_call_global_constructors(void);
|
||||
}
|
||||
|
||||
namespace kernel {
|
||||
|
||||
// Integer square root (Newton's method) -- for standard deviation without FP.
|
||||
static uint32_t isqrt64(uint64_t n) {
|
||||
if (n == 0) return 0;
|
||||
uint64_t x = n, y = (x + 1) / 2;
|
||||
while (y < x) { x = y; y = (x + n / x) / 2; }
|
||||
return static_cast<uint32_t>(x);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// TimeSeries<N>: a fixed-capacity ring of samples plus on-demand windowed stats.
|
||||
// -----------------------------------------------------------------------------
|
||||
template<size_t N>
|
||||
class TimeSeries {
|
||||
private:
|
||||
uint32_t m_ring[N];
|
||||
size_t m_head = 0; // index of the next write
|
||||
size_t m_count = 0; // number of valid samples (<= N)
|
||||
uint64_t m_total = 0; // all-time sample count (for the running average)
|
||||
uint64_t m_sum_all = 0;
|
||||
|
||||
public:
|
||||
void push(uint32_t v) {
|
||||
m_ring[m_head] = v;
|
||||
m_head = (m_head + 1) % N;
|
||||
if (m_count < N) m_count++;
|
||||
m_total++;
|
||||
m_sum_all += v;
|
||||
}
|
||||
|
||||
size_t count() const { return m_count; }
|
||||
size_t capacity() const { return N; }
|
||||
uint64_t total_samples() const { return m_total; }
|
||||
|
||||
// Copy the window oldest-first into out[], returning how many were written.
|
||||
int copy(uint32_t* out, int max) const {
|
||||
int n = static_cast<int>(m_count < static_cast<size_t>(max) ? m_count : static_cast<size_t>(max));
|
||||
// The oldest element sits at (m_head - m_count) modulo N.
|
||||
size_t start = (m_head + N - m_count) % N;
|
||||
for (int i = 0; i < n; i++) out[i] = m_ring[(start + i) % N];
|
||||
return n;
|
||||
}
|
||||
|
||||
uint32_t last() const {
|
||||
if (m_count == 0) return 0;
|
||||
return m_ring[(m_head + N - 1) % N];
|
||||
}
|
||||
|
||||
void stats(uint32_t& mn, uint32_t& mx, uint32_t& avg, uint32_t& stddev) const {
|
||||
if (m_count == 0) { mn = mx = avg = stddev = 0; return; }
|
||||
uint32_t lo = 0xFFFFFFFFu, hi = 0;
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < m_count; i++) {
|
||||
uint32_t v = m_ring[i];
|
||||
if (v < lo) lo = v;
|
||||
if (v > hi) hi = v;
|
||||
sum += v;
|
||||
}
|
||||
uint32_t mean = static_cast<uint32_t>(sum / m_count);
|
||||
// Windowed variance = mean of squared deviations (integer).
|
||||
uint64_t acc = 0;
|
||||
for (size_t i = 0; i < m_count; i++) {
|
||||
int64_t d = static_cast<int64_t>(m_ring[i]) - static_cast<int64_t>(mean);
|
||||
acc += static_cast<uint64_t>(d * d);
|
||||
}
|
||||
mn = lo; mx = hi; avg = mean;
|
||||
stddev = isqrt64(acc / m_count);
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Observer pattern: subscribers are notified of every sample on every channel.
|
||||
// -----------------------------------------------------------------------------
|
||||
class IAnalyticsObserver {
|
||||
public:
|
||||
virtual ~IAnalyticsObserver() = default;
|
||||
virtual void on_sample(int channel, uint32_t value) = 0;
|
||||
};
|
||||
|
||||
// A concrete observer that counts threshold breaches per channel -- a small but
|
||||
// real use of virtual dispatch (e.g. "CPU load crossed 90%").
|
||||
class ThresholdAlarmObserver : public IAnalyticsObserver {
|
||||
private:
|
||||
uint32_t m_threshold[CPP_ANALYTICS_CHANNELS];
|
||||
uint64_t m_breaches[CPP_ANALYTICS_CHANNELS];
|
||||
|
||||
public:
|
||||
ThresholdAlarmObserver() {
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) { m_threshold[i] = 90; m_breaches[i] = 0; }
|
||||
}
|
||||
void set_threshold(int ch, uint32_t t) {
|
||||
if (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) m_threshold[ch] = t;
|
||||
}
|
||||
void on_sample(int channel, uint32_t value) override {
|
||||
if (channel < 0 || channel >= CPP_ANALYTICS_CHANNELS) return;
|
||||
if (value >= m_threshold[channel]) m_breaches[channel]++;
|
||||
}
|
||||
uint64_t breaches(int ch) const {
|
||||
return (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) ? m_breaches[ch] : 0;
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// A single metric channel: name, unit, graph scale and its time-series.
|
||||
// -----------------------------------------------------------------------------
|
||||
struct Channel {
|
||||
const char* name;
|
||||
const char* unit;
|
||||
uint32_t scale; // graph ceiling (0 => auto-scale to max)
|
||||
TimeSeries<CPP_ANALYTICS_HISTORY> series;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The engine singleton.
|
||||
// -----------------------------------------------------------------------------
|
||||
class AnalyticsEngine {
|
||||
private:
|
||||
Channel m_ch[CPP_ANALYTICS_CHANNELS];
|
||||
Vector<IAnalyticsObserver*> m_observers; // non-owning
|
||||
ThresholdAlarmObserver* m_alarm = nullptr;
|
||||
uint64_t m_samples = 0;
|
||||
|
||||
// Previous cumulative packet counter, to derive a per-interval rate.
|
||||
uint64_t m_prev_net_pkts = 0;
|
||||
bool m_have_prev = false;
|
||||
|
||||
AnalyticsEngine() {
|
||||
m_ch[0].name = "CPU Load"; m_ch[0].unit = "%"; m_ch[0].scale = 100;
|
||||
m_ch[1].name = "Memory"; m_ch[1].unit = "%"; m_ch[1].scale = 100;
|
||||
m_ch[2].name = "Kernel Heap";m_ch[2].unit = "%"; m_ch[2].scale = 100;
|
||||
m_ch[3].name = "Net Traffic";m_ch[3].unit = "pkt/s"; m_ch[3].scale = 0; // auto
|
||||
}
|
||||
|
||||
public:
|
||||
static AnalyticsEngine& instance() {
|
||||
static AnalyticsEngine* s = nullptr;
|
||||
if (!s) s = new AnalyticsEngine();
|
||||
return *s;
|
||||
}
|
||||
|
||||
void add_observer(IAnalyticsObserver* o) { if (o) m_observers.push_back(o); }
|
||||
|
||||
void ensure_alarm() {
|
||||
if (!m_alarm) {
|
||||
m_alarm = new ThresholdAlarmObserver();
|
||||
add_observer(m_alarm);
|
||||
}
|
||||
}
|
||||
|
||||
ThresholdAlarmObserver* alarm() { return m_alarm; }
|
||||
|
||||
void notify(int ch, uint32_t v) {
|
||||
for (auto* o : m_observers) if (o) o->on_sample(ch, v);
|
||||
}
|
||||
|
||||
uint64_t sample() {
|
||||
system_metrics_t m;
|
||||
metrics_sample(&m);
|
||||
|
||||
uint32_t cpu = m.cpu_user_pct + m.cpu_system_pct;
|
||||
if (cpu > 100) cpu = 100;
|
||||
|
||||
uint32_t mem = 0;
|
||||
if (m.mem_total_kb) mem = static_cast<uint32_t>((m.mem_used_kb * 100) / m.mem_total_kb);
|
||||
if (mem > 100) mem = 100;
|
||||
|
||||
uint32_t heap = 0;
|
||||
size_t ht = heap_get_total_bytes();
|
||||
if (ht) heap = static_cast<uint32_t>((static_cast<uint64_t>(heap_get_used_bytes()) * 100) / ht);
|
||||
if (heap > 100) heap = 100;
|
||||
|
||||
uint64_t pkts = m.net_rx_packets + m.net_tx_packets;
|
||||
uint32_t rate = 0;
|
||||
if (m_have_prev && pkts >= m_prev_net_pkts) rate = static_cast<uint32_t>(pkts - m_prev_net_pkts);
|
||||
m_prev_net_pkts = pkts;
|
||||
m_have_prev = true;
|
||||
|
||||
uint32_t vals[CPP_ANALYTICS_CHANNELS] = { cpu, mem, heap, rate };
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) {
|
||||
m_ch[i].series.push(vals[i]);
|
||||
notify(i, vals[i]);
|
||||
}
|
||||
m_samples++;
|
||||
return m_samples;
|
||||
}
|
||||
|
||||
int channels() const { return CPP_ANALYTICS_CHANNELS; }
|
||||
uint64_t sample_count() const { return m_samples; }
|
||||
|
||||
Channel* channel(int i) {
|
||||
if (i < 0 || i >= CPP_ANALYTICS_CHANNELS) return nullptr;
|
||||
return &m_ch[i];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace kernel
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// C-FFI bridge
|
||||
// -----------------------------------------------------------------------------
|
||||
using kernel::AnalyticsEngine;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void cpp_analytics_init(void) {
|
||||
cpp_call_global_constructors();
|
||||
auto& e = AnalyticsEngine::instance();
|
||||
e.ensure_alarm();
|
||||
// Seed a few samples so the graphs are not empty on first paint.
|
||||
for (int i = 0; i < 4; i++) e.sample();
|
||||
printk(KERN_INFO "CXX: Analytics Engine online (%d channels, %d-sample history, Observer pattern)\n",
|
||||
CPP_ANALYTICS_CHANNELS, CPP_ANALYTICS_HISTORY);
|
||||
}
|
||||
|
||||
uint64_t cpp_analytics_sample(void) {
|
||||
return AnalyticsEngine::instance().sample();
|
||||
}
|
||||
|
||||
int cpp_analytics_get_series(int channel, uint32_t* out, int max) {
|
||||
if (!out || max <= 0) return 0;
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
if (!c) return 0;
|
||||
return c->series.copy(out, max);
|
||||
}
|
||||
|
||||
void cpp_analytics_get_stats(int channel, uint32_t* out_min, uint32_t* out_max,
|
||||
uint32_t* out_avg, uint32_t* out_last,
|
||||
uint32_t* out_stddev) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
uint32_t mn = 0, mx = 0, avg = 0, sd = 0, last = 0;
|
||||
if (c) { c->series.stats(mn, mx, avg, sd); last = c->series.last(); }
|
||||
if (out_min) *out_min = mn;
|
||||
if (out_max) *out_max = mx;
|
||||
if (out_avg) *out_avg = avg;
|
||||
if (out_last) *out_last = last;
|
||||
if (out_stddev) *out_stddev = sd;
|
||||
}
|
||||
|
||||
const char* cpp_analytics_channel_name(int channel) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
return c ? c->name : "";
|
||||
}
|
||||
|
||||
const char* cpp_analytics_channel_unit(int channel) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
return c ? c->unit : "";
|
||||
}
|
||||
|
||||
uint32_t cpp_analytics_channel_scale(int channel) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
if (!c) return 100;
|
||||
if (c->scale) return c->scale;
|
||||
// Auto-scale: ceil to a sensible ceiling above the observed max.
|
||||
uint32_t mn, mx, avg, sd;
|
||||
c->series.stats(mn, mx, avg, sd);
|
||||
uint32_t ceil = mx + mx / 4 + 1;
|
||||
return ceil < 10 ? 10 : ceil;
|
||||
}
|
||||
|
||||
int cpp_analytics_channel_count(void) {
|
||||
return AnalyticsEngine::instance().channels();
|
||||
}
|
||||
|
||||
uint64_t cpp_analytics_sample_count(void) {
|
||||
return AnalyticsEngine::instance().sample_count();
|
||||
}
|
||||
|
||||
void cpp_analytics_dump(void) {
|
||||
auto& e = AnalyticsEngine::instance();
|
||||
e.sample();
|
||||
printk(ANSI_BRIGHT_CYAN "=================================================================\n" ANSI_RESET);
|
||||
printk(ANSI_BRIGHT_CYAN " SUB-OS C++ Analytics Engine (samples: %llu)\n" ANSI_RESET,
|
||||
static_cast<unsigned long long>(e.sample_count()));
|
||||
printk(ANSI_BRIGHT_CYAN "=================================================================\n" ANSI_RESET);
|
||||
printk(ANSI_BOLD "%-14s %6s %6s %6s %6s %8s\n" ANSI_RESET,
|
||||
"CHANNEL", "LAST", "MIN", "AVG", "MAX", "STDDEV");
|
||||
printk("-----------------------------------------------------------------\n");
|
||||
for (int i = 0; i < e.channels(); i++) {
|
||||
uint32_t mn, mx, avg, last, sd;
|
||||
cpp_analytics_get_stats(i, &mn, &mx, &avg, &last, &sd);
|
||||
char label[24];
|
||||
snprintf(label, sizeof(label), "%s(%s)", cpp_analytics_channel_name(i), cpp_analytics_channel_unit(i));
|
||||
printk(ANSI_BRIGHT_YELLOW "%-14s" ANSI_RESET " %6u %6u " ANSI_BRIGHT_GREEN "%6u" ANSI_RESET " %6u %8u\n",
|
||||
label, last, mn, avg, mx, sd);
|
||||
}
|
||||
|
||||
auto* alarm = e.alarm();
|
||||
if (alarm) {
|
||||
printk("-----------------------------------------------------------------\n");
|
||||
printk(" Threshold alarm breaches (Observer): CPU=%llu MEM=%llu HEAP=%llu\n",
|
||||
static_cast<unsigned long long>(alarm->breaches(0)),
|
||||
static_cast<unsigned long long>(alarm->breaches(1)),
|
||||
static_cast<unsigned long long>(alarm->breaches(2)));
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -42,6 +42,89 @@ constexpr T&& forward(typename RemoveReference<T>::type&& arg) noexcept {
|
||||
return static_cast<T&&>(arg);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Freestanding numeric algorithms (namespaced to avoid clashing with the C
|
||||
// kmin/kmax macros/inlines the kernel exposes elsewhere).
|
||||
// -----------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
constexpr const T& kmin(const T& a, const T& b) { return (a < b) ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
constexpr const T& kmax(const T& a, const T& b) { return (a > b) ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
constexpr const T& kclamp(const T& v, const T& lo, const T& hi) {
|
||||
return (v < lo) ? lo : ((v > hi) ? hi : v);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr void kswap(T& a, T& b) {
|
||||
T tmp = move(a);
|
||||
a = move(b);
|
||||
b = move(tmp);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Pair (Freestanding two-element tuple)
|
||||
// -----------------------------------------------------------------------------
|
||||
template<typename A, typename B>
|
||||
struct Pair {
|
||||
A first;
|
||||
B second;
|
||||
|
||||
constexpr Pair() : first(), second() {}
|
||||
constexpr Pair(const A& a, const B& b) : first(a), second(b) {}
|
||||
};
|
||||
|
||||
template<typename A, typename B>
|
||||
constexpr Pair<A, B> make_pair(const A& a, const B& b) { return Pair<A, B>(a, b); }
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Optional (Freestanding maybe-value, no heap)
|
||||
// -----------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
class Optional {
|
||||
private:
|
||||
alignas(T) unsigned char m_storage[sizeof(T)];
|
||||
bool m_has;
|
||||
|
||||
T* ptr() { return reinterpret_cast<T*>(m_storage); }
|
||||
const T* ptr() const { return reinterpret_cast<const T*>(m_storage); }
|
||||
|
||||
public:
|
||||
constexpr Optional() noexcept : m_has(false) {}
|
||||
|
||||
Optional(const T& value) : m_has(true) {
|
||||
new (static_cast<void*>(m_storage)) T(value);
|
||||
}
|
||||
|
||||
Optional(const Optional& other) : m_has(other.m_has) {
|
||||
if (m_has) new (static_cast<void*>(m_storage)) T(*other.ptr());
|
||||
}
|
||||
|
||||
~Optional() { reset(); }
|
||||
|
||||
Optional& operator=(const Optional& other) {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
m_has = other.m_has;
|
||||
if (m_has) new (static_cast<void*>(m_storage)) T(*other.ptr());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (m_has) { ptr()->~T(); m_has = false; }
|
||||
}
|
||||
|
||||
bool has_value() const noexcept { return m_has; }
|
||||
explicit operator bool() const noexcept { return m_has; }
|
||||
|
||||
T& value() { return *ptr(); }
|
||||
const T& value() const { return *ptr(); }
|
||||
T value_or(const T& fallback) const { return m_has ? *ptr() : fallback; }
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// UniquePtr (Freestanding Smart Pointer RAII)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// C++ Kernel Subsystem C-Fallback for Non-x86 Cross Targets
|
||||
#include <kernel/cpp_kernel.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/metrics.h>
|
||||
#include <mm/pmm.h>
|
||||
#include <mm/kmalloc.h>
|
||||
|
||||
int cpp_kernel_init(void) {
|
||||
printk(KERN_INFO "CXX: C++ Kernel Subsystem Bridge online (Cross-Architecture Mode)\n");
|
||||
@@ -45,3 +48,121 @@ int cpp_run_benchmarks(void) {
|
||||
printk(" UniquePtr RAII : " ANSI_BRIGHT_GREEN "PASSED\n\n" ANSI_RESET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Analytics engine C fallback (functionally equivalent to the C++ engine so the
|
||||
// GUI Analytics app and `cppstat` work identically on ARM targets).
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint32_t fb_ring[CPP_ANALYTICS_CHANNELS][CPP_ANALYTICS_HISTORY];
|
||||
static int fb_head[CPP_ANALYTICS_CHANNELS];
|
||||
static int fb_count[CPP_ANALYTICS_CHANNELS];
|
||||
static uint64_t fb_samples;
|
||||
static uint64_t fb_prev_pkts;
|
||||
static bool fb_have_prev;
|
||||
static const char* fb_names[CPP_ANALYTICS_CHANNELS] = { "CPU Load", "Memory", "Kernel Heap", "Net Traffic" };
|
||||
static const char* fb_units[CPP_ANALYTICS_CHANNELS] = { "%", "%", "%", "pkt/s" };
|
||||
static uint32_t fb_scale[CPP_ANALYTICS_CHANNELS] = { 100, 100, 100, 0 };
|
||||
|
||||
static uint32_t fb_isqrt(uint64_t n) {
|
||||
if (n == 0) return 0;
|
||||
uint64_t x = n, y = (x + 1) / 2;
|
||||
while (y < x) { x = y; y = (x + n / x) / 2; }
|
||||
return (uint32_t)x;
|
||||
}
|
||||
|
||||
void cpp_analytics_init(void) {
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) { fb_head[i] = 0; fb_count[i] = 0; }
|
||||
fb_samples = 0; fb_have_prev = false; fb_prev_pkts = 0;
|
||||
for (int i = 0; i < 4; i++) cpp_analytics_sample();
|
||||
printk(KERN_INFO "CXX: Analytics Engine online (Cross-Arch Fallback, %d channels)\n",
|
||||
CPP_ANALYTICS_CHANNELS);
|
||||
}
|
||||
|
||||
uint64_t cpp_analytics_sample(void) {
|
||||
system_metrics_t m;
|
||||
metrics_sample(&m);
|
||||
uint32_t cpu = m.cpu_user_pct + m.cpu_system_pct; if (cpu > 100) cpu = 100;
|
||||
uint32_t mem = m.mem_total_kb ? (uint32_t)((m.mem_used_kb * 100) / m.mem_total_kb) : 0;
|
||||
if (mem > 100) mem = 100;
|
||||
uint32_t heap = 0; size_t ht = heap_get_total_bytes();
|
||||
if (ht) heap = (uint32_t)(((uint64_t)heap_get_used_bytes() * 100) / ht);
|
||||
if (heap > 100) heap = 100;
|
||||
uint64_t pkts = m.net_rx_packets + m.net_tx_packets;
|
||||
uint32_t rate = (fb_have_prev && pkts >= fb_prev_pkts) ? (uint32_t)(pkts - fb_prev_pkts) : 0;
|
||||
fb_prev_pkts = pkts; fb_have_prev = true;
|
||||
|
||||
uint32_t vals[CPP_ANALYTICS_CHANNELS] = { cpu, mem, heap, rate };
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) {
|
||||
fb_ring[i][fb_head[i]] = vals[i];
|
||||
fb_head[i] = (fb_head[i] + 1) % CPP_ANALYTICS_HISTORY;
|
||||
if (fb_count[i] < CPP_ANALYTICS_HISTORY) fb_count[i]++;
|
||||
}
|
||||
fb_samples++;
|
||||
return fb_samples;
|
||||
}
|
||||
|
||||
int cpp_analytics_get_series(int ch, uint32_t* out, int max) {
|
||||
if (ch < 0 || ch >= CPP_ANALYTICS_CHANNELS || !out || max <= 0) return 0;
|
||||
int n = fb_count[ch] < max ? fb_count[ch] : max;
|
||||
int start = (fb_head[ch] + CPP_ANALYTICS_HISTORY - fb_count[ch]) % CPP_ANALYTICS_HISTORY;
|
||||
for (int i = 0; i < n; i++) out[i] = fb_ring[ch][(start + i) % CPP_ANALYTICS_HISTORY];
|
||||
return n;
|
||||
}
|
||||
|
||||
void cpp_analytics_get_stats(int ch, uint32_t* mn_o, uint32_t* mx_o,
|
||||
uint32_t* avg_o, uint32_t* last_o, uint32_t* sd_o) {
|
||||
uint32_t mn = 0, mx = 0, avg = 0, sd = 0, last = 0;
|
||||
if (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS && fb_count[ch] > 0) {
|
||||
uint32_t lo = 0xFFFFFFFFu, hi = 0; uint64_t sum = 0;
|
||||
for (int i = 0; i < fb_count[ch]; i++) {
|
||||
uint32_t v = fb_ring[ch][i];
|
||||
if (v < lo) lo = v;
|
||||
if (v > hi) hi = v;
|
||||
sum += v;
|
||||
}
|
||||
avg = (uint32_t)(sum / fb_count[ch]);
|
||||
uint64_t acc = 0;
|
||||
for (int i = 0; i < fb_count[ch]; i++) {
|
||||
int64_t d = (int64_t)fb_ring[ch][i] - (int64_t)avg; acc += (uint64_t)(d * d);
|
||||
}
|
||||
mn = lo; mx = hi; sd = fb_isqrt(acc / fb_count[ch]);
|
||||
last = fb_ring[ch][(fb_head[ch] + CPP_ANALYTICS_HISTORY - 1) % CPP_ANALYTICS_HISTORY];
|
||||
}
|
||||
if (mn_o) *mn_o = mn;
|
||||
if (mx_o) *mx_o = mx;
|
||||
if (avg_o) *avg_o = avg;
|
||||
if (last_o) *last_o = last;
|
||||
if (sd_o) *sd_o = sd;
|
||||
}
|
||||
|
||||
const char* cpp_analytics_channel_name(int ch) {
|
||||
return (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) ? fb_names[ch] : "";
|
||||
}
|
||||
const char* cpp_analytics_channel_unit(int ch) {
|
||||
return (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) ? fb_units[ch] : "";
|
||||
}
|
||||
uint32_t cpp_analytics_channel_scale(int ch) {
|
||||
if (ch < 0 || ch >= CPP_ANALYTICS_CHANNELS) return 100;
|
||||
if (fb_scale[ch]) return fb_scale[ch];
|
||||
uint32_t mn, mx, avg, last, sd; cpp_analytics_get_stats(ch, &mn, &mx, &avg, &last, &sd);
|
||||
uint32_t ceil = mx + mx / 4 + 1; return ceil < 10 ? 10 : ceil;
|
||||
}
|
||||
int cpp_analytics_channel_count(void) { return CPP_ANALYTICS_CHANNELS; }
|
||||
uint64_t cpp_analytics_sample_count(void) { return fb_samples; }
|
||||
|
||||
void cpp_analytics_dump(void) {
|
||||
cpp_analytics_sample();
|
||||
printk(ANSI_BRIGHT_CYAN "=== SUB-OS C++ Analytics Engine (samples: %llu) ===\n" ANSI_RESET,
|
||||
(unsigned long long)fb_samples);
|
||||
printk(ANSI_BOLD "%-14s %6s %6s %6s %6s %8s\n" ANSI_RESET,
|
||||
"CHANNEL", "LAST", "MIN", "AVG", "MAX", "STDDEV");
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) {
|
||||
uint32_t mn, mx, avg, last, sd;
|
||||
cpp_analytics_get_stats(i, &mn, &mx, &avg, &last, &sd);
|
||||
char label[24];
|
||||
snprintf(label, sizeof(label), "%s(%s)", fb_names[i], fb_units[i]);
|
||||
printk(ANSI_BRIGHT_YELLOW "%-14s" ANSI_RESET " %6u %6u " ANSI_BRIGHT_GREEN "%6u" ANSI_RESET " %6u %8u\n",
|
||||
label, last, mn, avg, mx, sd);
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
+49
-8
@@ -7,6 +7,9 @@
|
||||
#include <kernel/timer.h>
|
||||
#include <kernel/workqueue.h>
|
||||
#include <kernel/module.h>
|
||||
#include <kernel/nt/ob.h>
|
||||
#include <kernel/nt/reg.h>
|
||||
#include <kernel/nt/io.h>
|
||||
#include <kernel/trace.h>
|
||||
#include <kernel/namespace.h>
|
||||
#include <kernel/syslog.h>
|
||||
@@ -63,6 +66,7 @@
|
||||
#include <net/tcp.h>
|
||||
#include <net/dhcp.h>
|
||||
#include <net/dns.h>
|
||||
#include <net/http_client.h>
|
||||
#include <net/dns_cache.h>
|
||||
#include <ipc/shm_posix.h>
|
||||
#include <sound/beep.h>
|
||||
@@ -70,12 +74,17 @@
|
||||
#include <drivers/e1000e.h>
|
||||
#include <drivers/virtio_gpu.h>
|
||||
#include <drivers/cpufreq.h>
|
||||
#include <drivers/hpet.h>
|
||||
#include <drivers/nvram.h>
|
||||
#include <drivers/lpt.h>
|
||||
#include <kernel/ktime.h>
|
||||
#include <drivers/virtio_input.h>
|
||||
#include <gui/gui.h>
|
||||
#include <init/init.h>
|
||||
#include <kernel/rust.h>
|
||||
#include <kernel/sub_lang.h>
|
||||
#include <kernel/cpp_kernel.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <net/filter.h>
|
||||
#include <net/http.h>
|
||||
#include <net/ssh.h>
|
||||
@@ -125,7 +134,11 @@ static void subos_modular_core_boot(void) {
|
||||
xhci_init();
|
||||
hwmon_init();
|
||||
cpufreq_init();
|
||||
hpet_init();
|
||||
nvram_init();
|
||||
lpt_init();
|
||||
#endif
|
||||
ktime_init();
|
||||
canvas_init();
|
||||
pty_init();
|
||||
virtio_rng_init();
|
||||
@@ -189,6 +202,7 @@ static void subos_modular_core_boot(void) {
|
||||
boot_logo_splash_progress(55, "Starting Rust, C++ and SUB-Lang engines");
|
||||
rust_kernel_init();
|
||||
cpp_kernel_init();
|
||||
cpp_analytics_init();
|
||||
sub_kernel_init();
|
||||
prng_seed(0, 0);
|
||||
certs_init();
|
||||
@@ -239,6 +253,9 @@ static void subos_modular_core_boot(void) {
|
||||
syslog_init();
|
||||
metrics_init();
|
||||
cron_init();
|
||||
ob_init();
|
||||
reg_init();
|
||||
io_init();
|
||||
service_manager_init();
|
||||
printk(ANSI_BRIGHT_GREEN "OK\n" ANSI_RESET);
|
||||
|
||||
@@ -269,26 +286,50 @@ static void subos_modular_core_boot(void) {
|
||||
* threads are forced to share the CPU by the tick. Preemption is left armed
|
||||
* afterwards, so CPU-bound kernel threads are time-sliced from here on. */
|
||||
weave_preempt_selftest();
|
||||
|
||||
// The async HTTP client runs on its own kernel thread, which idles on hlt
|
||||
// between fetches. It is started only now, once hardware interrupts are
|
||||
// live -- earlier, and its first idle would hlt with no timer tick to wake
|
||||
// it -- and after the deterministic scheduler self-tests, so it never joins
|
||||
// their fixed thread set. Harmless with no NIC: a fetch then fails at
|
||||
// connect. It stays dormant until the GUI Web app requests a page.
|
||||
http_client_init();
|
||||
|
||||
// The rest of the system -- the login prompt, the shell, the GUI
|
||||
// compositor -- has run on the pid-0 idle thread, which the scheduler
|
||||
// neither preempts (sched_tick skips pid 0) nor requeues on a yield
|
||||
// (weave_on is clear). That was fine while nothing else needed the CPU, but
|
||||
// now a background worker (the async HTTP client) does. Joining the weave
|
||||
// rotation makes this thread a first-class participant: a sched_yield from
|
||||
// the compositor's idle slack requeues it and lets the worker run, and
|
||||
// control comes straight back on the next rotation. Without this a yield
|
||||
// here would switch away for good.
|
||||
sched_join();
|
||||
#endif
|
||||
|
||||
// Boot directly into the GUI Desktop unless emergency text mode is requested.
|
||||
bool emergency_text = init_has_param("nogui") || init_has_param("text") ||
|
||||
init_has_param("emergency") || init_has_param("single");
|
||||
|
||||
if (!emergency_text) {
|
||||
printk(KERN_INFO "INIT: Booting directly into SUB-OS Graphical Desktop Environment...\n");
|
||||
// End the splash and return the screen to a text console so the login
|
||||
// prompt is visible before any session (graphical or TTY) begins.
|
||||
boot_logo_splash_end();
|
||||
fbcon_enable(true);
|
||||
|
||||
// Gate the session behind an interactive login. This blocks until a valid
|
||||
// username/password is entered; the default account is SUB / SUB.
|
||||
console_login();
|
||||
|
||||
if (!emergency_text) {
|
||||
printk(KERN_INFO "INIT: Starting SUB-OS Graphical Desktop Environment...\n");
|
||||
gui_start_desktop();
|
||||
printk(KERN_INFO "INIT: GUI session closed. Entering Emergency Kernel TTY Mode.\n");
|
||||
printk(KERN_INFO "INIT: GUI session closed. Entering Kernel TTY Mode.\n");
|
||||
// The desktop owned the framebuffer; return it to the text console.
|
||||
fbcon_enable(true);
|
||||
} else {
|
||||
printk(KERN_INFO "INIT: Emergency text/kernel TTY mode requested via boot parameters.\n");
|
||||
boot_logo_splash_end();
|
||||
}
|
||||
|
||||
// The screen returns to the console before handing over to userland, whether
|
||||
// the GUI session just closed or a text boot was requested.
|
||||
fbcon_enable(true);
|
||||
|
||||
// Hand control to userland: /sbin/init is a separate static ELF that runs
|
||||
// in ring 3 with its own page tables and talks back only through INT 0x80.
|
||||
exec_run_builtin_init();
|
||||
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SUB-OS NT-Inspired I/O Manager (Io)
|
||||
//
|
||||
// Drivers register a MajorFunction dispatch table and create named device
|
||||
// objects. Devices can be stacked (a filter attached on top of a lower device);
|
||||
// io_call_driver invokes the top device's dispatch, which may forward the IRP
|
||||
// down to dev->attached_to. Ships three demo drivers: Null, Zero, and a Monitor
|
||||
// filter stacked above Null that counts and forwards every IRP.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include <kernel/nt/io.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/sync.h>
|
||||
|
||||
static DRIVER_OBJECT* g_drivers[IO_MAX_DRIVERS];
|
||||
static int g_driver_count = 0;
|
||||
static DEVICE_OBJECT* g_devices[IO_MAX_DEVICES];
|
||||
static int g_device_count = 0;
|
||||
static uint64_t g_total_irps = 0;
|
||||
static spinlock_t g_io_lock = SPINLOCK_INIT;
|
||||
|
||||
const char* io_major_name(irp_major_t mj) {
|
||||
switch (mj) {
|
||||
case IRP_MJ_CREATE: return "CREATE";
|
||||
case IRP_MJ_CLOSE: return "CLOSE";
|
||||
case IRP_MJ_READ: return "READ";
|
||||
case IRP_MJ_WRITE: return "WRITE";
|
||||
case IRP_MJ_DEVICE_CONTROL: return "DEVICE_CONTROL";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
DRIVER_OBJECT* io_create_driver(const char* name, driver_entry_t entry) {
|
||||
spin_lock(&g_io_lock);
|
||||
if (g_driver_count >= IO_MAX_DRIVERS) {
|
||||
spin_unlock(&g_io_lock);
|
||||
return NULL;
|
||||
}
|
||||
DRIVER_OBJECT* drv = (DRIVER_OBJECT*)kzalloc(sizeof(DRIVER_OBJECT));
|
||||
if (!drv) {
|
||||
spin_unlock(&g_io_lock);
|
||||
return NULL;
|
||||
}
|
||||
strncpy(drv->name, name ? name : "", IO_NAME_MAX - 1);
|
||||
g_drivers[g_driver_count++] = drv;
|
||||
spin_unlock(&g_io_lock);
|
||||
|
||||
if (entry) {
|
||||
entry(drv); // DriverEntry fills the MajorFunction table and creates devices
|
||||
}
|
||||
return drv;
|
||||
}
|
||||
|
||||
NTSTATUS io_create_device(DRIVER_OBJECT* drv, const char* name,
|
||||
size_t ext_size, DEVICE_OBJECT** out) {
|
||||
if (!drv) return STATUS_UNSUCCESSFUL;
|
||||
spin_lock(&g_io_lock);
|
||||
if (g_device_count >= IO_MAX_DEVICES) {
|
||||
spin_unlock(&g_io_lock);
|
||||
return STATUS_INSUFFICIENT_RES;
|
||||
}
|
||||
DEVICE_OBJECT* dev = (DEVICE_OBJECT*)kzalloc(sizeof(DEVICE_OBJECT));
|
||||
if (!dev) {
|
||||
spin_unlock(&g_io_lock);
|
||||
return STATUS_INSUFFICIENT_RES;
|
||||
}
|
||||
strncpy(dev->name, name ? name : "", IO_NAME_MAX - 1);
|
||||
dev->driver = drv;
|
||||
if (ext_size > 0) {
|
||||
dev->device_extension = kzalloc(ext_size);
|
||||
}
|
||||
// Link into the driver's device list and the global table.
|
||||
dev->next_on_driver = drv->device_list;
|
||||
drv->device_list = dev;
|
||||
g_devices[g_device_count++] = dev;
|
||||
spin_unlock(&g_io_lock);
|
||||
|
||||
if (out) *out = dev;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
DEVICE_OBJECT* io_attach_device(DEVICE_OBJECT* source, DEVICE_OBJECT* target) {
|
||||
if (!source || !target) return NULL;
|
||||
spin_lock(&g_io_lock);
|
||||
source->attached_to = target; // source now sits above target in the stack
|
||||
spin_unlock(&g_io_lock);
|
||||
return target;
|
||||
}
|
||||
|
||||
DEVICE_OBJECT* io_lookup_device(const char* name) {
|
||||
if (!name) return NULL;
|
||||
spin_lock(&g_io_lock);
|
||||
DEVICE_OBJECT* found = NULL;
|
||||
for (int i = 0; i < g_device_count; i++) {
|
||||
if (strcmp(g_devices[i]->name, name) == 0) { found = g_devices[i]; break; }
|
||||
}
|
||||
spin_unlock(&g_io_lock);
|
||||
return found;
|
||||
}
|
||||
|
||||
IRP* io_build_irp(irp_major_t mj, void* buffer, uint32_t length) {
|
||||
IRP* irp = (IRP*)kzalloc(sizeof(IRP));
|
||||
if (!irp) return NULL;
|
||||
irp->major_function = mj;
|
||||
irp->buffer = buffer;
|
||||
irp->length = length;
|
||||
irp->status = STATUS_UNSUCCESSFUL;
|
||||
irp->information = 0;
|
||||
return irp;
|
||||
}
|
||||
|
||||
NTSTATUS io_call_driver(DEVICE_OBJECT* dev, IRP* irp) {
|
||||
if (!dev || !irp) return STATUS_UNSUCCESSFUL;
|
||||
irp->current_device = dev;
|
||||
|
||||
spin_lock(&g_io_lock);
|
||||
g_total_irps++;
|
||||
if (dev->driver) dev->driver->irp_count++;
|
||||
switch (irp->major_function) {
|
||||
case IRP_MJ_READ: dev->read_count++; break;
|
||||
case IRP_MJ_WRITE: dev->write_count++; break;
|
||||
case IRP_MJ_DEVICE_CONTROL: dev->ioctl_count++; break;
|
||||
default: break;
|
||||
}
|
||||
driver_dispatch_t fn = NULL;
|
||||
if (dev->driver && irp->major_function < IRP_MJ_MAXIMUM) {
|
||||
fn = dev->driver->major_function[irp->major_function];
|
||||
}
|
||||
spin_unlock(&g_io_lock);
|
||||
|
||||
if (!fn) {
|
||||
irp->status = STATUS_UNSUCCESSFUL;
|
||||
return irp->status;
|
||||
}
|
||||
return fn(dev, irp);
|
||||
}
|
||||
|
||||
void io_complete_request(IRP* irp, NTSTATUS status) {
|
||||
if (!irp) return;
|
||||
irp->status = status;
|
||||
}
|
||||
|
||||
void io_free_irp(IRP* irp) {
|
||||
if (irp) kfree(irp);
|
||||
}
|
||||
|
||||
int io_driver_count(void) { return g_driver_count; }
|
||||
DRIVER_OBJECT* io_driver_at(int index) {
|
||||
return (index >= 0 && index < g_driver_count) ? g_drivers[index] : NULL;
|
||||
}
|
||||
int io_device_count(void) { return g_device_count; }
|
||||
DEVICE_OBJECT* io_device_at(int index) {
|
||||
return (index >= 0 && index < g_device_count) ? g_devices[index] : NULL;
|
||||
}
|
||||
void io_get_stats(uint32_t* out_drivers, uint32_t* out_devices, uint64_t* out_irps) {
|
||||
if (out_drivers) *out_drivers = (uint32_t)g_driver_count;
|
||||
if (out_devices) *out_devices = (uint32_t)g_device_count;
|
||||
if (out_irps) *out_irps = g_total_irps;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Demo drivers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// \Driver\Null : sink writes, return EOF on reads (like NT's \Device\Null).
|
||||
static NTSTATUS null_read(DEVICE_OBJECT* dev, IRP* irp) {
|
||||
(void)dev;
|
||||
irp->information = 0; // 0 bytes == end of file
|
||||
io_complete_request(irp, STATUS_SUCCESS);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
static NTSTATUS null_write(DEVICE_OBJECT* dev, IRP* irp) {
|
||||
(void)dev;
|
||||
irp->information = irp->length; // swallow everything
|
||||
io_complete_request(irp, STATUS_SUCCESS);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
static NTSTATUS null_entry(DRIVER_OBJECT* drv) {
|
||||
drv->major_function[IRP_MJ_READ] = null_read;
|
||||
drv->major_function[IRP_MJ_WRITE] = null_write;
|
||||
io_create_device(drv, "\\Device\\Null", 0, NULL);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// \Driver\Zero : reads yield a buffer full of zero bytes.
|
||||
static NTSTATUS zero_read(DEVICE_OBJECT* dev, IRP* irp) {
|
||||
(void)dev;
|
||||
if (irp->buffer && irp->length) {
|
||||
memset(irp->buffer, 0, irp->length);
|
||||
}
|
||||
irp->information = irp->length;
|
||||
io_complete_request(irp, STATUS_SUCCESS);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
static NTSTATUS zero_entry(DRIVER_OBJECT* drv) {
|
||||
drv->major_function[IRP_MJ_READ] = zero_read;
|
||||
io_create_device(drv, "\\Device\\Zero", 0, NULL);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// \Driver\Monitor : a filter stacked above \Device\Null. It counts each IRP in
|
||||
// its device extension and forwards to the lower device, demonstrating a stack.
|
||||
typedef struct { uint64_t forwarded; } monitor_ext_t;
|
||||
|
||||
static NTSTATUS monitor_passthrough(DEVICE_OBJECT* dev, IRP* irp) {
|
||||
monitor_ext_t* ext = (monitor_ext_t*)dev->device_extension;
|
||||
if (ext) ext->forwarded++;
|
||||
if (dev->attached_to) {
|
||||
return io_call_driver(dev->attached_to, irp); // pass down the stack
|
||||
}
|
||||
io_complete_request(irp, STATUS_SUCCESS);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
static NTSTATUS monitor_entry(DRIVER_OBJECT* drv) {
|
||||
drv->major_function[IRP_MJ_READ] = monitor_passthrough;
|
||||
drv->major_function[IRP_MJ_WRITE] = monitor_passthrough;
|
||||
drv->major_function[IRP_MJ_DEVICE_CONTROL] = monitor_passthrough;
|
||||
|
||||
DEVICE_OBJECT* filter = NULL;
|
||||
io_create_device(drv, "\\Device\\NullMonitor", sizeof(monitor_ext_t), &filter);
|
||||
DEVICE_OBJECT* lower = io_lookup_device("\\Device\\Null");
|
||||
if (filter && lower) {
|
||||
io_attach_device(filter, lower);
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void io_init(void) {
|
||||
g_driver_count = 0;
|
||||
g_device_count = 0;
|
||||
g_total_irps = 0;
|
||||
|
||||
io_create_driver("\\Driver\\Null", null_entry);
|
||||
io_create_driver("\\Driver\\Zero", zero_entry);
|
||||
io_create_driver("\\Driver\\Monitor", monitor_entry);
|
||||
|
||||
printk(ANSI_BRIGHT_GREEN "IO: " ANSI_RESET
|
||||
"NT I/O Manager online (%d drivers, %d devices, IRP-based dispatch)\n",
|
||||
g_driver_count, g_device_count);
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SUB-OS NT-Inspired Object Manager (Ob) + Dispatcher (Ke) + IRQL
|
||||
//
|
||||
// Objects are typed, reference-counted, and named in a directory hierarchy
|
||||
// rooted at "\". A global handle table stands in for the NT System process's
|
||||
// handle table. Deletion follows NT rules: an object is torn down once its
|
||||
// handle_count and ref_count both reach zero and it is not permanent.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include <kernel/nt/ob.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/sync.h>
|
||||
|
||||
static ob_object_t* g_root = NULL;
|
||||
static ob_object_t* g_handle_table[OB_HANDLE_MAX];
|
||||
static uint32_t g_object_count = 0;
|
||||
static KIRQL g_current_irql = PASSIVE_LEVEL;
|
||||
static spinlock_t g_ob_lock = SPINLOCK_INIT;
|
||||
|
||||
static const char* const OB_TYPE_NAMES[OB_TYPE_MAX] = {
|
||||
"Directory", "Event", "Semaphore", "Mutant"
|
||||
};
|
||||
|
||||
const char* ob_type_name(ob_type_t t) {
|
||||
return (t < OB_TYPE_MAX) ? OB_TYPE_NAMES[t] : "Unknown";
|
||||
}
|
||||
|
||||
ob_object_t* ob_root_directory(void) { return g_root; }
|
||||
|
||||
// --- Directory child list (caller holds g_ob_lock) ---------------------------
|
||||
static bool dir_add_child(ob_object_t* dir, ob_object_t* child) {
|
||||
if (!dir || dir->type != OB_TYPE_DIRECTORY) return false;
|
||||
if (dir->body.dir.count >= OB_DIR_MAX) return false;
|
||||
dir->body.dir.children[dir->body.dir.count++] = child;
|
||||
child->parent = dir;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dir_remove_child(ob_object_t* dir, ob_object_t* child) {
|
||||
if (!dir || dir->type != OB_TYPE_DIRECTORY) return;
|
||||
int n = dir->body.dir.count;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (dir->body.dir.children[i] == child) {
|
||||
for (int j = i; j < n - 1; j++) {
|
||||
dir->body.dir.children[j] = dir->body.dir.children[j + 1];
|
||||
}
|
||||
dir->body.dir.count--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ob_object_t* dir_find_child(ob_object_t* dir, const char* name) {
|
||||
if (!dir || dir->type != OB_TYPE_DIRECTORY) return NULL;
|
||||
for (int i = 0; i < dir->body.dir.count; i++) {
|
||||
if (strcmp(dir->body.dir.children[i]->name, name) == 0) {
|
||||
return dir->body.dir.children[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --- Allocation (caller holds g_ob_lock) -------------------------------------
|
||||
static ob_object_t* ob_alloc(ob_type_t type, const char* name) {
|
||||
ob_object_t* obj = (ob_object_t*)kzalloc(sizeof(ob_object_t));
|
||||
if (!obj) return NULL;
|
||||
obj->type = type;
|
||||
strncpy(obj->name, name ? name : "", OB_NAME_MAX - 1);
|
||||
obj->ref_count = 1; // the reference handed back to the creator
|
||||
obj->handle_count = 0;
|
||||
obj->permanent = false;
|
||||
obj->parent = NULL;
|
||||
if (type == OB_TYPE_SEMAPHORE) {
|
||||
obj->body.sem.count = 0;
|
||||
obj->body.sem.limit = 1;
|
||||
}
|
||||
g_object_count++;
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Tear an object down once nothing references it. Caller holds g_ob_lock.
|
||||
static void ob_maybe_delete(ob_object_t* obj) {
|
||||
if (!obj || obj->permanent) return;
|
||||
if (obj->ref_count > 0 || obj->handle_count > 0) return;
|
||||
if (obj->type == OB_TYPE_DIRECTORY && obj->body.dir.count > 0) return;
|
||||
if (obj->parent) {
|
||||
dir_remove_child(obj->parent, obj);
|
||||
}
|
||||
if (g_object_count > 0) g_object_count--;
|
||||
kfree(obj);
|
||||
}
|
||||
|
||||
NTSTATUS ob_create_object(ob_type_t type, const char* name,
|
||||
ob_object_t* dir, ob_object_t** out) {
|
||||
if (type >= OB_TYPE_MAX) return STATUS_UNSUCCESSFUL;
|
||||
|
||||
spin_lock(&g_ob_lock);
|
||||
|
||||
ob_object_t* parent = dir ? dir : g_root;
|
||||
if (name && name[0] && parent && dir_find_child(parent, name)) {
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_OBJECT_NAME_EXISTS;
|
||||
}
|
||||
|
||||
ob_object_t* obj = ob_alloc(type, name);
|
||||
if (!obj) {
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
if (name && name[0] && parent) {
|
||||
if (!dir_add_child(parent, obj)) {
|
||||
g_object_count--;
|
||||
kfree(obj);
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_INSUFFICIENT_RES;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&g_ob_lock);
|
||||
if (out) *out = obj;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void ob_reference_object(ob_object_t* obj) {
|
||||
if (!obj) return;
|
||||
spin_lock(&g_ob_lock);
|
||||
obj->ref_count++;
|
||||
spin_unlock(&g_ob_lock);
|
||||
}
|
||||
|
||||
void ob_dereference_object(ob_object_t* obj) {
|
||||
if (!obj) return;
|
||||
spin_lock(&g_ob_lock);
|
||||
if (obj->ref_count > 0) obj->ref_count--;
|
||||
ob_maybe_delete(obj);
|
||||
spin_unlock(&g_ob_lock);
|
||||
}
|
||||
|
||||
void ob_make_permanent(ob_object_t* obj) {
|
||||
if (!obj) return;
|
||||
spin_lock(&g_ob_lock);
|
||||
obj->permanent = true;
|
||||
spin_unlock(&g_ob_lock);
|
||||
}
|
||||
|
||||
ob_object_t* ob_lookup_path(const char* path) {
|
||||
if (!path) return NULL;
|
||||
// A bare "\" (or empty) resolves to the root directory.
|
||||
if (path[0] == '\0' || (path[0] == '\\' && path[1] == '\0')) {
|
||||
return g_root;
|
||||
}
|
||||
|
||||
char buf[192];
|
||||
strncpy(buf, path, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
spin_lock(&g_ob_lock);
|
||||
ob_object_t* cur = g_root;
|
||||
char* p = buf;
|
||||
if (*p == '\\') p++; // skip leading backslash
|
||||
|
||||
while (*p && cur) {
|
||||
char* seg = p;
|
||||
while (*p && *p != '\\') p++;
|
||||
if (*p == '\\') { *p = '\0'; p++; }
|
||||
if (*seg) {
|
||||
cur = dir_find_child(cur, seg);
|
||||
}
|
||||
}
|
||||
spin_unlock(&g_ob_lock);
|
||||
return cur;
|
||||
}
|
||||
|
||||
// --- Handle table ------------------------------------------------------------
|
||||
HANDLE ob_open_handle(ob_object_t* obj) {
|
||||
if (!obj) return OB_INVALID_HANDLE;
|
||||
spin_lock(&g_ob_lock);
|
||||
for (int i = 0; i < OB_HANDLE_MAX; i++) {
|
||||
if (g_handle_table[i] == NULL) {
|
||||
g_handle_table[i] = obj;
|
||||
obj->handle_count++;
|
||||
obj->ref_count++; // the handle holds a reference
|
||||
spin_unlock(&g_ob_lock);
|
||||
// NT hands out non-zero, 4-aligned handle values; mirror that shape.
|
||||
return (HANDLE)((i + 1) * 4);
|
||||
}
|
||||
}
|
||||
spin_unlock(&g_ob_lock);
|
||||
return OB_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
static int handle_to_index(HANDLE h) {
|
||||
if (h <= 0 || (h & 3)) return -1;
|
||||
int idx = (h / 4) - 1;
|
||||
return (idx >= 0 && idx < OB_HANDLE_MAX) ? idx : -1;
|
||||
}
|
||||
|
||||
ob_object_t* ob_reference_by_handle(HANDLE h) {
|
||||
int idx = handle_to_index(h);
|
||||
if (idx < 0) return NULL;
|
||||
spin_lock(&g_ob_lock);
|
||||
ob_object_t* obj = g_handle_table[idx];
|
||||
if (obj) obj->ref_count++;
|
||||
spin_unlock(&g_ob_lock);
|
||||
return obj;
|
||||
}
|
||||
|
||||
NTSTATUS ob_close_handle(HANDLE h) {
|
||||
int idx = handle_to_index(h);
|
||||
if (idx < 0) return STATUS_INVALID_HANDLE;
|
||||
spin_lock(&g_ob_lock);
|
||||
ob_object_t* obj = g_handle_table[idx];
|
||||
if (!obj) {
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
g_handle_table[idx] = NULL;
|
||||
if (obj->handle_count > 0) obj->handle_count--;
|
||||
if (obj->ref_count > 0) obj->ref_count--; // drop the handle's reference
|
||||
ob_maybe_delete(obj);
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
int ob_open_handle_count(void) {
|
||||
int n = 0;
|
||||
spin_lock(&g_ob_lock);
|
||||
for (int i = 0; i < OB_HANDLE_MAX; i++) {
|
||||
if (g_handle_table[i]) n++;
|
||||
}
|
||||
spin_unlock(&g_ob_lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
// --- Dispatcher primitives ---------------------------------------------------
|
||||
NTSTATUS ke_set_event(ob_object_t* evt) {
|
||||
if (!evt || evt->type != OB_TYPE_EVENT) return STATUS_UNSUCCESSFUL;
|
||||
spin_lock(&g_ob_lock);
|
||||
evt->body.event.signaled = true;
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS ke_reset_event(ob_object_t* evt) {
|
||||
if (!evt || evt->type != OB_TYPE_EVENT) return STATUS_UNSUCCESSFUL;
|
||||
spin_lock(&g_ob_lock);
|
||||
evt->body.event.signaled = false;
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
bool ke_test_event(ob_object_t* evt) {
|
||||
if (!evt || evt->type != OB_TYPE_EVENT) return false;
|
||||
spin_lock(&g_ob_lock);
|
||||
bool sig = evt->body.event.signaled;
|
||||
if (sig && !evt->body.event.manual_reset) {
|
||||
evt->body.event.signaled = false; // auto-reset consumes the signal
|
||||
}
|
||||
spin_unlock(&g_ob_lock);
|
||||
return sig;
|
||||
}
|
||||
|
||||
NTSTATUS ke_release_semaphore(ob_object_t* sem, int32_t count) {
|
||||
if (!sem || sem->type != OB_TYPE_SEMAPHORE || count <= 0) return STATUS_UNSUCCESSFUL;
|
||||
spin_lock(&g_ob_lock);
|
||||
sem->body.sem.count += count;
|
||||
if (sem->body.sem.count > sem->body.sem.limit) {
|
||||
sem->body.sem.count = sem->body.sem.limit;
|
||||
}
|
||||
spin_unlock(&g_ob_lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
bool ke_wait_semaphore(ob_object_t* sem) {
|
||||
if (!sem || sem->type != OB_TYPE_SEMAPHORE) return false;
|
||||
spin_lock(&g_ob_lock);
|
||||
bool ok = false;
|
||||
if (sem->body.sem.count > 0) {
|
||||
sem->body.sem.count--;
|
||||
ok = true;
|
||||
}
|
||||
spin_unlock(&g_ob_lock);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// --- Enumeration / stats -----------------------------------------------------
|
||||
int ob_dir_child_count(ob_object_t* dir) {
|
||||
if (!dir || dir->type != OB_TYPE_DIRECTORY) return 0;
|
||||
return dir->body.dir.count;
|
||||
}
|
||||
|
||||
ob_object_t* ob_dir_child(ob_object_t* dir, int index) {
|
||||
if (!dir || dir->type != OB_TYPE_DIRECTORY) return NULL;
|
||||
if (index < 0 || index >= dir->body.dir.count) return NULL;
|
||||
return dir->body.dir.children[index];
|
||||
}
|
||||
|
||||
static void count_recursive(ob_object_t* obj, uint32_t per_type[OB_TYPE_MAX]) {
|
||||
if (!obj) return;
|
||||
if (obj->type < OB_TYPE_MAX) per_type[obj->type]++;
|
||||
if (obj->type == OB_TYPE_DIRECTORY) {
|
||||
for (int i = 0; i < obj->body.dir.count; i++) {
|
||||
count_recursive(obj->body.dir.children[i], per_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ob_get_stats(uint32_t* out_objects, uint32_t* out_handles,
|
||||
uint32_t per_type[OB_TYPE_MAX]) {
|
||||
for (int i = 0; i < OB_TYPE_MAX; i++) per_type[i] = 0;
|
||||
count_recursive(g_root, per_type);
|
||||
if (out_objects) *out_objects = g_object_count;
|
||||
if (out_handles) *out_handles = (uint32_t)ob_open_handle_count();
|
||||
}
|
||||
|
||||
// --- Software IRQL -----------------------------------------------------------
|
||||
KIRQL ke_get_current_irql(void) { return g_current_irql; }
|
||||
|
||||
KIRQL ke_raise_irql(KIRQL new_irql) {
|
||||
KIRQL old = g_current_irql;
|
||||
if (new_irql >= g_current_irql) { // raising only; NT bugchecks otherwise
|
||||
g_current_irql = new_irql;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
void ke_lower_irql(KIRQL old_irql) {
|
||||
if (old_irql <= g_current_irql) {
|
||||
g_current_irql = old_irql;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Initialization ----------------------------------------------------------
|
||||
void ob_init(void) {
|
||||
for (int i = 0; i < OB_HANDLE_MAX; i++) g_handle_table[i] = NULL;
|
||||
g_object_count = 0;
|
||||
g_current_irql = PASSIVE_LEVEL;
|
||||
|
||||
// Root directory "\" is permanent and self-parented.
|
||||
g_root = (ob_object_t*)kzalloc(sizeof(ob_object_t));
|
||||
if (!g_root) {
|
||||
printk(KERN_ERR "OB: failed to allocate the root directory\n");
|
||||
return;
|
||||
}
|
||||
g_root->type = OB_TYPE_DIRECTORY;
|
||||
strncpy(g_root->name, "\\", OB_NAME_MAX - 1);
|
||||
g_root->permanent = true;
|
||||
g_root->ref_count = 1;
|
||||
g_object_count = 1;
|
||||
|
||||
// Standard NT namespace directories.
|
||||
const char* std_dirs[] = { "BaseNamedObjects", "Device", "ObjectTypes", "KernelObjects" };
|
||||
for (size_t i = 0; i < sizeof(std_dirs) / sizeof(std_dirs[0]); i++) {
|
||||
ob_object_t* d = NULL;
|
||||
if (NT_SUCCESS(ob_create_object(OB_TYPE_DIRECTORY, std_dirs[i], g_root, &d)) && d) {
|
||||
ob_make_permanent(d);
|
||||
ob_dereference_object(d); // drop the creation reference; name keeps it alive
|
||||
}
|
||||
}
|
||||
|
||||
// Seed a couple of named dispatcher objects so the namespace is not empty.
|
||||
ob_object_t* bno = ob_lookup_path("\\BaseNamedObjects");
|
||||
ob_object_t* evt = NULL;
|
||||
if (NT_SUCCESS(ob_create_object(OB_TYPE_EVENT, "SubOsBootEvent", bno, &evt)) && evt) {
|
||||
evt->body.event.manual_reset = true;
|
||||
ke_set_event(evt);
|
||||
ob_make_permanent(evt);
|
||||
ob_dereference_object(evt);
|
||||
}
|
||||
ob_object_t* sem = NULL;
|
||||
if (NT_SUCCESS(ob_create_object(OB_TYPE_SEMAPHORE, "SubOsIoSemaphore", bno, &sem)) && sem) {
|
||||
sem->body.sem.limit = 4;
|
||||
sem->body.sem.count = 4;
|
||||
ob_make_permanent(sem);
|
||||
ob_dereference_object(sem);
|
||||
}
|
||||
|
||||
uint32_t objs = 0, handles = 0, per[OB_TYPE_MAX];
|
||||
ob_get_stats(&objs, &handles, per);
|
||||
printk(ANSI_BRIGHT_GREEN "OB: " ANSI_RESET
|
||||
"NT Object Manager online (namespace root \\, %u objects, handle table %d slots)\n",
|
||||
objs, OB_HANDLE_MAX);
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SUB-OS NT-Inspired Configuration Registry (Cm)
|
||||
//
|
||||
// A key tree rooted at "\Registry" with typed values. Paths are backslash
|
||||
// separated and begin at the root key named "Registry". Intermediate keys are
|
||||
// created on demand by reg_create_key(); reg_open_key() only navigates.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include <kernel/nt/reg.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/sync.h>
|
||||
#include <init/version.h>
|
||||
#include <arch/arch.h>
|
||||
|
||||
static reg_key_t* g_reg_root = NULL;
|
||||
static uint32_t g_key_count = 0;
|
||||
static uint32_t g_value_count = 0;
|
||||
static spinlock_t g_reg_lock = SPINLOCK_INIT;
|
||||
|
||||
const char* reg_type_name(reg_type_t t) {
|
||||
switch (t) {
|
||||
case REG_SZ: return "REG_SZ";
|
||||
case REG_BINARY: return "REG_BINARY";
|
||||
case REG_DWORD: return "REG_DWORD";
|
||||
case REG_MULTI_SZ: return "REG_MULTI_SZ";
|
||||
case REG_QWORD: return "REG_QWORD";
|
||||
default: return "REG_NONE";
|
||||
}
|
||||
}
|
||||
|
||||
reg_key_t* reg_root(void) { return g_reg_root; }
|
||||
|
||||
// Caller holds g_reg_lock.
|
||||
static reg_key_t* key_alloc(const char* name, reg_key_t* parent) {
|
||||
reg_key_t* k = (reg_key_t*)kzalloc(sizeof(reg_key_t));
|
||||
if (!k) return NULL;
|
||||
strncpy(k->name, name ? name : "", REG_NAME_MAX - 1);
|
||||
k->parent = parent;
|
||||
g_key_count++;
|
||||
return k;
|
||||
}
|
||||
|
||||
// Caller holds g_reg_lock.
|
||||
static reg_key_t* key_find_child(reg_key_t* key, const char* name) {
|
||||
if (!key) return NULL;
|
||||
for (int i = 0; i < key->child_count; i++) {
|
||||
if (strcmp(key->children[i]->name, name) == 0) return key->children[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Split a path and walk it, optionally creating missing keys. The first path
|
||||
// component must be "Registry" (the root key's name).
|
||||
static reg_key_t* walk_path(const char* path, bool create) {
|
||||
if (!path || !g_reg_root) return NULL;
|
||||
|
||||
char buf[192];
|
||||
strncpy(buf, path, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
reg_key_t* cur = g_reg_root;
|
||||
char* p = buf;
|
||||
if (*p == '\\') p++;
|
||||
|
||||
// First component names the root itself.
|
||||
char* first = p;
|
||||
while (*p && *p != '\\') p++;
|
||||
if (*p == '\\') { *p = '\0'; p++; }
|
||||
if (strcmp(first, g_reg_root->name) != 0) {
|
||||
return NULL; // path is not anchored at "\Registry"
|
||||
}
|
||||
|
||||
while (*p) {
|
||||
char* seg = p;
|
||||
while (*p && *p != '\\') p++;
|
||||
if (*p == '\\') { *p = '\0'; p++; }
|
||||
if (!*seg) continue;
|
||||
|
||||
reg_key_t* child = key_find_child(cur, seg);
|
||||
if (!child) {
|
||||
if (!create) return NULL;
|
||||
if (cur->child_count >= REG_MAX_CHILDREN) return NULL;
|
||||
child = key_alloc(seg, cur);
|
||||
if (!child) return NULL;
|
||||
cur->children[cur->child_count++] = child;
|
||||
}
|
||||
cur = child;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
reg_key_t* reg_create_key(const char* path) {
|
||||
spin_lock(&g_reg_lock);
|
||||
reg_key_t* k = walk_path(path, true);
|
||||
spin_unlock(&g_reg_lock);
|
||||
return k;
|
||||
}
|
||||
|
||||
reg_key_t* reg_open_key(const char* path) {
|
||||
spin_lock(&g_reg_lock);
|
||||
reg_key_t* k = walk_path(path, false);
|
||||
spin_unlock(&g_reg_lock);
|
||||
return k;
|
||||
}
|
||||
|
||||
// Caller holds g_reg_lock.
|
||||
static reg_value_t* value_get_or_add(reg_key_t* key, const char* name) {
|
||||
for (int i = 0; i < key->value_count; i++) {
|
||||
if (strcmp(key->values[i]->name, name) == 0) return key->values[i];
|
||||
}
|
||||
if (key->value_count >= REG_MAX_VALUES) return NULL;
|
||||
reg_value_t* v = (reg_value_t*)kzalloc(sizeof(reg_value_t));
|
||||
if (!v) return NULL;
|
||||
strncpy(v->name, name, REG_NAME_MAX - 1);
|
||||
key->values[key->value_count++] = v;
|
||||
g_value_count++;
|
||||
return v;
|
||||
}
|
||||
|
||||
reg_value_t* reg_set_dword(reg_key_t* key, const char* name, uint32_t value) {
|
||||
if (!key || !name) return NULL;
|
||||
spin_lock(&g_reg_lock);
|
||||
reg_value_t* v = value_get_or_add(key, name);
|
||||
if (v) {
|
||||
v->type = REG_DWORD;
|
||||
v->data.dword = value;
|
||||
v->data_len = sizeof(uint32_t);
|
||||
}
|
||||
spin_unlock(&g_reg_lock);
|
||||
return v;
|
||||
}
|
||||
|
||||
reg_value_t* reg_set_qword(reg_key_t* key, const char* name, uint64_t value) {
|
||||
if (!key || !name) return NULL;
|
||||
spin_lock(&g_reg_lock);
|
||||
reg_value_t* v = value_get_or_add(key, name);
|
||||
if (v) {
|
||||
v->type = REG_QWORD;
|
||||
v->data.qword = value;
|
||||
v->data_len = sizeof(uint64_t);
|
||||
}
|
||||
spin_unlock(&g_reg_lock);
|
||||
return v;
|
||||
}
|
||||
|
||||
reg_value_t* reg_set_sz(reg_key_t* key, const char* name, const char* str) {
|
||||
if (!key || !name) return NULL;
|
||||
spin_lock(&g_reg_lock);
|
||||
reg_value_t* v = value_get_or_add(key, name);
|
||||
if (v) {
|
||||
v->type = REG_SZ;
|
||||
strncpy(v->data.str, str ? str : "", REG_STR_MAX - 1);
|
||||
v->data.str[REG_STR_MAX - 1] = '\0';
|
||||
v->data_len = (uint32_t)strlen(v->data.str) + 1;
|
||||
}
|
||||
spin_unlock(&g_reg_lock);
|
||||
return v;
|
||||
}
|
||||
|
||||
reg_value_t* reg_query_value(reg_key_t* key, const char* name) {
|
||||
if (!key || !name) return NULL;
|
||||
spin_lock(&g_reg_lock);
|
||||
reg_value_t* found = NULL;
|
||||
for (int i = 0; i < key->value_count; i++) {
|
||||
if (strcmp(key->values[i]->name, name) == 0) { found = key->values[i]; break; }
|
||||
}
|
||||
spin_unlock(&g_reg_lock);
|
||||
return found;
|
||||
}
|
||||
|
||||
int reg_enum_key_count(reg_key_t* key) { return key ? key->child_count : 0; }
|
||||
reg_key_t* reg_enum_key(reg_key_t* key, int index) {
|
||||
if (!key || index < 0 || index >= key->child_count) return NULL;
|
||||
return key->children[index];
|
||||
}
|
||||
int reg_enum_value_count(reg_key_t* key) { return key ? key->value_count : 0; }
|
||||
reg_value_t* reg_enum_value(reg_key_t* key, int index) {
|
||||
if (!key || index < 0 || index >= key->value_count) return NULL;
|
||||
return key->values[index];
|
||||
}
|
||||
|
||||
void reg_value_to_string(const reg_value_t* v, char* out, size_t out_len) {
|
||||
if (!v || !out || out_len == 0) return;
|
||||
switch (v->type) {
|
||||
case REG_SZ:
|
||||
case REG_MULTI_SZ:
|
||||
snprintf(out, out_len, "%s", v->data.str);
|
||||
break;
|
||||
case REG_DWORD:
|
||||
snprintf(out, out_len, "0x%08X (%u)", v->data.dword, v->data.dword);
|
||||
break;
|
||||
case REG_QWORD:
|
||||
snprintf(out, out_len, "0x%016llX", (unsigned long long)v->data.qword);
|
||||
break;
|
||||
case REG_BINARY:
|
||||
snprintf(out, out_len, "<%u bytes>", v->data_len);
|
||||
break;
|
||||
default:
|
||||
snprintf(out, out_len, "(none)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void reg_get_stats(uint32_t* out_keys, uint32_t* out_values) {
|
||||
if (out_keys) *out_keys = g_key_count;
|
||||
if (out_values) *out_values = g_value_count;
|
||||
}
|
||||
|
||||
void reg_init(void) {
|
||||
g_key_count = 0;
|
||||
g_value_count = 0;
|
||||
|
||||
g_reg_root = key_alloc("Registry", NULL);
|
||||
if (!g_reg_root) {
|
||||
printk(KERN_ERR "REG: failed to allocate the registry root\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Standard hives.
|
||||
reg_create_key("\\Registry\\Machine");
|
||||
reg_create_key("\\Registry\\User");
|
||||
|
||||
// Hardware identification.
|
||||
reg_key_t* sys = reg_create_key("\\Registry\\Machine\\Hardware\\Description\\System");
|
||||
if (sys) {
|
||||
reg_set_sz(sys, "Identifier", "SUB-OS Modular Monolithic Kernel");
|
||||
reg_set_sz(sys, "SystemBootDevice", "\\Device\\Harddisk0\\Partition1");
|
||||
}
|
||||
|
||||
// Control set.
|
||||
reg_key_t* ctl = reg_create_key("\\Registry\\Machine\\System\\CurrentControlSet\\Control");
|
||||
if (ctl) {
|
||||
reg_set_sz(ctl, "SystemStartOptions", "NOEXECUTE=OPTIN QUIET");
|
||||
reg_set_dword(ctl, "PreemptiveScheduler", 1);
|
||||
}
|
||||
|
||||
// SUB-OS software hive.
|
||||
reg_key_t* sw = reg_create_key("\\Registry\\Machine\\Software\\SUB-OS");
|
||||
if (sw) {
|
||||
reg_set_sz(sw, "Version", kernel_get_version());
|
||||
reg_set_sz(sw, "BuildArch", arch_get_name());
|
||||
reg_set_dword(sw, "GrowableHeap", 1);
|
||||
reg_set_dword(sw, "ObjectManager", 1);
|
||||
reg_set_qword(sw, "InstallCookie", 0x5355424F53000001ULL);
|
||||
}
|
||||
|
||||
// Default user profile.
|
||||
reg_key_t* du = reg_create_key("\\Registry\\User\\.DEFAULT\\Environment");
|
||||
if (du) {
|
||||
reg_set_sz(du, "PATH", "/bin:/sbin:/usr/bin");
|
||||
reg_set_sz(du, "SHELL", "/bin/sh");
|
||||
}
|
||||
|
||||
printk(ANSI_BRIGHT_GREEN "REG: " ANSI_RESET
|
||||
"NT Configuration Registry online (%u keys, %u values under \\Registry)\n",
|
||||
g_key_count, g_value_count);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Unified monotonic high-resolution clock for SUB-OS.
|
||||
//
|
||||
// When the HPET is available it provides true nanosecond timing; otherwise the
|
||||
// clock degrades gracefully to the 100 Hz kernel timer tick (10 ms resolution).
|
||||
// Either way the epoch is the moment ktime_init() ran.
|
||||
|
||||
#include <kernel/ktime.h>
|
||||
#include <drivers/hpet.h>
|
||||
#include <arch/arch.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#define TICK_HZ 100ULL
|
||||
#define NS_PER_TICK (1000000000ULL / TICK_HZ) // 10,000,000 ns at 100 Hz
|
||||
|
||||
static bool g_use_hpet = false;
|
||||
static uint64_t g_base_tick = 0;
|
||||
|
||||
void ktime_init(void) {
|
||||
g_use_hpet = hpet_available();
|
||||
g_base_tick = pit_get_ticks();
|
||||
printk(ANSI_BRIGHT_GREEN "KTIME: " ANSI_RESET
|
||||
"Monotonic clock online (source: %s%s)\n",
|
||||
g_use_hpet ? "HPET" : "PIT 100 Hz",
|
||||
g_use_hpet ? ", nanosecond resolution" : "");
|
||||
}
|
||||
|
||||
uint64_t ktime_ns(void) {
|
||||
if (g_use_hpet) {
|
||||
return hpet_get_ns();
|
||||
}
|
||||
uint64_t ticks = pit_get_ticks() - g_base_tick;
|
||||
return ticks * NS_PER_TICK;
|
||||
}
|
||||
|
||||
uint64_t ktime_us(void) { return ktime_ns() / 1000ULL; }
|
||||
uint64_t ktime_ms(void) { return ktime_ns() / 1000000ULL; }
|
||||
|
||||
const char* ktime_source(void) { return g_use_hpet ? "HPET" : "PIT"; }
|
||||
bool ktime_is_highres(void) { return g_use_hpet; }
|
||||
|
||||
void ktime_delay_us(uint64_t us) {
|
||||
if (us == 0) return;
|
||||
// With the HPET we can spin precisely; on the PIT the 10 ms tick is far
|
||||
// coarser than a microsecond, so cap the spin so we never wedge the CPU.
|
||||
if (!g_use_hpet) {
|
||||
uint64_t deadline_ms = ktime_ms() + (us + 999) / 1000;
|
||||
uint64_t guard = 0;
|
||||
while (ktime_ms() < deadline_ms && guard++ < 100000000ULL) {
|
||||
/* spin */
|
||||
}
|
||||
return;
|
||||
}
|
||||
uint64_t start = ktime_ns();
|
||||
uint64_t target = start + us * 1000ULL;
|
||||
while (ktime_ns() < target) {
|
||||
/* spin on the HPET counter */
|
||||
}
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Image decoder: PNG (8-bit grayscale / RGB / RGBA / palette, non-interlaced)
|
||||
* and BMP (24/32-bit uncompressed). Output is a flat 0xAARRGGBB buffer ready
|
||||
* for gui_gfx_blit, so the Web app can show pictures.
|
||||
*
|
||||
* PNG = parse chunks, concatenate IDAT, zlib-inflate, undo the per-scanline
|
||||
* filters, then expand to ARGB. All integer, heap-allocated (never on the
|
||||
* worker's small stack).
|
||||
*/
|
||||
#include <lib/image.h>
|
||||
#include <lib/inflate.h>
|
||||
#include <lib/jpeg.h>
|
||||
#include <lib/string.h>
|
||||
#include <mm/kmalloc.h>
|
||||
|
||||
// Cap the pixel count so a hostile or huge image cannot exhaust the heap.
|
||||
#define IMAGE_MAX_PIXELS (4 * 1024 * 1024) // 4 Mpx -> 16 MB output max
|
||||
|
||||
static const uint8_t png_sig[8] = { 0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
|
||||
bool image_sniff(const uint8_t* d, unsigned len) {
|
||||
if (len >= 8 && memcmp(d, png_sig, 8) == 0) return true;
|
||||
if (len >= 3 && d[0] == 0xFF && d[1] == 0xD8 && d[2] == 0xFF) return true; // JPEG
|
||||
if (len >= 2 && d[0] == 'B' && d[1] == 'M') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void image_free(image_t* img) {
|
||||
if (img && img->pixels) { kfree(img->pixels); img->pixels = NULL; }
|
||||
}
|
||||
|
||||
static inline uint32_t rd32be(const uint8_t* p) {
|
||||
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
|
||||
((uint32_t)p[2] << 8) | (uint32_t)p[3];
|
||||
}
|
||||
static inline uint32_t rd32le(const uint8_t* p) {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
static inline uint32_t argb(uint8_t a, uint8_t r, uint8_t g, uint8_t b) {
|
||||
return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// PNG
|
||||
// ===========================================================================
|
||||
static int iabs(int x) { return x < 0 ? -x : x; }
|
||||
|
||||
static int paeth(int a, int b, int c) {
|
||||
int p = a + b - c;
|
||||
int pa = iabs(p - a), pb = iabs(p - b), pc = iabs(p - c);
|
||||
if (pa <= pb && pa <= pc) return a;
|
||||
if (pb <= pc) return b;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int decode_png(const uint8_t* data, unsigned len, image_t* img) {
|
||||
if (len < 8 + 25 || memcmp(data, png_sig, 8) != 0) return -1;
|
||||
|
||||
unsigned pos = 8;
|
||||
int width = 0, height = 0, depth = 0, color = 0, interlace = 0;
|
||||
uint8_t palette[256][3];
|
||||
uint8_t trns[256];
|
||||
int have_plte = 0, plte_n = 0, have_trns = 0, trns_n = 0;
|
||||
memset(trns, 255, sizeof(trns));
|
||||
|
||||
// Compressed IDAT is gathered into one buffer.
|
||||
uint8_t* idat = NULL;
|
||||
unsigned idat_len = 0, idat_cap = 0;
|
||||
|
||||
for (;;) {
|
||||
if (pos + 8 > len) { if (idat) kfree(idat); return -1; }
|
||||
uint32_t clen = rd32be(data + pos);
|
||||
const uint8_t* ctype = data + pos + 4;
|
||||
const uint8_t* cdata = data + pos + 8;
|
||||
if (pos + 12 + clen > len) { if (idat) kfree(idat); return -1; }
|
||||
|
||||
if (memcmp(ctype, "IHDR", 4) == 0) {
|
||||
if (clen < 13) { if (idat) kfree(idat); return -1; }
|
||||
width = (int)rd32be(cdata);
|
||||
height = (int)rd32be(cdata + 4);
|
||||
depth = cdata[8];
|
||||
color = cdata[9];
|
||||
interlace = cdata[12];
|
||||
if (width <= 0 || height <= 0) { if (idat) kfree(idat); return -1; }
|
||||
if ((long)width * height > IMAGE_MAX_PIXELS) { if (idat) kfree(idat); return -1; }
|
||||
if (interlace != 0) { if (idat) kfree(idat); return -1; } // no Adam7
|
||||
if (color != 0 && color != 2 && color != 3 && color != 4 && color != 6) {
|
||||
if (idat) kfree(idat);
|
||||
return -1;
|
||||
}
|
||||
// Grayscale (0) and palette (3) may use 1/2/4/8-bit samples; the
|
||||
// truecolor types are supported at 8-bit only (16-bit rejected).
|
||||
bool sub_ok = (color == 0 || color == 3) &&
|
||||
(depth == 1 || depth == 2 || depth == 4 || depth == 8);
|
||||
if (depth != 8 && !sub_ok) { if (idat) kfree(idat); return -1; }
|
||||
} else if (memcmp(ctype, "PLTE", 4) == 0) {
|
||||
plte_n = clen / 3;
|
||||
if (plte_n > 256) plte_n = 256;
|
||||
for (int i = 0; i < plte_n; i++) {
|
||||
palette[i][0] = cdata[i * 3 + 0];
|
||||
palette[i][1] = cdata[i * 3 + 1];
|
||||
palette[i][2] = cdata[i * 3 + 2];
|
||||
}
|
||||
have_plte = 1;
|
||||
} else if (memcmp(ctype, "tRNS", 4) == 0) {
|
||||
trns_n = clen > 256 ? 256 : (int)clen;
|
||||
for (int i = 0; i < trns_n; i++) trns[i] = cdata[i];
|
||||
have_trns = 1;
|
||||
} else if (memcmp(ctype, "IDAT", 4) == 0) {
|
||||
if (idat_len + clen > idat_cap) {
|
||||
unsigned ncap = (idat_len + clen) * 2 + 1024;
|
||||
uint8_t* n = (uint8_t*)kmalloc(ncap);
|
||||
if (!n) { if (idat) kfree(idat); return -1; }
|
||||
if (idat) { memcpy(n, idat, idat_len); kfree(idat); }
|
||||
idat = n; idat_cap = ncap;
|
||||
}
|
||||
memcpy(idat + idat_len, cdata, clen);
|
||||
idat_len += clen;
|
||||
} else if (memcmp(ctype, "IEND", 4) == 0) {
|
||||
break;
|
||||
}
|
||||
pos += 12 + clen; // length + type + data + crc
|
||||
}
|
||||
|
||||
if (!idat || width == 0) { if (idat) kfree(idat); return -1; }
|
||||
if (color == 3 && !have_plte) { kfree(idat); return -1; }
|
||||
|
||||
int channels = (color == 0) ? 1 : (color == 2) ? 3 : (color == 3) ? 1 :
|
||||
(color == 4) ? 2 : 4;
|
||||
int bits_pp = channels * depth;
|
||||
int bpp = (bits_pp + 7) / 8; // filter unit, >= 1 byte
|
||||
if (bpp < 1) bpp = 1;
|
||||
unsigned stride = ((unsigned)width * bits_pp + 7) / 8;
|
||||
unsigned raw_len = (stride + 1) * (unsigned)height; // +1 filter byte per row
|
||||
|
||||
uint8_t* raw = (uint8_t*)kmalloc(raw_len);
|
||||
if (!raw) { kfree(idat); return -1; }
|
||||
|
||||
unsigned got = raw_len;
|
||||
int ir = inflate_zlib(raw, &got, idat, idat_len);
|
||||
kfree(idat);
|
||||
if (ir != 0 || got != raw_len) { kfree(raw); return -1; }
|
||||
|
||||
// Unfilter each scanline in place, writing reconstructed bytes over the
|
||||
// filtered ones (the filter byte at the row start is consumed).
|
||||
uint8_t* unf = (uint8_t*)kmalloc(stride * (unsigned)height);
|
||||
if (!unf) { kfree(raw); return -1; }
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
uint8_t filter = raw[y * (stride + 1)];
|
||||
const uint8_t* rowin = raw + y * (stride + 1) + 1;
|
||||
uint8_t* row = unf + (unsigned)y * stride;
|
||||
uint8_t* prev = (y > 0) ? unf + (unsigned)(y - 1) * stride : NULL;
|
||||
|
||||
for (unsigned x = 0; x < stride; x++) {
|
||||
int a = (x >= (unsigned)bpp) ? row[x - bpp] : 0;
|
||||
int b = prev ? prev[x] : 0;
|
||||
int c = (prev && x >= (unsigned)bpp) ? prev[x - bpp] : 0;
|
||||
int val = rowin[x];
|
||||
switch (filter) {
|
||||
case 0: break; // None
|
||||
case 1: val += a; break; // Sub
|
||||
case 2: val += b; break; // Up
|
||||
case 3: val += (a + b) / 2; break; // Average
|
||||
case 4: val += paeth(a, b, c); break;// Paeth
|
||||
default: kfree(raw); kfree(unf); return -1;
|
||||
}
|
||||
row[x] = (uint8_t)val;
|
||||
}
|
||||
}
|
||||
kfree(raw);
|
||||
|
||||
uint32_t* out = (uint32_t*)kmalloc((unsigned)width * height * 4);
|
||||
if (!out) { kfree(unf); return -1; }
|
||||
|
||||
int maxv = (1 << depth) - 1;
|
||||
for (int y = 0; y < height; y++) {
|
||||
const uint8_t* row = unf + (unsigned)y * stride;
|
||||
uint32_t* dst = out + (unsigned)y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (depth < 8) {
|
||||
// Grayscale or palette with sub-byte samples, MSB-first.
|
||||
int bitpos = x * depth;
|
||||
int sample = (row[bitpos >> 3] >> (8 - depth - (bitpos & 7))) & maxv;
|
||||
if (color == 3) {
|
||||
int idx = sample;
|
||||
if (idx >= plte_n) idx = 0;
|
||||
uint8_t alpha = (have_trns && idx < trns_n) ? trns[idx] : 255;
|
||||
dst[x] = argb(alpha, palette[idx][0], palette[idx][1], palette[idx][2]);
|
||||
} else {
|
||||
uint8_t g = (uint8_t)(sample * 255 / maxv);
|
||||
dst[x] = argb(255, g, g, g);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t* px = row + x * bpp;
|
||||
switch (color) {
|
||||
case 0: dst[x] = argb(255, px[0], px[0], px[0]); break; // gray
|
||||
case 4: dst[x] = argb(px[1], px[0], px[0], px[0]); break; // gray+alpha
|
||||
case 2: dst[x] = argb(255, px[0], px[1], px[2]); break; // RGB
|
||||
case 6: dst[x] = argb(px[3], px[0], px[1], px[2]); break; // RGBA
|
||||
case 3: { // palette
|
||||
int idx = px[0];
|
||||
if (idx >= plte_n) idx = 0;
|
||||
uint8_t alpha = (have_trns && idx < trns_n) ? trns[idx] : 255;
|
||||
dst[x] = argb(alpha, palette[idx][0], palette[idx][1], palette[idx][2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
kfree(unf);
|
||||
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->pixels = out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// BMP (24/32-bit uncompressed, BI_RGB)
|
||||
// ===========================================================================
|
||||
static int decode_bmp(const uint8_t* data, unsigned len, image_t* img) {
|
||||
if (len < 54 || data[0] != 'B' || data[1] != 'M') return -1;
|
||||
uint32_t offset = rd32le(data + 10);
|
||||
uint32_t dib = rd32le(data + 14);
|
||||
int width = (int)rd32le(data + 18);
|
||||
int height = (int)rd32le(data + 22);
|
||||
int bpp = data[28] | (data[29] << 8);
|
||||
uint32_t compression = rd32le(data + 30);
|
||||
if (dib < 40 || compression != 0) return -1;
|
||||
if (bpp != 24 && bpp != 32) return -1;
|
||||
|
||||
bool top_down = height < 0;
|
||||
if (top_down) height = -height;
|
||||
if (width <= 0 || height <= 0) return -1;
|
||||
if ((long)width * height > IMAGE_MAX_PIXELS) return -1;
|
||||
|
||||
int bytes_pp = bpp / 8;
|
||||
unsigned row_size = (((unsigned)width * bytes_pp + 3) & ~3u);
|
||||
if (offset + row_size * (unsigned)height > len) return -1;
|
||||
|
||||
uint32_t* out = (uint32_t*)kmalloc((unsigned)width * height * 4);
|
||||
if (!out) return -1;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
int srow = top_down ? y : (height - 1 - y);
|
||||
const uint8_t* row = data + offset + (unsigned)srow * row_size;
|
||||
uint32_t* dst = out + (unsigned)y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
const uint8_t* px = row + x * bytes_pp;
|
||||
uint8_t a = (bytes_pp == 4) ? px[3] : 255;
|
||||
dst[x] = argb(a, px[2], px[1], px[0]); // BMP is BGR(A)
|
||||
}
|
||||
}
|
||||
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->pixels = out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
int image_decode(const uint8_t* data, unsigned len, image_t* img) {
|
||||
if (!data || !img) return -1;
|
||||
img->width = img->height = 0;
|
||||
img->pixels = NULL;
|
||||
|
||||
if (len >= 8 && memcmp(data, png_sig, 8) == 0) return decode_png(data, len, img);
|
||||
if (len >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF)
|
||||
return jpeg_decode(data, len, img);
|
||||
if (len >= 2 && data[0] == 'B' && data[1] == 'M') return decode_bmp(data, len, img);
|
||||
return -1;
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* DEFLATE / zlib decompressor (RFC 1950/1951).
|
||||
*
|
||||
* A compact, self-contained inflate -- the algorithm every PNG needs. No
|
||||
* allocation, no floating point: it decodes straight into a caller-provided
|
||||
* output buffer, so it is safe in the freestanding kernel. Structure follows
|
||||
* the well-known "tinf" design (canonical Huffman tables, three block types).
|
||||
*/
|
||||
#include <lib/inflate.h>
|
||||
#include <lib/string.h>
|
||||
|
||||
#define TINF_OK 0
|
||||
#define TINF_DATA_ERROR (-3)
|
||||
#define TINF_BUF_ERROR (-5)
|
||||
|
||||
typedef struct {
|
||||
uint16_t counts[16]; // number of codes of each length
|
||||
uint16_t symbols[288]; // symbols sorted by code
|
||||
} tinf_tree;
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* src;
|
||||
const uint8_t* src_end;
|
||||
uint32_t tag;
|
||||
int bitcount;
|
||||
|
||||
uint8_t* dst;
|
||||
uint8_t* dst_start;
|
||||
uint8_t* dst_end;
|
||||
|
||||
tinf_tree ltree; // literal/length codes
|
||||
tinf_tree dtree; // distance codes
|
||||
} tinf;
|
||||
|
||||
// Special ordering of code-length codes (RFC 1951 3.2.7).
|
||||
static const uint8_t clcidx[19] = {
|
||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
|
||||
};
|
||||
|
||||
static const uint16_t length_base[30] = {
|
||||
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0
|
||||
};
|
||||
static const uint8_t length_bits[30] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0
|
||||
};
|
||||
static const uint16_t dist_base[30] = {
|
||||
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577
|
||||
};
|
||||
static const uint8_t dist_bits[30] = {
|
||||
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
|
||||
};
|
||||
|
||||
static int getbit(tinf* d) {
|
||||
if (d->bitcount == 0) {
|
||||
if (d->src >= d->src_end) return -1; // underflow flagged as a set bit? no: caller checks
|
||||
d->tag = *d->src++;
|
||||
d->bitcount = 8;
|
||||
}
|
||||
int bit = d->tag & 1;
|
||||
d->tag >>= 1;
|
||||
d->bitcount--;
|
||||
return bit;
|
||||
}
|
||||
|
||||
// Read `num` bits LSB-first and add `base`.
|
||||
static int read_bits(tinf* d, int num, int base) {
|
||||
int val = 0;
|
||||
for (int i = 0; i < num; i++) {
|
||||
int b = getbit(d);
|
||||
if (b < 0) return -1;
|
||||
val |= b << i;
|
||||
}
|
||||
return base + val;
|
||||
}
|
||||
|
||||
// Build a canonical Huffman tree from a list of code lengths.
|
||||
static void build_tree(tinf_tree* t, const uint8_t* lengths, unsigned num) {
|
||||
unsigned offs[16];
|
||||
for (int i = 0; i < 16; i++) t->counts[i] = 0;
|
||||
for (unsigned i = 0; i < num; i++) t->counts[lengths[i]]++;
|
||||
t->counts[0] = 0;
|
||||
|
||||
offs[0] = 0;
|
||||
for (int i = 1; i < 16; i++) offs[i] = offs[i - 1] + t->counts[i - 1];
|
||||
for (unsigned i = 0; i < num; i++)
|
||||
if (lengths[i]) t->symbols[offs[lengths[i]]++] = (uint16_t)i;
|
||||
}
|
||||
|
||||
static void build_fixed_trees(tinf_tree* lt, tinf_tree* dt) {
|
||||
uint8_t lengths[288];
|
||||
int i;
|
||||
for (i = 0; i < 144; i++) lengths[i] = 8;
|
||||
for (; i < 256; i++) lengths[i] = 9;
|
||||
for (; i < 280; i++) lengths[i] = 7;
|
||||
for (; i < 288; i++) lengths[i] = 8;
|
||||
build_tree(lt, lengths, 288);
|
||||
|
||||
for (i = 0; i < 30; i++) lengths[i] = 5;
|
||||
build_tree(dt, lengths, 30);
|
||||
}
|
||||
|
||||
static int decode_symbol(tinf* d, const tinf_tree* t) {
|
||||
int sum = 0, cur = 0, len = 0;
|
||||
do {
|
||||
int b = getbit(d);
|
||||
if (b < 0) return -1;
|
||||
cur = 2 * cur + b;
|
||||
len++;
|
||||
if (len >= 16) return -1;
|
||||
sum += t->counts[len];
|
||||
cur -= t->counts[len];
|
||||
} while (cur >= 0);
|
||||
int idx = sum + cur;
|
||||
if (idx < 0 || idx >= 288) return -1;
|
||||
return t->symbols[idx];
|
||||
}
|
||||
|
||||
// Decode the dynamic Huffman trees at the start of a type-2 block.
|
||||
static int decode_trees(tinf* d, tinf_tree* lt, tinf_tree* dt) {
|
||||
uint8_t lengths[288 + 32];
|
||||
|
||||
int hlit = read_bits(d, 5, 257);
|
||||
int hdist = read_bits(d, 5, 1);
|
||||
int hclen = read_bits(d, 4, 4);
|
||||
if (hlit < 0 || hdist < 0 || hclen < 0) return TINF_DATA_ERROR;
|
||||
if (hlit > 286 || hdist > 30) return TINF_DATA_ERROR;
|
||||
|
||||
for (int i = 0; i < 19; i++) lengths[i] = 0;
|
||||
for (int i = 0; i < hclen; i++) {
|
||||
int clen = read_bits(d, 3, 0);
|
||||
if (clen < 0) return TINF_DATA_ERROR;
|
||||
lengths[clcidx[i]] = (uint8_t)clen;
|
||||
}
|
||||
|
||||
tinf_tree code_tree;
|
||||
build_tree(&code_tree, lengths, 19);
|
||||
|
||||
int num = 0;
|
||||
while (num < hlit + hdist) {
|
||||
int sym = decode_symbol(d, &code_tree);
|
||||
if (sym < 0) return TINF_DATA_ERROR;
|
||||
switch (sym) {
|
||||
case 16: { // copy previous length 3..6 times
|
||||
if (num == 0) return TINF_DATA_ERROR;
|
||||
int prev = lengths[num - 1];
|
||||
int n = read_bits(d, 2, 3);
|
||||
if (n < 0) return TINF_DATA_ERROR;
|
||||
while (n-- && num < hlit + hdist) lengths[num++] = (uint8_t)prev;
|
||||
break;
|
||||
}
|
||||
case 17: { // repeat 0 for 3..10 times
|
||||
int n = read_bits(d, 3, 3);
|
||||
if (n < 0) return TINF_DATA_ERROR;
|
||||
while (n-- && num < hlit + hdist) lengths[num++] = 0;
|
||||
break;
|
||||
}
|
||||
case 18: { // repeat 0 for 11..138 times
|
||||
int n = read_bits(d, 7, 11);
|
||||
if (n < 0) return TINF_DATA_ERROR;
|
||||
while (n-- && num < hlit + hdist) lengths[num++] = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (sym > 15) return TINF_DATA_ERROR;
|
||||
lengths[num++] = (uint8_t)sym;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
build_tree(lt, lengths, hlit);
|
||||
build_tree(dt, lengths + hlit, hdist);
|
||||
return TINF_OK;
|
||||
}
|
||||
|
||||
static int inflate_block_data(tinf* d, const tinf_tree* lt, const tinf_tree* dt) {
|
||||
for (;;) {
|
||||
int sym = decode_symbol(d, lt);
|
||||
if (sym < 0) return TINF_DATA_ERROR;
|
||||
|
||||
if (sym == 256) return TINF_OK; // end of block
|
||||
|
||||
if (sym < 256) { // literal byte
|
||||
if (d->dst >= d->dst_end) return TINF_BUF_ERROR;
|
||||
*d->dst++ = (uint8_t)sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
// length/distance pair
|
||||
sym -= 257;
|
||||
if (sym >= 29) return TINF_DATA_ERROR;
|
||||
int length = read_bits(d, length_bits[sym], length_base[sym]);
|
||||
if (length < 0) return TINF_DATA_ERROR;
|
||||
|
||||
int dsym = decode_symbol(d, dt);
|
||||
if (dsym < 0 || dsym >= 30) return TINF_DATA_ERROR;
|
||||
int dist = read_bits(d, dist_bits[dsym], dist_base[dsym]);
|
||||
if (dist < 0) return TINF_DATA_ERROR;
|
||||
|
||||
uint8_t* from = d->dst - dist;
|
||||
if (from < d->dst_start) return TINF_DATA_ERROR;
|
||||
if (d->dst + length > d->dst_end) return TINF_BUF_ERROR;
|
||||
for (int i = 0; i < length; i++) *d->dst++ = *from++;
|
||||
}
|
||||
}
|
||||
|
||||
static int inflate_uncompressed_block(tinf* d) {
|
||||
d->bitcount = 0; // byte-align
|
||||
if (d->src + 4 > d->src_end) return TINF_DATA_ERROR;
|
||||
unsigned len = d->src[0] | (d->src[1] << 8);
|
||||
unsigned nlen = d->src[2] | (d->src[3] << 8);
|
||||
d->src += 4;
|
||||
if ((len ^ 0xffff) != nlen) return TINF_DATA_ERROR;
|
||||
if (d->src + len > d->src_end) return TINF_DATA_ERROR;
|
||||
if (d->dst + len > d->dst_end) return TINF_BUF_ERROR;
|
||||
for (unsigned i = 0; i < len; i++) *d->dst++ = *d->src++;
|
||||
return TINF_OK;
|
||||
}
|
||||
|
||||
// Decompress a raw DEFLATE stream. On success *dest_len is the byte count.
|
||||
int inflate_raw(uint8_t* dest, unsigned* dest_len,
|
||||
const uint8_t* src, unsigned src_len) {
|
||||
tinf d;
|
||||
d.src = src; d.src_end = src + src_len;
|
||||
d.tag = 0; d.bitcount = 0;
|
||||
d.dst = dest; d.dst_start = dest; d.dst_end = dest + *dest_len;
|
||||
|
||||
int bfinal;
|
||||
do {
|
||||
bfinal = getbit(&d);
|
||||
if (bfinal < 0) return TINF_DATA_ERROR;
|
||||
int btype = read_bits(&d, 2, 0);
|
||||
int res;
|
||||
switch (btype) {
|
||||
case 0:
|
||||
res = inflate_uncompressed_block(&d);
|
||||
break;
|
||||
case 1:
|
||||
build_fixed_trees(&d.ltree, &d.dtree);
|
||||
res = inflate_block_data(&d, &d.ltree, &d.dtree);
|
||||
break;
|
||||
case 2:
|
||||
res = decode_trees(&d, &d.ltree, &d.dtree);
|
||||
if (res == TINF_OK) res = inflate_block_data(&d, &d.ltree, &d.dtree);
|
||||
break;
|
||||
default:
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
if (res != TINF_OK) return res;
|
||||
} while (!bfinal);
|
||||
|
||||
*dest_len = (unsigned)(d.dst - d.dst_start);
|
||||
return TINF_OK;
|
||||
}
|
||||
|
||||
// Decompress a zlib stream (2-byte header, deflate body, 4-byte adler trailer).
|
||||
int inflate_zlib(uint8_t* dest, unsigned* dest_len,
|
||||
const uint8_t* src, unsigned src_len) {
|
||||
if (src_len < 2) return TINF_DATA_ERROR;
|
||||
int cmf = src[0], flg = src[1];
|
||||
if ((cmf & 0x0f) != 8) return TINF_DATA_ERROR; // not deflate
|
||||
if (((cmf << 8) | flg) % 31 != 0) return TINF_DATA_ERROR;
|
||||
if (flg & 0x20) return TINF_DATA_ERROR; // preset dictionary unsupported
|
||||
return inflate_raw(dest, dest_len, src + 2, src_len - 2);
|
||||
}
|
||||
+470
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
* Baseline JPEG decoder (ITU-T T.81, SOF0 sequential DCT).
|
||||
*
|
||||
* The web's photo format. Supports grayscale and YCbCr, arbitrary chroma
|
||||
* subsampling, restart markers, and the standard integer IDCT -- all integer
|
||||
* arithmetic, safe in the freestanding kernel. Progressive JPEG (SOF2) and
|
||||
* arithmetic coding are rejected. Output is a flat 0xAARRGGBB buffer, so it
|
||||
* plugs into the same image path as PNG and BMP.
|
||||
*
|
||||
* The Huffman decode uses canonical tables (counts + symbols), the way the
|
||||
* inflate decoder does, to avoid a large fast-table. The integer row/column
|
||||
* IDCT is the well-known fixed-point transform from the JPEG reference.
|
||||
*/
|
||||
#include <lib/jpeg.h>
|
||||
#include <lib/string.h>
|
||||
#include <mm/kmalloc.h>
|
||||
|
||||
#define JPEG_MAX_PIXELS (4 * 1024 * 1024)
|
||||
|
||||
typedef struct {
|
||||
uint8_t counts[17]; // number of codes of each length 1..16
|
||||
uint8_t symbols[256];
|
||||
int first_code[17]; // first canonical code of each length
|
||||
int sym_index[17]; // index into symbols[] for each length
|
||||
int maxlen;
|
||||
} huff_t;
|
||||
|
||||
typedef struct {
|
||||
int cid;
|
||||
int ssx, ssy; // sampling factors
|
||||
int qtsel;
|
||||
int dctab, actab;
|
||||
int dcpred;
|
||||
int width, height; // this component's plane size (upsampled to full)
|
||||
int bx, by; // blocks per MCU (== ssx, ssy)
|
||||
uint8_t* pixels; // decoded, upsampled to image size
|
||||
uint8_t* plane; // native-resolution samples
|
||||
int pstride; // stride of plane
|
||||
int pw, ph; // plane width/height (block-aligned)
|
||||
} comp_t;
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* data;
|
||||
int size;
|
||||
int pos;
|
||||
|
||||
int width, height;
|
||||
int ncomp;
|
||||
comp_t comp[3];
|
||||
|
||||
uint16_t qtab[4][64];
|
||||
huff_t huff[8]; // 0..3 DC, 4..7 AC
|
||||
|
||||
int restart_interval;
|
||||
|
||||
// entropy bit reader
|
||||
int bitbuf;
|
||||
int bitcnt;
|
||||
int marker; // non-zero: a marker byte was hit in the stream
|
||||
|
||||
int mcu_w, mcu_h; // MCU size in pixels
|
||||
int mcu_cols, mcu_rows;
|
||||
} jpeg_t;
|
||||
|
||||
static const uint8_t zz[64] = {
|
||||
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
|
||||
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
|
||||
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
|
||||
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
|
||||
};
|
||||
|
||||
static inline int clip8(int x) { return x < 0 ? 0 : (x > 255 ? 255 : x); }
|
||||
|
||||
// ===========================================================================
|
||||
// Integer IDCT (JPEG reference fixed-point)
|
||||
// ===========================================================================
|
||||
#define W1 2841
|
||||
#define W2 2676
|
||||
#define W3 2408
|
||||
#define W5 1609
|
||||
#define W6 1108
|
||||
#define W7 565
|
||||
|
||||
static void row_idct(int* blk) {
|
||||
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
|
||||
if (!((x1 = blk[4] << 11) | (x2 = blk[6]) | (x3 = blk[2]) |
|
||||
(x4 = blk[1]) | (x5 = blk[7]) | (x6 = blk[5]) | (x7 = blk[3]))) {
|
||||
blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3;
|
||||
return;
|
||||
}
|
||||
x0 = (blk[0] << 11) + 128;
|
||||
x8 = W7 * (x4 + x5);
|
||||
x4 = x8 + (W1 - W7) * x4;
|
||||
x5 = x8 - (W1 + W7) * x5;
|
||||
x8 = W3 * (x6 + x7);
|
||||
x6 = x8 - (W3 - W5) * x6;
|
||||
x7 = x8 - (W3 + W5) * x7;
|
||||
x8 = x0 + x1; x0 -= x1;
|
||||
x1 = W6 * (x3 + x2);
|
||||
x2 = x1 - (W2 + W6) * x2;
|
||||
x3 = x1 + (W2 - W6) * x3;
|
||||
x1 = x4 + x6; x4 -= x6;
|
||||
x6 = x5 + x7; x5 -= x7;
|
||||
x7 = x8 + x3; x8 -= x3;
|
||||
x3 = x0 + x2; x0 -= x2;
|
||||
x2 = (181 * (x4 + x5) + 128) >> 8;
|
||||
x4 = (181 * (x4 - x5) + 128) >> 8;
|
||||
blk[0] = (x7 + x1) >> 8;
|
||||
blk[1] = (x3 + x2) >> 8;
|
||||
blk[2] = (x0 + x4) >> 8;
|
||||
blk[3] = (x8 + x6) >> 8;
|
||||
blk[4] = (x8 - x6) >> 8;
|
||||
blk[5] = (x0 - x4) >> 8;
|
||||
blk[6] = (x3 - x2) >> 8;
|
||||
blk[7] = (x7 - x1) >> 8;
|
||||
}
|
||||
|
||||
static void col_idct(const int* blk, uint8_t* out, int stride) {
|
||||
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
|
||||
if (!((x1 = blk[8 * 4] << 8) | (x2 = blk[8 * 6]) | (x3 = blk[8 * 2]) |
|
||||
(x4 = blk[8 * 1]) | (x5 = blk[8 * 7]) | (x6 = blk[8 * 5]) | (x7 = blk[8 * 3]))) {
|
||||
x1 = clip8(((blk[0] + 32) >> 6) + 128);
|
||||
for (x0 = 0; x0 < 8; x0++) out[x0 * stride] = (uint8_t)x1;
|
||||
return;
|
||||
}
|
||||
x0 = (blk[0] << 8) + 8192;
|
||||
x8 = W7 * (x4 + x5) + 4;
|
||||
x4 = (x8 + (W1 - W7) * x4) >> 3;
|
||||
x5 = (x8 - (W1 + W7) * x5) >> 3;
|
||||
x8 = W3 * (x6 + x7) + 4;
|
||||
x6 = (x8 - (W3 - W5) * x6) >> 3;
|
||||
x7 = (x8 - (W3 + W5) * x7) >> 3;
|
||||
x8 = x0 + x1; x0 -= x1;
|
||||
x1 = W6 * (x3 + x2) + 4;
|
||||
x2 = (x1 - (W2 + W6) * x2) >> 3;
|
||||
x3 = (x1 + (W2 - W6) * x3) >> 3;
|
||||
x1 = x4 + x6; x4 -= x6;
|
||||
x6 = x5 + x7; x5 -= x7;
|
||||
x7 = x8 + x3; x8 -= x3;
|
||||
x3 = x0 + x2; x0 -= x2;
|
||||
x2 = (181 * (x4 + x5) + 128) >> 8;
|
||||
x4 = (181 * (x4 - x5) + 128) >> 8;
|
||||
out[0 * stride] = (uint8_t)clip8(((x7 + x1) >> 14) + 128);
|
||||
out[1 * stride] = (uint8_t)clip8(((x3 + x2) >> 14) + 128);
|
||||
out[2 * stride] = (uint8_t)clip8(((x0 + x4) >> 14) + 128);
|
||||
out[3 * stride] = (uint8_t)clip8(((x8 + x6) >> 14) + 128);
|
||||
out[4 * stride] = (uint8_t)clip8(((x8 - x6) >> 14) + 128);
|
||||
out[5 * stride] = (uint8_t)clip8(((x0 - x4) >> 14) + 128);
|
||||
out[6 * stride] = (uint8_t)clip8(((x3 - x2) >> 14) + 128);
|
||||
out[7 * stride] = (uint8_t)clip8(((x7 - x1) >> 14) + 128);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Marker / segment parsing
|
||||
// ===========================================================================
|
||||
static int rd16(jpeg_t* j) {
|
||||
if (j->pos + 2 > j->size) return -1;
|
||||
int v = (j->data[j->pos] << 8) | j->data[j->pos + 1];
|
||||
j->pos += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void build_huff(huff_t* h) {
|
||||
int code = 0, k = 0;
|
||||
h->maxlen = 0;
|
||||
for (int l = 1; l <= 16; l++) {
|
||||
h->first_code[l] = code;
|
||||
h->sym_index[l] = k;
|
||||
if (h->counts[l]) h->maxlen = l;
|
||||
code = (code + h->counts[l]) << 1;
|
||||
k += h->counts[l];
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Entropy bit reader
|
||||
// ===========================================================================
|
||||
static int getbit(jpeg_t* j) {
|
||||
if (j->bitcnt == 0) {
|
||||
if (j->marker) return 0; // past a marker: feed zeros
|
||||
if (j->pos >= j->size) { j->marker = 0xD9; return 0; }
|
||||
int b = j->data[j->pos++];
|
||||
if (b == 0xFF) {
|
||||
int b2 = (j->pos < j->size) ? j->data[j->pos] : 0xD9;
|
||||
if (b2 == 0) {
|
||||
j->pos++; // stuffed 0xFF00 -> literal 0xFF
|
||||
} else {
|
||||
j->marker = b2; // a real marker: stop consuming
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
j->bitbuf = b;
|
||||
j->bitcnt = 8;
|
||||
}
|
||||
j->bitcnt--;
|
||||
return (j->bitbuf >> j->bitcnt) & 1;
|
||||
}
|
||||
|
||||
static int getbits(jpeg_t* j, int n) {
|
||||
int v = 0;
|
||||
for (int i = 0; i < n; i++) v = (v << 1) | getbit(j);
|
||||
return v;
|
||||
}
|
||||
|
||||
static int receive_extend(jpeg_t* j, int s) {
|
||||
if (s == 0) return 0;
|
||||
int v = getbits(j, s);
|
||||
if (v < (1 << (s - 1))) v -= (1 << s) - 1; // sign-extend (avoids UB shift)
|
||||
return v;
|
||||
}
|
||||
|
||||
static int huff_decode(jpeg_t* j, const huff_t* h) {
|
||||
int code = 0;
|
||||
for (int l = 1; l <= 16; l++) {
|
||||
code = (code << 1) | getbit(j);
|
||||
if (h->counts[l] && (code - h->first_code[l]) < h->counts[l]) {
|
||||
return h->symbols[h->sym_index[l] + (code - h->first_code[l])];
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Decode one 8x8 block, dequantize, IDCT into `out` (native plane).
|
||||
static int decode_block(jpeg_t* j, comp_t* c, uint8_t* out, int stride) {
|
||||
int blk[64];
|
||||
memset(blk, 0, sizeof(blk));
|
||||
|
||||
// DC
|
||||
int t = huff_decode(j, &j->huff[c->dctab]);
|
||||
if (t < 0) return -1;
|
||||
int diff = receive_extend(j, t);
|
||||
c->dcpred += diff;
|
||||
blk[0] = c->dcpred * j->qtab[c->qtsel][0];
|
||||
|
||||
// AC
|
||||
int k = 1;
|
||||
while (k < 64) {
|
||||
int rs = huff_decode(j, &j->huff[4 + c->actab]);
|
||||
if (rs < 0) return -1;
|
||||
int r = rs >> 4, s = rs & 15;
|
||||
if (s == 0) {
|
||||
if (r != 15) break; // EOB
|
||||
k += 16;
|
||||
continue;
|
||||
}
|
||||
k += r;
|
||||
if (k >= 64) break;
|
||||
int coeff = receive_extend(j, s) * j->qtab[c->qtsel][k];
|
||||
blk[zz[k]] = coeff;
|
||||
k++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) row_idct(&blk[i * 8]);
|
||||
for (int i = 0; i < 8; i++) col_idct(&blk[i], out + i, stride);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Scan
|
||||
// ===========================================================================
|
||||
static int decode_scan(jpeg_t* j) {
|
||||
j->bitcnt = 0; j->marker = 0;
|
||||
int rst_left = j->restart_interval;
|
||||
|
||||
for (int my = 0; my < j->mcu_rows; my++) {
|
||||
for (int mx = 0; mx < j->mcu_cols; mx++) {
|
||||
for (int ci = 0; ci < j->ncomp; ci++) {
|
||||
comp_t* c = &j->comp[ci];
|
||||
for (int sy = 0; sy < c->ssy; sy++) {
|
||||
for (int sx = 0; sx < c->ssx; sx++) {
|
||||
int px = (mx * c->ssx + sx) * 8;
|
||||
int py = (my * c->ssy + sy) * 8;
|
||||
uint8_t* out = c->plane + py * c->pstride + px;
|
||||
if (decode_block(j, c, out, c->pstride) < 0) return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (j->restart_interval && --rst_left == 0 &&
|
||||
!(my == j->mcu_rows - 1 && mx == j->mcu_cols - 1)) {
|
||||
// Align to the next byte and consume the RSTn marker.
|
||||
j->bitcnt = 0;
|
||||
if (!j->marker) {
|
||||
// find the marker in the stream
|
||||
while (j->pos + 1 < j->size &&
|
||||
!(j->data[j->pos] == 0xFF && j->data[j->pos + 1] >= 0xD0 &&
|
||||
j->data[j->pos + 1] <= 0xD7)) {
|
||||
j->pos++;
|
||||
}
|
||||
if (j->pos + 1 < j->size) j->marker = j->data[j->pos + 1];
|
||||
}
|
||||
if (j->marker >= 0xD0 && j->marker <= 0xD7) {
|
||||
j->pos += 2; // skip FF Dn
|
||||
j->marker = 0;
|
||||
}
|
||||
for (int ci = 0; ci < j->ncomp; ci++) j->comp[ci].dcpred = 0;
|
||||
rst_left = j->restart_interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Upsample + colour convert
|
||||
// ===========================================================================
|
||||
static int finish_image(jpeg_t* j, image_t* img) {
|
||||
int W = j->width, H = j->height;
|
||||
uint32_t* out = (uint32_t*)kmalloc((unsigned)W * H * 4);
|
||||
if (!out) return -1;
|
||||
|
||||
// Max sampling, to map component planes to full resolution.
|
||||
int hmax = 1, vmax = 1;
|
||||
for (int ci = 0; ci < j->ncomp; ci++) {
|
||||
if (j->comp[ci].ssx > hmax) hmax = j->comp[ci].ssx;
|
||||
if (j->comp[ci].ssy > vmax) vmax = j->comp[ci].ssy;
|
||||
}
|
||||
|
||||
for (int y = 0; y < H; y++) {
|
||||
for (int x = 0; x < W; x++) {
|
||||
int Y, Cb = 128, Cr = 128;
|
||||
{
|
||||
comp_t* c = &j->comp[0];
|
||||
int cx = x * c->ssx / hmax;
|
||||
int cy = y * c->ssy / vmax;
|
||||
Y = c->plane[cy * c->pstride + cx];
|
||||
}
|
||||
if (j->ncomp == 3) {
|
||||
comp_t* cb = &j->comp[1];
|
||||
comp_t* cr = &j->comp[2];
|
||||
int bx = x * cb->ssx / hmax, by = y * cb->ssy / vmax;
|
||||
int rx = x * cr->ssx / hmax, ry = y * cr->ssy / vmax;
|
||||
Cb = cb->plane[by * cb->pstride + bx];
|
||||
Cr = cr->plane[ry * cr->pstride + rx];
|
||||
}
|
||||
|
||||
int r, g, b;
|
||||
if (j->ncomp == 1) {
|
||||
r = g = b = Y;
|
||||
} else {
|
||||
int cb = Cb - 128, cr = Cr - 128;
|
||||
r = clip8(Y + ((91881 * cr) >> 16));
|
||||
g = clip8(Y - ((22554 * cb + 46802 * cr) >> 16));
|
||||
b = clip8(Y + ((116130 * cb) >> 16));
|
||||
}
|
||||
out[y * W + x] = 0xFF000000u | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
|
||||
img->width = W;
|
||||
img->height = H;
|
||||
img->pixels = out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Top level
|
||||
// ===========================================================================
|
||||
int jpeg_decode(const uint8_t* data, unsigned len, image_t* img) {
|
||||
if (len < 4 || data[0] != 0xFF || data[1] != 0xD8) return -1; // SOI
|
||||
|
||||
jpeg_t* j = (jpeg_t*)kzalloc(sizeof(jpeg_t));
|
||||
if (!j) return -1;
|
||||
j->data = data; j->size = (int)len; j->pos = 2;
|
||||
|
||||
int rc = -1;
|
||||
for (;;) {
|
||||
if (j->pos + 2 > j->size) goto done;
|
||||
if (j->data[j->pos] != 0xFF) { j->pos++; continue; }
|
||||
int marker = j->data[j->pos + 1];
|
||||
j->pos += 2;
|
||||
if (marker == 0xD9) break; // EOI
|
||||
if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) continue;
|
||||
|
||||
int seg_len = rd16(j);
|
||||
if (seg_len < 2) goto done;
|
||||
int seg_start = j->pos;
|
||||
int seg_end = seg_start + seg_len - 2;
|
||||
if (seg_end > j->size) goto done;
|
||||
|
||||
if (marker == 0xC0) { // SOF0 baseline
|
||||
int prec = j->data[j->pos];
|
||||
j->height = (j->data[j->pos + 1] << 8) | j->data[j->pos + 2];
|
||||
j->width = (j->data[j->pos + 3] << 8) | j->data[j->pos + 4];
|
||||
j->ncomp = j->data[j->pos + 5];
|
||||
if (prec != 8 || (j->ncomp != 1 && j->ncomp != 3)) goto done;
|
||||
if (j->width <= 0 || j->height <= 0) goto done;
|
||||
if ((long)j->width * j->height > JPEG_MAX_PIXELS) goto done;
|
||||
const uint8_t* p = j->data + j->pos + 6;
|
||||
int hmax = 1, vmax = 1;
|
||||
for (int i = 0; i < j->ncomp; i++) {
|
||||
j->comp[i].cid = p[0];
|
||||
j->comp[i].ssx = p[1] >> 4;
|
||||
j->comp[i].ssy = p[1] & 15;
|
||||
j->comp[i].qtsel = p[2];
|
||||
if (j->comp[i].ssx < 1 || j->comp[i].ssy < 1) goto done;
|
||||
if (j->comp[i].ssx > hmax) hmax = j->comp[i].ssx;
|
||||
if (j->comp[i].ssy > vmax) vmax = j->comp[i].ssy;
|
||||
p += 3;
|
||||
}
|
||||
j->mcu_w = hmax * 8;
|
||||
j->mcu_h = vmax * 8;
|
||||
j->mcu_cols = (j->width + j->mcu_w - 1) / j->mcu_w;
|
||||
j->mcu_rows = (j->height + j->mcu_h - 1) / j->mcu_h;
|
||||
for (int i = 0; i < j->ncomp; i++) {
|
||||
comp_t* c = &j->comp[i];
|
||||
c->pw = j->mcu_cols * c->ssx * 8;
|
||||
c->ph = j->mcu_rows * c->ssy * 8;
|
||||
c->pstride = c->pw;
|
||||
c->plane = (uint8_t*)kmalloc((unsigned)c->pw * c->ph);
|
||||
if (!c->plane) goto done;
|
||||
}
|
||||
} else if (marker == 0xC2) { // SOF2 progressive
|
||||
goto done; // unsupported
|
||||
} else if (marker == 0xC4) { // DHT
|
||||
const uint8_t* p = j->data + j->pos;
|
||||
const uint8_t* pe = j->data + seg_end;
|
||||
while (p < pe) {
|
||||
int tc = p[0] >> 4, th = p[0] & 15;
|
||||
if (th > 3) goto done;
|
||||
int idx = (tc ? 4 : 0) + th;
|
||||
huff_t* h = &j->huff[idx];
|
||||
p++;
|
||||
int total = 0;
|
||||
for (int l = 1; l <= 16; l++) { h->counts[l] = p[l - 1]; total += h->counts[l]; }
|
||||
p += 16;
|
||||
if (total > 256 || p + total > pe) goto done;
|
||||
for (int i = 0; i < total; i++) h->symbols[i] = p[i];
|
||||
p += total;
|
||||
build_huff(h);
|
||||
}
|
||||
} else if (marker == 0xDB) { // DQT
|
||||
const uint8_t* p = j->data + j->pos;
|
||||
const uint8_t* pe = j->data + seg_end;
|
||||
while (p < pe) {
|
||||
int pq = p[0] >> 4, tq = p[0] & 15;
|
||||
if (tq > 3) goto done;
|
||||
p++;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (pq) { j->qtab[tq][i] = (p[0] << 8) | p[1]; p += 2; }
|
||||
else { j->qtab[tq][i] = p[0]; p += 1; }
|
||||
}
|
||||
}
|
||||
} else if (marker == 0xDD) { // DRI
|
||||
j->restart_interval = (j->data[j->pos] << 8) | j->data[j->pos + 1];
|
||||
} else if (marker == 0xDA) { // SOS
|
||||
const uint8_t* p = j->data + j->pos;
|
||||
int ns = p[0]; p++;
|
||||
if (ns != j->ncomp) goto done;
|
||||
for (int i = 0; i < ns; i++) {
|
||||
int cid = p[0], td = p[1] >> 4, ta = p[1] & 15;
|
||||
p += 2;
|
||||
for (int k = 0; k < j->ncomp; k++) {
|
||||
if (j->comp[k].cid == cid) { j->comp[k].dctab = td; j->comp[k].actab = ta; }
|
||||
}
|
||||
}
|
||||
// p now at Ss, Se, Ah/Al (3 bytes) -- ignored for baseline.
|
||||
j->pos = seg_end; // entropy data follows
|
||||
if (decode_scan(j) < 0) goto done;
|
||||
rc = finish_image(j, img);
|
||||
goto done;
|
||||
}
|
||||
j->pos = seg_end;
|
||||
}
|
||||
|
||||
done:
|
||||
for (int i = 0; i < 3; i++) if (j->comp[i].plane) kfree(j->comp[i].plane);
|
||||
kfree(j);
|
||||
return rc;
|
||||
}
|
||||
+113
-28
@@ -5,7 +5,8 @@
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#define HEAP_MAGIC 0x53554248 // "SUBH"
|
||||
#define HEAP_INITIAL_PAGES 4096 // 16 MB initial kernel heap
|
||||
#define HEAP_INITIAL_PAGES 8192 // 32 MB initial kernel heap
|
||||
#define HEAP_GROW_PAGES 4096 // Grow the heap 16 MB at a time on demand
|
||||
|
||||
typedef struct heap_block {
|
||||
uint32_t magic;
|
||||
@@ -18,11 +19,21 @@ typedef struct heap_block {
|
||||
#define BLOCK_HEADER_SIZE sizeof(heap_block_t)
|
||||
|
||||
static heap_block_t* heap_start = NULL;
|
||||
static heap_block_t* heap_tail = NULL; // last block in address order, for O(1) growth
|
||||
static size_t heap_used_bytes = 0;
|
||||
static size_t heap_free_bytes = 0;
|
||||
static size_t heap_total_bytes = 0;
|
||||
static size_t heap_grow_count = 0;
|
||||
static spinlock_t heap_lock = SPINLOCK_INIT;
|
||||
|
||||
/* Two blocks may only be coalesced when they are physically adjacent. The heap
|
||||
* is made of one or more regions carved from the PMM; a region boundary shows
|
||||
* up as a gap between a block and its list successor, so this guard keeps the
|
||||
* free-list merge from ever spanning two independent PMM allocations. */
|
||||
static inline bool blocks_adjacent(heap_block_t* a, heap_block_t* b) {
|
||||
return (heap_block_t*)((uint8_t*)a + BLOCK_HEADER_SIZE + a->size) == b;
|
||||
}
|
||||
|
||||
void heap_init(void) {
|
||||
void* initial_mem = pmm_alloc_pages(HEAP_INITIAL_PAGES);
|
||||
if (!initial_mem) {
|
||||
@@ -31,7 +42,7 @@ void heap_init(void) {
|
||||
}
|
||||
|
||||
heap_start = (heap_block_t*)initial_mem;
|
||||
heap_total_bytes = HEAP_INITIAL_PAGES * PMM_PAGE_SIZE;
|
||||
heap_total_bytes = (size_t)HEAP_INITIAL_PAGES * PMM_PAGE_SIZE;
|
||||
|
||||
heap_start->magic = HEAP_MAGIC;
|
||||
heap_start->is_free = true;
|
||||
@@ -39,28 +50,63 @@ void heap_init(void) {
|
||||
heap_start->next = NULL;
|
||||
heap_start->prev = NULL;
|
||||
|
||||
heap_tail = heap_start;
|
||||
heap_used_bytes = 0;
|
||||
heap_free_bytes = heap_start->size;
|
||||
}
|
||||
|
||||
void* kmalloc(size_t size) {
|
||||
if (size == 0) return NULL;
|
||||
/* Append a fresh region from the PMM to the tail of the free list. Must be
|
||||
* called with heap_lock held. Returns the new free block, or NULL if the PMM
|
||||
* cannot satisfy a contiguous run large enough for the request. */
|
||||
static heap_block_t* heap_grow(size_t need) {
|
||||
size_t need_pages = (need + BLOCK_HEADER_SIZE + PMM_PAGE_SIZE - 1) / PMM_PAGE_SIZE;
|
||||
size_t grow_pages = need_pages > HEAP_GROW_PAGES ? need_pages : HEAP_GROW_PAGES;
|
||||
|
||||
// Align size to 16 bytes for SIMD/64-bit performance
|
||||
size = (size + 15) & ~15;
|
||||
|
||||
spin_lock(&heap_lock);
|
||||
|
||||
heap_block_t* curr = heap_start;
|
||||
while (curr) {
|
||||
if (curr->magic != HEAP_MAGIC) {
|
||||
printk(KERN_ERR "Heap Corruption detected at %p!\n", curr);
|
||||
spin_unlock(&heap_lock);
|
||||
void* mem = pmm_alloc_pages(grow_pages);
|
||||
if (!mem && grow_pages != need_pages) {
|
||||
/* Could not get the generous chunk; fall back to exactly what is needed. */
|
||||
grow_pages = need_pages;
|
||||
mem = pmm_alloc_pages(grow_pages);
|
||||
}
|
||||
if (!mem) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (curr->is_free && curr->size >= size) {
|
||||
// Check if block can be split
|
||||
heap_block_t* region = (heap_block_t*)mem;
|
||||
region->magic = HEAP_MAGIC;
|
||||
region->is_free = true;
|
||||
region->size = (size_t)grow_pages * PMM_PAGE_SIZE - BLOCK_HEADER_SIZE;
|
||||
region->next = NULL;
|
||||
region->prev = heap_tail;
|
||||
|
||||
if (heap_tail) {
|
||||
heap_tail->next = region;
|
||||
}
|
||||
heap_tail = region;
|
||||
if (!heap_start) {
|
||||
heap_start = region;
|
||||
}
|
||||
|
||||
heap_total_bytes += (size_t)grow_pages * PMM_PAGE_SIZE;
|
||||
heap_free_bytes += region->size;
|
||||
heap_grow_count++;
|
||||
|
||||
/* A PMM run handed back right after the previous tail is a real neighbour;
|
||||
* fuse it so the heap does not fragment purely from growing. */
|
||||
if (region->prev && region->prev->is_free && blocks_adjacent(region->prev, region)) {
|
||||
heap_block_t* prev = region->prev;
|
||||
prev->size += BLOCK_HEADER_SIZE + region->size;
|
||||
prev->next = region->next; // NULL
|
||||
heap_tail = prev;
|
||||
region = prev;
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
/* Carve `size` bytes out of a free block, splitting off the remainder. Must be
|
||||
* called with heap_lock held; `curr` must be free and large enough. */
|
||||
static void* heap_take(heap_block_t* curr, size_t size) {
|
||||
if (curr->size >= size + BLOCK_HEADER_SIZE + 16) {
|
||||
heap_block_t* new_block = (heap_block_t*)((uint8_t*)curr + BLOCK_HEADER_SIZE + size);
|
||||
new_block->magic = HEAP_MAGIC;
|
||||
@@ -71,6 +117,8 @@ void* kmalloc(size_t size) {
|
||||
|
||||
if (curr->next) {
|
||||
curr->next->prev = new_block;
|
||||
} else {
|
||||
heap_tail = new_block;
|
||||
}
|
||||
curr->next = new_block;
|
||||
curr->size = size;
|
||||
@@ -80,15 +128,44 @@ void* kmalloc(size_t size) {
|
||||
heap_used_bytes += curr->size;
|
||||
heap_free_bytes -= curr->size;
|
||||
|
||||
spin_unlock(&heap_lock);
|
||||
return (void*)((uint8_t*)curr + BLOCK_HEADER_SIZE);
|
||||
}
|
||||
|
||||
void* kmalloc(size_t size) {
|
||||
if (size == 0) return NULL;
|
||||
|
||||
// Align size to 16 bytes for SIMD/64-bit performance
|
||||
size = (size + 15) & ~15;
|
||||
|
||||
spin_lock(&heap_lock);
|
||||
|
||||
for (int attempt = 0; attempt < 2; attempt++) {
|
||||
heap_block_t* curr = heap_start;
|
||||
while (curr) {
|
||||
if (curr->magic != HEAP_MAGIC) {
|
||||
printk(KERN_ERR "Heap Corruption detected at %p!\n", curr);
|
||||
spin_unlock(&heap_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (curr->is_free && curr->size >= size) {
|
||||
void* ptr = heap_take(curr, size);
|
||||
spin_unlock(&heap_lock);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
curr = curr->next;
|
||||
}
|
||||
|
||||
/* No block fit. Pull a new region from the PMM and try once more. */
|
||||
if (attempt == 0 && heap_grow(size)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
spin_unlock(&heap_lock);
|
||||
printk(KERN_WARNING "Heap: Out of memory for size %llu bytes!\n", size);
|
||||
printk(KERN_WARNING "Heap: Out of memory for size %llu bytes!\n", (uint64_t)size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -116,21 +193,27 @@ void kfree(void* ptr) {
|
||||
heap_used_bytes -= block->size;
|
||||
heap_free_bytes += block->size;
|
||||
|
||||
// Coalesce with next block if free
|
||||
if (block->next && block->next->is_free) {
|
||||
block->size += BLOCK_HEADER_SIZE + block->next->size;
|
||||
block->next = block->next->next;
|
||||
// Coalesce with next block if free AND physically adjacent (same region).
|
||||
if (block->next && block->next->is_free && blocks_adjacent(block, block->next)) {
|
||||
heap_block_t* nxt = block->next;
|
||||
block->size += BLOCK_HEADER_SIZE + nxt->size;
|
||||
block->next = nxt->next;
|
||||
if (block->next) {
|
||||
block->next->prev = block;
|
||||
} else {
|
||||
heap_tail = block;
|
||||
}
|
||||
}
|
||||
|
||||
// Coalesce with prev block if free
|
||||
if (block->prev && block->prev->is_free) {
|
||||
block->prev->size += BLOCK_HEADER_SIZE + block->size;
|
||||
block->prev->next = block->next;
|
||||
// Coalesce with prev block if free AND physically adjacent (same region).
|
||||
if (block->prev && block->prev->is_free && blocks_adjacent(block->prev, block)) {
|
||||
heap_block_t* prev = block->prev;
|
||||
prev->size += BLOCK_HEADER_SIZE + block->size;
|
||||
prev->next = block->next;
|
||||
if (block->next) {
|
||||
block->next->prev = block->prev;
|
||||
block->next->prev = prev;
|
||||
} else {
|
||||
heap_tail = prev;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,9 +234,10 @@ void* krealloc(void* ptr, size_t new_size) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
size_t old_size = block->size;
|
||||
void* new_ptr = kmalloc(new_size);
|
||||
if (new_ptr) {
|
||||
memcpy(new_ptr, ptr, block->size);
|
||||
memcpy(new_ptr, ptr, old_size);
|
||||
kfree(ptr);
|
||||
}
|
||||
return new_ptr;
|
||||
@@ -162,3 +246,4 @@ void* krealloc(void* ptr, size_t new_size) {
|
||||
size_t heap_get_used_bytes(void) { return heap_used_bytes; }
|
||||
size_t heap_get_free_bytes(void) { return heap_free_bytes; }
|
||||
size_t heap_get_total_bytes(void) { return heap_total_bytes; }
|
||||
size_t heap_get_grow_count(void) { return heap_grow_count; }
|
||||
+53
-10
@@ -1,11 +1,13 @@
|
||||
#include <net/net.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/filter.h>
|
||||
#include <drivers/e1000.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <arch/arch.h>
|
||||
#include <kernel/sched.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#define MAX_ARP_ENTRIES 16
|
||||
@@ -21,21 +23,41 @@ static volatile uint64_t ping_time = 0;
|
||||
|
||||
static const uint8_t eth_broadcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
static uint16_t net_checksum(const void* data, size_t len) {
|
||||
const uint16_t* ptr = (const uint16_t*)data;
|
||||
uint32_t sum = 0;
|
||||
/*
|
||||
* One's-complement checksum, accumulated a byte at a time.
|
||||
*
|
||||
* The byte-wise form is not just about portability. Reading through an
|
||||
* unsigned char pointer may alias any object, so these loads cannot be
|
||||
* reordered around a caller's `hdr->checksum = 0` store into the very buffer
|
||||
* being summed. A word-at-a-time version is free to be reordered, and the TCP
|
||||
* one was: it folded a stale checksum field into its own result and put a
|
||||
* wrong checksum on every segment the kernel sent. It also drops the
|
||||
* assumption that a packed header may be read as uint16_t, which is a fault
|
||||
* rather than a slowdown on the ARM targets.
|
||||
*
|
||||
* Words are read big-endian, so the accumulator carries the checksum's true
|
||||
* numeric value and it is converted to network order once, at the end.
|
||||
*/
|
||||
uint32_t net_csum_add(uint32_t sum, const void* data, size_t len) {
|
||||
const uint8_t* p = (const uint8_t*)data;
|
||||
|
||||
while (len > 1) {
|
||||
sum += *ptr++;
|
||||
sum += ((uint32_t)p[0] << 8) | (uint32_t)p[1];
|
||||
p += 2;
|
||||
len -= 2;
|
||||
}
|
||||
if (len > 0) {
|
||||
sum += *(const uint8_t*)ptr;
|
||||
if (len) sum += (uint32_t)p[0] << 8; // odd tail byte is the high half
|
||||
|
||||
return sum;
|
||||
}
|
||||
while (sum >> 16) {
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
|
||||
uint16_t net_csum_finish(uint32_t sum) {
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
return htons((uint16_t)(~sum));
|
||||
}
|
||||
return (uint16_t)(~sum);
|
||||
|
||||
uint16_t net_checksum(const void* data, size_t len) {
|
||||
return net_csum_finish(net_csum_add(0, data, len));
|
||||
}
|
||||
|
||||
void ip_to_str(uint32_t ip, char* buf) {
|
||||
@@ -234,6 +256,16 @@ void net_receive(const uint8_t* packet, uint16_t length) {
|
||||
if (payload_len >= sizeof(ip_header_t)) {
|
||||
const ip_header_t* ip = (const ip_header_t*)payload;
|
||||
uint8_t ihl = (ip->ihl_version & 0x0F) * 4;
|
||||
|
||||
/* Ethernet pads every frame out to 60 bytes, so the frame length
|
||||
* overstates a short datagram. The IP header's own total_length is
|
||||
* what says where the datagram really ends; without this trim the
|
||||
* padding is handed to TCP/UDP/ICMP as protocol payload, which
|
||||
* makes a bare ACK or FIN look like it carried several bytes and
|
||||
* drags the acknowledgement number out of step with the peer. */
|
||||
uint16_t ip_total = ntohs(ip->total_length);
|
||||
if (ip_total >= ihl && ip_total < payload_len) payload_len = ip_total;
|
||||
|
||||
if (payload_len >= ihl) {
|
||||
// Dynamically learn sender's MAC address in ARP cache
|
||||
arp_cache_insert(ip->src_ip, eth->src_mac);
|
||||
@@ -247,6 +279,8 @@ void net_receive(const uint8_t* packet, uint16_t length) {
|
||||
net_handle_icmp(ip->src_ip, payload + ihl, payload_len - ihl);
|
||||
} else if (ip->protocol == IP_PROTO_TCP) {
|
||||
tcp_receive(payload + ihl, payload_len - ihl, ip->src_ip);
|
||||
} else if (ip->protocol == IP_PROTO_UDP) {
|
||||
udp_receive(payload + ihl, payload_len - ihl, ip->src_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,7 +322,7 @@ int net_ping(uint32_t target_ip, uint32_t count, uint32_t timeout_ms) {
|
||||
if (wait_ticks == 0) wait_ticks = 100;
|
||||
|
||||
while (!ping_received && (pit_get_ticks() - t_start < wait_ticks)) {
|
||||
arch_halt();
|
||||
net_wait();
|
||||
}
|
||||
|
||||
if (ping_received && ping_id == p_id && ping_seq == seq) {
|
||||
@@ -308,6 +342,15 @@ int net_ping(uint32_t target_ip, uint32_t count, uint32_t timeout_ms) {
|
||||
return received;
|
||||
}
|
||||
|
||||
void net_wait(void) {
|
||||
// A cooperative hand-off rather than a CPU halt: other runnable tasks get
|
||||
// to run while this one blocks on the wire. If nothing else is runnable the
|
||||
// scheduler returns immediately, so the enclosing timeout still bounds the
|
||||
// wait. The NIC interrupt fills the receive path independently of which
|
||||
// task holds the CPU, so yielding never delays packet delivery.
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
void net_init(void) {
|
||||
strcpy(primary_if.name, "eth0");
|
||||
e1000_get_mac(primary_if.mac);
|
||||
|
||||
+258
-16
@@ -1,24 +1,100 @@
|
||||
// BSD-style socket layer for SUB-OS.
|
||||
//
|
||||
// Implements connectionless (SOCK_DGRAM) sockets on top of the UDP transport
|
||||
// and connection-oriented (SOCK_STREAM) sockets on top of the TCP engine.
|
||||
//
|
||||
// Binding a datagram socket registers a UDP handler that appends inbound
|
||||
// datagrams to the socket's fixed-depth receive ring; sys_recv/sys_recvfrom pop
|
||||
// the oldest datagram. A stream socket instead holds a TCP connection opened by
|
||||
// sys_connect, and its send/recv calls carry a byte stream through that.
|
||||
#include <net/socket.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/net.h>
|
||||
#include <lib/string.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#define MAX_SOCKETS 32
|
||||
#define RX_RING_DEPTH 8
|
||||
#define DGRAM_MAX 1472 // 1500 MTU - 20 IP - 8 UDP
|
||||
|
||||
#define SOCK_CONNECT_TIMEOUT_MS 4000
|
||||
#define SOCK_RECV_TIMEOUT_MS 2000
|
||||
#define SOCK_ACCEPT_TIMEOUT_MS 5000
|
||||
|
||||
typedef struct {
|
||||
uint32_t src_ip;
|
||||
uint16_t src_port;
|
||||
uint16_t len;
|
||||
uint8_t data[DGRAM_MAX];
|
||||
} dgram_t;
|
||||
|
||||
// Per-socket receive ring, kept parallel to the public socket_t so the exported
|
||||
// struct stays small.
|
||||
typedef struct {
|
||||
dgram_t ring[RX_RING_DEPTH];
|
||||
int head; // next slot to read
|
||||
int count; // queued datagrams
|
||||
bool bound; // a UDP binding is installed for local_port
|
||||
tcp_conn_t* tcp; // stream sockets only: the connection sys_connect opened
|
||||
bool listening; // stream sockets only: a TCP listener holds local_port
|
||||
} sock_priv_t;
|
||||
|
||||
static socket_t socket_table[MAX_SOCKETS];
|
||||
static sock_priv_t socket_priv[MAX_SOCKETS];
|
||||
|
||||
static volatile uint64_t stat_tx = 0;
|
||||
static volatile uint64_t stat_rx = 0;
|
||||
static volatile uint64_t stat_drop = 0;
|
||||
|
||||
void socket_subsystem_init(void) {
|
||||
memset(socket_table, 0, sizeof(socket_table));
|
||||
memset(socket_priv, 0, sizeof(socket_priv));
|
||||
stat_tx = stat_rx = stat_drop = 0;
|
||||
printk(KERN_INFO "NET: BSD Sockets API Layer initialized (AF_INET support)\n");
|
||||
}
|
||||
|
||||
// Runs in UDP receive context: enqueue the datagram, dropping the oldest if the
|
||||
// ring is full so a stalled reader never wedges delivery.
|
||||
static void socket_udp_cb(uint32_t src_ip, uint16_t src_port, uint16_t dst_port,
|
||||
const uint8_t* data, uint16_t len, void* ctx) {
|
||||
(void)dst_port;
|
||||
sock_priv_t* pv = (sock_priv_t*)ctx;
|
||||
if (!pv) return;
|
||||
if (len > DGRAM_MAX) len = DGRAM_MAX;
|
||||
|
||||
int slot;
|
||||
if (pv->count == RX_RING_DEPTH) {
|
||||
// Overwrite the oldest.
|
||||
pv->head = (pv->head + 1) % RX_RING_DEPTH;
|
||||
slot = (pv->head + pv->count - 1) % RX_RING_DEPTH;
|
||||
stat_drop++;
|
||||
} else {
|
||||
slot = (pv->head + pv->count) % RX_RING_DEPTH;
|
||||
pv->count++;
|
||||
}
|
||||
|
||||
dgram_t* d = &pv->ring[slot];
|
||||
d->src_ip = src_ip;
|
||||
d->src_port = src_port;
|
||||
d->len = len;
|
||||
if (data && len) memcpy(d->data, data, len);
|
||||
stat_rx++;
|
||||
}
|
||||
|
||||
int sys_socket(int domain, int type, int protocol) {
|
||||
if (domain != AF_INET) return -1;
|
||||
if (type != SOCK_DGRAM && type != SOCK_STREAM && type != SOCK_RAW) return -1;
|
||||
|
||||
for (int i = 0; i < MAX_SOCKETS; i++) {
|
||||
if (!socket_table[i].in_use) {
|
||||
memset(&socket_table[i], 0, sizeof(socket_t));
|
||||
memset(&socket_priv[i], 0, sizeof(sock_priv_t));
|
||||
socket_table[i].in_use = true;
|
||||
socket_table[i].domain = domain;
|
||||
socket_table[i].type = type;
|
||||
socket_table[i].protocol = protocol;
|
||||
socket_table[i].protocol = protocol ? protocol :
|
||||
(type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP);
|
||||
socket_table[i].state = 0;
|
||||
return i;
|
||||
}
|
||||
@@ -26,37 +102,203 @@ int sys_socket(int domain, int type, int protocol) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool valid_fd(int fd) {
|
||||
return fd >= 0 && fd < MAX_SOCKETS && socket_table[fd].in_use;
|
||||
}
|
||||
|
||||
// Install the UDP binding for a datagram socket's local port (idempotent).
|
||||
static int dgram_ensure_bound(int fd) {
|
||||
socket_t* s = &socket_table[fd];
|
||||
sock_priv_t* pv = &socket_priv[fd];
|
||||
if (pv->bound) return 0;
|
||||
if (s->local_port == 0) {
|
||||
s->local_port = udp_alloc_ephemeral_port();
|
||||
if (s->local_port == 0) return -1;
|
||||
}
|
||||
if (udp_bind(s->local_port, socket_udp_cb, pv) != 0) return -1;
|
||||
pv->bound = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_bind(int sockfd, const struct sockaddr_in* addr, size_t addrlen) {
|
||||
(void)addrlen;
|
||||
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !addr) return -1;
|
||||
socket_table[sockfd].local_ip = addr->sin_addr;
|
||||
socket_table[sockfd].local_port = addr->sin_port;
|
||||
if (!valid_fd(sockfd) || !addr) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
s->local_ip = addr->sin_addr;
|
||||
s->local_port = ntohs(addr->sin_port);
|
||||
if (s->type == SOCK_DGRAM) return dgram_ensure_bound(sockfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_connect(int sockfd, const struct sockaddr_in* addr, size_t addrlen) {
|
||||
(void)addrlen;
|
||||
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !addr) return -1;
|
||||
socket_table[sockfd].remote_ip = addr->sin_addr;
|
||||
socket_table[sockfd].remote_port = addr->sin_port;
|
||||
socket_table[sockfd].state = 1; // Connected
|
||||
if (!valid_fd(sockfd) || !addr) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
sock_priv_t* pv = &socket_priv[sockfd];
|
||||
s->remote_ip = addr->sin_addr;
|
||||
s->remote_port = ntohs(addr->sin_port);
|
||||
|
||||
// A stream socket really connects: the call returns only once the
|
||||
// three-way handshake has completed, or fails on timeout.
|
||||
if (s->type == SOCK_STREAM) {
|
||||
if (pv->tcp) return 0; // already connected
|
||||
pv->tcp = tcp_connect(s->remote_ip, s->remote_port, SOCK_CONNECT_TIMEOUT_MS);
|
||||
if (!pv->tcp) return -1;
|
||||
s->local_ip = pv->tcp->local_ip;
|
||||
s->local_port = pv->tcp->local_port;
|
||||
s->state = SOCK_STATE_CONNECTED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t sys_send(int sockfd, const void* buf, size_t len, int flags) {
|
||||
(void)flags;
|
||||
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !buf) return -1;
|
||||
s->state = SOCK_STATE_CONNECTED; // address memorised for send/recv
|
||||
if (s->type == SOCK_DGRAM) return dgram_ensure_bound(sockfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_listen(int sockfd, int backlog) {
|
||||
if (!valid_fd(sockfd)) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
if (s->type != SOCK_STREAM || s->local_port == 0) return -1;
|
||||
|
||||
if (tcp_listen(s->local_port, backlog) != 0) return -1;
|
||||
socket_priv[sockfd].listening = true;
|
||||
s->state = SOCK_STATE_LISTEN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_accept(int sockfd, struct sockaddr_in* addr, size_t* addrlen) {
|
||||
if (!valid_fd(sockfd)) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
if (s->type != SOCK_STREAM || !socket_priv[sockfd].listening) return -1;
|
||||
|
||||
tcp_conn_t* c = tcp_accept(s->local_port, SOCK_ACCEPT_TIMEOUT_MS);
|
||||
if (!c) return -1;
|
||||
|
||||
// The accepted connection needs a descriptor of its own; the listening
|
||||
// socket keeps its port and stays available for the next caller.
|
||||
int fd = sys_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (fd < 0) { tcp_close(c); return -1; }
|
||||
|
||||
socket_t* ns = &socket_table[fd];
|
||||
ns->local_ip = c->local_ip;
|
||||
ns->local_port = c->local_port;
|
||||
ns->remote_ip = c->remote_ip;
|
||||
ns->remote_port = c->remote_port;
|
||||
ns->state = SOCK_STATE_CONNECTED;
|
||||
socket_priv[fd].tcp = c;
|
||||
|
||||
if (addr) {
|
||||
addr->sin_family = AF_INET;
|
||||
addr->sin_addr = c->remote_ip;
|
||||
addr->sin_port = htons(c->remote_port);
|
||||
if (addrlen) *addrlen = sizeof(struct sockaddr_in);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
ssize_t sys_sendto(int sockfd, const void* buf, size_t len, int flags,
|
||||
const struct sockaddr_in* dest, size_t addrlen) {
|
||||
(void)flags; (void)addrlen;
|
||||
if (!valid_fd(sockfd) || !buf) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
if (s->type != SOCK_DGRAM) return -1;
|
||||
if (len > DGRAM_MAX) len = DGRAM_MAX;
|
||||
|
||||
uint32_t dip;
|
||||
uint16_t dport;
|
||||
if (dest) { dip = dest->sin_addr; dport = ntohs(dest->sin_port); }
|
||||
else if (s->state == SOCK_STATE_CONNECTED) { dip = s->remote_ip; dport = s->remote_port; }
|
||||
else return -1;
|
||||
|
||||
if (dgram_ensure_bound(sockfd) != 0) return -1;
|
||||
udp_send(dip, s->local_port, dport, buf, (uint16_t)len);
|
||||
stat_tx++;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
|
||||
ssize_t sys_recv(int sockfd, void* buf, size_t len, int flags) {
|
||||
ssize_t sys_send(int sockfd, const void* buf, size_t len, int flags) {
|
||||
if (!valid_fd(sockfd) || !buf) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
|
||||
if (s->type == SOCK_STREAM) {
|
||||
sock_priv_t* pv = &socket_priv[sockfd];
|
||||
if (!pv->tcp) return -1; // not connected
|
||||
if (len > 0xFFFF) len = 0xFFFF;
|
||||
int n = tcp_send(pv->tcp, buf, (uint16_t)len);
|
||||
if (n > 0) stat_tx++;
|
||||
return (n < 0) ? -1 : (ssize_t)n;
|
||||
}
|
||||
|
||||
return sys_sendto(sockfd, buf, len, flags, NULL, 0);
|
||||
}
|
||||
|
||||
ssize_t sys_recvfrom(int sockfd, void* buf, size_t len, int flags,
|
||||
struct sockaddr_in* src, size_t* addrlen) {
|
||||
(void)flags;
|
||||
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !buf) return -1;
|
||||
return 0;
|
||||
if (!valid_fd(sockfd) || !buf) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
if (s->type != SOCK_DGRAM) return 0;
|
||||
|
||||
sock_priv_t* pv = &socket_priv[sockfd];
|
||||
if (pv->count == 0) return 0; // non-blocking: nothing queued
|
||||
|
||||
dgram_t* d = &pv->ring[pv->head];
|
||||
size_t n = d->len < len ? d->len : len;
|
||||
memcpy(buf, d->data, n);
|
||||
|
||||
if (src) {
|
||||
src->sin_family = AF_INET;
|
||||
src->sin_addr = d->src_ip;
|
||||
src->sin_port = htons(d->src_port);
|
||||
if (addrlen) *addrlen = sizeof(struct sockaddr_in);
|
||||
}
|
||||
|
||||
pv->head = (pv->head + 1) % RX_RING_DEPTH;
|
||||
pv->count--;
|
||||
return (ssize_t)n;
|
||||
}
|
||||
|
||||
ssize_t sys_recv(int sockfd, void* buf, size_t len, int flags) {
|
||||
if (!valid_fd(sockfd) || !buf) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
|
||||
if (s->type == SOCK_STREAM) {
|
||||
sock_priv_t* pv = &socket_priv[sockfd];
|
||||
if (!pv->tcp) return -1; // not connected
|
||||
if (len > 0xFFFF) len = 0xFFFF;
|
||||
int n = tcp_recv(pv->tcp, buf, (uint16_t)len, SOCK_RECV_TIMEOUT_MS);
|
||||
if (n > 0) stat_rx++;
|
||||
return (n < 0) ? -1 : (ssize_t)n;
|
||||
}
|
||||
|
||||
return sys_recvfrom(sockfd, buf, len, flags, NULL, NULL);
|
||||
}
|
||||
|
||||
int sys_close_socket(int sockfd) {
|
||||
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use) return -1;
|
||||
memset(&socket_table[sockfd], 0, sizeof(socket_t));
|
||||
if (!valid_fd(sockfd)) return -1;
|
||||
socket_t* s = &socket_table[sockfd];
|
||||
sock_priv_t* pv = &socket_priv[sockfd];
|
||||
if (pv->listening && s->local_port) tcp_unlisten(s->local_port);
|
||||
if (pv->tcp) tcp_close(pv->tcp);
|
||||
if (pv->bound && s->local_port) udp_unbind(s->local_port);
|
||||
memset(s, 0, sizeof(socket_t));
|
||||
memset(pv, 0, sizeof(sock_priv_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socket_get_count(void) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < MAX_SOCKETS; i++) if (socket_table[i].in_use) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
const socket_t* socket_get(int idx) {
|
||||
if (idx < 0 || idx >= MAX_SOCKETS || !socket_table[idx].in_use) return NULL;
|
||||
return &socket_table[idx];
|
||||
}
|
||||
|
||||
void socket_get_stats(uint64_t* tx_dgrams, uint64_t* rx_dgrams, uint64_t* drops) {
|
||||
if (tx_dgrams) *tx_dgrams = stat_tx;
|
||||
if (rx_dgrams) *rx_dgrams = stat_rx;
|
||||
if (drops) *drops = stat_drop;
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* Asynchronous HTTP/1.0 client for SUB-OS.
|
||||
*
|
||||
* One worker thread, created at boot, services a single-slot mailbox. A
|
||||
* requester parses a URL into a job, drops it in the mailbox, and polls the
|
||||
* job's state; the worker performs the blocking DNS + TCP fetch and marks the
|
||||
* job DONE or ERROR. Because the blocking happens on the worker rather than in
|
||||
* the caller, the GUI compositor keeps running at full frame rate during a
|
||||
* fetch that may take seconds.
|
||||
*
|
||||
* See http_client.h for the ownership rules; the two critical sections that
|
||||
* enforce them (worker completion and requester release) run with interrupts
|
||||
* disabled, which on this single-CPU kernel makes the hand-off indivisible.
|
||||
*/
|
||||
#include <net/http_client.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/tls.h>
|
||||
#include <net/dns.h>
|
||||
#include <net/net.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/task.h>
|
||||
#include <kernel/sched.h>
|
||||
#include <arch/arch.h>
|
||||
|
||||
#define HTTP_CONNECT_TIMEOUT_MS 4000
|
||||
#define HTTP_RECV_TIMEOUT_MS 3000
|
||||
|
||||
static task_t* worker_task = NULL;
|
||||
static http_fetch_t* volatile mailbox = NULL; // job awaiting the worker
|
||||
|
||||
// ===========================================================================
|
||||
// URL parsing
|
||||
//
|
||||
// Accepts host, host/path, host:port/path, with an optional http:// prefix.
|
||||
// Anything it cannot make sense of leaves the job unusable and is reported as
|
||||
// an error rather than guessed at.
|
||||
// ===========================================================================
|
||||
// Parse an absolute URL into its parts. Returns false only when there is no
|
||||
// host to be found. `is_https` reports the scheme so the caller can refuse a
|
||||
// TLS target cleanly rather than speaking plaintext to port 443.
|
||||
static bool split_url(const char* url, char* host, size_t host_cap,
|
||||
char* path, size_t path_cap, uint16_t* port, bool* is_https) {
|
||||
host[0] = '\0';
|
||||
strcpy(path, "/");
|
||||
*port = 80;
|
||||
*is_https = false;
|
||||
|
||||
if (!url) return false;
|
||||
if (strncmp(url, "http://", 7) == 0) {
|
||||
url += 7;
|
||||
} else if (strncmp(url, "https://", 8) == 0) {
|
||||
url += 8;
|
||||
*is_https = true;
|
||||
*port = 443;
|
||||
}
|
||||
|
||||
// Host (and optional :port) up to the first '/'.
|
||||
char hostport[192];
|
||||
int i = 0;
|
||||
while (url[i] && url[i] != '/' && i < (int)sizeof(hostport) - 1) {
|
||||
hostport[i] = url[i];
|
||||
i++;
|
||||
}
|
||||
hostport[i] = '\0';
|
||||
const char* rest = url + i; // "" or "/path..."
|
||||
|
||||
char* colon = strchr(hostport, ':');
|
||||
if (colon) {
|
||||
*colon = '\0';
|
||||
int p = 0;
|
||||
for (const char* c = colon + 1; *c >= '0' && *c <= '9'; c++) p = p * 10 + (*c - '0');
|
||||
if (p > 0 && p < 65536) *port = (uint16_t)p;
|
||||
}
|
||||
strncpy(host, hostport, host_cap - 1);
|
||||
host[host_cap - 1] = '\0';
|
||||
|
||||
if (rest[0] == '/') {
|
||||
strncpy(path, rest, path_cap - 1);
|
||||
path[path_cap - 1] = '\0';
|
||||
}
|
||||
return host[0] != '\0';
|
||||
}
|
||||
|
||||
static void parse_url(const char* url, http_fetch_t* f) {
|
||||
split_url(url, f->host, sizeof(f->host), f->path, sizeof(f->path), &f->port, &f->tls);
|
||||
}
|
||||
|
||||
// Find a header's value in the response, case-insensitively. Copies the value
|
||||
// (trimmed, up to the CRLF) into out. Returns false if the header is absent.
|
||||
static bool find_header(const char* buf, int len, const char* name,
|
||||
char* out, size_t out_cap) {
|
||||
size_t nlen = strlen(name);
|
||||
// Scan line by line; the status line is skipped naturally (no match).
|
||||
for (int i = 0; i + (int)nlen < len; i++) {
|
||||
if (i != 0 && buf[i - 1] != '\n') continue; // only at a line start
|
||||
|
||||
// Case-insensitive compare of the header name.
|
||||
size_t k = 0;
|
||||
while (k < nlen) {
|
||||
char a = buf[i + k], b = name[k];
|
||||
if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
|
||||
if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a');
|
||||
if (a != b) break;
|
||||
k++;
|
||||
}
|
||||
if (k != nlen || buf[i + k] != ':') continue;
|
||||
|
||||
const char* v = buf + i + nlen + 1;
|
||||
const char* end = buf + len;
|
||||
while (v < end && (*v == ' ' || *v == '\t')) v++;
|
||||
size_t o = 0;
|
||||
while (v < end && *v != '\r' && *v != '\n' && o < out_cap - 1) out[o++] = *v++;
|
||||
out[o] = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve a Location value against the current request. Absolute URLs replace
|
||||
// host/port/path; a path-only Location keeps the host. Returns false and sets
|
||||
// f->err when the target is HTTPS, which this TLS-less client cannot follow.
|
||||
static bool apply_redirect(http_fetch_t* f, const char* location) {
|
||||
if (location[0] == '/' || location[0] == '\0') {
|
||||
// Same host, new path.
|
||||
strncpy(f->path, location[0] ? location : "/", sizeof(f->path) - 1);
|
||||
f->path[sizeof(f->path) - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strncmp(location, "http://", 7) != 0 && strncmp(location, "https://", 8) != 0) {
|
||||
// A bare relative reference; treat it as a rooted path.
|
||||
snprintf(f->path, sizeof(f->path), "/%s", location);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool https;
|
||||
char host[128], path[256];
|
||||
uint16_t port;
|
||||
if (!split_url(location, host, sizeof(host), path, sizeof(path), &port, &https)) {
|
||||
snprintf(f->err, sizeof(f->err), "redirect to a URL we cannot parse");
|
||||
return false;
|
||||
}
|
||||
strcpy(f->host, host);
|
||||
strcpy(f->path, path);
|
||||
f->port = port;
|
||||
f->tls = https; // follow http->https upgrades; TLS handles it
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse the numeric status out of "HTTP/1.x NNN ...", or 0 if absent.
|
||||
static int parse_status(const char* buf, int len) {
|
||||
if (len < 12 || strncmp(buf, "HTTP/", 5) != 0) return 0;
|
||||
const char* p = buf;
|
||||
const char* end = buf + len;
|
||||
while (p < end && *p != ' ') p++; // skip "HTTP/1.x"
|
||||
while (p < end && *p == ' ') p++;
|
||||
int code = 0;
|
||||
while (p < end && *p >= '0' && *p <= '9') code = code * 10 + (*p++ - '0');
|
||||
return code;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// The fetch itself, run on the worker thread.
|
||||
//
|
||||
// It fills in the result fields but deliberately does NOT touch f->state:
|
||||
// publishing the terminal state is the finalize step's job, done under
|
||||
// interrupts-off. If do_fetch flipped the state itself, the requester could
|
||||
// observe DONE and free the job in the instant before finalize ran, and
|
||||
// finalize would then read freed memory. Returning the state instead keeps the
|
||||
// job private to the worker until the atomic hand-off.
|
||||
// ===========================================================================
|
||||
#define HTTP_MAX_REDIRECTS 5
|
||||
|
||||
// Perform one request/response against the current host/path/port, over TLS
|
||||
// when the URL is https. Returns the HTTP status code, or a negative error
|
||||
// code; the raw response lands in f->buf.
|
||||
static int do_one_request(http_fetch_t* f) {
|
||||
f->ip = ip_parse(f->host);
|
||||
if (f->ip == 0) f->ip = dns_resolve(f->host);
|
||||
if (f->ip == 0) {
|
||||
snprintf(f->err, sizeof(f->err), "cannot resolve %s", f->host);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char req[512];
|
||||
int rl = snprintf(req, sizeof(req),
|
||||
"GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: SUB-OS/webfetch\r\n"
|
||||
"Connection: close\r\n\r\n", f->path, f->host);
|
||||
|
||||
f->len = 0;
|
||||
|
||||
if (f->tls) {
|
||||
tls_conn_t* c = tls_connect(f->host, f->ip, f->port, HTTP_CONNECT_TIMEOUT_MS);
|
||||
if (!c) {
|
||||
snprintf(f->err, sizeof(f->err), "TLS to %s:%u failed: %s",
|
||||
f->host, f->port, tls_last_error());
|
||||
return -1;
|
||||
}
|
||||
if (tls_send(c, req, rl) < 0) {
|
||||
snprintf(f->err, sizeof(f->err), "TLS send failed");
|
||||
tls_close(c);
|
||||
return -1;
|
||||
}
|
||||
while (f->len < f->cap - 1) {
|
||||
int n = tls_recv(c, f->buf + f->len, f->cap - 1 - f->len, HTTP_RECV_TIMEOUT_MS);
|
||||
if (n <= 0) break;
|
||||
f->len += n;
|
||||
}
|
||||
tls_close(c);
|
||||
} else {
|
||||
tcp_conn_t* c = tcp_connect(f->ip, f->port, HTTP_CONNECT_TIMEOUT_MS);
|
||||
if (!c) {
|
||||
snprintf(f->err, sizeof(f->err), "connect to %s:%u refused/timed out",
|
||||
f->host, f->port);
|
||||
return -1;
|
||||
}
|
||||
if (tcp_send(c, req, (uint16_t)rl) < 0) {
|
||||
snprintf(f->err, sizeof(f->err), "send failed");
|
||||
tcp_close(c);
|
||||
return -1;
|
||||
}
|
||||
while (f->len < f->cap - 1) {
|
||||
int n = tcp_recv(c, f->buf + f->len, (uint16_t)(f->cap - 1 - f->len),
|
||||
HTTP_RECV_TIMEOUT_MS);
|
||||
if (n <= 0) break;
|
||||
f->len += n;
|
||||
}
|
||||
tcp_close(c);
|
||||
}
|
||||
|
||||
f->buf[f->len] = '\0';
|
||||
if (f->len == 0) {
|
||||
snprintf(f->err, sizeof(f->err), "no data received");
|
||||
return -1;
|
||||
}
|
||||
return parse_status(f->buf, f->len);
|
||||
}
|
||||
|
||||
// Fills in the result fields but deliberately does NOT touch f->state:
|
||||
// publishing the terminal state is finalize's job, done under interrupts-off.
|
||||
// Returning the state keeps the job private to the worker until that hand-off.
|
||||
//
|
||||
// A 3xx response with a Location header is followed, up to a bounded number of
|
||||
// hops, so a real-world URL that redirects (a bare host to a path, http to
|
||||
// https, a canonical host) resolves to actual content -- including the very
|
||||
// common http->https upgrade, now that TLS is available.
|
||||
static int do_fetch(http_fetch_t* f) {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
f->redirects = 0;
|
||||
|
||||
for (;;) {
|
||||
int status = do_one_request(f);
|
||||
if (status < 0) return HTTP_STATE_ERROR; // f->err already set
|
||||
|
||||
bool is_redirect = (status == 301 || status == 302 || status == 303 ||
|
||||
status == 307 || status == 308);
|
||||
char location[512];
|
||||
if (is_redirect && f->redirects < HTTP_MAX_REDIRECTS &&
|
||||
find_header(f->buf, f->len, "Location", location, sizeof(location))) {
|
||||
if (!apply_redirect(f, location)) return HTTP_STATE_ERROR;
|
||||
f->redirects++;
|
||||
continue;
|
||||
}
|
||||
|
||||
f->status_code = status;
|
||||
f->elapsed_ms = (uint32_t)((pit_get_ticks() - t0) * 10);
|
||||
return HTTP_STATE_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_job(http_fetch_t* f) {
|
||||
if (!f) return;
|
||||
if (f->buf) kfree(f->buf);
|
||||
kfree(f);
|
||||
}
|
||||
|
||||
// Publish the terminal state, or free the job if the requester walked away
|
||||
// while the fetch was in flight. Interrupts are off across the decision so it
|
||||
// serialises against http_fetch_release: exactly one of the two frees the job,
|
||||
// and the requester never sees a non-RUNNING state for a job it has released.
|
||||
static void finalize(http_fetch_t* f, int result) {
|
||||
unsigned long fl = arch_irq_save();
|
||||
bool abandoned = f->abandoned;
|
||||
if (!abandoned) f->state = result;
|
||||
arch_irq_restore(fl);
|
||||
|
||||
if (abandoned) free_job(f);
|
||||
}
|
||||
|
||||
static void http_worker_main(void* arg) {
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
unsigned long fl = arch_irq_save();
|
||||
http_fetch_t* job = mailbox;
|
||||
mailbox = NULL;
|
||||
arch_irq_restore(fl);
|
||||
|
||||
if (!job) {
|
||||
// Idle: yield rather than halt. Halting would hold the CPU until a
|
||||
// timer tick and starve the compositor between ticks; a cooperative
|
||||
// yield hands the CPU straight back and the two ping-pong cheaply.
|
||||
net_wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
int result = do_fetch(job);
|
||||
finalize(job, result);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Public API
|
||||
// ===========================================================================
|
||||
void http_client_init(void) {
|
||||
mailbox = NULL;
|
||||
worker_task = task_create("http-worker", http_worker_main, NULL, 0);
|
||||
if (worker_task) {
|
||||
printk(KERN_INFO "NET: async HTTP client worker online\n");
|
||||
} else {
|
||||
printk(KERN_WARNING "NET: async HTTP client worker failed to start\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool http_client_busy(void) {
|
||||
return mailbox != NULL;
|
||||
}
|
||||
|
||||
http_fetch_t* http_fetch_start(const char* url, int cap) {
|
||||
if (!worker_task || !url || cap < 64) return NULL;
|
||||
if (mailbox) return NULL; // a fetch is already queued
|
||||
|
||||
http_fetch_t* f = (http_fetch_t*)kzalloc(sizeof(http_fetch_t));
|
||||
if (!f) return NULL;
|
||||
|
||||
f->buf = (char*)kzalloc(cap);
|
||||
if (!f->buf) { kfree(f); return NULL; }
|
||||
f->cap = cap;
|
||||
f->state = HTTP_STATE_RUNNING;
|
||||
|
||||
parse_url(url, f);
|
||||
if (f->host[0] == '\0') {
|
||||
kfree(f->buf); kfree(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Hand the job to the worker. Re-check the mailbox under interrupts-off so
|
||||
// two near-simultaneous starts cannot both install a job.
|
||||
unsigned long fl = arch_irq_save();
|
||||
if (mailbox) { arch_irq_restore(fl); kfree(f->buf); kfree(f); return NULL; }
|
||||
mailbox = f;
|
||||
arch_irq_restore(fl);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void http_fetch_release(http_fetch_t* f) {
|
||||
if (!f) return;
|
||||
|
||||
unsigned long fl = arch_irq_save();
|
||||
if (f->state == HTTP_STATE_RUNNING) {
|
||||
// The worker still owns it (queued or in flight): let it free the job
|
||||
// when it finishes rather than pulling the memory out from under it.
|
||||
f->abandoned = true;
|
||||
arch_irq_restore(fl);
|
||||
return;
|
||||
}
|
||||
arch_irq_restore(fl);
|
||||
|
||||
free_job(f);
|
||||
}
|
||||
+209
-2
@@ -1,13 +1,220 @@
|
||||
// DHCP (Dynamic Host Configuration Protocol) client for SUB-OS.
|
||||
//
|
||||
// Performs the classic four-way handshake over UDP: broadcast DISCOVER, receive
|
||||
// OFFER, broadcast REQUEST, receive ACK. On ACK the offered IP address, subnet
|
||||
// mask, gateway and DNS server are applied to the primary interface. Each phase
|
||||
// is bounded by a timeout so an absent server degrades to the static fallback
|
||||
// configuration rather than stalling boot.
|
||||
#include <net/dhcp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/net.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <arch/arch.h>
|
||||
|
||||
#define DHCP_SERVER_PORT 67
|
||||
#define DHCP_CLIENT_PORT 68
|
||||
#define DHCP_MAGIC 0x63825363u
|
||||
#define DHCP_TIMEOUT_MS 2500
|
||||
|
||||
// BOOTP message ops and DHCP message types (option 53).
|
||||
#define BOOTREQUEST 1
|
||||
#define BOOTREPLY 2
|
||||
#define DHCPDISCOVER 1
|
||||
#define DHCPOFFER 2
|
||||
#define DHCPREQUEST 3
|
||||
#define DHCPACK 5
|
||||
#define DHCPNAK 6
|
||||
|
||||
typedef struct {
|
||||
uint8_t op;
|
||||
uint8_t htype;
|
||||
uint8_t hlen;
|
||||
uint8_t hops;
|
||||
uint32_t xid;
|
||||
uint16_t secs;
|
||||
uint16_t flags;
|
||||
uint32_t ciaddr;
|
||||
uint32_t yiaddr;
|
||||
uint32_t siaddr;
|
||||
uint32_t giaddr;
|
||||
uint8_t chaddr[16];
|
||||
uint8_t sname[64];
|
||||
uint8_t file[128];
|
||||
uint32_t cookie;
|
||||
uint8_t options[312];
|
||||
} __attribute__((packed)) dhcp_packet_t;
|
||||
|
||||
static dhcp_lease_t g_lease;
|
||||
|
||||
// Filled from UDP receive context on OFFER/ACK.
|
||||
static volatile bool reply_ready = false;
|
||||
static volatile uint8_t reply_type = 0;
|
||||
static volatile uint32_t reply_xid = 0;
|
||||
static volatile uint32_t off_yiaddr = 0;
|
||||
static volatile uint32_t off_mask = 0;
|
||||
static volatile uint32_t off_router = 0;
|
||||
static volatile uint32_t off_dns = 0;
|
||||
static volatile uint32_t off_server = 0;
|
||||
static volatile uint32_t off_lease = 0;
|
||||
|
||||
void dhcp_init(void) {
|
||||
memset(&g_lease, 0, sizeof(g_lease));
|
||||
reply_ready = false;
|
||||
printk(KERN_INFO "DHCP: Dynamic Host Configuration Protocol client ready\n");
|
||||
}
|
||||
|
||||
const dhcp_lease_t* dhcp_get_lease(void) { return &g_lease; }
|
||||
|
||||
// Append a TLV option; returns the new write offset.
|
||||
static int opt_put(uint8_t* opts, int o, uint8_t code, uint8_t len, const void* val) {
|
||||
opts[o++] = code;
|
||||
opts[o++] = len;
|
||||
if (len && val) { memcpy(&opts[o], val, len); o += len; }
|
||||
return o;
|
||||
}
|
||||
|
||||
static void dhcp_udp_cb(uint32_t src_ip, uint16_t src_port, uint16_t dst_port,
|
||||
const uint8_t* data, uint16_t len, void* ctx) {
|
||||
(void)src_ip; (void)src_port; (void)dst_port; (void)ctx;
|
||||
if (len < 240) return;
|
||||
const dhcp_packet_t* p = (const dhcp_packet_t*)data;
|
||||
if (p->op != BOOTREPLY) return;
|
||||
if (ntohl(p->cookie) != DHCP_MAGIC) return;
|
||||
|
||||
uint32_t mask = 0, router = 0, dns = 0, server = 0, lease = 0;
|
||||
uint8_t mtype = 0;
|
||||
|
||||
int optlen = len - (int)((const uint8_t*)p->options - (const uint8_t*)p);
|
||||
int i = 0;
|
||||
while (i < optlen) {
|
||||
uint8_t code = p->options[i++];
|
||||
if (code == 0xFF) break; // end
|
||||
if (code == 0x00) continue; // pad
|
||||
if (i >= optlen) break;
|
||||
uint8_t l = p->options[i++];
|
||||
if (i + l > optlen) break;
|
||||
const uint8_t* v = &p->options[i];
|
||||
switch (code) {
|
||||
case 53: mtype = v[0]; break;
|
||||
case 1: if (l >= 4) memcpy(&mask, v, 4); break;
|
||||
case 3: if (l >= 4) memcpy(&router, v, 4); break;
|
||||
case 6: if (l >= 4) memcpy(&dns, v, 4); break;
|
||||
case 54: if (l >= 4) memcpy(&server, v, 4); break;
|
||||
case 51: if (l >= 4) { uint32_t t; memcpy(&t, v, 4); lease = ntohl(t); } break;
|
||||
default: break;
|
||||
}
|
||||
i += l;
|
||||
}
|
||||
|
||||
reply_type = mtype;
|
||||
reply_xid = ntohl(p->xid);
|
||||
off_yiaddr = p->yiaddr;
|
||||
off_mask = mask;
|
||||
off_router = router;
|
||||
off_dns = dns;
|
||||
off_server = server;
|
||||
off_lease = lease;
|
||||
reply_ready = true;
|
||||
}
|
||||
|
||||
// Build a DISCOVER or REQUEST and broadcast it. requested_ip/server are set for
|
||||
// REQUEST (0 for DISCOVER).
|
||||
static void dhcp_send(uint8_t msgtype, uint32_t xid, const uint8_t* mac,
|
||||
uint32_t requested_ip, uint32_t server_id) {
|
||||
dhcp_packet_t* p = (dhcp_packet_t*)kzalloc(sizeof(dhcp_packet_t));
|
||||
if (!p) return;
|
||||
|
||||
p->op = BOOTREQUEST;
|
||||
p->htype = 1; // Ethernet
|
||||
p->hlen = 6;
|
||||
p->xid = htonl(xid);
|
||||
p->flags = htons(0x8000); // ask the server to broadcast its reply
|
||||
memcpy(p->chaddr, mac, 6);
|
||||
p->cookie = htonl(DHCP_MAGIC);
|
||||
|
||||
int o = 0;
|
||||
o = opt_put(p->options, o, 53, 1, &msgtype);
|
||||
if (msgtype == DHCPREQUEST) {
|
||||
if (requested_ip) o = opt_put(p->options, o, 50, 4, &requested_ip);
|
||||
if (server_id) o = opt_put(p->options, o, 54, 4, &server_id);
|
||||
}
|
||||
uint8_t plist[4] = {1, 3, 6, 15}; // mask, router, dns, domain
|
||||
o = opt_put(p->options, o, 55, sizeof(plist), plist);
|
||||
p->options[o++] = 0xFF; // end
|
||||
|
||||
int fixed = (int)((uint8_t*)p->options - (uint8_t*)p);
|
||||
udp_send_broadcast(DHCP_CLIENT_PORT, DHCP_SERVER_PORT, p, (uint16_t)(fixed + o));
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
static bool wait_reply(uint32_t xid, uint8_t want_type) {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
uint64_t wait_ticks = DHCP_TIMEOUT_MS / 10;
|
||||
while (pit_get_ticks() - t0 < wait_ticks) {
|
||||
if (reply_ready && reply_xid == xid && reply_type == want_type) return true;
|
||||
arch_halt();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int dhcp_request_lease(void) {
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
if (!nif) return -1;
|
||||
|
||||
printk(KERN_INFO "DHCP: Discovering network configuration via broadcast...\n");
|
||||
printk(KERN_INFO "DHCP: Lease granted: IP 10.0.2.15, Mask 255.255.255.0, GW 10.0.2.2, DNS 10.0.2.3\n");
|
||||
return 0;
|
||||
|
||||
if (udp_bind(DHCP_CLIENT_PORT, dhcp_udp_cb, NULL) != 0) {
|
||||
printk(ANSI_YELLOW "DHCP: client port 68 unavailable\n" ANSI_RESET);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t xid = (uint32_t)(0x53554200u ^ pit_get_ticks());
|
||||
int rc = -1;
|
||||
|
||||
// DISCOVER -> OFFER
|
||||
reply_ready = false;
|
||||
dhcp_send(DHCPDISCOVER, xid, nif->mac, 0, 0);
|
||||
if (wait_reply(xid, DHCPOFFER)) {
|
||||
uint32_t offered = off_yiaddr;
|
||||
uint32_t server = off_server;
|
||||
char ips[16]; ip_to_str(offered, ips);
|
||||
printk(KERN_INFO "DHCP: OFFER received: %s\n", ips);
|
||||
|
||||
// REQUEST -> ACK
|
||||
reply_ready = false;
|
||||
dhcp_send(DHCPREQUEST, xid, nif->mac, offered, server);
|
||||
if (wait_reply(xid, DHCPACK)) {
|
||||
nif->ip = off_yiaddr;
|
||||
if (off_mask) nif->subnet = off_mask;
|
||||
if (off_router) nif->gateway = off_router;
|
||||
if (off_dns) nif->dns = off_dns;
|
||||
|
||||
g_lease.ip = nif->ip;
|
||||
g_lease.mask = nif->subnet;
|
||||
g_lease.gateway = nif->gateway;
|
||||
g_lease.dns = nif->dns;
|
||||
g_lease.server = off_server;
|
||||
g_lease.lease_secs = off_lease ? off_lease : 86400;
|
||||
g_lease.bound = true;
|
||||
|
||||
char a[16], m[16], gw[16], d[16];
|
||||
ip_to_str(nif->ip, a); ip_to_str(nif->subnet, m);
|
||||
ip_to_str(nif->gateway, gw); ip_to_str(nif->dns, d);
|
||||
printk(ANSI_BRIGHT_GREEN
|
||||
"DHCP: Lease granted: IP %s, Mask %s, GW %s, DNS %s (lease %us)\n"
|
||||
ANSI_RESET, a, m, gw, d, g_lease.lease_secs);
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
udp_unbind(DHCP_CLIENT_PORT);
|
||||
|
||||
if (rc != 0) {
|
||||
char a[16]; ip_to_str(nif->ip, a);
|
||||
printk(ANSI_YELLOW "DHCP: no response; keeping static config %s\n" ANSI_RESET, a);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
+172
-6
@@ -1,17 +1,183 @@
|
||||
// DNS (Domain Name System) stub resolver for SUB-OS.
|
||||
//
|
||||
// Resolves A records by sending a real DNS query over UDP/53 to the interface's
|
||||
// configured nameserver and parsing the response. Results are cached (with TTL)
|
||||
// via the in-kernel DNS cache; the resolver first consults the cache, then a
|
||||
// small built-in hosts table, and only then hits the wire. Every network wait
|
||||
// is bounded so a missing or silent server degrades to a timeout, never a hang.
|
||||
#include <net/dns.h>
|
||||
#include <net/net.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/dns_cache.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <arch/arch.h>
|
||||
|
||||
#define DNS_PORT 53
|
||||
#define DNS_TIMEOUT_MS 2000
|
||||
#define DNS_MAX_MSG 512
|
||||
|
||||
typedef struct {
|
||||
uint16_t id;
|
||||
uint16_t flags;
|
||||
uint16_t qdcount;
|
||||
uint16_t ancount;
|
||||
uint16_t nscount;
|
||||
uint16_t arcount;
|
||||
} __attribute__((packed)) dns_header_t;
|
||||
|
||||
// Filled from UDP receive context when a matching reply arrives.
|
||||
static volatile bool dns_reply_ready = false;
|
||||
static volatile uint16_t dns_reply_id = 0;
|
||||
static volatile uint32_t dns_reply_ip = 0;
|
||||
static volatile uint32_t dns_reply_ttl = 0;
|
||||
|
||||
void dns_init(void) {
|
||||
dns_reply_ready = false;
|
||||
printk(KERN_INFO "DNS: Domain Name System Resolver client ready (Nameserver: 10.0.2.3)\n");
|
||||
}
|
||||
|
||||
// Encode "www.example.com" as DNS labels: 3www7example3com0.
|
||||
static int dns_encode_name(const char* host, uint8_t* out, int max) {
|
||||
int o = 0;
|
||||
const char* seg = host;
|
||||
while (*seg) {
|
||||
const char* dot = seg;
|
||||
while (*dot && *dot != '.') dot++;
|
||||
int seglen = (int)(dot - seg);
|
||||
if (seglen <= 0 || seglen > 63 || o + seglen + 1 >= max) return -1;
|
||||
out[o++] = (uint8_t)seglen;
|
||||
for (int i = 0; i < seglen; i++) out[o++] = (uint8_t)seg[i];
|
||||
seg = (*dot == '.') ? dot + 1 : dot;
|
||||
}
|
||||
if (o + 1 >= max) return -1;
|
||||
out[o++] = 0; // root label
|
||||
return o;
|
||||
}
|
||||
|
||||
// Skip a (possibly compressed) name in the answer section, returning the offset
|
||||
// just past it. Compression pointers (0xC0) end the name in two bytes.
|
||||
static int dns_skip_name(const uint8_t* msg, int off, int len) {
|
||||
while (off < len) {
|
||||
uint8_t b = msg[off];
|
||||
if (b == 0) return off + 1;
|
||||
if ((b & 0xC0) == 0xC0) return off + 2;
|
||||
off += b + 1;
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
static void dns_udp_cb(uint32_t src_ip, uint16_t src_port, uint16_t dst_port,
|
||||
const uint8_t* data, uint16_t len, void* ctx) {
|
||||
(void)src_ip; (void)src_port; (void)dst_port; (void)ctx;
|
||||
if (len < sizeof(dns_header_t)) return;
|
||||
|
||||
const dns_header_t* h = (const dns_header_t*)data;
|
||||
uint16_t id = ntohs(h->id);
|
||||
uint16_t ancount = ntohs(h->ancount);
|
||||
uint16_t qdcount = ntohs(h->qdcount);
|
||||
if (ancount == 0) { dns_reply_id = id; dns_reply_ready = true; return; }
|
||||
|
||||
int off = sizeof(dns_header_t);
|
||||
// Skip the echoed question(s): name + qtype(2) + qclass(2).
|
||||
for (uint16_t q = 0; q < qdcount; q++) {
|
||||
off = dns_skip_name(data, off, len);
|
||||
off += 4;
|
||||
}
|
||||
|
||||
for (uint16_t a = 0; a < ancount && off + 12 <= len; a++) {
|
||||
off = dns_skip_name(data, off, len);
|
||||
if (off + 10 > len) break;
|
||||
uint16_t type = ntohs(*(const uint16_t*)(data + off));
|
||||
uint32_t ttl = ntohl(*(const uint32_t*)(data + off + 4));
|
||||
uint16_t rdlen = ntohs(*(const uint16_t*)(data + off + 8));
|
||||
off += 10;
|
||||
if (off + rdlen > len) break;
|
||||
if (type == 1 && rdlen == 4) { // A record
|
||||
uint32_t ip;
|
||||
memcpy(&ip, data + off, 4); // already network byte order == our storage
|
||||
dns_reply_ip = ip;
|
||||
dns_reply_ttl = ttl ? ttl : 300;
|
||||
dns_reply_id = id;
|
||||
dns_reply_ready = true;
|
||||
return;
|
||||
}
|
||||
off += rdlen;
|
||||
}
|
||||
dns_reply_id = id;
|
||||
dns_reply_ready = true; // answered, but no A record found
|
||||
}
|
||||
|
||||
// Built-in hosts fallback for when the wire query yields nothing.
|
||||
static uint32_t dns_static_host(const char* host) {
|
||||
if (strcmp(host, "localhost") == 0) return ip_parse("127.0.0.1");
|
||||
if (strcmp(host, "gateway") == 0) return ip_parse("10.0.2.2");
|
||||
if (strcmp(host, "dns.google") == 0) return 0x08080808;
|
||||
if (strcmp(host, "google.com") == 0) return ip_parse("142.250.190.46");
|
||||
if (strcmp(host, "github.com") == 0) return ip_parse("140.82.121.4");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t dns_resolve(const char* hostname) {
|
||||
if (!hostname) return 0;
|
||||
if (strcmp(hostname, "localhost") == 0) return ip_parse("127.0.0.1");
|
||||
if (strcmp(hostname, "gateway") == 0) return ip_parse("10.0.2.2");
|
||||
if (strcmp(hostname, "google.com") == 0) return ip_parse("142.250.190.46");
|
||||
if (strcmp(hostname, "github.com") == 0) return ip_parse("140.82.121.4");
|
||||
return ip_parse("10.0.2.15");
|
||||
if (!hostname || !*hostname) return 0;
|
||||
|
||||
// Dotted-quad literal: no lookup needed.
|
||||
{
|
||||
bool numeric = true;
|
||||
for (const char* p = hostname; *p; p++) {
|
||||
if (!((*p >= '0' && *p <= '9') || *p == '.')) { numeric = false; break; }
|
||||
}
|
||||
if (numeric) return ip_parse(hostname);
|
||||
}
|
||||
|
||||
// 1) Cache.
|
||||
uint32_t cached;
|
||||
if (dns_cache_lookup(hostname, &cached)) return cached;
|
||||
|
||||
// 2) Wire query.
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
uint32_t server = (nif && nif->dns) ? nif->dns : ip_parse("10.0.2.3");
|
||||
uint16_t sport = udp_alloc_ephemeral_port();
|
||||
if (sport && udp_bind(sport, dns_udp_cb, NULL) == 0) {
|
||||
uint8_t query[DNS_MAX_MSG];
|
||||
dns_header_t* h = (dns_header_t*)query;
|
||||
uint16_t qid = (uint16_t)(0x5300u ^ (pit_get_ticks() & 0xFF));
|
||||
h->id = htons(qid);
|
||||
h->flags = htons(0x0100); // standard query, recursion desired
|
||||
h->qdcount = htons(1);
|
||||
h->ancount = 0; h->nscount = 0; h->arcount = 0;
|
||||
|
||||
int off = sizeof(dns_header_t);
|
||||
int nl = dns_encode_name(hostname, query + off, DNS_MAX_MSG - off - 4);
|
||||
if (nl > 0) {
|
||||
off += nl;
|
||||
*(uint16_t*)(query + off) = htons(1); off += 2; // QTYPE A
|
||||
*(uint16_t*)(query + off) = htons(1); off += 2; // QCLASS IN
|
||||
|
||||
dns_reply_ready = false;
|
||||
dns_reply_ip = 0;
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
udp_send(server, sport, DNS_PORT, query, (uint16_t)off);
|
||||
|
||||
uint64_t wait_ticks = DNS_TIMEOUT_MS / 10;
|
||||
while (!dns_reply_ready && (pit_get_ticks() - t0 < wait_ticks)) {
|
||||
net_wait();
|
||||
}
|
||||
|
||||
if (dns_reply_ready && dns_reply_id == qid && dns_reply_ip != 0) {
|
||||
uint32_t ip = dns_reply_ip;
|
||||
udp_unbind(sport);
|
||||
dns_cache_insert(hostname, ip, dns_reply_ttl);
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
udp_unbind(sport);
|
||||
}
|
||||
|
||||
// 3) Static hosts fallback.
|
||||
uint32_t st = dns_static_host(hostname);
|
||||
if (st) { dns_cache_insert(hostname, st, 300); return st; }
|
||||
return 0;
|
||||
}
|
||||
+15
-12
@@ -1,5 +1,11 @@
|
||||
// DNS Resolver Cache with TTL Management and Metrics for SUB-OS
|
||||
// DNS Resolver Cache with TTL Management and Metrics for SUB-OS.
|
||||
//
|
||||
// IPs are stored in the same convention as the rest of the network stack:
|
||||
// network byte order in memory (the first octet in the lowest byte), exactly as
|
||||
// produced by ip_parse() and consumed by ip_to_str(). Storing them any other
|
||||
// way would hand dns_resolve() callers a byte-swapped address.
|
||||
#include <net/dns_cache.h>
|
||||
#include <net/net.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
@@ -10,12 +16,12 @@ static uint64_t total_hits = 0;
|
||||
|
||||
void dns_cache_init(void) {
|
||||
memset(dns_table, 0, sizeof(dns_table));
|
||||
// Seed common local and well-known hosts
|
||||
dns_cache_insert("localhost", 0x7F000001, 86400); // 127.0.0.1
|
||||
dns_cache_insert("gateway.local", 0x0A000202, 3600); // 10.0.2.2
|
||||
dns_cache_insert("dns.google", 0x08080808, 7200); // 8.8.8.8
|
||||
dns_cache_insert("one.one.one.one", 0x01010101, 7200); // 1.1.1.1
|
||||
dns_cache_insert("github.com", 0x8C527964, 300); // 140.82.121.4
|
||||
// Seed common local and well-known hosts (network byte order via ip_parse).
|
||||
dns_cache_insert("localhost", ip_parse("127.0.0.1"), 86400);
|
||||
dns_cache_insert("gateway.local", ip_parse("10.0.2.2"), 3600);
|
||||
dns_cache_insert("dns.google", ip_parse("8.8.8.8"), 7200);
|
||||
dns_cache_insert("one.one.one.one", ip_parse("1.1.1.1"), 7200);
|
||||
dns_cache_insert("github.com", ip_parse("140.82.121.4"), 300);
|
||||
|
||||
printk(KERN_INFO "DNS: High-performance DNS Cache table initialized (Capacity: %d)\n", DNS_CACHE_MAX_ENTRIES);
|
||||
}
|
||||
@@ -63,7 +69,7 @@ void dns_cache_insert(const char* hostname, uint32_t ip, uint32_t ttl) {
|
||||
|
||||
void dns_cache_flush(void) {
|
||||
memset(dns_table, 0, sizeof(dns_table));
|
||||
dns_cache_insert("localhost", 0x7F000001, 86400);
|
||||
dns_cache_insert("localhost", ip_parse("127.0.0.1"), 86400);
|
||||
printk(ANSI_YELLOW "DNS: Resolver cache flushed.\n" ANSI_RESET);
|
||||
}
|
||||
|
||||
@@ -78,11 +84,8 @@ void dns_cache_dump(void) {
|
||||
|
||||
for (int i = 0; i < DNS_CACHE_MAX_ENTRIES; i++) {
|
||||
if (dns_table[i].active) {
|
||||
uint32_t ip = dns_table[i].ip;
|
||||
char ip_str[20];
|
||||
snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u",
|
||||
(ip >> 24) & 0xFF, (ip >> 16) & 0xFF,
|
||||
(ip >> 8) & 0xFF, ip & 0xFF);
|
||||
ip_to_str(dns_table[i].ip, ip_str);
|
||||
|
||||
printk("%-28s " ANSI_BRIGHT_YELLOW "%-16s" ANSI_RESET " %8u %8llu\n",
|
||||
dns_table[i].hostname, ip_str,
|
||||
|
||||
+748
-123
@@ -1,3 +1,29 @@
|
||||
/*
|
||||
* TCP for SUB-OS.
|
||||
*
|
||||
* The engine drives both ends of a connection:
|
||||
*
|
||||
* Passive open -- an inbound SYN to a listening service (sshd, httpd)
|
||||
* creates a server connection, which is answered straight
|
||||
* out of the receive path.
|
||||
* Active open -- tcp_connect() performs a real three-way handshake, after
|
||||
* which tcp_send/tcp_recv carry a byte stream and
|
||||
* tcp_close() shuts it down in order.
|
||||
*
|
||||
* Inbound segments are matched against the connection table first and only
|
||||
* then offered to the listeners, which is what lets a reply to one of our own
|
||||
* outbound connections through -- previously anything that was not addressed
|
||||
* to a listening port was answered with RST, so the SYN-ACK completing our own
|
||||
* handshake would have been rejected.
|
||||
*
|
||||
* Transmission is stop-and-wait rather than a sliding window: one segment is
|
||||
* outstanding at a time and is retransmitted until acknowledged. That is
|
||||
* enough to be correct over a lossy path and keeps the retransmission state to
|
||||
* a single sequence number, at the cost of throughput on a long fat link.
|
||||
*
|
||||
* Every blocking call is bounded against the timer tick and halts the CPU
|
||||
* between checks, so a silent peer costs a timeout rather than a hang.
|
||||
*/
|
||||
#include <net/tcp.h>
|
||||
#include <net/net.h>
|
||||
#include <net/ssh.h>
|
||||
@@ -6,66 +32,99 @@
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <crypto/crypto.h>
|
||||
#include <arch/arch.h>
|
||||
|
||||
#define MAX_TCP_CONNS 32
|
||||
#define MAX_TCP_CONNS 64
|
||||
|
||||
typedef struct {
|
||||
uint32_t src_ip;
|
||||
uint32_t dst_ip;
|
||||
uint8_t zero;
|
||||
uint8_t protocol;
|
||||
uint16_t tcp_len;
|
||||
} __attribute__((packed)) tcp_pseudo_header_t;
|
||||
// The timer tick is 100 Hz, so one tick is 10 ms.
|
||||
#define TCP_RTO_TICKS 50 // 500 ms retransmission timeout
|
||||
#define TCP_MAX_RETRIES 5
|
||||
#define TCP_CLOSE_TICKS 50 // how long tcp_close waits for the shutdown
|
||||
#define TCP_TIME_WAIT_TICKS 200 // 2 s; a deliberately shortened 2*MSL
|
||||
|
||||
// A connection this end closed after serving one request (httpd/sshd) does not
|
||||
// need the full linger: we sent the FIN, the peer is expected to be a
|
||||
// short-lived client, and holding the slot for two seconds is what let a burst
|
||||
// of requests exhaust the table. A brief linger still absorbs a retransmitted
|
||||
// FIN while freeing the slot roughly ten times sooner.
|
||||
#define TCP_SVC_TIME_WAIT_TICKS 25 // 250 ms
|
||||
|
||||
#define TCP_EPHEMERAL_LO 49152
|
||||
#define TCP_EPHEMERAL_HI 65535
|
||||
|
||||
static tcp_conn_t tcp_connections[MAX_TCP_CONNS];
|
||||
|
||||
static uint16_t tcp_checksum(uint32_t src_ip, uint32_t dst_ip, const tcp_header_t* tcp, const void* payload, uint16_t payload_len) {
|
||||
uint16_t tcp_total_len = sizeof(tcp_header_t) + payload_len;
|
||||
// A claimed port, plus the connections whose handshakes have completed on it
|
||||
// and are waiting to be collected by tcp_accept.
|
||||
typedef struct {
|
||||
uint16_t port;
|
||||
bool in_use;
|
||||
int backlog;
|
||||
tcp_conn_t* queue[TCP_BACKLOG_MAX];
|
||||
int count;
|
||||
} tcp_listener_t;
|
||||
|
||||
tcp_pseudo_header_t ph;
|
||||
ph.src_ip = src_ip;
|
||||
ph.dst_ip = dst_ip;
|
||||
ph.zero = 0;
|
||||
ph.protocol = IP_PROTO_TCP;
|
||||
ph.tcp_len = htons(tcp_total_len);
|
||||
static tcp_listener_t tcp_listeners[TCP_MAX_LISTENERS];
|
||||
|
||||
// Cumulative connections that have reached ESTABLISHED, and the high-water mark
|
||||
// of simultaneously-live table entries. Diagnostic only.
|
||||
static uint64_t tcp_total_opened = 0;
|
||||
static int tcp_peak_conns = 0;
|
||||
|
||||
static void tcp_note_peak(void) {
|
||||
int live = 0;
|
||||
for (size_t i = 0; i < MAX_TCP_CONNS; i++) if (tcp_connections[i].in_use) live++;
|
||||
if (live > tcp_peak_conns) tcp_peak_conns = live;
|
||||
}
|
||||
|
||||
static uint16_t tcp_checksum(uint32_t src_ip, uint32_t dst_ip, const tcp_header_t* tcp,
|
||||
const void* payload, uint16_t payload_len) {
|
||||
uint16_t tcp_total_len = (uint16_t)(sizeof(tcp_header_t) + payload_len);
|
||||
|
||||
// The pseudo-header, laid out explicitly rather than as a packed struct:
|
||||
// src and dst already hold network order, so their bytes are summed as-is.
|
||||
uint8_t tail[4] = {
|
||||
0, IP_PROTO_TCP,
|
||||
(uint8_t)(tcp_total_len >> 8), (uint8_t)(tcp_total_len & 0xFF)
|
||||
};
|
||||
|
||||
uint32_t sum = 0;
|
||||
const uint16_t* ptr = (const uint16_t*)&ph;
|
||||
for (size_t i = 0; i < sizeof(ph) / 2; i++) {
|
||||
sum += *ptr++;
|
||||
}
|
||||
sum = net_csum_add(sum, &src_ip, sizeof(src_ip));
|
||||
sum = net_csum_add(sum, &dst_ip, sizeof(dst_ip));
|
||||
sum = net_csum_add(sum, tail, sizeof(tail));
|
||||
sum = net_csum_add(sum, tcp, sizeof(tcp_header_t));
|
||||
if (payload && payload_len > 0) sum = net_csum_add(sum, payload, payload_len);
|
||||
|
||||
ptr = (const uint16_t*)tcp;
|
||||
for (size_t i = 0; i < sizeof(tcp_header_t) / 2; i++) {
|
||||
sum += *ptr++;
|
||||
}
|
||||
|
||||
if (payload && payload_len > 0) {
|
||||
ptr = (const uint16_t*)payload;
|
||||
size_t len = payload_len;
|
||||
while (len > 1) {
|
||||
sum += *ptr++;
|
||||
len -= 2;
|
||||
}
|
||||
if (len > 0) {
|
||||
sum += *(const uint8_t*)ptr;
|
||||
}
|
||||
}
|
||||
|
||||
while (sum >> 16) {
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
return (uint16_t)(~sum);
|
||||
return net_csum_finish(sum);
|
||||
}
|
||||
|
||||
void tcp_init(void) {
|
||||
memset(tcp_connections, 0, sizeof(tcp_connections));
|
||||
printk(KERN_INFO "NET: TCP (Transmission Control Protocol) state machine online\n");
|
||||
memset(tcp_listeners, 0, sizeof(tcp_listeners));
|
||||
printk(KERN_INFO "NET: TCP state machine online (active + passive open)\n");
|
||||
}
|
||||
|
||||
void tcp_send_packet(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
// ===========================================================================
|
||||
// Sequence arithmetic
|
||||
//
|
||||
// Sequence numbers wrap at 2^32, so they are compared by the sign of their
|
||||
// difference rather than by magnitude.
|
||||
// ===========================================================================
|
||||
static inline bool seq_ge(uint32_t a, uint32_t b) { return (int32_t)(a - b) >= 0; }
|
||||
static inline bool seq_gt(uint32_t a, uint32_t b) { return (int32_t)(a - b) > 0; }
|
||||
|
||||
static inline uint64_t ms_to_ticks(uint32_t ms) {
|
||||
uint64_t t = ms / 10;
|
||||
return t ? t : 1;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Transmission
|
||||
// ===========================================================================
|
||||
static void tcp_tx_full(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
uint32_t seq, uint32_t ack, uint8_t flags,
|
||||
const void* payload, uint16_t payload_len) {
|
||||
const void* payload, uint16_t payload_len, uint16_t window) {
|
||||
net_if_t* netif = net_get_primary_if();
|
||||
uint32_t src_ip = netif ? netif->ip : 0x0A00020F; // 10.0.2.15
|
||||
|
||||
@@ -80,7 +139,7 @@ void tcp_send_packet(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
tcp->ack_num = htonl(ack);
|
||||
tcp->data_offset_reserved = (sizeof(tcp_header_t) / 4) << 4;
|
||||
tcp->flags = flags;
|
||||
tcp->window_size = htons(65535);
|
||||
tcp->window_size = htons(window);
|
||||
tcp->urgent_pointer = 0;
|
||||
|
||||
if (payload && payload_len > 0) {
|
||||
@@ -94,31 +153,364 @@ void tcp_send_packet(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static tcp_conn_t* find_or_alloc_conn(uint32_t r_ip, uint16_t r_port, uint16_t l_port) {
|
||||
void tcp_send_packet(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
uint32_t seq, uint32_t ack, uint8_t flags,
|
||||
const void* payload, uint16_t payload_len) {
|
||||
tcp_tx_full(dst_ip, src_port, dst_port, seq, ack, flags, payload, payload_len, 65535);
|
||||
}
|
||||
|
||||
// Free space in the receive ring, which is what we advertise as our window so
|
||||
// a peer stops before overrunning a reader that has fallen behind.
|
||||
static uint16_t tcp_window(const tcp_conn_t* c) {
|
||||
if (!c->rx_buf) return 65535;
|
||||
return (uint16_t)(TCP_RX_BUF_SIZE - c->rx_count);
|
||||
}
|
||||
|
||||
// Send on a connection at an explicit sequence number. Retransmissions reuse
|
||||
// the original segment's sequence number, which is why this is separate from
|
||||
// the connection's current send position.
|
||||
static void tcp_tx_seq(tcp_conn_t* c, uint32_t seq, uint8_t flags,
|
||||
const void* payload, uint16_t payload_len) {
|
||||
tcp_tx_full(c->remote_ip, c->local_port, c->remote_port,
|
||||
seq, c->ack_num, flags, payload, payload_len, tcp_window(c));
|
||||
}
|
||||
|
||||
static void tcp_tx(tcp_conn_t* c, uint8_t flags, const void* payload, uint16_t payload_len) {
|
||||
tcp_tx_seq(c, c->seq_num, flags, payload, payload_len);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Connection table
|
||||
// ===========================================================================
|
||||
static void conn_free(tcp_conn_t* c) {
|
||||
if (c->rx_buf) { kfree(c->rx_buf); c->rx_buf = NULL; }
|
||||
memset(c, 0, sizeof(*c));
|
||||
}
|
||||
|
||||
// A connection in TIME_WAIT still occupies its slot so that a stray duplicate
|
||||
// from the old incarnation cannot be mistaken for traffic on a new one.
|
||||
static void reap_time_wait(void) {
|
||||
uint64_t now = pit_get_ticks();
|
||||
for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
|
||||
if (tcp_connections[i].in_use &&
|
||||
tcp_connections[i].remote_ip == r_ip &&
|
||||
tcp_connections[i].remote_port == r_port &&
|
||||
tcp_connections[i].local_port == l_port) {
|
||||
return &tcp_connections[i];
|
||||
tcp_conn_t* c = &tcp_connections[i];
|
||||
if (!c->in_use || c->state != TCP_STATE_TIME_WAIT) continue;
|
||||
|
||||
uint64_t linger = (c->role == TCP_ROLE_SERVICE)
|
||||
? TCP_SVC_TIME_WAIT_TICKS : TCP_TIME_WAIT_TICKS;
|
||||
if ((now - c->closed_at) >= linger) conn_free(c);
|
||||
}
|
||||
}
|
||||
|
||||
static tcp_conn_t* find_conn(uint32_t r_ip, uint16_t r_port, uint16_t l_port) {
|
||||
for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
|
||||
tcp_conn_t* c = &tcp_connections[i];
|
||||
if (c->in_use && c->remote_ip == r_ip &&
|
||||
c->remote_port == r_port && c->local_port == l_port) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static tcp_conn_t* alloc_conn(void) {
|
||||
reap_time_wait();
|
||||
for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
|
||||
if (!tcp_connections[i].in_use) {
|
||||
memset(&tcp_connections[i], 0, sizeof(tcp_conn_t));
|
||||
tcp_connections[i].in_use = true;
|
||||
tcp_connections[i].remote_ip = r_ip;
|
||||
tcp_connections[i].remote_port = r_port;
|
||||
tcp_connections[i].local_port = l_port;
|
||||
tcp_connections[i].local_ip = 0x0A00020F;
|
||||
tcp_connections[i].state = TCP_STATE_CLOSED;
|
||||
tcp_connections[i].seq_num = 1000;
|
||||
tcp_connections[i].ack_num = 0;
|
||||
tcp_note_peak();
|
||||
return &tcp_connections[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static tcp_conn_t* alloc_conn_server(uint32_t r_ip, uint16_t r_port, uint16_t l_port) {
|
||||
tcp_conn_t* c = alloc_conn();
|
||||
if (!c) return NULL;
|
||||
|
||||
net_if_t* netif = net_get_primary_if();
|
||||
c->local_ip = netif ? netif->ip : 0x0A00020F;
|
||||
c->remote_ip = r_ip;
|
||||
c->remote_port = r_port;
|
||||
c->local_port = l_port;
|
||||
c->role = TCP_ROLE_SERVICE;
|
||||
c->state = TCP_STATE_CLOSED;
|
||||
c->seq_num = 1000;
|
||||
c->ack_num = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
// A connection born from an inbound SYN on a listening port. It carries a
|
||||
// stream, so unlike the inline-service role it needs a receive buffer.
|
||||
static tcp_conn_t* alloc_conn_accepted(uint32_t r_ip, uint16_t r_port, uint16_t l_port) {
|
||||
tcp_conn_t* c = alloc_conn();
|
||||
if (!c) return NULL;
|
||||
|
||||
c->rx_buf = (uint8_t*)kzalloc(TCP_RX_BUF_SIZE);
|
||||
if (!c->rx_buf) { conn_free(c); return NULL; }
|
||||
|
||||
net_if_t* netif = net_get_primary_if();
|
||||
c->local_ip = netif ? netif->ip : 0x0A00020F;
|
||||
c->remote_ip = r_ip;
|
||||
c->remote_port = r_port;
|
||||
c->local_port = l_port;
|
||||
c->role = TCP_ROLE_ACCEPTED;
|
||||
c->iss = prng_rand32();
|
||||
c->seq_num = c->iss;
|
||||
c->snd_una = c->iss;
|
||||
return c;
|
||||
}
|
||||
|
||||
static uint16_t alloc_ephemeral_port(void) {
|
||||
for (int attempt = 0; attempt < 512; attempt++) {
|
||||
uint16_t p = (uint16_t)(TCP_EPHEMERAL_LO +
|
||||
(prng_rand32() % (TCP_EPHEMERAL_HI - TCP_EPHEMERAL_LO + 1)));
|
||||
bool taken = false;
|
||||
for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
|
||||
if (tcp_connections[i].in_use && tcp_connections[i].local_port == p) {
|
||||
taken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!taken) return p;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Receive ring
|
||||
// ===========================================================================
|
||||
|
||||
// Store what fits and report how much was taken, so the acknowledgement only
|
||||
// ever covers bytes actually kept.
|
||||
static uint16_t rx_append(tcp_conn_t* c, const uint8_t* data, uint16_t len) {
|
||||
if (!c->rx_buf || !data) return 0;
|
||||
|
||||
uint16_t stored = 0;
|
||||
while (stored < len && c->rx_count < TCP_RX_BUF_SIZE) {
|
||||
uint16_t tail = (uint16_t)((c->rx_head + c->rx_count) % TCP_RX_BUF_SIZE);
|
||||
c->rx_buf[tail] = data[stored++];
|
||||
c->rx_count++;
|
||||
}
|
||||
return stored;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Listener table
|
||||
// ===========================================================================
|
||||
static tcp_listener_t* find_listener(uint16_t port) {
|
||||
for (int i = 0; i < TCP_MAX_LISTENERS; i++) {
|
||||
if (tcp_listeners[i].in_use && tcp_listeners[i].port == port) {
|
||||
return &tcp_listeners[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// A handshake finished on a listening port: hand the connection to whoever is
|
||||
// in tcp_accept. If nobody has drained the backlog, the peer is refused rather
|
||||
// than left believing it has an open connection nobody will ever read.
|
||||
static void listener_enqueue(tcp_conn_t* c) {
|
||||
tcp_listener_t* l = find_listener(c->local_port);
|
||||
if (!l) return;
|
||||
|
||||
if (l->count >= l->backlog) {
|
||||
tcp_tx(c, TCP_FLAG_RST, NULL, 0);
|
||||
conn_free(c);
|
||||
return;
|
||||
}
|
||||
l->queue[l->count++] = c;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Stream state machine, shared by active opens and accepted connections
|
||||
// ===========================================================================
|
||||
static void tcp_stream_input(tcp_conn_t* c, uint32_t seq, uint32_t ack, uint8_t flags,
|
||||
const uint8_t* payload, uint16_t payload_len) {
|
||||
if (flags & TCP_FLAG_RST) {
|
||||
c->reset = true;
|
||||
c->state = TCP_STATE_CLOSED;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->state == TCP_STATE_SYN_SENT) {
|
||||
if ((flags & (TCP_FLAG_SYN | TCP_FLAG_ACK)) == (TCP_FLAG_SYN | TCP_FLAG_ACK)) {
|
||||
// The SYN we sent occupies one sequence number, so a SYN-ACK that
|
||||
// completes our handshake acknowledges exactly iss + 1.
|
||||
if (ack != c->iss + 1) return;
|
||||
c->snd_una = ack;
|
||||
c->ack_num = seq + 1;
|
||||
c->state = TCP_STATE_ESTABLISHED;
|
||||
tcp_total_opened++;
|
||||
tcp_tx(c, TCP_FLAG_ACK, NULL, 0);
|
||||
} else if (flags & TCP_FLAG_SYN) {
|
||||
// Simultaneous open: both ends sent a SYN at once.
|
||||
c->ack_num = seq + 1;
|
||||
c->state = TCP_STATE_SYN_RCVD;
|
||||
tcp_tx_seq(c, c->iss, TCP_FLAG_SYN | TCP_FLAG_ACK, NULL, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->state == TCP_STATE_SYN_RCVD) {
|
||||
if ((flags & TCP_FLAG_ACK) && seq_ge(ack, c->iss + 1)) {
|
||||
c->snd_una = ack;
|
||||
c->state = TCP_STATE_ESTABLISHED;
|
||||
tcp_total_opened++;
|
||||
// A passive open is only complete now: this is the third leg of
|
||||
// the handshake, so the connection can finally be handed out.
|
||||
if (c->role == TCP_ROLE_ACCEPTED) listener_enqueue(c);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->state == TCP_STATE_CLOSED) return;
|
||||
|
||||
// A lingering connection only has to keep acknowledging the peer's FIN,
|
||||
// whether it is a retransmission or the peer's first one arriving after we
|
||||
// stopped waiting for it.
|
||||
if (c->state == TCP_STATE_TIME_WAIT) {
|
||||
if (flags & TCP_FLAG_FIN) {
|
||||
if (seq == c->ack_num) c->ack_num++; // the FIN itself
|
||||
c->peer_fin = true;
|
||||
tcp_tx(c, TCP_FLAG_ACK, NULL, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance the send window, ignoring anything that acknowledges more than
|
||||
// has actually been sent.
|
||||
if ((flags & TCP_FLAG_ACK) && seq_gt(ack, c->snd_una) && seq_ge(c->seq_num, ack)) {
|
||||
c->snd_una = ack;
|
||||
}
|
||||
|
||||
// Accept in-order data. Anything else earns a duplicate acknowledgement
|
||||
// telling the peer where we actually are.
|
||||
if (payload_len > 0 && !c->peer_fin) {
|
||||
if (seq == c->ack_num) {
|
||||
c->ack_num += rx_append(c, payload, payload_len);
|
||||
}
|
||||
tcp_tx(c, TCP_FLAG_ACK, NULL, 0);
|
||||
}
|
||||
|
||||
// A FIN sits after the segment's data, so it is only in order once every
|
||||
// preceding byte has been taken.
|
||||
if ((flags & TCP_FLAG_FIN) && !c->peer_fin && c->ack_num == seq + payload_len) {
|
||||
c->ack_num++;
|
||||
c->peer_fin = true;
|
||||
tcp_tx(c, TCP_FLAG_ACK, NULL, 0);
|
||||
|
||||
if (c->state == TCP_STATE_ESTABLISHED) c->state = TCP_STATE_CLOSE_WAIT;
|
||||
else if (c->state == TCP_STATE_FIN_WAIT1) c->state = TCP_STATE_CLOSING;
|
||||
else if (c->state == TCP_STATE_FIN_WAIT2) {
|
||||
c->state = TCP_STATE_TIME_WAIT;
|
||||
c->closed_at = pit_get_ticks();
|
||||
}
|
||||
}
|
||||
|
||||
// Our own FIN also consumes a sequence number: once snd_una reaches
|
||||
// seq_num there is nothing of ours left outstanding.
|
||||
if (c->snd_una == c->seq_num) {
|
||||
if (c->state == TCP_STATE_FIN_WAIT1) {
|
||||
c->state = TCP_STATE_FIN_WAIT2;
|
||||
} else if (c->state == TCP_STATE_CLOSING) {
|
||||
c->state = TCP_STATE_TIME_WAIT;
|
||||
c->closed_at = pit_get_ticks();
|
||||
} else if (c->state == TCP_STATE_LAST_ACK) {
|
||||
c->state = TCP_STATE_CLOSED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Server state machine
|
||||
//
|
||||
// Behaviour is unchanged from the original listener: SSH and HTTP are served
|
||||
// straight out of the receive path, one request per segment.
|
||||
// ===========================================================================
|
||||
static void tcp_server_input(tcp_conn_t* c, uint32_t src_ip, uint16_t src_port,
|
||||
uint16_t dst_port, uint32_t seq_num, uint8_t flags,
|
||||
const uint8_t* payload, uint16_t payload_len) {
|
||||
bool is_ssh_port = (dst_port == sshd_get_port() && sshd_is_running());
|
||||
bool is_http_port = (dst_port == 80 && httpd_is_running());
|
||||
|
||||
if (flags & TCP_FLAG_SYN) {
|
||||
c->state = TCP_STATE_SYN_RCVD;
|
||||
c->ack_num = seq_num + 1;
|
||||
c->seq_num = 10000;
|
||||
|
||||
tcp_send_packet(src_ip, dst_port, src_port, c->seq_num, c->ack_num,
|
||||
TCP_FLAG_SYN | TCP_FLAG_ACK, NULL, 0);
|
||||
c->seq_num++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags & TCP_FLAG_FIN) {
|
||||
c->ack_num = seq_num + 1;
|
||||
tcp_send_packet(src_ip, dst_port, src_port, c->seq_num, c->ack_num,
|
||||
TCP_FLAG_ACK | TCP_FLAG_FIN, NULL, 0);
|
||||
conn_free(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags & TCP_FLAG_RST) {
|
||||
conn_free(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->state == TCP_STATE_SYN_RCVD) {
|
||||
c->state = TCP_STATE_ESTABLISHED;
|
||||
tcp_total_opened++;
|
||||
if (is_ssh_port && payload_len == 0) {
|
||||
const char* banner = SSH_BANNER;
|
||||
uint16_t blen = (uint16_t)strlen(banner);
|
||||
tcp_send_packet(src_ip, dst_port, src_port, c->seq_num, c->ack_num,
|
||||
TCP_FLAG_PSH | TCP_FLAG_ACK, banner, blen);
|
||||
c->seq_num += blen;
|
||||
return;
|
||||
}
|
||||
}
|
||||
c->state = TCP_STATE_ESTABLISHED;
|
||||
|
||||
if (payload_len == 0) return;
|
||||
c->ack_num = seq_num + payload_len;
|
||||
|
||||
if (is_ssh_port) {
|
||||
char resp[1024];
|
||||
int rlen = sshd_process_packet((const char*)payload, payload_len, resp, sizeof(resp));
|
||||
if (rlen > 0) {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, c->seq_num, c->ack_num,
|
||||
TCP_FLAG_PSH | TCP_FLAG_ACK, resp, (uint16_t)rlen);
|
||||
c->seq_num += rlen;
|
||||
} else {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, c->seq_num, c->ack_num,
|
||||
TCP_FLAG_ACK, NULL, 0);
|
||||
}
|
||||
} else if (is_http_port) {
|
||||
char resp[2048];
|
||||
int rlen = httpd_handle_request((const char*)payload, resp, sizeof(resp));
|
||||
if (rlen > 0) {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, c->seq_num, c->ack_num,
|
||||
TCP_FLAG_PSH | TCP_FLAG_ACK, resp, (uint16_t)rlen);
|
||||
c->seq_num += rlen;
|
||||
|
||||
// The response advertises "Connection: close", so honour it: send
|
||||
// the FIN now and let the connection linger in TIME_WAIT until the
|
||||
// reaper takes it. Without this every served request held its slot
|
||||
// for good and the 32-entry table filled up after 32 hits.
|
||||
tcp_send_packet(src_ip, dst_port, src_port, c->seq_num, c->ack_num,
|
||||
TCP_FLAG_FIN | TCP_FLAG_ACK, NULL, 0);
|
||||
c->seq_num++;
|
||||
c->state = TCP_STATE_TIME_WAIT;
|
||||
c->closed_at = pit_get_ticks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Receive dispatch
|
||||
// ===========================================================================
|
||||
void tcp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) {
|
||||
if (!packet || length < sizeof(tcp_header_t)) return;
|
||||
|
||||
@@ -128,98 +520,318 @@ void tcp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) {
|
||||
uint32_t seq_num = ntohl(tcp->seq_num);
|
||||
uint32_t ack_num = ntohl(tcp->ack_num);
|
||||
uint8_t flags = tcp->flags;
|
||||
(void)ack_num;
|
||||
|
||||
uint8_t header_len = (tcp->data_offset_reserved >> 4) * 4;
|
||||
const uint8_t* payload = packet + header_len;
|
||||
uint16_t payload_len = length > header_len ? length - header_len : 0;
|
||||
if (header_len < sizeof(tcp_header_t) || header_len > length) return;
|
||||
|
||||
// Check if target port is SSH (22) or HTTP (80)
|
||||
const uint8_t* payload = packet + header_len;
|
||||
uint16_t payload_len = length - header_len;
|
||||
|
||||
// 1. An established connection -- ours or a peer's -- owns this segment.
|
||||
tcp_conn_t* conn = find_conn(src_ip, src_port, dst_port);
|
||||
if (conn) {
|
||||
// Lingering is role-independent: whichever half opened the connection,
|
||||
// all a TIME_WAIT entry owes the peer is an acknowledgement of its FIN.
|
||||
if (conn->state == TCP_STATE_TIME_WAIT) {
|
||||
tcp_stream_input(conn, seq_num, ack_num, flags, payload, payload_len);
|
||||
} else if (conn->role == TCP_ROLE_SERVICE) {
|
||||
tcp_server_input(conn, src_ip, src_port, dst_port, seq_num, flags,
|
||||
payload, payload_len);
|
||||
} else {
|
||||
tcp_stream_input(conn, seq_num, ack_num, flags, payload, payload_len);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. A SYN to a port someone called tcp_listen on starts a passive open.
|
||||
// Only a SYN may do so: anything else for an unknown 4-tuple is stale.
|
||||
if ((flags & TCP_FLAG_SYN) && !(flags & TCP_FLAG_ACK) && find_listener(dst_port)) {
|
||||
conn = alloc_conn_accepted(src_ip, src_port, dst_port);
|
||||
if (conn) {
|
||||
conn->ack_num = seq_num + 1;
|
||||
conn->state = TCP_STATE_SYN_RCVD;
|
||||
tcp_tx_seq(conn, conn->iss, TCP_FLAG_SYN | TCP_FLAG_ACK, NULL, 0);
|
||||
conn->seq_num = conn->iss + 1; // our SYN consumes a sequence number
|
||||
} else {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, 0, seq_num + 1,
|
||||
TCP_FLAG_RST | TCP_FLAG_ACK, NULL, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Otherwise one of the built-in services may want it.
|
||||
bool is_ssh_port = (dst_port == sshd_get_port() && sshd_is_running());
|
||||
bool is_http_port = (dst_port == 80 && httpd_is_running());
|
||||
if (is_ssh_port || is_http_port) {
|
||||
conn = alloc_conn_server(src_ip, src_port, dst_port);
|
||||
if (conn) {
|
||||
tcp_server_input(conn, src_ip, src_port, dst_port, seq_num, flags,
|
||||
payload, payload_len);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_ssh_port && !is_http_port) {
|
||||
// Port not open: send TCP RST
|
||||
// 3. Nothing is listening and nothing matches: refuse the segment.
|
||||
if (!(flags & TCP_FLAG_RST)) {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, 0, seq_num + 1, TCP_FLAG_RST | TCP_FLAG_ACK, NULL, 0);
|
||||
tcp_send_packet(src_ip, dst_port, src_port, 0, seq_num + 1,
|
||||
TCP_FLAG_RST | TCP_FLAG_ACK, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Active open API
|
||||
// ===========================================================================
|
||||
tcp_conn_t* tcp_connect(uint32_t dst_ip, uint16_t dst_port, uint32_t timeout_ms) {
|
||||
if (dst_ip == 0 || dst_port == 0) return NULL;
|
||||
|
||||
tcp_conn_t* c = alloc_conn();
|
||||
if (!c) return NULL;
|
||||
|
||||
c->rx_buf = (uint8_t*)kzalloc(TCP_RX_BUF_SIZE);
|
||||
if (!c->rx_buf) { conn_free(c); return NULL; }
|
||||
|
||||
c->local_port = alloc_ephemeral_port();
|
||||
if (c->local_port == 0) { conn_free(c); return NULL; }
|
||||
|
||||
net_if_t* netif = net_get_primary_if();
|
||||
c->local_ip = netif ? netif->ip : 0x0A00020F;
|
||||
c->remote_ip = dst_ip;
|
||||
c->remote_port = dst_port;
|
||||
c->role = TCP_ROLE_CLIENT;
|
||||
c->iss = prng_rand32();
|
||||
c->seq_num = c->iss;
|
||||
c->snd_una = c->iss;
|
||||
c->ack_num = 0;
|
||||
c->state = TCP_STATE_SYN_SENT;
|
||||
|
||||
tcp_tx_seq(c, c->iss, TCP_FLAG_SYN, NULL, 0);
|
||||
c->seq_num = c->iss + 1; // the SYN consumes one sequence number
|
||||
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
uint64_t wait = ms_to_ticks(timeout_ms);
|
||||
uint64_t last = t0;
|
||||
|
||||
while ((pit_get_ticks() - t0) < wait) {
|
||||
if (c->state == TCP_STATE_ESTABLISHED) return c;
|
||||
if (c->reset || c->state == TCP_STATE_CLOSED) break;
|
||||
|
||||
if ((pit_get_ticks() - last) >= TCP_RTO_TICKS) {
|
||||
tcp_tx_seq(c, c->iss, TCP_FLAG_SYN, NULL, 0);
|
||||
last = pit_get_ticks();
|
||||
}
|
||||
net_wait();
|
||||
}
|
||||
|
||||
conn_free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int tcp_send(tcp_conn_t* c, const void* data, uint16_t len) {
|
||||
if (!c || !c->in_use || !data) return -1;
|
||||
if (c->state != TCP_STATE_ESTABLISHED && c->state != TCP_STATE_CLOSE_WAIT) return -1;
|
||||
if (len == 0) return 0;
|
||||
|
||||
const uint8_t* p = (const uint8_t*)data;
|
||||
uint16_t sent = 0;
|
||||
|
||||
while (sent < len) {
|
||||
uint16_t chunk = (uint16_t)(len - sent);
|
||||
if (chunk > TCP_MSS) chunk = TCP_MSS;
|
||||
|
||||
uint32_t seg_seq = c->seq_num;
|
||||
uint32_t seg_end = seg_seq + chunk;
|
||||
|
||||
// Publish the segment as sent before waiting: the acknowledgement is
|
||||
// only accepted if it falls within what seq_num says we have sent.
|
||||
c->seq_num = seg_end;
|
||||
|
||||
int tries = 0;
|
||||
for (;;) {
|
||||
tcp_tx_seq(c, seg_seq, TCP_FLAG_PSH | TCP_FLAG_ACK, p + sent, chunk);
|
||||
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
while ((pit_get_ticks() - t0) < TCP_RTO_TICKS) {
|
||||
if (seq_ge(c->snd_una, seg_end) || c->reset) break;
|
||||
net_wait();
|
||||
}
|
||||
|
||||
if (seq_ge(c->snd_una, seg_end)) break;
|
||||
if (c->reset) return sent ? (int)sent : -1;
|
||||
if (++tries >= TCP_MAX_RETRIES) return sent ? (int)sent : -1;
|
||||
}
|
||||
|
||||
sent += chunk;
|
||||
}
|
||||
|
||||
return (int)sent;
|
||||
}
|
||||
|
||||
int tcp_recv(tcp_conn_t* c, void* buf, uint16_t len, uint32_t timeout_ms) {
|
||||
if (!c || !c->in_use || !buf || len == 0) return -1;
|
||||
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
uint64_t wait = ms_to_ticks(timeout_ms);
|
||||
|
||||
while (c->rx_count == 0) {
|
||||
if (c->reset) return -1;
|
||||
if (c->peer_fin) return 0; // stream ended
|
||||
if ((pit_get_ticks() - t0) >= wait) return 0; // nothing arrived
|
||||
net_wait();
|
||||
}
|
||||
|
||||
uint16_t before = c->rx_count;
|
||||
uint16_t n = (c->rx_count < len) ? c->rx_count : len;
|
||||
|
||||
uint8_t* out = (uint8_t*)buf;
|
||||
for (uint16_t i = 0; i < n; i++) {
|
||||
out[i] = c->rx_buf[(c->rx_head + i) % TCP_RX_BUF_SIZE];
|
||||
}
|
||||
|
||||
c->rx_head = (uint16_t)((c->rx_head + n) % TCP_RX_BUF_SIZE);
|
||||
c->rx_count = (uint16_t)(c->rx_count - n);
|
||||
|
||||
// If the window had closed down far enough that the peer may have stalled,
|
||||
// tell it immediately that there is room again.
|
||||
if (before >= TCP_RX_BUF_SIZE / 2 && c->state == TCP_STATE_ESTABLISHED) {
|
||||
tcp_tx(c, TCP_FLAG_ACK, NULL, 0);
|
||||
}
|
||||
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
void tcp_close(tcp_conn_t* c) {
|
||||
if (!c || !c->in_use) return;
|
||||
|
||||
if (c->state == TCP_STATE_ESTABLISHED || c->state == TCP_STATE_CLOSE_WAIT) {
|
||||
// Closing after the peer already sent its FIN is the passive case and
|
||||
// finishes in LAST_ACK; closing first is the active case.
|
||||
bool passive = (c->state == TCP_STATE_CLOSE_WAIT);
|
||||
|
||||
tcp_tx(c, TCP_FLAG_FIN | TCP_FLAG_ACK, NULL, 0);
|
||||
c->seq_num++; // our FIN consumes a sequence number
|
||||
c->state = passive ? TCP_STATE_LAST_ACK : TCP_STATE_FIN_WAIT1;
|
||||
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
while ((pit_get_ticks() - t0) < TCP_CLOSE_TICKS) {
|
||||
if (c->reset) break;
|
||||
if (c->state == TCP_STATE_CLOSED || c->state == TCP_STATE_TIME_WAIT) break;
|
||||
net_wait();
|
||||
}
|
||||
}
|
||||
|
||||
// A close we started may not have finished inside that wait: the peer is
|
||||
// entitled to take its time sending its own FIN. Falling into TIME_WAIT
|
||||
// rather than freeing the slot means that late FIN gets acknowledged
|
||||
// properly instead of being answered with a reset.
|
||||
if (c->state == TCP_STATE_FIN_WAIT1 || c->state == TCP_STATE_FIN_WAIT2 ||
|
||||
c->state == TCP_STATE_CLOSING) {
|
||||
c->state = TCP_STATE_TIME_WAIT;
|
||||
}
|
||||
|
||||
if (c->state == TCP_STATE_TIME_WAIT) {
|
||||
// Hold the slot so a late duplicate cannot land on a new connection.
|
||||
// The receive buffer is not needed for that, so give it back now.
|
||||
if (c->rx_buf) { kfree(c->rx_buf); c->rx_buf = NULL; }
|
||||
c->rx_head = c->rx_count = 0;
|
||||
c->closed_at = pit_get_ticks();
|
||||
return;
|
||||
}
|
||||
|
||||
tcp_conn_t* conn = find_or_alloc_conn(src_ip, src_port, dst_port);
|
||||
if (!conn) return;
|
||||
|
||||
// Handle TCP SYN (Connection Initiation from Client)
|
||||
if (flags & TCP_FLAG_SYN) {
|
||||
conn->state = TCP_STATE_SYN_RCVD;
|
||||
conn->ack_num = seq_num + 1;
|
||||
conn->seq_num = 10000;
|
||||
|
||||
// Send SYN-ACK
|
||||
tcp_send_packet(src_ip, dst_port, src_port, conn->seq_num, conn->ack_num,
|
||||
TCP_FLAG_SYN | TCP_FLAG_ACK, NULL, 0);
|
||||
conn->seq_num++;
|
||||
return;
|
||||
conn_free(c);
|
||||
}
|
||||
|
||||
// Handle TCP FIN (Connection Termination)
|
||||
if (flags & TCP_FLAG_FIN) {
|
||||
conn->ack_num = seq_num + 1;
|
||||
tcp_send_packet(src_ip, dst_port, src_port, conn->seq_num, conn->ack_num,
|
||||
TCP_FLAG_ACK | TCP_FLAG_FIN, NULL, 0);
|
||||
conn->in_use = false;
|
||||
conn->state = TCP_STATE_CLOSED;
|
||||
return;
|
||||
// ===========================================================================
|
||||
// Passive open API
|
||||
// ===========================================================================
|
||||
int tcp_listen(uint16_t port, int backlog) {
|
||||
if (port == 0) return -1;
|
||||
if (find_listener(port)) return -1; // already claimed
|
||||
if (backlog < 1) backlog = 1;
|
||||
if (backlog > TCP_BACKLOG_MAX) backlog = TCP_BACKLOG_MAX;
|
||||
|
||||
for (int i = 0; i < TCP_MAX_LISTENERS; i++) {
|
||||
if (tcp_listeners[i].in_use) continue;
|
||||
memset(&tcp_listeners[i], 0, sizeof(tcp_listener_t));
|
||||
tcp_listeners[i].in_use = true;
|
||||
tcp_listeners[i].port = port;
|
||||
tcp_listeners[i].backlog = backlog;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle TCP RST
|
||||
if (flags & TCP_FLAG_RST) {
|
||||
conn->in_use = false;
|
||||
conn->state = TCP_STATE_CLOSED;
|
||||
return;
|
||||
tcp_conn_t* tcp_accept(uint16_t port, uint32_t timeout_ms) {
|
||||
tcp_listener_t* l = find_listener(port);
|
||||
if (!l) return NULL;
|
||||
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
uint64_t wait = ms_to_ticks(timeout_ms);
|
||||
|
||||
while (l->count == 0) {
|
||||
if ((pit_get_ticks() - t0) >= wait) return NULL;
|
||||
net_wait();
|
||||
}
|
||||
|
||||
// Handle Data / ACK
|
||||
if (conn->state == TCP_STATE_SYN_RCVD) {
|
||||
conn->state = TCP_STATE_ESTABLISHED;
|
||||
if (is_ssh_port && payload_len == 0) {
|
||||
const char* banner = SSH_BANNER;
|
||||
uint16_t blen = (uint16_t)strlen(banner);
|
||||
tcp_send_packet(src_ip, dst_port, src_port, conn->seq_num, conn->ack_num,
|
||||
TCP_FLAG_PSH | TCP_FLAG_ACK, banner, blen);
|
||||
conn->seq_num += blen;
|
||||
return;
|
||||
tcp_conn_t* c = l->queue[0];
|
||||
for (int i = 1; i < l->count; i++) l->queue[i - 1] = l->queue[i];
|
||||
l->count--;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
conn->state = TCP_STATE_ESTABLISHED;
|
||||
|
||||
if (payload_len > 0) {
|
||||
conn->ack_num = seq_num + payload_len;
|
||||
void tcp_unlisten(uint16_t port) {
|
||||
tcp_listener_t* l = find_listener(port);
|
||||
if (!l) return;
|
||||
|
||||
if (is_ssh_port) {
|
||||
char resp[1024];
|
||||
int rlen = sshd_process_packet((const char*)payload, payload_len, resp, sizeof(resp));
|
||||
if (rlen > 0) {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, conn->seq_num, conn->ack_num,
|
||||
TCP_FLAG_PSH | TCP_FLAG_ACK, resp, (uint16_t)rlen);
|
||||
conn->seq_num += rlen;
|
||||
} else {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, conn->seq_num, conn->ack_num,
|
||||
TCP_FLAG_ACK, NULL, 0);
|
||||
// Anything still queued was never handed to a reader, so refuse it rather
|
||||
// than leaving the peer with a connection nobody owns.
|
||||
for (int i = 0; i < l->count; i++) {
|
||||
tcp_conn_t* c = l->queue[i];
|
||||
if (!c || !c->in_use) continue;
|
||||
tcp_tx(c, TCP_FLAG_RST, NULL, 0);
|
||||
conn_free(c);
|
||||
}
|
||||
} else if (is_http_port) {
|
||||
char resp[2048];
|
||||
int rlen = httpd_handle_request((const char*)payload, resp, sizeof(resp));
|
||||
if (rlen > 0) {
|
||||
tcp_send_packet(src_ip, dst_port, src_port, conn->seq_num, conn->ack_num,
|
||||
TCP_FLAG_PSH | TCP_FLAG_ACK, resp, (uint16_t)rlen);
|
||||
conn->seq_num += rlen;
|
||||
memset(l, 0, sizeof(*l));
|
||||
}
|
||||
|
||||
int tcp_get_listener_count(void) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < TCP_MAX_LISTENERS; i++) if (tcp_listeners[i].in_use) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
uint16_t tcp_get_listener_port(int idx) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < TCP_MAX_LISTENERS; i++) {
|
||||
if (!tcp_listeners[i].in_use) continue;
|
||||
if (n++ == idx) return tcp_listeners[i].port;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_get_listener_backlog(int idx) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < TCP_MAX_LISTENERS; i++) {
|
||||
if (!tcp_listeners[i].in_use) continue;
|
||||
if (n++ == idx) return tcp_listeners[i].count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool tcp_is_established(const tcp_conn_t* c) {
|
||||
return c && c->in_use && c->state == TCP_STATE_ESTABLISHED;
|
||||
}
|
||||
|
||||
bool tcp_peer_closed(const tcp_conn_t* c) {
|
||||
return c && c->in_use && c->peer_fin;
|
||||
}
|
||||
|
||||
size_t tcp_get_connections_count(void) {
|
||||
// Expired TIME_WAIT slots are normally reclaimed when the next connection
|
||||
// is allocated, which never happens if none follows. netstat and the
|
||||
// Network Monitor poll this, so it doubles as the periodic tick that stops
|
||||
// finished connections lingering in the table for good.
|
||||
reap_time_wait();
|
||||
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
|
||||
if (tcp_connections[i].in_use) count++;
|
||||
@@ -231,3 +843,16 @@ const tcp_conn_t* tcp_get_connection(size_t index) {
|
||||
if (index >= MAX_TCP_CONNS || !tcp_connections[index].in_use) return NULL;
|
||||
return &tcp_connections[index];
|
||||
}
|
||||
|
||||
int tcp_conn_table_size(void) { return MAX_TCP_CONNS; }
|
||||
uint64_t tcp_get_total_opened(void) { return tcp_total_opened; }
|
||||
int tcp_get_peak_connections(void) { return tcp_peak_conns; }
|
||||
|
||||
const char* tcp_state_name(uint8_t state) {
|
||||
static const char* const names[] = {
|
||||
"CLOSED", "LISTEN", "SYN_SENT", "SYN_RCVD", "ESTABLISHED",
|
||||
"FIN_WAIT1", "FIN_WAIT2", "CLOSE_WAIT", "CLOSING", "LAST_ACK", "TIME_WAIT"
|
||||
};
|
||||
if (state >= sizeof(names) / sizeof(names[0])) return "UNKNOWN";
|
||||
return names[state];
|
||||
}
|
||||
+196
-9
@@ -1,26 +1,213 @@
|
||||
// UDP (User Datagram Protocol) transport for SUB-OS.
|
||||
//
|
||||
// Provides connectionless datagram delivery on top of the IPv4 layer. Inbound
|
||||
// datagrams are demultiplexed by destination port to a small table of bound
|
||||
// handlers (used by the socket layer, the DNS resolver and the DHCP client);
|
||||
// unclaimed ports are counted as drops. Outbound datagrams are checksummed with
|
||||
// the standard IPv4 pseudo-header.
|
||||
#include <net/udp.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#define UDP_MAX_BINDINGS 32
|
||||
#define UDP_EPHEMERAL_LO 49152u
|
||||
#define UDP_EPHEMERAL_HI 65535u
|
||||
|
||||
typedef struct {
|
||||
uint16_t port;
|
||||
udp_handler_t handler;
|
||||
void* ctx;
|
||||
bool active;
|
||||
} udp_binding_t;
|
||||
|
||||
// Pseudo-header used only for checksum computation (never transmitted).
|
||||
typedef struct {
|
||||
uint32_t src_ip;
|
||||
uint32_t dst_ip;
|
||||
uint8_t zero;
|
||||
uint8_t protocol;
|
||||
uint16_t udp_len;
|
||||
} __attribute__((packed)) udp_pseudo_header_t;
|
||||
|
||||
static udp_binding_t bindings[UDP_MAX_BINDINGS];
|
||||
static uint16_t next_ephemeral = UDP_EPHEMERAL_LO;
|
||||
|
||||
static volatile uint64_t stat_rx = 0;
|
||||
static volatile uint64_t stat_tx = 0;
|
||||
static volatile uint64_t stat_drop = 0;
|
||||
|
||||
static uint16_t udp_checksum(uint32_t src_ip, uint32_t dst_ip,
|
||||
const udp_header_t* udp, const void* payload,
|
||||
uint16_t payload_len) {
|
||||
uint16_t udp_len = (uint16_t)(sizeof(udp_header_t) + payload_len);
|
||||
|
||||
// The pseudo-header, laid out explicitly rather than as a packed struct:
|
||||
// src and dst already hold network order, so their bytes are summed as-is.
|
||||
uint8_t tail[4] = {
|
||||
0, IP_PROTO_UDP, (uint8_t)(udp_len >> 8), (uint8_t)(udp_len & 0xFF)
|
||||
};
|
||||
|
||||
uint32_t sum = 0;
|
||||
sum = net_csum_add(sum, &src_ip, sizeof(src_ip));
|
||||
sum = net_csum_add(sum, &dst_ip, sizeof(dst_ip));
|
||||
sum = net_csum_add(sum, tail, sizeof(tail));
|
||||
sum = net_csum_add(sum, udp, sizeof(udp_header_t));
|
||||
if (payload && payload_len > 0) sum = net_csum_add(sum, payload, payload_len);
|
||||
|
||||
uint16_t cksum = net_csum_finish(sum);
|
||||
// A zero checksum means "not computed" on the wire; send 0xFFFF instead.
|
||||
return cksum ? cksum : 0xFFFF;
|
||||
}
|
||||
|
||||
void udp_init(void) {
|
||||
memset(bindings, 0, sizeof(bindings));
|
||||
next_ephemeral = UDP_EPHEMERAL_LO;
|
||||
stat_rx = stat_tx = stat_drop = 0;
|
||||
printk(KERN_INFO "NET: UDP (User Datagram Protocol) transport layer online\n");
|
||||
}
|
||||
|
||||
void udp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) {
|
||||
(void)packet; (void)length; (void)src_ip;
|
||||
int udp_bind(uint16_t port, udp_handler_t handler, void* ctx) {
|
||||
if (!handler || port == 0) return -1;
|
||||
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
|
||||
if (bindings[i].active && bindings[i].port == port) return -1;
|
||||
}
|
||||
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
|
||||
if (!bindings[i].active) {
|
||||
bindings[i].port = port;
|
||||
bindings[i].handler = handler;
|
||||
bindings[i].ctx = ctx;
|
||||
bindings[i].active = true;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, const void* data, uint16_t len) {
|
||||
uint8_t buffer[1500];
|
||||
udp_header_t* udp = (udp_header_t*)buffer;
|
||||
void udp_unbind(uint16_t port) {
|
||||
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
|
||||
if (bindings[i].active && bindings[i].port == port) {
|
||||
bindings[i].active = false;
|
||||
bindings[i].handler = NULL;
|
||||
bindings[i].ctx = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t udp_alloc_ephemeral_port(void) {
|
||||
for (uint32_t tries = 0; tries <= (UDP_EPHEMERAL_HI - UDP_EPHEMERAL_LO); tries++) {
|
||||
uint16_t candidate = next_ephemeral;
|
||||
if (next_ephemeral >= UDP_EPHEMERAL_HI) next_ephemeral = UDP_EPHEMERAL_LO;
|
||||
else next_ephemeral++;
|
||||
|
||||
bool taken = false;
|
||||
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
|
||||
if (bindings[i].active && bindings[i].port == candidate) { taken = true; break; }
|
||||
}
|
||||
if (!taken) return candidate;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void udp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) {
|
||||
if (!packet || length < sizeof(udp_header_t)) { stat_drop++; return; }
|
||||
|
||||
const udp_header_t* udp = (const udp_header_t*)packet;
|
||||
uint16_t dst_port = ntohs(udp->dst_port);
|
||||
uint16_t src_port = ntohs(udp->src_port);
|
||||
uint16_t udp_len = ntohs(udp->length);
|
||||
|
||||
if (udp_len < sizeof(udp_header_t) || udp_len > length) { stat_drop++; return; }
|
||||
|
||||
const uint8_t* data = packet + sizeof(udp_header_t);
|
||||
uint16_t data_len = udp_len - sizeof(udp_header_t);
|
||||
|
||||
stat_rx++;
|
||||
|
||||
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
|
||||
if (bindings[i].active && bindings[i].port == dst_port && bindings[i].handler) {
|
||||
bindings[i].handler(src_ip, src_port, dst_port, data, data_len, bindings[i].ctx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No listener on this port.
|
||||
stat_drop++;
|
||||
}
|
||||
|
||||
void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
|
||||
const void* data, uint16_t len) {
|
||||
uint16_t total = (uint16_t)(sizeof(udp_header_t) + len);
|
||||
uint8_t* buf = (uint8_t*)kzalloc(total);
|
||||
if (!buf) return;
|
||||
|
||||
udp_header_t* udp = (udp_header_t*)buf;
|
||||
udp->src_port = htons(src_port);
|
||||
udp->dst_port = htons(dst_port);
|
||||
udp->length = htons((uint16_t)(sizeof(udp_header_t) + len));
|
||||
udp->length = htons(total);
|
||||
udp->checksum = 0;
|
||||
|
||||
if (data && len > 0) {
|
||||
memcpy(buffer + sizeof(udp_header_t), data, len);
|
||||
if (data && len > 0) memcpy(buf + sizeof(udp_header_t), data, len);
|
||||
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
uint32_t src_ip = nif ? nif->ip : 0;
|
||||
udp->checksum = udp_checksum(src_ip, dst_ip, udp, data, len);
|
||||
|
||||
net_send_ip(dst_ip, IP_PROTO_UDP, buf, total);
|
||||
stat_tx++;
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
net_send_ip(dst_ip, IP_PROTO_UDP, buffer, (uint16_t)(sizeof(udp_header_t) + len));
|
||||
void udp_send_broadcast(uint16_t src_port, uint16_t dst_port,
|
||||
const void* data, uint16_t len) {
|
||||
// Build IPv4 + UDP by hand: src 0.0.0.0, dst 255.255.255.255, sent to the
|
||||
// Ethernet broadcast address without ARP (the interface has no IP yet).
|
||||
static const uint8_t bcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
uint16_t udp_total = (uint16_t)(sizeof(udp_header_t) + len);
|
||||
uint16_t ip_total = (uint16_t)(sizeof(ip_header_t) + udp_total);
|
||||
|
||||
uint8_t* buf = (uint8_t*)kzalloc(ip_total);
|
||||
if (!buf) return;
|
||||
|
||||
ip_header_t* ip = (ip_header_t*)buf;
|
||||
ip->ihl_version = 0x45;
|
||||
ip->tos = 0;
|
||||
ip->total_length = htons(ip_total);
|
||||
ip->id = htons(0x5342); // 'SB'
|
||||
ip->flags_fragment = 0;
|
||||
ip->ttl = 64;
|
||||
ip->protocol = IP_PROTO_UDP;
|
||||
ip->checksum = 0;
|
||||
ip->src_ip = 0x00000000;
|
||||
ip->dst_ip = 0xFFFFFFFF;
|
||||
|
||||
// IPv4 header checksum, over the header just written (with checksum zero).
|
||||
ip->checksum = net_checksum(ip, sizeof(ip_header_t));
|
||||
|
||||
udp_header_t* udp = (udp_header_t*)(buf + sizeof(ip_header_t));
|
||||
udp->src_port = htons(src_port);
|
||||
udp->dst_port = htons(dst_port);
|
||||
udp->length = htons(udp_total);
|
||||
udp->checksum = 0;
|
||||
if (data && len > 0) memcpy((uint8_t*)udp + sizeof(udp_header_t), data, len);
|
||||
udp->checksum = udp_checksum(0x00000000, 0xFFFFFFFF, udp, data, len);
|
||||
|
||||
net_send_eth(bcast_mac, ETHERTYPE_IPV4, buf, ip_total);
|
||||
stat_tx++;
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
void udp_get_stats(uint64_t* rx, uint64_t* tx, uint64_t* drop) {
|
||||
if (rx) *rx = stat_rx;
|
||||
if (tx) *tx = stat_tx;
|
||||
if (drop) *drop = stat_drop;
|
||||
}
|
||||
|
||||
int udp_get_bindings(uint16_t* ports_out, int max) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < UDP_MAX_BINDINGS && n < max; i++) {
|
||||
if (bindings[i].active) ports_out[n++] = bindings[i].port;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -30,7 +30,7 @@ int sshd_start(uint16_t port) {
|
||||
ssh_sessions[0].in_use = true;
|
||||
ssh_sessions[0].state = SSH_STATE_SHELL_ACTIVE;
|
||||
ssh_sessions[0].authenticated = true;
|
||||
strcpy(ssh_sessions[0].username, "root");
|
||||
strcpy(ssh_sessions[0].username, "SUB");
|
||||
ssh_sessions[0].client_ip = 0x0A000202; // 10.0.2.2 (QEMU Host Gateway)
|
||||
ssh_sessions[0].client_port = 52341;
|
||||
ssh_sessions[0].connect_time = pit_get_ticks() / 100;
|
||||
|
||||
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
* TLS 1.3 client for SUB-OS (RFC 8446), single suite:
|
||||
* TLS_CHACHA20_POLY1305_SHA256 over X25519. See net/tls.h for the scope and
|
||||
* the honest security caveat (the server certificate is not verified).
|
||||
*
|
||||
* Structure:
|
||||
* - a small record layer (read/write, plaintext then AEAD-protected),
|
||||
* - the handshake: ClientHello -> ServerHello -> key schedule -> decrypt the
|
||||
* server flight -> send Finished -> switch to application keys,
|
||||
* - the TLS 1.3 key schedule driven by crypto/hkdf.c over a running
|
||||
* transcript hash.
|
||||
*
|
||||
* All large buffers live in the heap-allocated connection object, never on the
|
||||
* stack, because this runs on the 16 KB worker-thread stack.
|
||||
*/
|
||||
#include <net/tls.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/net.h>
|
||||
#include <crypto/crypto.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#define TLS_PLAINTEXT_MAX 16384
|
||||
#define TLS_REC_MAX (TLS_PLAINTEXT_MAX + 256)
|
||||
|
||||
// Content types.
|
||||
#define CT_CHANGE_CIPHER_SPEC 20
|
||||
#define CT_ALERT 21
|
||||
#define CT_HANDSHAKE 22
|
||||
#define CT_APPLICATION_DATA 23
|
||||
|
||||
// Handshake message types.
|
||||
#define HS_CLIENT_HELLO 1
|
||||
#define HS_SERVER_HELLO 2
|
||||
#define HS_NEW_SESSION_TICKET 4
|
||||
#define HS_ENCRYPTED_EXT 8
|
||||
#define HS_CERTIFICATE 11
|
||||
#define HS_CERT_REQUEST 13
|
||||
#define HS_CERT_VERIFY 15
|
||||
#define HS_FINISHED 20
|
||||
|
||||
#define TLS_AEAD_TAG 16
|
||||
|
||||
struct tls_conn {
|
||||
tcp_conn_t* tcp;
|
||||
|
||||
sha256_ctx_t transcript;
|
||||
|
||||
uint8_t priv[32]; // our X25519 private scalar
|
||||
uint8_t handshake_secret[32];
|
||||
uint8_t c_hs_secret[32]; // client handshake traffic secret
|
||||
|
||||
uint8_t c_hs_key[32], c_hs_iv[12];
|
||||
uint8_t s_hs_key[32], s_hs_iv[12];
|
||||
uint8_t c_ap_key[32], c_ap_iv[12];
|
||||
uint8_t s_ap_key[32], s_ap_iv[12];
|
||||
|
||||
uint64_t send_seq;
|
||||
uint64_t recv_seq;
|
||||
bool app_keys; // switched from handshake to application keys
|
||||
bool established;
|
||||
|
||||
// Decrypted application-data left over from a record, awaiting the reader.
|
||||
uint8_t rbuf[TLS_PLAINTEXT_MAX];
|
||||
int rbuf_len, rbuf_off;
|
||||
|
||||
// Decrypted handshake bytes of the current record, consumed by hs_getbytes.
|
||||
uint8_t hs_cur[TLS_PLAINTEXT_MAX];
|
||||
int hs_cur_len, hs_cur_off;
|
||||
|
||||
// Scratch for record assembly / reads (kept off the stack).
|
||||
uint8_t rec[TLS_REC_MAX];
|
||||
uint8_t pt[TLS_PLAINTEXT_MAX + 1];
|
||||
};
|
||||
|
||||
static char g_tls_err[96] = "";
|
||||
static void tls_err(const char* m) {
|
||||
strncpy(g_tls_err, m, sizeof(g_tls_err) - 1);
|
||||
g_tls_err[sizeof(g_tls_err) - 1] = '\0';
|
||||
}
|
||||
const char* tls_last_error(void) { return g_tls_err; }
|
||||
|
||||
// ===========================================================================
|
||||
// TCP framing helpers
|
||||
// ===========================================================================
|
||||
static bool read_exact(tcp_conn_t* c, uint8_t* buf, int n, uint32_t timeout_ms) {
|
||||
int got = 0;
|
||||
while (got < n) {
|
||||
int r = tcp_recv(c, buf + got, (uint16_t)(n - got), timeout_ms);
|
||||
if (r <= 0) return false;
|
||||
got += r;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send_all(tcp_conn_t* c, const uint8_t* buf, int n) {
|
||||
return tcp_send(c, buf, (uint16_t)n) == n;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Transcript hash
|
||||
// ===========================================================================
|
||||
static void transcript_add(tls_conn_t* t, const uint8_t* data, int len) {
|
||||
sha256_update(&t->transcript, data, len);
|
||||
}
|
||||
static void transcript_hash(tls_conn_t* t, uint8_t out[32]) {
|
||||
sha256_ctx_t copy = t->transcript; // snapshot; hashing is non-destructive
|
||||
sha256_final(out, ©);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Key schedule (RFC 8446 section 7.1)
|
||||
// ===========================================================================
|
||||
static void derive_traffic_keys(const uint8_t secret[32],
|
||||
uint8_t key[32], uint8_t iv[12]) {
|
||||
tls13_expand_label(secret, "key", NULL, 0, key, 32);
|
||||
tls13_expand_label(secret, "iv", NULL, 0, iv, 12);
|
||||
}
|
||||
|
||||
// After ClientHello and ServerHello are in the transcript, derive the
|
||||
// handshake traffic keys from the X25519 shared secret.
|
||||
static void key_schedule_handshake(tls_conn_t* t, const uint8_t shared[32]) {
|
||||
uint8_t empty_hash[32];
|
||||
sha256((const uint8_t*)"", 0, empty_hash);
|
||||
|
||||
uint8_t psk[32] = { 0 };
|
||||
uint8_t early_secret[32];
|
||||
hkdf_extract(NULL, 0, psk, 32, early_secret);
|
||||
|
||||
uint8_t derived[32];
|
||||
tls13_derive_secret(early_secret, "derived", empty_hash, derived);
|
||||
hkdf_extract(derived, 32, shared, 32, t->handshake_secret);
|
||||
|
||||
uint8_t th[32];
|
||||
transcript_hash(t, th); // CH || SH
|
||||
|
||||
uint8_t s_hs_secret[32];
|
||||
tls13_derive_secret(t->handshake_secret, "c hs traffic", th, t->c_hs_secret);
|
||||
tls13_derive_secret(t->handshake_secret, "s hs traffic", th, s_hs_secret);
|
||||
|
||||
derive_traffic_keys(t->c_hs_secret, t->c_hs_key, t->c_hs_iv);
|
||||
derive_traffic_keys(s_hs_secret, t->s_hs_key, t->s_hs_iv);
|
||||
|
||||
t->send_seq = t->recv_seq = 0;
|
||||
}
|
||||
|
||||
// After the server Finished is in the transcript, derive the application keys.
|
||||
static void key_schedule_application(tls_conn_t* t) {
|
||||
uint8_t empty_hash[32];
|
||||
sha256((const uint8_t*)"", 0, empty_hash);
|
||||
|
||||
uint8_t derived[32];
|
||||
tls13_derive_secret(t->handshake_secret, "derived", empty_hash, derived);
|
||||
|
||||
uint8_t psk[32] = { 0 };
|
||||
uint8_t master[32];
|
||||
hkdf_extract(derived, 32, psk, 32, master);
|
||||
|
||||
uint8_t th[32];
|
||||
transcript_hash(t, th); // CH .. server Finished
|
||||
|
||||
uint8_t c_ap_secret[32], s_ap_secret[32];
|
||||
tls13_derive_secret(master, "c ap traffic", th, c_ap_secret);
|
||||
tls13_derive_secret(master, "s ap traffic", th, s_ap_secret);
|
||||
|
||||
derive_traffic_keys(c_ap_secret, t->c_ap_key, t->c_ap_iv);
|
||||
derive_traffic_keys(s_ap_secret, t->s_ap_key, t->s_ap_iv);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Record layer
|
||||
// ===========================================================================
|
||||
static void build_nonce(uint8_t nonce[12], const uint8_t iv[12], uint64_t seq) {
|
||||
memcpy(nonce, iv, 12);
|
||||
for (int i = 0; i < 8; i++) nonce[11 - i] ^= (uint8_t)(seq >> (8 * i));
|
||||
}
|
||||
|
||||
// Encrypt `data` (of inner content type `inner_type`) as one record and send it.
|
||||
static bool write_record(tls_conn_t* t, uint8_t inner_type,
|
||||
const uint8_t* data, int len) {
|
||||
const uint8_t* key = t->app_keys ? t->c_ap_key : t->c_hs_key;
|
||||
const uint8_t* iv = t->app_keys ? t->c_ap_iv : t->c_hs_iv;
|
||||
|
||||
int ptlen = len + 1; // data || inner_type
|
||||
int reclen = ptlen + TLS_AEAD_TAG;
|
||||
|
||||
uint8_t* out = t->rec;
|
||||
out[0] = CT_APPLICATION_DATA;
|
||||
out[1] = 0x03; out[2] = 0x03;
|
||||
out[3] = (uint8_t)(reclen >> 8);
|
||||
out[4] = (uint8_t)(reclen & 0xff);
|
||||
|
||||
memcpy(t->pt, data, len);
|
||||
t->pt[len] = inner_type;
|
||||
|
||||
uint8_t nonce[12];
|
||||
build_nonce(nonce, iv, t->send_seq);
|
||||
|
||||
uint8_t tag[16];
|
||||
chacha20poly1305_encrypt(out + 5, tag, key, nonce, out, 5, t->pt, ptlen);
|
||||
memcpy(out + 5 + ptlen, tag, TLS_AEAD_TAG);
|
||||
|
||||
t->send_seq++;
|
||||
return send_all(t->tcp, out, 5 + reclen);
|
||||
}
|
||||
|
||||
// Read one record. On an encrypted record, decrypt it and return the inner
|
||||
// content type in *inner_type and the plaintext length; ChangeCipherSpec
|
||||
// records are skipped transparently. Cleartext handshake (ServerHello) is
|
||||
// returned as-is. Returns -1 on error.
|
||||
static int read_record(tls_conn_t* t, uint8_t* inner_type,
|
||||
uint8_t* out, int outcap, uint32_t timeout_ms) {
|
||||
for (;;) {
|
||||
uint8_t hdr[5];
|
||||
if (!read_exact(t->tcp, hdr, 5, timeout_ms)) { tls_err("record header read failed"); return -1; }
|
||||
int type = hdr[0];
|
||||
int len = (hdr[3] << 8) | hdr[4];
|
||||
if (len < 0 || len > TLS_REC_MAX) { tls_err("record too large"); return -1; }
|
||||
|
||||
if (!read_exact(t->tcp, t->rec, len, timeout_ms)) { tls_err("record body read failed"); return -1; }
|
||||
|
||||
if (type == CT_CHANGE_CIPHER_SPEC) continue; // middlebox compat: ignore
|
||||
|
||||
if (type == CT_HANDSHAKE) {
|
||||
// Cleartext handshake (only ServerHello before keys are set).
|
||||
if (len > outcap) { tls_err("handshake record too large"); return -1; }
|
||||
memcpy(out, t->rec, len);
|
||||
*inner_type = CT_HANDSHAKE;
|
||||
return len;
|
||||
}
|
||||
|
||||
if (type == CT_ALERT && !t->app_keys && t->recv_seq == 0 && t->send_seq == 0) {
|
||||
tls_err("server sent a cleartext alert");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (type == CT_APPLICATION_DATA) {
|
||||
const uint8_t* key = t->app_keys ? t->s_ap_key : t->s_hs_key;
|
||||
const uint8_t* iv = t->app_keys ? t->s_ap_iv : t->s_hs_iv;
|
||||
if (len < TLS_AEAD_TAG) { tls_err("short encrypted record"); return -1; }
|
||||
int ct_len = len - TLS_AEAD_TAG;
|
||||
|
||||
uint8_t nonce[12];
|
||||
build_nonce(nonce, iv, t->recv_seq);
|
||||
|
||||
if (!chacha20poly1305_decrypt(t->pt, key, nonce, hdr, 5,
|
||||
t->rec, ct_len, t->rec + ct_len)) {
|
||||
tls_err("record decrypt/auth failed");
|
||||
return -1;
|
||||
}
|
||||
t->recv_seq++;
|
||||
|
||||
// Strip zero padding; the last non-zero byte is the inner type.
|
||||
int n = ct_len;
|
||||
while (n > 0 && t->pt[n - 1] == 0) n--;
|
||||
if (n == 0) { tls_err("empty inner plaintext"); return -1; }
|
||||
*inner_type = t->pt[n - 1];
|
||||
int body = n - 1;
|
||||
if (body > outcap) { tls_err("inner plaintext too large"); return -1; }
|
||||
memcpy(out, t->pt, body);
|
||||
return body;
|
||||
}
|
||||
|
||||
tls_err("unexpected record type");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Handshake byte stream (server flight), feeding the transcript as it goes
|
||||
// ===========================================================================
|
||||
static bool hs_getbytes(tls_conn_t* t, uint8_t* dst, int n, uint32_t timeout_ms) {
|
||||
int got = 0;
|
||||
while (got < n) {
|
||||
if (t->hs_cur_off >= t->hs_cur_len) {
|
||||
uint8_t inner_type;
|
||||
int r = read_record(t, &inner_type, t->hs_cur, sizeof(t->hs_cur), timeout_ms);
|
||||
if (r < 0) return false;
|
||||
if (inner_type == CT_ALERT) { tls_err("alert during handshake"); return false; }
|
||||
if (inner_type != CT_HANDSHAKE) { tls_err("non-handshake in flight"); return false; }
|
||||
transcript_add(t, t->hs_cur, r); // every handshake byte is hashed
|
||||
t->hs_cur_len = r;
|
||||
t->hs_cur_off = 0;
|
||||
}
|
||||
int take = t->hs_cur_len - t->hs_cur_off;
|
||||
if (take > n - got) take = n - got;
|
||||
memcpy(dst + got, t->hs_cur + t->hs_cur_off, take);
|
||||
t->hs_cur_off += take;
|
||||
got += take;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// ClientHello
|
||||
// ===========================================================================
|
||||
// A tiny append-and-backpatch buffer.
|
||||
typedef struct { uint8_t* p; int len; int cap; } buf_t;
|
||||
static void bput(buf_t* b, uint8_t v) { if (b->len < b->cap) b->p[b->len] = v; b->len++; }
|
||||
static void bput16(buf_t* b, uint16_t v) { bput(b, v >> 8); bput(b, v & 0xff); }
|
||||
static void bputn(buf_t* b, const uint8_t* d, int n) { for (int i = 0; i < n; i++) bput(b, d[i]); }
|
||||
|
||||
static bool send_client_hello(tls_conn_t* t, const char* host, const uint8_t pub[32]) {
|
||||
uint8_t body[1024];
|
||||
buf_t b = { body, 0, sizeof(body) };
|
||||
|
||||
bput16(&b, 0x0303); // legacy_version
|
||||
uint8_t random[32];
|
||||
prng_get_bytes(random, 32);
|
||||
bputn(&b, random, 32);
|
||||
|
||||
uint8_t sid[32];
|
||||
prng_get_bytes(sid, 32);
|
||||
bput(&b, 32); bputn(&b, sid, 32); // legacy_session_id (compat)
|
||||
|
||||
bput16(&b, 2); bput16(&b, 0x1303); // cipher_suites: chacha20-poly1305
|
||||
bput(&b, 1); bput(&b, 0); // compression: null
|
||||
|
||||
// Extensions.
|
||||
int ext_len_at = b.len;
|
||||
bput16(&b, 0); // placeholder for extensions length
|
||||
int ext_start = b.len;
|
||||
|
||||
// supported_versions (43): TLS 1.3
|
||||
bput16(&b, 43); bput16(&b, 3); bput(&b, 2); bput16(&b, 0x0304);
|
||||
|
||||
// supported_groups (10): x25519
|
||||
bput16(&b, 10); bput16(&b, 4); bput16(&b, 2); bput16(&b, 0x001d);
|
||||
|
||||
// signature_algorithms (13): a common set (unused -- we do not verify)
|
||||
static const uint16_t sigs[] = { 0x0403, 0x0804, 0x0401, 0x0805, 0x0806, 0x0807 };
|
||||
int nsig = (int)(sizeof(sigs) / sizeof(sigs[0]));
|
||||
bput16(&b, 13); bput16(&b, 2 + nsig * 2); bput16(&b, nsig * 2);
|
||||
for (int i = 0; i < nsig; i++) bput16(&b, sigs[i]);
|
||||
|
||||
// key_share (51): x25519 public key
|
||||
bput16(&b, 51); bput16(&b, 38); bput16(&b, 36);
|
||||
bput16(&b, 0x001d); bput16(&b, 32); bputn(&b, pub, 32);
|
||||
|
||||
// server_name (0): SNI
|
||||
int hlen = (int)strlen(host);
|
||||
bput16(&b, 0); bput16(&b, hlen + 5); bput16(&b, hlen + 3);
|
||||
bput(&b, 0); bput16(&b, hlen); bputn(&b, (const uint8_t*)host, hlen);
|
||||
|
||||
// ALPN (16): http/1.1
|
||||
bput16(&b, 16); bput16(&b, 11); bput16(&b, 9); bput(&b, 8);
|
||||
bputn(&b, (const uint8_t*)"http/1.1", 8);
|
||||
|
||||
int ext_len = b.len - ext_start;
|
||||
body[ext_len_at] = (uint8_t)(ext_len >> 8);
|
||||
body[ext_len_at + 1] = (uint8_t)(ext_len & 0xff);
|
||||
|
||||
if (b.len > (int)sizeof(body)) { tls_err("ClientHello overflow"); return false; }
|
||||
|
||||
// Wrap in a handshake header, then a plaintext record.
|
||||
uint8_t hs[4 + sizeof(body)];
|
||||
hs[0] = HS_CLIENT_HELLO;
|
||||
hs[1] = (uint8_t)(b.len >> 16);
|
||||
hs[2] = (uint8_t)(b.len >> 8);
|
||||
hs[3] = (uint8_t)(b.len);
|
||||
memcpy(hs + 4, body, b.len);
|
||||
int hs_len = 4 + b.len;
|
||||
|
||||
transcript_add(t, hs, hs_len);
|
||||
|
||||
uint8_t rec[5 + 4 + sizeof(body)];
|
||||
rec[0] = CT_HANDSHAKE; rec[1] = 0x03; rec[2] = 0x01; // legacy 0x0301 for CH
|
||||
rec[3] = (uint8_t)(hs_len >> 8);
|
||||
rec[4] = (uint8_t)(hs_len & 0xff);
|
||||
memcpy(rec + 5, hs, hs_len);
|
||||
return send_all(t->tcp, rec, 5 + hs_len);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// ServerHello
|
||||
// ===========================================================================
|
||||
static bool parse_server_hello(tls_conn_t* t, const uint8_t* msg, int len,
|
||||
uint8_t server_pub[32]) {
|
||||
// msg is the handshake message: type(1) len(3) body.
|
||||
if (len < 4 || msg[0] != HS_SERVER_HELLO) { tls_err("expected ServerHello"); return false; }
|
||||
const uint8_t* p = msg + 4;
|
||||
const uint8_t* end = msg + len;
|
||||
|
||||
if (end - p < 2 + 32 + 1) { tls_err("ServerHello truncated"); return false; }
|
||||
p += 2; // legacy_version
|
||||
p += 32; // random
|
||||
int sid_len = *p++;
|
||||
if (end - p < sid_len + 3) { tls_err("ServerHello truncated"); return false; }
|
||||
p += sid_len; // session id echo
|
||||
|
||||
uint16_t cipher = ((uint16_t)p[0] << 8) | p[1]; p += 2;
|
||||
if (cipher != 0x1303) { tls_err("server chose an unsupported cipher"); return false; }
|
||||
p += 1; // compression
|
||||
|
||||
if (end - p < 2) { tls_err("ServerHello no extensions"); return false; }
|
||||
int ext_total = (p[0] << 8) | p[1]; p += 2;
|
||||
const uint8_t* ext_end = p + ext_total;
|
||||
if (ext_end > end) { tls_err("ServerHello ext overflow"); return false; }
|
||||
|
||||
bool got_key = false;
|
||||
while (p + 4 <= ext_end) {
|
||||
uint16_t etype = ((uint16_t)p[0] << 8) | p[1];
|
||||
uint16_t elen = ((uint16_t)p[2] << 8) | p[3];
|
||||
p += 4;
|
||||
if (p + elen > ext_end) { tls_err("ServerHello ext len"); return false; }
|
||||
|
||||
if (etype == 51) { // key_share
|
||||
if (elen < 4) { tls_err("bad key_share"); return false; }
|
||||
uint16_t group = ((uint16_t)p[0] << 8) | p[1];
|
||||
uint16_t klen = ((uint16_t)p[2] << 8) | p[3];
|
||||
if (group != 0x001d || klen != 32 || elen < 4 + 32) {
|
||||
tls_err("server key_share is not X25519");
|
||||
return false;
|
||||
}
|
||||
memcpy(server_pub, p + 4, 32);
|
||||
got_key = true;
|
||||
}
|
||||
p += elen;
|
||||
}
|
||||
if (!got_key) { tls_err("server sent no X25519 key_share (HelloRetry?)"); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Server flight: EncryptedExtensions, Certificate*, CertVerify, Finished
|
||||
// ===========================================================================
|
||||
static bool read_server_flight(tls_conn_t* t, uint32_t timeout_ms) {
|
||||
for (;;) {
|
||||
uint8_t hdr[4];
|
||||
if (!hs_getbytes(t, hdr, 4, timeout_ms)) return false;
|
||||
int htype = hdr[0];
|
||||
int hlen = (hdr[1] << 16) | (hdr[2] << 8) | hdr[3];
|
||||
|
||||
// Consume (and discard) the message body; every byte was already fed to
|
||||
// the transcript by hs_getbytes.
|
||||
uint8_t skip[512];
|
||||
int left = hlen;
|
||||
while (left > 0) {
|
||||
int chunk = left < (int)sizeof(skip) ? left : (int)sizeof(skip);
|
||||
if (!hs_getbytes(t, skip, chunk, timeout_ms)) return false;
|
||||
left -= chunk;
|
||||
}
|
||||
|
||||
if (htype == HS_FINISHED) return true; // end of the server flight
|
||||
if (htype == HS_CERT_REQUEST) {
|
||||
// We do not present a client certificate; most public servers do not
|
||||
// request one. Proceeding without it is the documented limitation.
|
||||
}
|
||||
// EE / Certificate / CertificateVerify: accepted without verification.
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Client Finished
|
||||
// ===========================================================================
|
||||
static bool send_client_finished(tls_conn_t* t) {
|
||||
uint8_t finished_key[32];
|
||||
tls13_expand_label(t->c_hs_secret, "finished", NULL, 0, finished_key, 32);
|
||||
|
||||
uint8_t th[32];
|
||||
transcript_hash(t, th); // CH .. server Finished
|
||||
|
||||
uint8_t verify[32];
|
||||
hmac_sha256(finished_key, 32, th, 32, verify);
|
||||
|
||||
uint8_t msg[4 + 32];
|
||||
msg[0] = HS_FINISHED; msg[1] = 0; msg[2] = 0; msg[3] = 32;
|
||||
memcpy(msg + 4, verify, 32);
|
||||
|
||||
// Sent under handshake keys.
|
||||
return write_record(t, CT_HANDSHAKE, msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Public API
|
||||
// ===========================================================================
|
||||
tls_conn_t* tls_connect(const char* host, uint32_t ip, uint16_t port,
|
||||
uint32_t timeout_ms) {
|
||||
tls_err("");
|
||||
if (!host || ip == 0 || port == 0) { tls_err("bad arguments"); return NULL; }
|
||||
|
||||
tls_conn_t* t = (tls_conn_t*)kzalloc(sizeof(tls_conn_t));
|
||||
if (!t) { tls_err("out of memory"); return NULL; }
|
||||
|
||||
sha256_init(&t->transcript);
|
||||
|
||||
t->tcp = tcp_connect(ip, port, timeout_ms);
|
||||
if (!t->tcp) { tls_err("TCP connect failed"); kfree(t); return NULL; }
|
||||
|
||||
uint8_t pub[32];
|
||||
prng_get_bytes(t->priv, 32);
|
||||
x25519_base(pub, t->priv);
|
||||
|
||||
if (!send_client_hello(t, host, pub)) { tls_err("ClientHello send failed"); goto fail; }
|
||||
|
||||
// ServerHello (cleartext handshake record).
|
||||
uint8_t inner_type;
|
||||
int shlen = read_record(t, &inner_type, t->hs_cur, sizeof(t->hs_cur), timeout_ms);
|
||||
if (shlen < 0) goto fail;
|
||||
if (inner_type != CT_HANDSHAKE) { tls_err("expected ServerHello record"); goto fail; }
|
||||
transcript_add(t, t->hs_cur, shlen);
|
||||
|
||||
uint8_t server_pub[32];
|
||||
if (!parse_server_hello(t, t->hs_cur, shlen, server_pub)) goto fail;
|
||||
|
||||
uint8_t shared[32];
|
||||
x25519(shared, t->priv, server_pub);
|
||||
key_schedule_handshake(t, shared);
|
||||
|
||||
// Server flight is now encrypted under the server handshake key.
|
||||
if (!read_server_flight(t, timeout_ms)) goto fail;
|
||||
|
||||
// Application keys are derived over CH..server Finished.
|
||||
key_schedule_application(t);
|
||||
|
||||
if (!send_client_finished(t)) { tls_err("Finished send failed"); goto fail; }
|
||||
|
||||
// From here on both directions use the application keys.
|
||||
t->app_keys = true;
|
||||
t->send_seq = t->recv_seq = 0;
|
||||
t->established = true;
|
||||
return t;
|
||||
|
||||
fail:
|
||||
if (t->tcp) tcp_close(t->tcp);
|
||||
kfree(t);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int tls_send(tls_conn_t* t, const void* data, int len) {
|
||||
if (!t || !t->established) return -1;
|
||||
const uint8_t* p = (const uint8_t*)data;
|
||||
int sent = 0;
|
||||
while (sent < len) {
|
||||
int chunk = len - sent;
|
||||
if (chunk > TLS_PLAINTEXT_MAX) chunk = TLS_PLAINTEXT_MAX;
|
||||
if (!write_record(t, CT_APPLICATION_DATA, p + sent, chunk)) return sent ? sent : -1;
|
||||
sent += chunk;
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
int tls_recv(tls_conn_t* t, void* buf, int len, uint32_t timeout_ms) {
|
||||
if (!t || !t->established) return -1;
|
||||
|
||||
// Serve any leftover decrypted application data first.
|
||||
if (t->rbuf_off >= t->rbuf_len) {
|
||||
for (;;) {
|
||||
uint8_t inner_type;
|
||||
int r = read_record(t, &inner_type, t->rbuf, sizeof(t->rbuf), timeout_ms);
|
||||
if (r < 0) return -1;
|
||||
if (inner_type == CT_APPLICATION_DATA) { t->rbuf_len = r; t->rbuf_off = 0; break; }
|
||||
if (inner_type == CT_HANDSHAKE) continue; // NewSessionTicket etc.: ignore
|
||||
if (inner_type == CT_ALERT) return 0; // close_notify or fatal: end of stream
|
||||
// anything else: ignore and keep reading
|
||||
}
|
||||
}
|
||||
|
||||
int avail = t->rbuf_len - t->rbuf_off;
|
||||
int n = avail < len ? avail : len;
|
||||
memcpy(buf, t->rbuf + t->rbuf_off, n);
|
||||
t->rbuf_off += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
void tls_close(tls_conn_t* t) {
|
||||
if (!t) return;
|
||||
if (t->tcp) tcp_close(t->tcp);
|
||||
kfree(t);
|
||||
}
|
||||
@@ -215,3 +215,69 @@ int rust_aead_decrypt(const uint8_t* key, const uint8_t* nonce, const uint8_t* a
|
||||
if (cipher && plain_out && cipher_len > 0) memcpy(plain_out, cipher, cipher_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- Checksums & non-cryptographic hashing (C fallback for non-x86 targets) ---
|
||||
// These mirror rust/src/checksum/mod.rs bit-for-bit so callers get identical
|
||||
// results whether the native Rust staticlib or this bridge is linked.
|
||||
uint32_t rust_crc32c(const uint8_t* data, size_t len) {
|
||||
if (!data) return 0;
|
||||
uint32_t crc = 0xFFFFFFFFu;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for (int b = 0; b < 8; b++) {
|
||||
uint32_t mask = -(crc & 1u);
|
||||
crc = (crc >> 1) ^ (0x82F63B78u & mask);
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
uint32_t rust_adler32(const uint8_t* data, size_t len) {
|
||||
if (!data) return 1;
|
||||
uint32_t a = 1, b = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
a = (a + data[i]) % 65521u;
|
||||
b = (b + a) % 65521u;
|
||||
}
|
||||
return (b << 16) | a;
|
||||
}
|
||||
|
||||
uint64_t rust_fnv1a64(const uint8_t* data, size_t len) {
|
||||
uint64_t hash = 0xcbf29ce484222325ull;
|
||||
if (!data) return hash;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
hash ^= data[i];
|
||||
hash *= 0x00000100000001B3ull;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// --- RLE codec (C fallback, mirrors rust/src/compress/mod.rs) ---
|
||||
int rust_rle_compress(const uint8_t* in_buf, size_t in_len, uint8_t* out_buf, size_t max_out) {
|
||||
if (!in_buf || !out_buf) return -1;
|
||||
size_t out = 0, i = 0;
|
||||
while (i < in_len) {
|
||||
uint8_t value = in_buf[i];
|
||||
size_t run = 1;
|
||||
while (i + run < in_len && in_buf[i + run] == value && run < 255) run++;
|
||||
if (out + 2 > max_out) return -1;
|
||||
out_buf[out++] = (uint8_t)run;
|
||||
out_buf[out++] = value;
|
||||
i += run;
|
||||
}
|
||||
return (int)out;
|
||||
}
|
||||
|
||||
int rust_rle_decompress(const uint8_t* in_buf, size_t in_len, uint8_t* out_buf, size_t max_out) {
|
||||
if (!in_buf || !out_buf) return -1;
|
||||
size_t out = 0, i = 0;
|
||||
while (i + 1 < in_len) {
|
||||
size_t run = in_buf[i];
|
||||
uint8_t value = in_buf[i + 1];
|
||||
if (run == 0) return -1;
|
||||
if (out + run > max_out) return -1;
|
||||
for (size_t k = 0; k < run; k++) out_buf[out++] = value;
|
||||
i += 2;
|
||||
}
|
||||
return (int)out;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//! Memory-safe checksum and non-cryptographic hash engine for SUB-OS.
|
||||
//!
|
||||
//! Implements CRC-32C (Castagnoli, the checksum used by ext4 metadata, iSCSI
|
||||
//! and SSE4.2's `crc32` instruction), the Adler-32 rolling checksum from zlib,
|
||||
//! and the 64-bit FNV-1a hash used for fast in-kernel hash-table keying. All
|
||||
//! three are pure `no_std` computations over borrowed slices -- no allocation,
|
||||
//! no unsafe outside the thin FFI shims.
|
||||
|
||||
/// CRC-32C (Castagnoli) using the reflected polynomial 0x82F63B78. Computed
|
||||
/// bit-by-bit so no lookup table has to live in the kernel image.
|
||||
pub fn crc32c(data: &[u8]) -> u32 {
|
||||
let mut crc: u32 = 0xFFFF_FFFF;
|
||||
for &byte in data {
|
||||
crc ^= byte as u32;
|
||||
for _ in 0..8 {
|
||||
let mask = (crc & 1).wrapping_neg(); // 0xFFFFFFFF if LSB set, else 0
|
||||
crc = (crc >> 1) ^ (0x82F6_3B78 & mask);
|
||||
}
|
||||
}
|
||||
!crc
|
||||
}
|
||||
|
||||
/// Adler-32 (zlib): two running 16-bit sums modulo 65521.
|
||||
pub fn adler32(data: &[u8]) -> u32 {
|
||||
const MOD: u32 = 65521;
|
||||
let mut a: u32 = 1;
|
||||
let mut b: u32 = 0;
|
||||
for &byte in data {
|
||||
a = (a + byte as u32) % MOD;
|
||||
b = (b + a) % MOD;
|
||||
}
|
||||
(b << 16) | a
|
||||
}
|
||||
|
||||
/// 64-bit FNV-1a hash. Good spread and speed for hash-table keys.
|
||||
pub fn fnv1a64(data: &[u8]) -> u64 {
|
||||
const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
const PRIME: u64 = 0x0000_0100_0000_01B3;
|
||||
let mut hash = OFFSET;
|
||||
for &byte in data {
|
||||
hash ^= byte as u64;
|
||||
hash = hash.wrapping_mul(PRIME);
|
||||
}
|
||||
hash
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C ABI shims
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_crc32c(data: *const u8, len: usize) -> u32 {
|
||||
if data.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let bytes = unsafe { core::slice::from_raw_parts(data, len) };
|
||||
crc32c(bytes)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_adler32(data: *const u8, len: usize) -> u32 {
|
||||
if data.is_null() {
|
||||
return 1; // Adler-32 of the empty string
|
||||
}
|
||||
let bytes = unsafe { core::slice::from_raw_parts(data, len) };
|
||||
adler32(bytes)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_fnv1a64(data: *const u8, len: usize) -> u64 {
|
||||
if data.is_null() {
|
||||
return 0xcbf2_9ce4_8422_2325;
|
||||
}
|
||||
let bytes = unsafe { core::slice::from_raw_parts(data, len) };
|
||||
fnv1a64(bytes)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//! Memory-safe run-length encoding (RLE) codec for SUB-OS.
|
||||
//!
|
||||
//! A compact PackBits-style byte codec: the stream is a sequence of
|
||||
//! (count, value) pairs where `count` is 1..=255. It is intended for run-heavy
|
||||
//! data (framebuffer scanlines, zeroed pages, simple bitmaps) and is fully
|
||||
//! `no_std` with no allocation -- the caller owns both buffers.
|
||||
|
||||
/// Compress `input` into `output` as (count, value) pairs. Returns the number
|
||||
/// of bytes written, or -1 if `output` is too small.
|
||||
pub fn rle_compress(input: &[u8], output: &mut [u8]) -> isize {
|
||||
let mut out = 0usize;
|
||||
let mut i = 0usize;
|
||||
let len = input.len();
|
||||
|
||||
while i < len {
|
||||
let value = input[i];
|
||||
let mut run = 1usize;
|
||||
while i + run < len && input[i + run] == value && run < 255 {
|
||||
run += 1;
|
||||
}
|
||||
if out + 2 > output.len() {
|
||||
return -1;
|
||||
}
|
||||
output[out] = run as u8;
|
||||
output[out + 1] = value;
|
||||
out += 2;
|
||||
i += run;
|
||||
}
|
||||
out as isize
|
||||
}
|
||||
|
||||
/// Expand a (count, value) stream back into `output`. Returns the number of
|
||||
/// bytes written, or -1 on a malformed stream or an output overflow.
|
||||
pub fn rle_decompress(input: &[u8], output: &mut [u8]) -> isize {
|
||||
let mut out = 0usize;
|
||||
let mut i = 0usize;
|
||||
let len = input.len();
|
||||
|
||||
while i + 1 < len {
|
||||
let run = input[i] as usize;
|
||||
let value = input[i + 1];
|
||||
if run == 0 {
|
||||
return -1; // a zero run is never emitted by the compressor
|
||||
}
|
||||
if out + run > output.len() {
|
||||
return -1;
|
||||
}
|
||||
for _ in 0..run {
|
||||
output[out] = value;
|
||||
out += 1;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
out as isize
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C ABI shims
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_rle_compress(
|
||||
in_buf: *const u8,
|
||||
in_len: usize,
|
||||
out_buf: *mut u8,
|
||||
max_out: usize,
|
||||
) -> i32 {
|
||||
if in_buf.is_null() || out_buf.is_null() {
|
||||
return -1;
|
||||
}
|
||||
unsafe {
|
||||
let input = core::slice::from_raw_parts(in_buf, in_len);
|
||||
let output = core::slice::from_raw_parts_mut(out_buf, max_out);
|
||||
rle_compress(input, output) as i32
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_rle_decompress(
|
||||
in_buf: *const u8,
|
||||
in_len: usize,
|
||||
out_buf: *mut u8,
|
||||
max_out: usize,
|
||||
) -> i32 {
|
||||
if in_buf.is_null() || out_buf.is_null() {
|
||||
return -1;
|
||||
}
|
||||
unsafe {
|
||||
let input = core::slice::from_raw_parts(in_buf, in_len);
|
||||
let output = core::slice::from_raw_parts_mut(out_buf, max_out);
|
||||
rle_decompress(input, output) as i32
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
pub mod printk;
|
||||
pub mod drivers;
|
||||
pub mod crypto;
|
||||
pub mod checksum;
|
||||
pub mod compress;
|
||||
pub mod fs;
|
||||
pub mod kernel;
|
||||
pub mod storage;
|
||||
@@ -42,6 +44,13 @@ pub extern "C" fn rust_kernel_init() -> i32 {
|
||||
// Ping Rust watchdog
|
||||
kernel::watchdog::rust_watchdog_ping(1);
|
||||
|
||||
// Self-test the checksum engine against a known vector ("SUB-OS").
|
||||
let crc = checksum::crc32c(b"SUB-OS");
|
||||
kprintln!(
|
||||
"\x1b[32mRUST:\x1b[0m Checksum engine online (CRC-32C, Adler-32, FNV-1a) [crc32c(\"SUB-OS\")=0x{:08x}]",
|
||||
crc
|
||||
);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
@@ -407,56 +407,68 @@ def run_dialog_gui():
|
||||
continue
|
||||
|
||||
items = submenu["items"]
|
||||
item_opts = []
|
||||
is_radio = any(i.get("type") == "radio" for i in items)
|
||||
|
||||
item_opts = []
|
||||
if is_radio:
|
||||
# Single-choice group (architecture, optimization). Use a --menu,
|
||||
# not a --radiolist: in a radiolist the arrow keys only move the
|
||||
# highlight and <Space> is required to move the (*) dot, so a user
|
||||
# who highlights another architecture and presses <Enter> confirms
|
||||
# the ORIGINAL choice -- the selection appears to do nothing. With
|
||||
# --menu, <Enter> selects the highlighted row directly.
|
||||
default_item = None
|
||||
for item in items:
|
||||
item_opts.extend([item["id"], item["label"]])
|
||||
group = item.get("group")
|
||||
cur = cfg.get("CONFIG_ARCH") if group == "arch" else (
|
||||
cfg.get("CONFIG_OPTIMIZATION") if group == "opt" else None)
|
||||
if cur is not None and cur == item["val"]:
|
||||
default_item = item["id"]
|
||||
|
||||
cmd = ["dialog", "--clear", "--backtitle", backtitle,
|
||||
"--title", submenu["title"], "--help-button"]
|
||||
if default_item:
|
||||
cmd += ["--default-item", default_item]
|
||||
cmd += ["--menu",
|
||||
"Highlight a choice with the arrow keys and press <Enter> to select it.",
|
||||
"20", "78", "10"] + item_opts
|
||||
else:
|
||||
for item in items:
|
||||
itype = item.get("type")
|
||||
if itype == "bool":
|
||||
status = "ON" if cfg.get(item["id"], False) else "OFF"
|
||||
item_opts.extend([item["id"], item["label"], status])
|
||||
elif itype == "radio":
|
||||
group = item.get("group")
|
||||
if group == "arch":
|
||||
status = "ON" if cfg.get("CONFIG_ARCH") == item["val"] else "OFF"
|
||||
elif group == "opt":
|
||||
status = "ON" if cfg.get("CONFIG_OPTIMIZATION") == item["val"] else "OFF"
|
||||
else:
|
||||
status = "OFF"
|
||||
item_opts.extend([item["id"], item["label"], status])
|
||||
|
||||
is_radiolist = any(i.get("type") == "radio" for i in items)
|
||||
dialog_type = "--radiolist" if is_radiolist else "--checklist"
|
||||
|
||||
cmd = [
|
||||
"dialog",
|
||||
"--clear",
|
||||
"--backtitle", backtitle,
|
||||
"--title", submenu["title"],
|
||||
"--help-button",
|
||||
"--help-status",
|
||||
dialog_type,
|
||||
cmd = ["dialog", "--clear", "--backtitle", backtitle,
|
||||
"--title", submenu["title"], "--help-button", "--help-status",
|
||||
"--checklist",
|
||||
"Use <Space> to toggle features [*], <Enter> to confirm, <Help> for info.",
|
||||
"20", "78", "10"
|
||||
] + item_opts
|
||||
"20", "78", "10"] + item_opts
|
||||
|
||||
res = subprocess.run(cmd, stderr=subprocess.PIPE, text=True)
|
||||
ret = res.returncode
|
||||
out = res.stderr.strip()
|
||||
|
||||
if ret == 0: # OK / Confirm
|
||||
_apply_selection(cfg, items, [t.strip('"') for t in out.split()], is_radiolist)
|
||||
if is_radio:
|
||||
# --menu returns just the highlighted tag.
|
||||
_apply_selection(cfg, items, [out.strip('"')], True)
|
||||
else:
|
||||
_apply_selection(cfg, items, [t.strip('"') for t in out.split()], False)
|
||||
current_menu_id = "main"
|
||||
|
||||
elif ret == 1 or ret == 255: # Cancel or Esc
|
||||
current_menu_id = "main"
|
||||
|
||||
elif ret == 2: # Help button
|
||||
# With --help-status the payload is "HELP <focused-tag> <on-tags...>".
|
||||
# Re-apply the on-tags first so the toggles made before pressing
|
||||
# <Help> survive the round trip, then show the focused item's help.
|
||||
if is_radio:
|
||||
# --menu help payload is just "HELP <focused-tag>".
|
||||
focus = _help_tag(out)
|
||||
else:
|
||||
# --help-status payload is "HELP <focused-tag> <on-tags...>";
|
||||
# re-apply the on-tags so toggles made before <Help> survive.
|
||||
tokens = _help_tokens(out)
|
||||
focus = tokens[0] if tokens else None
|
||||
_apply_selection(cfg, items, tokens[1:], is_radiolist)
|
||||
_apply_selection(cfg, items, tokens[1:], False)
|
||||
focused = next((i for i in items if i["id"] == focus), None)
|
||||
if focused:
|
||||
help_title = f"{focused['label']} - Help"
|
||||
@@ -467,8 +479,10 @@ def run_dialog_gui():
|
||||
subprocess.run(["dialog", "--backtitle", backtitle, "--title", help_title,
|
||||
"--msgbox", help_text, "16", "72"])
|
||||
|
||||
save_config(cfg)
|
||||
print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
|
||||
# Saving is handled inside the loop: the <Save> button writes immediately,
|
||||
# and <Exit> asks first and writes only on "Yes". Saving again here would
|
||||
# override an explicit "No", so just report the resulting state.
|
||||
print(f"*** Kernel configuration ready: {CONFIG_FILE} (ARCH={cfg.get('CONFIG_ARCH', 'x86_64')}) ***")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pure Python Curses GUI Renderer (Pixel-Perfect Linux lxdialog Replica)
|
||||
@@ -829,7 +843,12 @@ def main():
|
||||
print(f"menuconfig: lxdialog front-end failed ({exc}); "
|
||||
f"falling back to the curses UI.", file=sys.stderr)
|
||||
|
||||
# Fallback to Curses GUI replica
|
||||
# Fallback to the curses GUI replica, but only when we genuinely have an
|
||||
# interactive terminal on both ends. Probing curses without a real TTY makes
|
||||
# it fail deep inside the wrapper's teardown (e.g. "nocbreak() returned
|
||||
# ERR"), which looked like a crash; gate on isatty so a piped or CI
|
||||
# invocation drops straight to the non-interactive writer instead.
|
||||
if sys.stdin.isatty() and sys.stdout.isatty():
|
||||
try:
|
||||
import curses
|
||||
curses.wrapper(run_curses_gui)
|
||||
@@ -838,8 +857,12 @@ def main():
|
||||
except Exception as exc:
|
||||
print(f"menuconfig: curses UI unavailable ({exc}); "
|
||||
f"writing the current configuration non-interactively.", file=sys.stderr)
|
||||
else:
|
||||
print("menuconfig: no interactive terminal detected; "
|
||||
"writing the current configuration non-interactively.", file=sys.stderr)
|
||||
|
||||
# Last resort: no usable TTY at all.
|
||||
# Last resort: no usable TTY at all. Preserve an existing .config if present,
|
||||
# otherwise fall back to the architecture default so the build never stalls.
|
||||
cfg = load_config()
|
||||
save_config(cfg)
|
||||
print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
Emits init/logo_data.c: a 4-bit palette-indexed bitmap plus the ARGB palette
|
||||
and an ASCII-art rendition used before the framebuffer is up.
|
||||
|
||||
python3 scripts/mklogo.py ~/Pictures/logo_SUB.png
|
||||
python3 scripts/mklogo.py assets/logo_SUB_OS.png
|
||||
|
||||
Re-run this whenever the source artwork changes; init/logo_data.c is generated
|
||||
and should not be hand-edited.
|
||||
|
||||
+82
-2
@@ -2,6 +2,7 @@
|
||||
#include <crypto/crypto.h>
|
||||
#include <lib/string.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <drivers/keyboard.h>
|
||||
|
||||
static user_account_t user_table[MAX_USERS];
|
||||
static size_t user_count = 0;
|
||||
@@ -23,11 +24,17 @@ void auth_init(void) {
|
||||
user_count = 0;
|
||||
current_uid = 0;
|
||||
|
||||
// Seed default users
|
||||
auth_add_user("root", "root", 0, 0, "/root", "/bin/sh");
|
||||
// Seed default users. SUB/SUB is the primary superuser account -- the way
|
||||
// Kali ships with kali/kali -- so the username and password both default to
|
||||
// "SUB". It is seeded first, so uid 0 resolves to it as the logged-in user.
|
||||
auth_add_user("SUB", "SUB", 0, 0, "/root", "/bin/sh");
|
||||
auth_add_user("root", "root", 0, 0, "/root", "/bin/sh"); // kept for compatibility
|
||||
auth_add_user("admin", "admin123", 1000, 1000, "/home/admin", "/bin/sh");
|
||||
auth_add_user("guest", "guest", 1001, 1001, "/home/guest", "/bin/sh");
|
||||
|
||||
// Log in as SUB by default.
|
||||
auth_set_current_user("SUB");
|
||||
|
||||
printk(KERN_INFO "AUTH: Pluggable Authentication & SHA-256 Shadow Password engine active\n");
|
||||
}
|
||||
|
||||
@@ -117,3 +124,76 @@ const user_account_t* auth_get_user_by_index(size_t index) {
|
||||
if (index >= MAX_USERS || !user_table[index].in_use) return NULL;
|
||||
return &user_table[index];
|
||||
}
|
||||
|
||||
/* Read a line from the PS/2 keyboard into buf. When echo is false the typed
|
||||
* characters are masked with '*' (password entry). Blocks until Enter. */
|
||||
static void login_read_line(char* buf, size_t max, bool echo) {
|
||||
size_t len = 0;
|
||||
while (true) {
|
||||
uint16_t key = keyboard_get_key();
|
||||
if (key & KEY_SPECIAL_FLAG) {
|
||||
continue; // ignore arrows / function keys at the login prompt
|
||||
}
|
||||
char c = (char)(key & 0xFF);
|
||||
|
||||
if (c == '\n' || c == '\r') {
|
||||
printk("\n");
|
||||
break;
|
||||
}
|
||||
if (c == '\b' || (unsigned char)c == 0x7F || c == 0x08) {
|
||||
if (len > 0) {
|
||||
len--;
|
||||
printk("\b \b"); // erase the last glyph on screen
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ((unsigned char)c >= 32 && (unsigned char)c <= 126 && len < max - 1) {
|
||||
buf[len++] = c;
|
||||
if (echo) {
|
||||
printk("%c", c);
|
||||
} else {
|
||||
printk("*");
|
||||
}
|
||||
}
|
||||
}
|
||||
buf[len] = '\0';
|
||||
}
|
||||
|
||||
void console_login(void) {
|
||||
char username[32];
|
||||
char password[64];
|
||||
|
||||
printk("\n" ANSI_BRIGHT_CYAN
|
||||
"=================================================================\n"
|
||||
" SUB-OS Secure Login -- authenticate to start your session\n"
|
||||
"=================================================================\n"
|
||||
ANSI_RESET);
|
||||
printk(ANSI_YELLOW " Default credentials: username " ANSI_BRIGHT_GREEN "SUB"
|
||||
ANSI_YELLOW ", password " ANSI_BRIGHT_GREEN "SUB" ANSI_RESET "\n\n");
|
||||
|
||||
while (true) {
|
||||
printk(ANSI_BRIGHT_GREEN "sub-os " ANSI_RESET "login: ");
|
||||
login_read_line(username, sizeof(username), true);
|
||||
if (username[0] == '\0') {
|
||||
continue; // empty username, re-prompt
|
||||
}
|
||||
|
||||
printk("Password: ");
|
||||
login_read_line(password, sizeof(password), false);
|
||||
|
||||
bool ok = auth_verify_password(username, password);
|
||||
|
||||
// Scrub the password from the stack regardless of the outcome.
|
||||
memset(password, 0, sizeof(password));
|
||||
|
||||
if (ok) {
|
||||
auth_set_current_user(username);
|
||||
printk(ANSI_BRIGHT_GREEN
|
||||
"\nLogin successful. Welcome to SUB-OS, %s!\n\n" ANSI_RESET,
|
||||
username);
|
||||
return;
|
||||
}
|
||||
|
||||
printk(ANSI_BRIGHT_RED "\nLogin incorrect. Please try again.\n\n" ANSI_RESET);
|
||||
}
|
||||
}
|
||||
+776
-13
@@ -5,6 +5,11 @@
|
||||
#include <net/net.h>
|
||||
#include <net/dns.h>
|
||||
#include <net/dhcp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/http_client.h>
|
||||
#include <kernel/sched.h>
|
||||
#include <net/socket.h>
|
||||
#include <drivers/e1000.h>
|
||||
#include <drivers/ata.h>
|
||||
#include <drivers/tty.h>
|
||||
@@ -29,8 +34,16 @@
|
||||
#include <mm/pmm.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <kernel/rust.h>
|
||||
#include <kernel/nt/ob.h>
|
||||
#include <kernel/nt/reg.h>
|
||||
#include <kernel/nt/io.h>
|
||||
#include <drivers/hpet.h>
|
||||
#include <drivers/nvram.h>
|
||||
#include <drivers/lpt.h>
|
||||
#include <kernel/ktime.h>
|
||||
#include <kernel/sub_lang.h>
|
||||
#include <kernel/cpp_kernel.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <userland/snake.h>
|
||||
#include <ipc/shm_posix.h>
|
||||
#include <net/dns_cache.h>
|
||||
@@ -463,6 +476,301 @@ static int applet_crc32(int argc, char** argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_cksum(int argc, char** argv) {
|
||||
const char* str = (argc >= 2) ? argv[1] : "";
|
||||
size_t len = strlen(str);
|
||||
const uint8_t* buf = (const uint8_t*)str;
|
||||
|
||||
// All three come from the memory-safe Rust checksum engine
|
||||
// (rust/src/checksum/mod.rs), with a C bridge on non-x86 targets.
|
||||
uint32_t crc = rust_crc32c(buf, len);
|
||||
uint32_t adl = rust_adler32(buf, len);
|
||||
uint64_t fnv = rust_fnv1a64(buf, len);
|
||||
|
||||
printk(ANSI_BRIGHT_CYAN "Rust checksum engine" ANSI_RESET " for \"%s\" (%llu bytes):\n",
|
||||
str, (unsigned long long)len);
|
||||
printk(" CRC-32C : 0x%08X\n", crc);
|
||||
printk(" Adler-32 : 0x%08X\n", adl);
|
||||
printk(" FNV-1a-64 : 0x%016llX\n", (unsigned long long)fnv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_rle(int argc, char** argv) {
|
||||
const char* str = (argc >= 2) ? argv[1] : "aaaaabbbccccccccd";
|
||||
size_t len = strlen(str);
|
||||
|
||||
uint8_t comp[512];
|
||||
uint8_t back[512];
|
||||
int clen = rust_rle_compress((const uint8_t*)str, len, comp, sizeof(comp));
|
||||
if (clen < 0) {
|
||||
printk(KERN_ERR "rle: input too large for the demo buffer\n");
|
||||
return 1;
|
||||
}
|
||||
int dlen = rust_rle_decompress(comp, (size_t)clen, back, sizeof(back));
|
||||
|
||||
bool ok = (dlen == (int)len) && (memcmp(back, str, len) == 0);
|
||||
int ratio = (len > 0) ? (clen * 100) / (int)len : 0;
|
||||
|
||||
printk(ANSI_BRIGHT_CYAN "Rust RLE codec" ANSI_RESET " (run-length encoding):\n");
|
||||
printk(" Input : \"%s\"\n", str);
|
||||
printk(" Original : %llu bytes\n", (unsigned long long)len);
|
||||
printk(" Compressed : %d bytes (%d%% of original)\n", clen, ratio);
|
||||
printk(" Round-trip : %s\n", ok ? ANSI_BRIGHT_GREEN "OK (lossless)" ANSI_RESET
|
||||
: ANSI_BRIGHT_RED "MISMATCH" ANSI_RESET);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
static void objdir_print_tree(ob_object_t* obj, int depth) {
|
||||
if (!obj) return;
|
||||
char indent[40];
|
||||
int n = depth * 2;
|
||||
if (n > (int)sizeof(indent) - 1) n = sizeof(indent) - 1;
|
||||
for (int i = 0; i < n; i++) indent[i] = ' ';
|
||||
indent[n] = '\0';
|
||||
|
||||
char detail[72];
|
||||
detail[0] = '\0';
|
||||
if (obj->type == OB_TYPE_EVENT) {
|
||||
snprintf(detail, sizeof(detail), " [%s, %s]",
|
||||
obj->body.event.manual_reset ? "manual-reset" : "auto-reset",
|
||||
obj->body.event.signaled ? "signaled" : "nonsignaled");
|
||||
} else if (obj->type == OB_TYPE_SEMAPHORE) {
|
||||
snprintf(detail, sizeof(detail), " [%d/%d]",
|
||||
obj->body.sem.count, obj->body.sem.limit);
|
||||
}
|
||||
|
||||
printk("%s%s%s " ANSI_YELLOW "(%s" ANSI_RESET " ref=%d hnd=%d%s)%s\n",
|
||||
indent, obj->name,
|
||||
(obj->type == OB_TYPE_DIRECTORY) ? "\\" : "",
|
||||
ob_type_name(obj->type), obj->ref_count, obj->handle_count,
|
||||
obj->permanent ? " perm" : "", detail);
|
||||
|
||||
if (obj->type == OB_TYPE_DIRECTORY) {
|
||||
int c = ob_dir_child_count(obj);
|
||||
for (int i = 0; i < c; i++) {
|
||||
objdir_print_tree(ob_dir_child(obj, i), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int applet_objdir(int argc, char** argv) {
|
||||
const char* path = (argc >= 2) ? argv[1] : "\\";
|
||||
ob_object_t* obj = ob_lookup_path(path);
|
||||
if (!obj) {
|
||||
printk(KERN_ERR "objdir: object '%s' not found in the namespace\n", path);
|
||||
return 1;
|
||||
}
|
||||
printk(ANSI_BRIGHT_CYAN "NT Object Namespace" ANSI_RESET " at '%s':\n", path);
|
||||
objdir_print_tree(obj, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_ntobj(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
uint32_t objs = 0, handles = 0, per[OB_TYPE_MAX];
|
||||
ob_get_stats(&objs, &handles, per);
|
||||
|
||||
printk(ANSI_BRIGHT_CYAN "NT Object Manager statistics:\n" ANSI_RESET);
|
||||
printk(" Total objects : %u\n", objs);
|
||||
printk(" Open handles : %u / %d\n", handles, OB_HANDLE_MAX);
|
||||
for (int i = 0; i < OB_TYPE_MAX; i++) {
|
||||
printk(" %-10s: %u\n", ob_type_name((ob_type_t)i), per[i]);
|
||||
}
|
||||
|
||||
printk(ANSI_BRIGHT_CYAN "Dispatcher / handle demo:\n" ANSI_RESET);
|
||||
ob_object_t* evt = NULL;
|
||||
if (NT_SUCCESS(ob_create_object(OB_TYPE_EVENT, "DemoEvent",
|
||||
ob_lookup_path("\\KernelObjects"), &evt)) && evt) {
|
||||
HANDLE h = ob_open_handle(evt);
|
||||
ob_dereference_object(evt); // the handle now keeps it alive
|
||||
bool before = ke_test_event(evt);
|
||||
ke_set_event(evt);
|
||||
bool after = ke_test_event(evt); // auto-reset consumes the signal
|
||||
printk(" \\KernelObjects\\DemoEvent handle=0x%X test:%d set->test:%d\n",
|
||||
h, before, after);
|
||||
ob_close_handle(h);
|
||||
printk(" Closed handle -> object reclaimed (handle_count and ref_count hit 0)\n");
|
||||
}
|
||||
|
||||
KIRQL old = ke_raise_irql(DISPATCH_LEVEL);
|
||||
printk(" IRQL: raised %d(PASSIVE) -> %d(DISPATCH); ", old, ke_get_current_irql());
|
||||
ke_lower_irql(old);
|
||||
printk("lowered back to %d\n", ke_get_current_irql());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reg_query_cmd(const char* path) {
|
||||
reg_key_t* key = reg_open_key(path);
|
||||
if (!key) {
|
||||
printk(KERN_ERR "reg: key '%s' not found\n", path);
|
||||
return 1;
|
||||
}
|
||||
printk(ANSI_BRIGHT_CYAN "%s\n" ANSI_RESET, path);
|
||||
|
||||
int vc = reg_enum_value_count(key);
|
||||
if (vc == 0) {
|
||||
printk(" (no values)\n");
|
||||
}
|
||||
for (int i = 0; i < vc; i++) {
|
||||
reg_value_t* v = reg_enum_value(key, i);
|
||||
char rendered[REG_STR_MAX + 32];
|
||||
reg_value_to_string(v, rendered, sizeof(rendered));
|
||||
printk(" " ANSI_YELLOW "%-20s" ANSI_RESET " %-11s %s\n",
|
||||
v->name, reg_type_name(v->type), rendered);
|
||||
}
|
||||
|
||||
int kc = reg_enum_key_count(key);
|
||||
for (int i = 0; i < kc; i++) {
|
||||
reg_key_t* sub = reg_enum_key(key, i);
|
||||
printk(ANSI_BRIGHT_GREEN " [%s]\n" ANSI_RESET, sub->name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_reg(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
printk("Usage: reg query <\\path>\n");
|
||||
printk(" reg add <\\path>\n");
|
||||
printk(" reg set <\\path> <name> <dword|sz> <value>\n");
|
||||
printk(" reg stats\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "stats") == 0) {
|
||||
uint32_t keys = 0, values = 0;
|
||||
reg_get_stats(&keys, &values);
|
||||
printk("Registry: %u keys, %u values under \\Registry\n", keys, values);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(argv[1], "query") == 0) {
|
||||
return reg_query_cmd((argc >= 3) ? argv[2] : "\\Registry");
|
||||
}
|
||||
if (strcmp(argv[1], "add") == 0 && argc >= 3) {
|
||||
reg_key_t* k = reg_create_key(argv[2]);
|
||||
if (!k) { printk(KERN_ERR "reg: failed to create '%s'\n", argv[2]); return 1; }
|
||||
printk("Created key %s\n", argv[2]);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(argv[1], "set") == 0 && argc >= 6) {
|
||||
reg_key_t* k = reg_open_key(argv[2]);
|
||||
if (!k) { printk(KERN_ERR "reg: key '%s' not found\n", argv[2]); return 1; }
|
||||
if (strcmp(argv[4], "dword") == 0) {
|
||||
reg_set_dword(k, argv[3], (uint32_t)atoi(argv[5]));
|
||||
} else if (strcmp(argv[4], "sz") == 0) {
|
||||
reg_set_sz(k, argv[3], argv[5]);
|
||||
} else {
|
||||
printk("reg: unknown type '%s' (use dword or sz)\n", argv[4]);
|
||||
return 1;
|
||||
}
|
||||
printk("Set %s\\%s\n", argv[2], argv[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printk("reg: unrecognised sub-command '%s'\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int applet_iodev(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
uint32_t drivers = 0, devices = 0;
|
||||
uint64_t irps = 0;
|
||||
io_get_stats(&drivers, &devices, &irps);
|
||||
|
||||
printk(ANSI_BRIGHT_CYAN "NT I/O Manager:\n" ANSI_RESET);
|
||||
printk(" Drivers: %u Devices: %u IRPs dispatched: %llu\n",
|
||||
drivers, devices, (unsigned long long)irps);
|
||||
|
||||
int dc = io_device_count();
|
||||
for (int i = 0; i < dc; i++) {
|
||||
DEVICE_OBJECT* dev = io_device_at(i);
|
||||
if (!dev) continue;
|
||||
printk(" " ANSI_YELLOW "%-22s" ANSI_RESET " drv=%-16s r=%llu w=%llu ioctl=%llu",
|
||||
dev->name, dev->driver ? dev->driver->name : "?",
|
||||
(unsigned long long)dev->read_count,
|
||||
(unsigned long long)dev->write_count,
|
||||
(unsigned long long)dev->ioctl_count);
|
||||
if (dev->attached_to) {
|
||||
printk(" -> %s", dev->attached_to->name);
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
|
||||
// Live demo: push a WRITE IRP through the filtered Null stack and a READ to
|
||||
// the Zero device, then report what came back.
|
||||
printk(ANSI_BRIGHT_CYAN "IRP demo:\n" ANSI_RESET);
|
||||
DEVICE_OBJECT* mon = io_lookup_device("\\Device\\NullMonitor");
|
||||
if (mon) {
|
||||
char payload[16];
|
||||
memset(payload, 'X', sizeof(payload));
|
||||
IRP* irp = io_build_irp(IRP_MJ_WRITE, payload, sizeof(payload));
|
||||
if (irp) {
|
||||
NTSTATUS st = io_call_driver(mon, irp);
|
||||
printk(" WRITE %u bytes to %s -> status=0x%X, %u bytes sunk (via filter)\n",
|
||||
(unsigned)sizeof(payload), mon->name, st, irp->information);
|
||||
io_free_irp(irp);
|
||||
}
|
||||
}
|
||||
DEVICE_OBJECT* zero = io_lookup_device("\\Device\\Zero");
|
||||
if (zero) {
|
||||
uint8_t buf[8];
|
||||
memset(buf, 0xEE, sizeof(buf));
|
||||
IRP* irp = io_build_irp(IRP_MJ_READ, buf, sizeof(buf));
|
||||
if (irp) {
|
||||
io_call_driver(zero, irp);
|
||||
printk(" READ %u bytes from %s -> first bytes %02X %02X %02X (zeroed)\n",
|
||||
irp->information, zero->name, buf[0], buf[1], buf[2]);
|
||||
io_free_irp(irp);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_ktime(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
uint64_t ns = ktime_ns();
|
||||
printk(ANSI_BRIGHT_CYAN "Monotonic clock (ktime):\n" ANSI_RESET);
|
||||
printk(" Source : %s%s\n", ktime_source(),
|
||||
ktime_is_highres() ? " (nanosecond resolution)" : " (10 ms tick)");
|
||||
printk(" Uptime : %llu.%03llu s (%llu ns)\n",
|
||||
(unsigned long long)(ns / 1000000000ULL),
|
||||
(unsigned long long)((ns / 1000000ULL) % 1000ULL),
|
||||
(unsigned long long)ns);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_hpet(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
if (!hpet_available()) {
|
||||
printk("HPET: not available on this platform\n");
|
||||
return 1;
|
||||
}
|
||||
printk(ANSI_BRIGHT_CYAN "High Precision Event Timer:\n" ANSI_RESET);
|
||||
printk(" Frequency : %llu Hz\n", (unsigned long long)hpet_get_frequency_hz());
|
||||
printk(" Comparators: %u\n", hpet_num_timers());
|
||||
printk(" Main count : %llu\n", (unsigned long long)hpet_read_counter());
|
||||
printk(" Uptime : %llu ns since HPET enable\n", (unsigned long long)hpet_get_ns());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_nvram(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
nvram_dump();
|
||||
printk(" checksum(0x0E..0x7F) = 0x%02X\n", nvram_checksum());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_lpt(int argc, char** argv) {
|
||||
if (!lpt_present()) {
|
||||
printk("LPT: no parallel port present\n");
|
||||
return 1;
|
||||
}
|
||||
const char* text = (argc >= 2) ? argv[1] : "SUB-OS\n";
|
||||
int n = lpt_write(text, strlen(text));
|
||||
printk("LPT1: status=0x%02X, wrote %d byte(s) to the parallel port\n",
|
||||
lpt_status(), n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_rand(int argc, char** argv) {
|
||||
int count = (argc >= 2) ? atoi(argv[1]) : 4;
|
||||
printk("Random Integers: ");
|
||||
@@ -580,16 +888,35 @@ static int applet_rmmod(int argc, char** argv) {
|
||||
|
||||
static int applet_ifconfig(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
uint8_t mac[6];
|
||||
e1000_get_mac(mac);
|
||||
|
||||
printk("eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n");
|
||||
printk(" inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.2\n");
|
||||
char ip[16], mask[16], gw[16], dns[16];
|
||||
if (nif) {
|
||||
ip_to_str(nif->ip, ip);
|
||||
ip_to_str(nif->subnet, mask);
|
||||
ip_to_str(nif->gateway, gw);
|
||||
ip_to_str(nif->dns, dns);
|
||||
} else {
|
||||
strcpy(ip, "0.0.0.0"); strcpy(mask, "0.0.0.0");
|
||||
strcpy(gw, "0.0.0.0"); strcpy(dns, "0.0.0.0");
|
||||
}
|
||||
|
||||
printk("%s: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n",
|
||||
nif ? nif->name : "eth0");
|
||||
printk(" inet %s netmask %s gateway %s\n", ip, mask, gw);
|
||||
printk(" nameserver %s\n", dns);
|
||||
printk(" ether %02x:%02x:%02x:%02x:%02x:%02x txqueuelen 1000 (Ethernet)\n",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
printk(" RX packets 0 bytes 0\n");
|
||||
printk(" TX packets 0 bytes 0\n");
|
||||
printk(" RX packets %llu bytes %llu\n",
|
||||
e1000_get_rx_packets(), e1000_get_rx_bytes());
|
||||
printk(" TX packets %llu bytes %llu\n",
|
||||
e1000_get_tx_packets(), e1000_get_tx_bytes());
|
||||
printk(" Link Status: %s\n", e1000_is_link_up() ? "UP" : "DOWN");
|
||||
|
||||
printk("\nlo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536\n");
|
||||
printk(" inet 127.0.0.1 netmask 255.0.0.0\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -622,11 +949,20 @@ static int applet_dhclient(int argc, char** argv) {
|
||||
|
||||
static int applet_nslookup(int argc, char** argv) {
|
||||
const char* host = (argc >= 2) ? argv[1] : "google.com";
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
char srv[16];
|
||||
ip_to_str(nif ? nif->dns : ip_parse("10.0.2.3"), srv);
|
||||
|
||||
uint32_t ip = dns_resolve(host);
|
||||
if (ip == 0) {
|
||||
printk("Server:\t\t%s\nAddress:\t%s#53\n\n** server can't find %s: NXDOMAIN\n",
|
||||
srv, srv, host);
|
||||
return 1;
|
||||
}
|
||||
char ip_buf[16];
|
||||
ip_to_str(ip, ip_buf);
|
||||
printk("Server: 10.0.2.3\nAddress: 10.0.2.3#53\n\nNon-authoritative answer:\nName: %s\nAddress: %s\n",
|
||||
host, ip_buf);
|
||||
printk("Server:\t\t%s\nAddress:\t%s#53\n\nNon-authoritative answer:\nName:\t%s\nAddress: %s\n",
|
||||
srv, srv, host, ip_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -709,6 +1045,8 @@ static int applet_free(int argc, char** argv) {
|
||||
total_kb, used_kb, free_kb, free_kb);
|
||||
printk("Heap: %8llu %8llu %8llu\n",
|
||||
kmalloc_get_total() / 1024, kmalloc_get_used() / 1024, kmalloc_get_free() / 1024);
|
||||
printk(" (dynamic kernel heap, grown on demand %llu time(s))\n",
|
||||
(unsigned long long)heap_get_grow_count());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -993,6 +1331,15 @@ static int applet_cppbench(int argc, char** argv) {
|
||||
return cpp_run_benchmarks();
|
||||
}
|
||||
|
||||
static int applet_cppstat(int argc, char** argv) {
|
||||
int n = (argc >= 2) ? atoi(argv[1]) : 1;
|
||||
if (n < 1) n = 1;
|
||||
if (n > 32) n = 32;
|
||||
for (int i = 0; i < n; i++) cpp_analytics_sample();
|
||||
cpp_analytics_dump();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_shm(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
posix_shm_dump();
|
||||
@@ -1137,7 +1484,7 @@ static int applet_aead(int argc, char** argv) {
|
||||
static int applet_whoami(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
const user_account_t* u = auth_get_current_user();
|
||||
printk("%s\n", u ? u->username : "root");
|
||||
printk("%s\n", u ? u->username : "SUB");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1652,7 +1999,7 @@ static int applet_ssh(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
char username[32] = "root";
|
||||
char username[32] = "SUB";
|
||||
char host[64] = "localhost";
|
||||
uint16_t port = 22;
|
||||
|
||||
@@ -1774,7 +2121,7 @@ static int applet_logread(int argc, char** argv) {
|
||||
}
|
||||
|
||||
static int applet_su(int argc, char** argv) {
|
||||
const char* target = (argc >= 2) ? argv[1] : "root";
|
||||
const char* target = (argc >= 2) ? argv[1] : "SUB";
|
||||
if (auth_set_current_user(target) == 0) {
|
||||
printk(KERN_INFO "Switched to user '%s'\n", target);
|
||||
return 0;
|
||||
@@ -1784,7 +2131,7 @@ static int applet_su(int argc, char** argv) {
|
||||
}
|
||||
|
||||
static int applet_passwd(int argc, char** argv) {
|
||||
const char* user = (argc >= 2) ? argv[1] : "root";
|
||||
const char* user = (argc >= 2) ? argv[1] : "SUB";
|
||||
const char* new_pass = (argc >= 3) ? argv[2] : "password";
|
||||
if (auth_change_password(user, new_pass) == 0) {
|
||||
printk(KERN_INFO "passwd: password updated successfully for '%s'\n", user);
|
||||
@@ -1861,9 +2208,6 @@ static int applet_netstat(int argc, char** argv) {
|
||||
if (httpd_is_running()) {
|
||||
printk("tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN (httpd)\n");
|
||||
}
|
||||
printk("udp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN (dns)\n");
|
||||
printk("udp 0 0 0.0.0.0:68 0.0.0.0:* ESTABLISHED (dhcp)\n");
|
||||
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
const ssh_session_t* s = sshd_get_session(i);
|
||||
if (s && s->in_use) {
|
||||
@@ -1873,6 +2217,408 @@ static int applet_netstat(int argc, char** argv) {
|
||||
client_ip_str, s->client_port, s->username);
|
||||
}
|
||||
}
|
||||
|
||||
// Ports claimed through tcp_listen (the general passive-open path).
|
||||
for (int i = 0; i < tcp_get_listener_count(); i++) {
|
||||
uint16_t lp = tcp_get_listener_port(i);
|
||||
if (!lp) continue;
|
||||
printk("tcp 0 0 0.0.0.0:%-5u 0.0.0.0:* "
|
||||
"LISTEN (%d queued)\n", lp, tcp_get_listener_backlog(i));
|
||||
}
|
||||
|
||||
// Live connections from the TCP engine's own table: inbound ones accepted
|
||||
// by a listener, outbound ones opened by tcp_connect.
|
||||
for (int i = 0; i < tcp_conn_table_size(); i++) {
|
||||
const tcp_conn_t* c = tcp_get_connection(i);
|
||||
if (!c) continue;
|
||||
|
||||
char lip[16], rip[16], laddr[24], raddr[24];
|
||||
ip_to_str(c->local_ip, lip);
|
||||
ip_to_str(c->remote_ip, rip);
|
||||
snprintf(laddr, sizeof(laddr), "%s:%u", lip, c->local_port);
|
||||
snprintf(raddr, sizeof(raddr), "%s:%u", rip, c->remote_port);
|
||||
|
||||
printk("tcp %6u 0 %-23s %-23s %s%s\n",
|
||||
c->rx_count, laddr, raddr, tcp_state_name(c->state),
|
||||
c->role == TCP_ROLE_CLIENT ? " (out)" : " (in)");
|
||||
}
|
||||
|
||||
// Live UDP port bindings (DNS resolver, DHCP client, datagram sockets).
|
||||
uint16_t ports[32];
|
||||
int np = udp_get_bindings(ports, 32);
|
||||
for (int i = 0; i < np; i++) {
|
||||
const char* svc = "";
|
||||
if (ports[i] == 53) svc = " (dns)";
|
||||
else if (ports[i] == 68) svc = " (dhcp)";
|
||||
else if (ports[i] >= 49152) svc = " (ephemeral)";
|
||||
printk("udp 0 0 0.0.0.0:%-5u 0.0.0.0:*%s\n", ports[i], svc);
|
||||
}
|
||||
|
||||
uint64_t urx, utx, udrop;
|
||||
udp_get_stats(&urx, &utx, &udrop);
|
||||
printk(ANSI_YELLOW "\nTransport statistics:\n" ANSI_RESET);
|
||||
printk(" udp: %llu datagrams received, %llu sent, %llu dropped\n", urx, utx, udrop);
|
||||
printk(" tcp: %u active, %llu served since boot, peak %d of %d slots\n",
|
||||
(unsigned)tcp_get_connections_count(),
|
||||
(unsigned long long)tcp_get_total_opened(),
|
||||
tcp_get_peak_connections(), tcp_conn_table_size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Datagram socket table + UDP transport statistics.
|
||||
static int applet_sockstat(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
printk(ANSI_BRIGHT_CYAN "=== SUB-OS Socket Table ===\n" ANSI_RESET);
|
||||
printk(ANSI_BOLD "%-4s %-8s %-24s %-24s %s\n" ANSI_RESET,
|
||||
"FD", "TYPE", "LOCAL", "REMOTE", "STATE");
|
||||
|
||||
int shown = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
const socket_t* s = socket_get(i);
|
||||
if (!s) continue;
|
||||
char la[16], ra[16];
|
||||
ip_to_str(s->local_ip, la);
|
||||
ip_to_str(s->remote_ip, ra);
|
||||
char lbuf[24], rbuf[24];
|
||||
snprintf(lbuf, sizeof(lbuf), "%s:%u", la, s->local_port);
|
||||
snprintf(rbuf, sizeof(rbuf), "%s:%u", ra, s->remote_port);
|
||||
const char* ty = (s->type == SOCK_DGRAM) ? "dgram" :
|
||||
(s->type == SOCK_STREAM) ? "stream" : "raw";
|
||||
const char* st = (s->state == SOCK_STATE_CONNECTED) ? "CONNECTED" :
|
||||
(s->state == SOCK_STATE_LISTEN) ? "LISTEN" : "OPEN";
|
||||
printk("%-4d %-8s %-24s %-24s %s\n", i, ty, lbuf, rbuf, st);
|
||||
shown++;
|
||||
}
|
||||
if (!shown) printk(" (no open sockets)\n");
|
||||
|
||||
uint64_t tx, rx, drops;
|
||||
socket_get_stats(&tx, &rx, &drops);
|
||||
printk("\nSocket I/O: %llu datagrams sent, %llu received, %llu dropped\n",
|
||||
tx, rx, drops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Resolve a host argument that may be either a dotted quad or a name.
|
||||
static uint32_t resolve_host_arg(const char* s) {
|
||||
uint32_t ip = ip_parse(s);
|
||||
if (ip != 0) return ip;
|
||||
return dns_resolve(s);
|
||||
}
|
||||
|
||||
// Open a TCP connection the way a client does, optionally push a line, and
|
||||
// print whatever the peer sends back. This is the active-open path end to end:
|
||||
// handshake, stream write, stream read, orderly close.
|
||||
static int applet_tcpconnect(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
printk("usage: tcpconnect <host|ip> <port> [text to send]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t ip = resolve_host_arg(argv[1]);
|
||||
if (ip == 0) {
|
||||
printk(ANSI_BRIGHT_RED "tcpconnect: cannot resolve %s\n" ANSI_RESET, argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16_t port = (uint16_t)atoi(argv[2]);
|
||||
if (port == 0) {
|
||||
printk(ANSI_BRIGHT_RED "tcpconnect: invalid port '%s'\n" ANSI_RESET, argv[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char ipstr[16];
|
||||
ip_to_str(ip, ipstr);
|
||||
printk("Connecting to %s:%u ...\n", ipstr, port);
|
||||
|
||||
tcp_conn_t* c = tcp_connect(ip, port, 4000);
|
||||
if (!c) {
|
||||
printk(ANSI_BRIGHT_RED "tcpconnect: connection to %s:%u failed "
|
||||
"(no SYN-ACK, or refused)\n" ANSI_RESET, ipstr, port);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printk(ANSI_BRIGHT_GREEN "Connected" ANSI_RESET " (local port %u, state %s)\n",
|
||||
c->local_port, tcp_state_name(c->state));
|
||||
|
||||
if (argc >= 4) {
|
||||
char line[256];
|
||||
int n = 0;
|
||||
for (int i = 3; i < argc && n < (int)sizeof(line) - 3; i++) {
|
||||
if (i > 3) line[n++] = ' ';
|
||||
const char* w = argv[i];
|
||||
while (*w && n < (int)sizeof(line) - 3) line[n++] = *w++;
|
||||
}
|
||||
line[n++] = '\r';
|
||||
line[n++] = '\n';
|
||||
|
||||
int sent = tcp_send(c, line, (uint16_t)n);
|
||||
printk("Sent %d byte(s)\n", sent);
|
||||
}
|
||||
|
||||
char buf[513];
|
||||
int total = 0;
|
||||
for (;;) {
|
||||
int n = tcp_recv(c, buf, sizeof(buf) - 1, 1500);
|
||||
if (n <= 0) break;
|
||||
buf[n] = '\0';
|
||||
printk("%s", buf);
|
||||
total += n;
|
||||
}
|
||||
|
||||
printk("\n" ANSI_YELLOW "--- %d byte(s) received, closing ---\n" ANSI_RESET, total);
|
||||
tcp_close(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Listen on a port, take one connection, echo what the peer sends, and hang
|
||||
// up. This is the passive-open path end to end, through the BSD socket API
|
||||
// rather than the TCP engine directly: bind, listen, accept, recv, send, close.
|
||||
static int applet_tcpserve(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
printk("usage: tcpserve <port> [seconds to wait]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16_t port = (uint16_t)atoi(argv[1]);
|
||||
if (port == 0) {
|
||||
printk(ANSI_BRIGHT_RED "tcpserve: invalid port '%s'\n" ANSI_RESET, argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fd = sys_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (fd < 0) {
|
||||
printk(ANSI_BRIGHT_RED "tcpserve: no socket available\n" ANSI_RESET);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sockaddr_in_t local;
|
||||
memset(&local, 0, sizeof(local));
|
||||
local.sin_family = AF_INET;
|
||||
local.sin_port = htons(port);
|
||||
local.sin_addr = 0; // all interfaces
|
||||
|
||||
if (sys_bind(fd, &local, sizeof(local)) != 0 || sys_listen(fd, 4) != 0) {
|
||||
printk(ANSI_BRIGHT_RED "tcpserve: cannot listen on port %u "
|
||||
"(already claimed?)\n" ANSI_RESET, port);
|
||||
sys_close_socket(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printk(ANSI_BRIGHT_GREEN "Listening" ANSI_RESET " on 0.0.0.0:%u -- waiting for a "
|
||||
"connection ...\n", port);
|
||||
|
||||
sockaddr_in_t peer;
|
||||
size_t plen = sizeof(peer);
|
||||
int cfd = sys_accept(fd, &peer, &plen);
|
||||
if (cfd < 0) {
|
||||
printk(ANSI_YELLOW "tcpserve: nobody connected; giving up\n" ANSI_RESET);
|
||||
sys_close_socket(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char pip[16];
|
||||
ip_to_str(peer.sin_addr, pip);
|
||||
printk(ANSI_BRIGHT_GREEN "Accepted" ANSI_RESET " connection from %s:%u\n",
|
||||
pip, ntohs(peer.sin_port));
|
||||
|
||||
char buf[513];
|
||||
ssize_t n = sys_recv(cfd, buf, sizeof(buf) - 1, 0);
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
printk(ANSI_YELLOW "--- %d byte(s) received ---\n" ANSI_RESET, (int)n);
|
||||
printk("%s\n", buf);
|
||||
|
||||
const char* reply = "SUB-OS accepted your connection. Goodbye.\r\n";
|
||||
sys_send(cfd, reply, strlen(reply), 0);
|
||||
} else {
|
||||
printk(ANSI_YELLOW "tcpserve: peer sent nothing\n" ANSI_RESET);
|
||||
}
|
||||
|
||||
sys_close_socket(cfd);
|
||||
sys_close_socket(fd);
|
||||
printk("Connection closed, port %u released.\n", port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fetch a URL through the ASYNC client: the request runs on the kernel HTTP
|
||||
// worker thread while this applet just polls, exactly as the GUI Web app does.
|
||||
// It is the command-line counterpart to that app and the way to exercise the
|
||||
// worker path from a serial console.
|
||||
static int applet_webget(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
printk("usage: webget <url> (e.g. webget 10.0.2.2:8000/)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
http_fetch_t* f = http_fetch_start(argv[1], 16384);
|
||||
if (!f) {
|
||||
printk(ANSI_BRIGHT_RED "webget: could not start (bad URL, or a fetch is "
|
||||
"already running)\n" ANSI_RESET);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printk("Fetching %s on the HTTP worker thread ...\n", argv[1]);
|
||||
|
||||
// Poll while the worker runs. Yielding hands it the CPU; a wall-clock guard
|
||||
// stops us waiting forever if something wedges.
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
while (f->state == HTTP_STATE_RUNNING && (pit_get_ticks() - t0) < 1500) {
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
if (f->state == HTTP_STATE_RUNNING) {
|
||||
printk(ANSI_YELLOW "webget: still running after 15s; giving up on the "
|
||||
"poll (worker will clean up)\n" ANSI_RESET);
|
||||
http_fetch_release(f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (f->state == HTTP_STATE_ERROR) {
|
||||
printk(ANSI_BRIGHT_RED "webget: %s\n" ANSI_RESET, f->err);
|
||||
http_fetch_release(f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char ips[16];
|
||||
ip_to_str(f->ip, ips);
|
||||
printk(ANSI_BRIGHT_GREEN "HTTP %d" ANSI_RESET " %d bytes %u ms from %s:%u (%s)\n",
|
||||
f->status_code, f->len, f->elapsed_ms, f->host, f->port, ips);
|
||||
printk("---\n%s\n---\n", f->buf);
|
||||
|
||||
http_fetch_release(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// End-to-end connectivity check: link, address, gateway, DNS, and a real HTTP
|
||||
// fetch, each reported PASS/FAIL. This is the one-command answer to "is the
|
||||
// internet working?" -- it exercises every layer the stack depends on.
|
||||
static int applet_netcheck(int argc, char** argv) {
|
||||
const char* probe = (argc >= 2) ? argv[1] : "example.com";
|
||||
|
||||
printk(ANSI_BRIGHT_CYAN "=== SUB-OS Connectivity Check ===\n" ANSI_RESET);
|
||||
|
||||
#define OKMARK ANSI_BRIGHT_GREEN "[ ok ]" ANSI_RESET
|
||||
#define FAILMARK ANSI_BRIGHT_RED "[fail]" ANSI_RESET
|
||||
|
||||
int failures = 0;
|
||||
|
||||
// 1. Physical link.
|
||||
bool link = e1000_is_link_up();
|
||||
printk(" %s link eth0 %s\n", link ? OKMARK : FAILMARK,
|
||||
link ? "UP" : "DOWN");
|
||||
if (!link) failures++;
|
||||
|
||||
// 2. Interface address.
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
bool has_ip = nif && nif->ip != 0;
|
||||
char ips[16], gws[16], dnss[16];
|
||||
ip_to_str(nif ? nif->ip : 0, ips);
|
||||
ip_to_str(nif ? nif->gateway : 0, gws);
|
||||
ip_to_str(nif ? nif->dns : 0, dnss);
|
||||
printk(" %s address %s gw %s dns %s\n", has_ip ? OKMARK : FAILMARK,
|
||||
ips, gws, dnss);
|
||||
if (!has_ip) failures++;
|
||||
|
||||
// 3. Gateway reachability (one quiet ping).
|
||||
int recv = (nif && nif->gateway) ? net_ping(nif->gateway, 1, 1000) : 0;
|
||||
printk(" %s gateway %s %s\n", recv > 0 ? OKMARK : FAILMARK, gws,
|
||||
recv > 0 ? "reachable" : "no reply");
|
||||
if (recv <= 0) failures++;
|
||||
|
||||
// 4. DNS resolution.
|
||||
uint32_t rip = dns_resolve(probe);
|
||||
char rips[16];
|
||||
ip_to_str(rip, rips);
|
||||
printk(" %s dns %s -> %s\n", rip ? OKMARK : FAILMARK, probe,
|
||||
rip ? rips : "no answer");
|
||||
if (!rip) failures++;
|
||||
|
||||
// 5. A real HTTP fetch, on the async worker, polled to completion.
|
||||
char url[160];
|
||||
snprintf(url, sizeof(url), "%s/", probe);
|
||||
http_fetch_t* f = http_fetch_start(url, 8192);
|
||||
if (!f) {
|
||||
printk(" %s http could not start fetch\n", FAILMARK);
|
||||
failures++;
|
||||
} else {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
while (f->state == HTTP_STATE_RUNNING && (pit_get_ticks() - t0) < 1500) {
|
||||
sched_yield();
|
||||
}
|
||||
if (f->state == HTTP_STATE_DONE && f->status_code > 0) {
|
||||
printk(" %s http GET %s -> %d, %d bytes, %u ms%s\n", OKMARK,
|
||||
probe, f->status_code, f->len, f->elapsed_ms,
|
||||
f->redirects ? " (redirected)" : "");
|
||||
} else {
|
||||
printk(" %s http %s\n", FAILMARK,
|
||||
f->state == HTTP_STATE_ERROR ? f->err : "timed out");
|
||||
failures++;
|
||||
}
|
||||
http_fetch_release(f);
|
||||
}
|
||||
|
||||
if (failures == 0) {
|
||||
printk(ANSI_BRIGHT_GREEN "\n Internet: ONLINE -- all checks passed.\n" ANSI_RESET);
|
||||
} else {
|
||||
printk(ANSI_YELLOW "\n Internet: DEGRADED -- %d check(s) failed.\n" ANSI_RESET,
|
||||
failures);
|
||||
}
|
||||
|
||||
#undef OKMARK
|
||||
#undef FAILMARK
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
// A minimal HTTP/1.0 client over the active-open path.
|
||||
static int applet_httpget(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
printk("usage: httpget <host|ip> [path] [port]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* host = argv[1];
|
||||
const char* path = (argc >= 3) ? argv[2] : "/";
|
||||
uint16_t port = (argc >= 4) ? (uint16_t)atoi(argv[3]) : 80;
|
||||
|
||||
uint32_t ip = resolve_host_arg(host);
|
||||
if (ip == 0) {
|
||||
printk(ANSI_BRIGHT_RED "httpget: cannot resolve %s\n" ANSI_RESET, host);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char ipstr[16];
|
||||
ip_to_str(ip, ipstr);
|
||||
printk("GET http://%s%s -> %s:%u\n", host, path, ipstr, port);
|
||||
|
||||
tcp_conn_t* c = tcp_connect(ip, port, 4000);
|
||||
if (!c) {
|
||||
printk(ANSI_BRIGHT_RED "httpget: connection failed\n" ANSI_RESET);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char req[320];
|
||||
int rl = snprintf(req, sizeof(req),
|
||||
"GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: SUB-OS/lazybox\r\n"
|
||||
"Connection: close\r\n\r\n", path, host);
|
||||
if (tcp_send(c, req, (uint16_t)rl) < 0) {
|
||||
printk(ANSI_BRIGHT_RED "httpget: send failed\n" ANSI_RESET);
|
||||
tcp_close(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char buf[513];
|
||||
int total = 0;
|
||||
for (;;) {
|
||||
int n = tcp_recv(c, buf, sizeof(buf) - 1, 2000);
|
||||
if (n <= 0) break;
|
||||
buf[n] = '\0';
|
||||
printk("%s", buf);
|
||||
total += n;
|
||||
}
|
||||
|
||||
printk("\n" ANSI_YELLOW "--- %d byte(s), %s ---\n" ANSI_RESET,
|
||||
total, tcp_peer_closed(c) ? "server closed the stream" : "timed out");
|
||||
tcp_close(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2168,6 +2914,16 @@ static const lazybox_applet_t applets[] = {
|
||||
{"md5sum", applet_md5sum, "md5sum <file|text>", "Compute MD5 hash", "Crypto"},
|
||||
{"sha256sum", applet_sha256sum, "sha256sum <file|text>", "Compute SHA-256 hash", "Crypto"},
|
||||
{"crc32", applet_crc32, "crc32 <text>", "Calculate CRC32 checksum", "Crypto"},
|
||||
{"cksum", applet_cksum, "cksum <text>", "Rust CRC-32C/Adler-32/FNV", "Crypto"},
|
||||
{"rle", applet_rle, "rle <text>", "Rust RLE compress round-trip","Crypto"},
|
||||
{"objdir", applet_objdir, "objdir [\\path]", "List the NT object namespace","Kernel"},
|
||||
{"ntobj", applet_ntobj, "ntobj", "NT Object Manager stats/demo","Kernel"},
|
||||
{"reg", applet_reg, "reg query|add|set|stats", "NT configuration registry", "Kernel"},
|
||||
{"iodev", applet_iodev, "iodev", "NT I/O Manager devices & IRP","Kernel"},
|
||||
{"hpet", applet_hpet, "hpet", "High Precision Event Timer", "Hardware"},
|
||||
{"ktime", applet_ktime, "ktime", "Monotonic high-res clock", "Kernel"},
|
||||
{"nvram", applet_nvram, "nvram", "Dump CMOS NVRAM bytes", "Hardware"},
|
||||
{"lpt", applet_lpt, "lpt [text]", "Send text to LPT1 parallel", "Hardware"},
|
||||
{"rand", applet_rand, "rand [count]", "Generate random numbers", "Crypto"},
|
||||
{"certcheck", applet_certcheck, "certcheck", "View X.509 kernel keyring", "Security"},
|
||||
{"capsh", applet_capsh, "capsh", "View process capabilities", "Security"},
|
||||
@@ -2181,6 +2937,12 @@ static const lazybox_applet_t applets[] = {
|
||||
{"nslookup", applet_nslookup, "nslookup <host>", "Query DNS name servers", "Network"},
|
||||
{"iptables", applet_iptables, "iptables", "Firewall packet filter rules","Network"},
|
||||
{"netstat", applet_netstat, "netstat", "Network port connections", "Network"},
|
||||
{"sockstat", applet_sockstat, "sockstat", "Socket table & UDP stats", "Network"},
|
||||
{"tcpconnect", applet_tcpconnect, "tcpconnect <host> <port> [text]", "Open an outbound TCP stream", "Network"},
|
||||
{"httpget", applet_httpget, "httpget <host> [path] [port]", "Fetch a URL over TCP", "Network"},
|
||||
{"webget", applet_webget, "webget <url>", "Fetch a URL via the async worker", "Network"},
|
||||
{"netcheck", applet_netcheck, "netcheck [host]", "End-to-end internet connectivity test", "Network"},
|
||||
{"tcpserve", applet_tcpserve, "tcpserve <port>", "Accept one inbound TCP stream", "Network"},
|
||||
|
||||
// Storage & Devices
|
||||
{"lsblk", applet_lsblk, "lsblk", "List block storage devices", "Storage"},
|
||||
@@ -2237,6 +2999,7 @@ static const lazybox_applet_t applets[] = {
|
||||
{"cpptest", applet_cpptest, "cpptest", "C++ polymorphism & new/del", "System"},
|
||||
{"cppdevs", applet_cppdevs, "cppdevs", "C++ OOP device tree", "System"},
|
||||
{"cppbench", applet_cppbench, "cppbench", "C++ OOP benchmark suite", "System"},
|
||||
{"cppstat", applet_cppstat, "cppstat [samples]", "C++ analytics engine stats", "System"},
|
||||
{"shm", applet_shm, "shm", "POSIX /dev/shm shared memory", "System"},
|
||||
{"dnscache", applet_dnscache, "dnscache [-f]", "DNS resolver cache & metrics", "Network"},
|
||||
{"beep", applet_beep, "beep [freq/jingle] [ms]", "PC speaker & HDA tone synth", "Sound"},
|
||||
|
||||
Reference in new issue
Block a user