26 Commits
Author SHA1 Message Date
subhobhai943andClaude Opus 4.8 a0cd95cdc9 docs: add Contributing, Security, and Roadmap docs; link them from the README
Create docs/CONTRIBUTING.md (build/verify workflow, coding style, kernel
gotchas, commit & PR conventions), docs/SECURITY.md (supported versions,
private vulnerability reporting, documented caveats), and docs/ROADMAP.md
(shipped in v1.0.0 plus near/mid/long-term plans). Add a Documentation and
Contributing section to the README pointing at all four docs.

Note: used the conventional CONTRIBUTING.md / SECURITY.md filenames (in
docs/) so GitHub auto-detects them as community-health files.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 22:10:28 +05:30
subhobhai943andClaude Opus 4.8 bc4e19bb37 build: add Linux-style version/codename block to the Makefile
Mirror the Linux kernel Makefile header: VERSION / PATCHLEVEL / SUBLEVEL /
EXTRAVERSION / NAME at the top, where NAME carries the release codename
(Titan). Resolves to 1.0.0-stable, kept in sync with include/init/version.h.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 21:47:37 +05:30
subhobhai943andClaude Opus 4.8 8023338df2 release(v1.0.0): first stable SUB-OS -- logo in README, graduate 0.2.0-lts to 1.0.0-stable
Promote SUB-OS out of alpha/beta to its first stable milestone. Bump
version.h to 1.0.0 with the "1.0.0-stable" banner string across every
arch target (codename Titan). Drop the ASCII wordmark from the README in
favour of the official circular SUB-OS logo image, and update the version
and release badges plus the Official Release link to v1.0.0.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 21:17:47 +05:30
subhobhai943andClaude Opus 4.8 cf7bf59481 feat(brand): official SUB-OS logo -- add "OS" to the dragon mark
Take the circular SUB dragon logo and lock up an "OS" wordmark beneath
the "SUB" letters, in the dragon's accent blue (#08B3FA) with the same
oblique cut and black keyline, so the mark reads "SUB-OS" as one unit.
Store the artwork in assets/ as the canonical source and regenerate the
boot splash asset (init/logo_data.c) from it; the "OS" stays legible at
the 160x160 boot resolution. Point mklogo.py's example at the new source.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 21:04:00 +05:30
subhobhai943andClaude Opus 4.8 db962de248 feat(lib): baseline JPEG decoder -- the browser shows photos
Adds a from-scratch baseline JPEG decoder (ITU-T T.81, SOF0 sequential DCT),
so the Web app now displays the web's dominant photo format alongside PNG and
BMP. image_decode dispatches on the JPEG magic, so the browser needs no change
beyond that.

lib/jpeg.c handles grayscale and YCbCr, arbitrary chroma subsampling
(4:4:4/4:2:2/4:2:0), restart markers, and multiple DQT/DHT tables. Huffman
uses canonical tables (counts + symbols, like the inflate decoder) to avoid a
large fast-table; the transform is the standard fixed-point integer row/column
IDCT; colour is integer YCbCr->RGB. All heap-allocated, no floating point, so
it is safe in the freestanding kernel. Progressive (SOF2) and arithmetic-coded
JPEGs are rejected rather than mis-decoded.

Verified on the host against PIL-encoded references before going in the kernel,
the same way the crypto and PNG decoders were: grayscale decodes at mean
absolute error 0.9/channel, 4:4:4 at 1.7 (pure IDCT rounding vs libjpeg), and
4:2:0 at 4.3 (nearest-neighbour chroma upsampling softens colour edges -- a
known, visually-fine artifact). In QEMU the Web app fetched a 400x260 JPEG over
HTTP and rendered it correctly -- gradient, an antialiased red circle, a green
box, a yellow diagonal. Builds clean on x86_64, aarch64 and armv8i.

Limits: baseline only (no progressive), and chroma upsampling is
nearest-neighbour rather than the smoother interpolation libjpeg uses. The
browser still shows images by URL, not inline in a page.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 19:24:13 +05:30
subhobhai943andClaude Opus 4.8 fe33ddc79b feat(lib,gui): the browser shows images -- a PNG/BMP decoder, DEFLATE and all
The Web app could only render text. Now it decodes and displays images, so a
picture on the web actually shows up.

  - lib/inflate.c   DEFLATE / zlib decompressor (RFC 1950/1951): the algorithm
                    every PNG needs. Canonical Huffman, all three block types,
                    decodes into a caller buffer -- no allocation, integer only.
  - lib/image.c     PNG decoder (8-bit RGB/RGBA/grayscale/gray+alpha, and
                    1/2/4/8-bit grayscale & palette, non-interlaced; tRNS
                    transparency) plus BMP (24/32-bit). Parses chunks,
                    inflates IDAT, undoes the five scanline filters, and expands
                    to a flat 0xAARRGGBB buffer ready for gui_gfx_blit. A pixel
                    cap guards the heap.
  - Web app: if a response body sniffs as an image it is decoded once and drawn
    instead of text -- scaled to fit the panel width, vertically scrollable,
    alpha-composited over the background. The per-fetch cap grew to 128 KB so
    real images and longer pages fit.

Both decoders were verified on the host against generated references before
going in the kernel: inflate round-trips six zlib/raw streams (empty, back-
references, dynamic Huffman, stored blocks, 20 KB random, mixed); the image
decoder reproduces PIL-made PNGs of every supported colour type -- grayscale,
RGB, RGBA, 4-bit palette, gray+alpha -- and a BMP, with exact pixel matches.
In QEMU the Web app fetched a 360x240 PNG over HTTP and rendered it perfectly:
gradient, an antialiased red circle, a green box, a yellow diagonal, shown as
"image 360x240". Builds clean on x86_64, aarch64 and armv8i.

Still image-by-URL, not inline <img> within a page (that needs sub-resource
fetching and layout); and no JPEG/GIF (different codecs). PNG and BMP cover a
large share of the web's images.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 18:53:38 +05:30
subhobhai943andClaude Opus 4.8 8a55ac345b feat(crypto,net): TLS 1.3 client -- SUB-OS can now browse HTTPS sites
The browser could only reach plain HTTP, which is almost none of the real
web. This adds a from-scratch TLS 1.3 client so it can fetch https:// pages,
including real sites on the open internet.

Crypto primitives (all integer-only, freestanding, verified on the host
against their RFC test vectors before going in the kernel):
  - crypto/x25519.c            X25519 ECDH (RFC 7748), compact TweetNaCl field
                               arithmetic.
  - crypto/chacha20poly1305.c  ChaCha20 + Poly1305 AEAD (RFC 8439).
  - crypto/hkdf.c              HMAC-SHA256, HKDF (RFC 2104/5869) and the TLS 1.3
                               HKDF-Expand-Label / Derive-Secret helpers, on top
                               of the existing SHA-256.
A host harness checked every one against its published vectors (X25519 s6.1,
ChaCha20 s2.4.2, AEAD s2.8.2 incl. tamper rejection, HKDF TC1) -- all pass.

TLS 1.3 client (net/tls.c), single suite TLS_CHACHA20_POLY1305_SHA256 over
X25519: ClientHello (with SNI and ALPN http/1.1) -> ServerHello -> the RFC 8446
key schedule over a running transcript hash -> decrypt the server flight ->
send Finished -> switch to application keys. A small record layer handles the
plaintext-then-AEAD split and the middlebox ChangeCipherSpec. All large buffers
live in the heap-allocated connection, never on the 16 KB worker stack.

http_client routes https:// through TLS instead of raw TCP, so the async
worker, webget, netcheck and the GUI Web app all get HTTPS for free; the
redirect follower now follows http->https upgrades. The Web app's default page
is now https://example.com/.

Verified end to end on x86_64: the handshake interoperates with openssl
s_server (TLS 1.3, ChaCha20) -- GET returned HTTP 200 with the decrypted body
-- and, over QEMU's NAT, `webget https://example.com/` and the GUI Web app both
loaded the real Example Domain page served by Cloudflare over TLS 1.3 (HTTP
200, 828 bytes). The server only accepts our Finished if the transcript hash
and key schedule match byte-for-byte, so a 200 is proof the whole stack is
correct. Builds clean on x86_64, aarch64 and armv8i.

SECURITY CAVEAT, stated plainly: the server certificate is NOT verified. The
channel is encrypted and interoperates, 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 and
is not built here. This is fine for a hobby OS over a trusted local NAT; it is
not fit to protect real secrets. Also: TLS 1.3 only, one cipher suite, and
still no JavaScript/media -- so JS-heavy or app-style sites (YouTube) remain
out of reach; this makes static/text HTTPS pages readable.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 18:08:51 +05:30
subhobhai943andClaude Opus 4.8 9f90361265 feat(gui): make the Web app a real browser -- clickable links, history, relative URLs
The Web app fetched and rendered pages but could not browse between them.
Now it does. In Reader view, anchors are parsed as the markup is stripped and
recorded as links spanning the text they enclose; those spans render in the
accent colour with an underline, and a click on one follows it.

  - Link capture: html_to_text now tracks <a href> open/close, storing each
    link's [start,end) span in the processed text plus its href in a per-page
    string pool. Rendering overlays link spans in accent + underline; a body
    click maps pixel -> line -> text offset -> containing link.
  - Relative resolution: a link's href is resolved against the page's own
    address (captured from the fetch's final host/port/path). Root-relative
    (/path), document-relative (page.html) and absolute URLs all work; the
    default port is omitted from the rebuilt URL. mailto:/javascript:/#frag
    are treated as non-navigable.
  - History: a back stack (depth 16) records each address before navigating; a
    Back button, lit only when there is somewhere to return to, pops it.
  - https:// links resolve through and are refused by the fetch layer with the
    existing clear message rather than being silently dropped.

Verified on x86_64 with real synthetic clicks against a small multi-page HTTP
site: the home page rendered its two links highlighted; clicking "Go to Page
Two" navigated (URL bar updated to the resolved http://host/page2.html, host
logged GET /page2.html) and Page Two rendered with its own links; the Back
button returned to the home page (host logged GET /). Builds clean on x86_64,
aarch64 and armv8i.

Known limit: no TLS, so https:// pages are reported not shown -- plain HTTP
browsing only.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 12:17:59 +05:30
subhobhai943andClaude Opus 4.8 f6e4511047 feat(net,gui): real internet access -- redirects, HTTPS guard, netcheck, and a live ONLINE indicator
The stack could already reach the real internet once the UDP-checksum and
scheduler fixes landed; this makes it work on real-world URLs and gives the
desktop a first-class "is the internet up?" answer.

HTTP client
  - Follows 3xx redirects (Location header, absolute or relative) up to five
    hops, so a bare host that 301s to a path or a canonical host resolves to
    actual content. Verified: `webget google.com/` follows the 301 to
    www.google.com and returns its HTTP 200 homepage.
  - Refuses https:// targets -- at the initial URL and on redirect -- with a
    clear message instead of speaking plaintext to port 443 and timing out.
  - Split the URL parser out so both the initial request and redirect targets
    go through the same path; added a case-insensitive header finder.

netcheck (CLI)
  One command that walks the whole chain -- link, address, gateway ping, DNS,
  and a real HTTP fetch -- and reports each PASS/FAIL, ending in ONLINE or
  DEGRADED. The one-shot answer to "is the internet working?".

Network Monitor (GUI)
  A live Internet: ONLINE/OFFLINE badge with the resolved IP, HTTP status and
  latency. It auto-probes once on open and re-checks on the Check button, all
  through the async HTTP worker so the window never blocks.

Verified against the real internet through QEMU's NAT: DNS resolved
example.com to a live Cloudflare address; `netcheck` passed all five layers
(Internet: ONLINE); the GUI badge showed "ONLINE 104.20.23.154 HTTP 200 240
ms"; redirect following and the HTTPS refusal both behaved. Builds clean on
x86_64, aarch64 and armv8i.

Known limit: no TLS, so HTTPS URLs are reported rather than fetched -- plain
HTTP only.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 02:44:53 +05:30
subhobhai943andClaude Opus 4.8 dd4922767a feat(net,gui,sched): async HTTP client + Web browser app, and the scheduler fix that makes background work possible
The visible goal was a Web app: type a URL, see the page, fetched over the
TCP stack SUB-OS built. Getting there surfaced -- and forced fixes to --
two deeper problems.

Async HTTP client (net/http_client.c)
  tcp_connect/recv all block, so fetching from the GUI would freeze the
  compositor. Fetches now run on one long-lived kernel worker thread that
  services a single-slot mailbox; the requester posts a job and polls its
  state. Ownership follows the state, and the two hand-off points (worker
  completion, requester release) run under interrupts-off, so a window can
  close mid-fetch with neither leak nor use-after-free. do_fetch returns
  its terminal state rather than publishing it, closing a window where the
  requester could free the job before finalize read it.

Cooperative scheduling for the idle thread (the core fix)
  The login prompt, the shell and the GUI compositor all run on the pid-0
  idle thread, which the scheduler by design never preempts (sched_tick
  skips pid 0) and never requeues on a yield (weave_on clear). So it could
  neither be preempted to let the worker run, nor cleanly yield -- the
  worker was simply starved, and an early attempt to yield from it froze
  the shell outright. The idle thread now joins the weave rotation once
  hardware interrupts are live, and the network wait loops (tcp, dns, ping,
  the worker's idle) yield through a new net_wait() instead of halting the
  CPU. The compositor spends its frame-budget slack yielding too. Result:
  a background fetch progresses while the desktop holds 54 FPS.

Web app (gui/gui_app_web.c)
  A text browser: URL bar, Go, and a Raw/Reader toggle. Raw shows the
  response verbatim; Reader strips markup (dropping <script>/<style>
  bodies), decodes the common entities, and word-wraps the body. The line
  index is cached against content/view/width so scrolling and idle
  repaints are free. It only ever polls the worker, so it never stalls.

TCP throughput (the limit flagged last time)
  The 32-slot table with a 2 s TIME_WAIT linger capped sustained serving
  at ~16 req/s. The table is now 64 slots, a connection this end closed
  after serving a request lingers 250 ms rather than 2 s, and expired
  TIME_WAIT entries are reaped from the count path so a quiet table still
  drains. netstat/netmon gained served-since-boot and peak-slots counters.

Also: a `webget` CLI (the async client from a shell), listen/accept
introspection already present, and a globe icon for the app.

Verified on x86_64: `webget` fetched an 812-byte page (HTTP 200, 10 ms) on
the worker thread; the GUI Web app rendered the same page in Reader mode
while the desktop held 54 FPS; an 80-request burst through the 64-slot
table returned 80/80 (was 39/40 before) and netstat reported "80 served,
peak 33 of 64 slots" -- a peak that would have overflowed the old table.
Builds clean on x86_64, aarch64 and armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 02:21:48 +05:30
subhobhai943andClaude Opus 4.8 eb92502deb feat(net,gui): TCP listen/accept, and a Network Monitor to watch it
Kernel: the passive-open half of TCP. tcp_listen claims a port, inbound
SYNs to it are completed by the receive path, and the finished
connections queue in a backlog until tcp_accept collects them. Accepted
connections run the same stream state machine as active opens, so
tcp_send/tcp_recv/tcp_close work on them unchanged. sys_listen and
sys_accept expose this through the BSD socket API, with sys_accept
returning a fresh descriptor and leaving the listener available for the
next caller. The built-in sshd and httpd predate this and are still
answered inline from the receive path; the role they use is now named
TCP_ROLE_SERVICE to keep that distinction explicit.

GUI: a Network Monitor app. Interface configuration and link state, a
rolling dual-line chart of receive and transmit rates sampled from the
NIC counters on a fixed 2 Hz cadence (so it reads in packets per half
second no matter how often the compositor repaints), byte and packet
totals, and the TCP engine's connection table with each connection's real
state and direction, scrollable when it outgrows the window.

Two bugs the monitor made visible, both fixed:

  - Served HTTP connections were never closed. The response advertises
    "Connection: close" but the engine left the connection ESTABLISHED
    forever, so the 32-entry table filled up and the server stopped
    answering after 32 requests. The service path now sends its FIN after
    the response and lingers in TIME_WAIT until the reaper takes it. 40
    sequential requests through a 32-slot table now yield 39 successes
    where the 33rd onwards previously failed permanently.

  - tcp_close gave up in FIN_WAIT2 after 500 ms and freed the slot, so a
    peer whose own FIN arrived later got an RST instead of an ACK. It now
    falls into TIME_WAIT instead, and TIME_WAIT acknowledges a first FIN
    rather than only a retransmitted one. Expired TIME_WAIT entries are
    also reaped from tcp_get_connections_count, so a table that sees no
    further connections still empties out.

Verified on x86_64 against real peers, checked packet by packet in a
capture: a host client connected to `tcpserve 9000` in the guest and got
its reply -- SYN/SYN-ACK/ACK with a random ISN, 30-byte request, 43-byte
reply, and a clean four-way close whose final ACK is correct even when
the peer's FIN arrives 1.2 s late. Zero bad checksums throughout. The
monitor was screenshotted under live load showing connections moving
through TIME_WAIT and being reclaimed. Builds clean on x86_64, aarch64
and armv8i.

Known limit: 32 connection slots with a 2 s TIME_WAIT linger means
sustained rates much above ~16 requests/second can transiently exhaust
the table; it recovers on its own as entries expire.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 01:27:17 +05:30
subhobhai943andClaude Opus 4.8 65fc5535c8 feat(net): active-open TCP, and fix two bugs that broke TCP on the wire
Adds the client half of TCP -- tcp_connect/tcp_send/tcp_recv/tcp_close --
and wires SOCK_STREAM sockets to it, so the kernel can originate an
outbound stream rather than only answer inbound ones. Inbound segments
are now matched against the connection table before being offered to the
listeners; previously anything not addressed to a listening port was
answered with RST, which would have rejected the SYN-ACK completing our
own handshake.

Transmission is stop-and-wait: one segment outstanding, retransmitted
until acknowledged. 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. Connections linger in TIME_WAIT so a late duplicate cannot
be mistaken for traffic on a new incarnation.

Verifying it against a real peer uncovered two pre-existing bugs that had
made TCP unusable in both directions. Neither was visible from inside the
guest, which is why the servers had looked fine:

1. The TCP and UDP checksums were computed by reading the header a
   uint16_t at a time, immediately after the caller stored zero into that
   same header's checksum field. Nothing stops the compiler reordering
   those word reads around that store, and at -O2 it did: the stale
   checksum field was folded into its own result. Every TCP segment the
   kernel had ever sent carried a wrong checksum and was silently dropped
   by the peer -- no client could complete a handshake with sshd or
   httpd, and it is the likely reason DHCP and DNS replies never arrived.
   The checksum is now summed a byte at a time through unsigned char
   pointers, which may alias any object and so cannot be reordered, and
   which also drops the alignment assumption that reading a packed header
   as uint16_t made -- a fault rather than a slowdown on ARM. The helper
   is shared by IP, TCP and UDP instead of being open-coded four times.

2. net_receive derived the datagram length from the Ethernet frame rather
   than from the IP header's total_length, so the padding on any frame
   shorter than 60 bytes was delivered as protocol payload. A bare ACK or
   FIN appeared to carry six bytes, which pushed the acknowledgement
   number out of step with the peer and turned every close into a FIN
   retransmission storm answered with RST.

Also adds `tcpconnect` and `httpget` applets, and gives netstat the real
per-connection state and direction from the engine's table.

Verified on x86_64 against a real TCP stack, checked packet by packet in
a capture: `httpget` fetched a 349-byte page from a server on the host --
handshake, 81-byte request, response, orderly four-way close, every
sequence number exact, zero bad checksums, no retransmissions. In the
other direction curl on the host now fetches a live page from the guest's
httpd, which had never worked. Builds clean on x86_64, aarch64 and armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 20:34:26 +05:30
subhobhai943andClaude Opus 4.8 c9dfa6a1aa feat(gui): Sorry -- an apology app with a live parametric heart
A new desktop app whose whole point is to say sorry, and to look like
saying it took effort. Nothing in it is a static bitmap.

The heart is the classic parametric heart curve, x = 16sin^3(t) and
y = 13cos(t) - 5cos(2t) - 2cos(3t) - cos(4t), precomputed as a 96-point
integer polygon normalised to a half-width of 1000 and scanline-filled at
whatever size the beat currently has it at. Even-odd crossing counting is
what keeps the notch between the lobes open. The beat is a two-thump
cardiac envelope -- a strong systolic beat and a softer echo -- and the
fill carries a vertical gradient, a soft specular highlight on the upper
lobe, and a six-layer glow. The kernel is built -mno-sse with no floating
point, so every curve, gradient, easing and physics step is integer-only
and needs no trigonometry at runtime.

Around it: three apology letters that type themselves out a character at
a time, and a slow drift of small hearts using the same fill routine.
Answering "I forgive you" brightens the heart and bursts the drift
outward; answering "Not yet" dims it, slows the beat, and starts an
honest count of how long it has been waiting. Neither answer is an exit.

Supporting changes, all of them general rather than app-specific:

  - gui_desktop_request_animation_frame(), so an app can opt into
    full-rate recompositing instead of the ~10 Hz live-content
    heartbeat. It is renewed per paint, so it lapses by itself when the
    app stops animating or its window closes.
  - gui_button_styled() and gui_hitzone() in SUB-WT, letting an app with
    its own palette keep the toolkit's click handling. gui_button_colored
    is now a thin wrapper over the former.
  - A heart glyph in the icon set.

The glow layers skip whatever the heart body is about to cover, which
cuts their blended-pixel cost by roughly 3.5x and pays for the extra
layers that turn contour rings into a smooth halo.

Verified in QEMU on x86_64: all three states driven by real clicks --
hover, forgiveness (brighter heart, burst, live typewriter) and waiting
(dimmed heart, slower beat, running counter). Builds clean on x86_64,
aarch64 and armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 18:21:22 +05:30
subhobhai943andClaude Opus 4.8 5bd18c4733 feat(cpp,gui): C++ Analytics Engine + live GUI Analytics app
Kernel (C++17 freestanding):
  - New Analytics Engine (kernel/cpp/cpp_analytics.cpp): templated rolling
    time-series (TimeSeries<N>) sampling four live channels -- CPU load %,
    memory %, kernel-heap %, network pkt/interval -- with windowed statistics
    (min/max/mean/stddev) computed in pure integer math (kernel is -mno-sse, so
    no FP; stddev uses a Newton integer isqrt). Demonstrates virtual dispatch
    via an Observer pattern (IAnalyticsObserver + a ThresholdAlarmObserver that
    counts breaches) and RAII singletons.
  - Extended cpp_containers.hpp with Optional<T>, Pair<A,B>, and the kmin/kmax/
    kclamp/kswap algorithms.
  - Full C fallback (cpp_fallback.c) so the engine is functionally identical on
    aarch64/armv8i, which don't build the .cpp path.
  - Wired cpp_analytics_init() into boot [8/14]; seeds a few samples so graphs
    are populated immediately.

GUI:
  - New "Analytics" desktop app (gui/gui_apps.c): a 2x2 grid of live line-charts
    backed by the C++ engine, each with gridlines, a soft baseline fill, and
    min/avg/max(/stddev) overlays; click a panel to focus one channel full-size,
    click again to return. Throttled to ~2 Hz sampling off the compositor's
    repaint cadence. Registered on the desktop and in the start menu.

Userland: new `cppstat [samples]` applet dumps the engine's per-channel stats
and Observer breach counters.

Verified in QEMU (x86_64): boots clean on x86_64/aarch64/armv8i; `cppstat`
prints live stats (CPU 6%, Mem 7%, Heap 22%, computed min/avg/max/stddev,
Observer breaches); the GUI Analytics window renders all four graphs with the
Kernel Heap curve visibly climbing 22->55% as windows open, compositor steady
at ~53 FPS.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 10:42:48 +05:30
subhobhai943andClaude Opus 4.8 cd840d5799 feat(net): real UDP transport, BSD datagram sockets, DNS resolver, DHCP client
Turn the networking stubs into a working transport/application stack on top of
the existing L2/L3 (Ethernet/ARP/IPv4/ICMP) core.

UDP (net/ipv4/udp.c)
  - Port-demux table with bound receive handlers, pseudo-header checksum,
    ephemeral-port allocation, limited-broadcast send (0.0.0.0 -> 255.255.255.255
    for DHCP before the interface has an address), and rx/tx/drop counters.
  - Wired into the IPv4 receive path (IP_PROTO_UDP dispatch in net/core/net.c).

BSD sockets (net/core/socket.c)
  - Real SOCK_DGRAM sockets backed by UDP: bind installs a UDP handler that fills
    a per-socket receive ring; send/sendto and recv/recvfrom move datagrams;
    ephemeral auto-bind, connect() memorises the peer. Socket table + I/O stats
    exposed for introspection.

DNS resolver (net/ipv4/dns.c)
  - Builds and parses real A-record queries over UDP/53 (label encoding,
    compression-pointer aware answer walk), cache-first with TTL, static-hosts
    and dotted-quad fast paths, and a bounded timeout that degrades to fallback
    instead of hanging.

DHCP client (net/ipv4/dhcp.c)
  - Full DISCOVER/OFFER/REQUEST/ACK handshake over UDP broadcast, option parsing
    (mask/router/dns/lease/server-id), applies the lease to the primary
    interface, bounded timeouts keep the static config on no response.

Fix (net/ipv4/dns_cache.c): the cache stored IPs as 0xAABBCCDD numeric while the
rest of the stack uses network-byte-order-in-memory (ip_parse/ip_to_str), so a
resolved address came back byte-swapped (nslookup github.com -> 100.121.82.140).
Seeds and dump now use ip_parse/ip_to_str; verified 140.82.121.4.

Userland (lazybox): live ifconfig (real interface + NIC counters, loopback),
netstat with live UDP bindings and transport stats, new `sockstat`, richer
nslookup. Verified in QEMU: boot clean on x86_64/aarch64/armv8i, login as SUB,
ping/nslookup/dhclient/netstat/sockstat all run; DHCP/DNS wire replies are
blocked by the sandbox's slirp but time out cleanly and fall back as designed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 10:20:58 +05:30
subhobhai943andClaude Opus 4.8 2c2e2d536e perf(gui): scene-dirty compositor + cursor overlay + 60 FPS pacer + animations
Rework the compositor so it stops repainting the whole screen every frame,
tripling the frame rate (17 -> 54 FPS at HD) and adding smooth animation.

- Scene-dirty skipping (gui/gui_desktop.c): the full scene is recomposited only
  when something other than the bare pointer changed -- input, an open animation,
  or a ~10 Hz heartbeat for live content (clock, graphs). Idle and pointer-only
  frames skip the recomposite entirely.
- Cursor save-under (gui/gui_cursor.c): the pointer is an overlay that stashes
  and restores the pixels beneath it, so moving the mouse never forces a full
  repaint -- pointer motion runs at the frame cap.
- 60 FPS frame pacer: each iteration is timed against the monotonic clock and
  padded to a 16.666 ms budget with the new ktime_delay_us() (HPET-precise
  busy-wait), giving consistent pacing instead of a free-running spin.
- Window open animation (gui/gui_wm.c): new windows grow from 60% to full size
  with a cubic ease-out over ~160 ms; gui_wm_update() drives it and reports
  while any window is still animating so the compositor keeps redrawing.
- Default display mode set to 1280x720 (HD) -- the sweet spot where the
  software compositor sustains a near-60 FPS cursor with headroom for effects.

Kernel: kernel/time/ktime.c gains ktime_delay_us(), a precise HPET-based delay
(bounded PIT fallback) used by the pacer.

Verified live (screenshotted): boots to 1280x720, logs in, tray reads ~54 FPS
(green) while the pointer moves, no cursor ghosting, windows render cleanly.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 23:05:44 +05:30
subhobhai943andClaude Opus 4.8 4ecf119377 feat(gui,kernel): 1080p HD desktop, polished chrome, ktime clock + live FPS
GUI polish / HD:
- Default display mode raised to 1920x1080 (video=1920x1080); make run/vnc use
  vgamem_mb=64 and -m 512M. The growable heap absorbs the larger backbuffer,
  shadow and wallpaper (observed: one 16 MB growth to a 48 MB heap).
- Wallpaper: three-stop vertical gradient, a soft concentric-frame edge vignette
  for depth, a finer faint grid, and a centred brand mark.
- Window chrome: brighter/saturated titlebar gradient when focused, an accent
  underline on the active window, and larger/softer drop shadows.
- Default session windows are placed and sized for the HD work area.
- Live FPS pill in the taskbar tray (green/amber/red by rate); tray widened so
  FPS + CPU trace + memory badge + clock no longer overlap.

Kernel:
- kernel/time/ktime.c + include/kernel/ktime.h (new): a unified monotonic
  high-resolution clock that prefers the HPET (nanosecond) and falls back to the
  100 Hz PIT tick. Wired ktime_init() into boot; drives the compositor's FPS
  sampling. New `ktime` LazyBox applet reports the source and uptime.

Verified live (screenshotted): boots to 1920x1080, KTIME reports
"source: HPET, nanosecond resolution", the polished desktop renders with the
active-window accent and vignette, and the tray shows a live FPS reading.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 22:40:35 +05:30
subhobhai943andClaude Opus 4.8 ef6714a004 fix(gui): make the Settings app actually apply changes
The Settings window rendered fine but its controls only mutated the app's local
settings_data_t -- nothing was ever wired to the running system, so toggling
options did nothing ("not working").

- Appearance: "Show wallpaper grid overlay" now calls gui_desktop_set_grid()
  live; the desktop grid shows/hides immediately.
- Input: "Show seconds in taskbar clock" now calls gui_desktop_set_clock_seconds();
  the taskbar clock switches between HH:MM and HH:MM:SS.
- Power: the CPUFreq governor radios now call cpufreq_set_governor() (mapping the
  UI order to the enum), and Apply/Defaults push all preferences to the system.
- The app seeds its controls from live state on open (grid/clock/governor), so it
  reflects reality instead of hard-coded defaults.

Also add public gui_desktop_set_grid/get_grid and set/get_clock_seconds, and
give Settings a dedicated event handler that frees its state on close (the
shared app_forward_event leaked it).

Verified: Settings renders and its controls are wired to real effects; clean
build on x86_64/aarch64/armv8i; boots to the login gate with 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 21:42:19 +05:30
subhobhai943andClaude Opus 4.8 0377726615 feat(drivers): HPET high-res timer, LPT parallel port, CMOS NVRAM
Add three real platform device drivers (x86 hardware, ARM-stubbed):

- drivers/timer/hpet.c + include/drivers/hpet.h: High Precision Event Timer.
  Maps the HPET block at the standard 0xFED00000, reads the femtosecond period
  from the capability register, enables the main counter and exposes a
  monotonic nanosecond time source (hpet_get_ns) far finer than the 100 Hz PIT.
- drivers/char/lpt.c + include/drivers/lpt.h: IBM PC parallel port (LPT1) with
  the Centronics data/strobe handshake over ports 0x378/0x379/0x37A.
- drivers/char/nvram.c + include/drivers/nvram.h: MC146818 CMOS NVRAM read/write
  over 0x70/0x71, preserving the NMI-disable bit, with checksum and hex dump.

Wired all three into the [5/14] device-enumeration boot step and added LazyBox
applets `hpet`, `lpt`, and `nvram`.

Verified live under QEMU (screenshotted): HPET reports 100 MHz / 3 comparators /
10 ns period and its counter advances ~1.4 s of real time between two `hpet`
runs; NVRAM dumps 128 bytes with a computed checksum; LPT1 detected at 0x378.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 21:04:03 +05:30
subhobhai943andClaude Opus 4.8 ea937a699c feat(kernel,gui): NT I/O Manager (IRP-based drivers) + Registry Editor
Kernel (NT I/O subsystem):
- kernel/nt/io.c + include/kernel/nt/io.h (new): DRIVER_OBJECTs with a
  MajorFunction dispatch table, DEVICE_OBJECTs that can be stacked (attached),
  and IRPs pushed down the stack via io_call_driver / completed with
  io_complete_request. Ships three demo drivers: \Driver\Null (write sink,
  EOF reads), \Driver\Zero (zero-filled reads), and \Driver\Monitor -- a filter
  attached above \Device\Null that counts and forwards each IRP down the stack.
  Wired into the [12/14] boot step.
- LazyBox `iodev` lists drivers/devices with the stack and IRP counters, then
  runs a live demo: a WRITE IRP through the filtered Null stack and a READ from
  the Zero device.

GUI:
- gui/gui_apps.c: "Registry Editor" -- navigate the NT \Registry hive tree,
  click subkeys to descend and ".." to go up, with a values pane showing each
  value's name/type/data. Registered in the app table (start menu).

Verified live in the GUI terminal (screenshotted): 3 drivers / 3 devices, the
NullMonitor->Null device stack, a 16-byte WRITE sunk through the filter
(status 0x0), and an 8-byte zeroed READ from \Device\Zero. Clean build on
x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:51:14 +05:30
subhobhai943andClaude Opus 4.8 526ad8c741 feat(kernel,gui): NT Configuration Registry + GUI Object Browser
Kernel (NT Configuration Manager):
- kernel/nt/reg.c + include/kernel/nt/reg.h (new): a hive/key/value registry
  rooted at "\Registry", with typed values (REG_SZ/DWORD/QWORD/BINARY/MULTI_SZ,
  type codes matching Win32). reg_create_key/reg_open_key walk backslash paths;
  setters/getters, enumeration, and value->string rendering. reg_init() seeds
  \Registry\Machine (Hardware, System\CurrentControlSet\Control, Software\SUB-OS)
  and \Registry\User\.DEFAULT. Wired into the [12/14] boot step.
- LazyBox `reg query|add|set|stats` explores and edits the registry.

GUI:
- gui/gui_apps.c: "Object Browser" -- a WinObj-style live tree view of the NT
  object namespace (\), flattening directories/events/semaphores/mutants with
  indentation, per-type colouring, ref/handle counts, live object/handle stats,
  and click-to-scroll. Registered in the app table (start menu).

Verified live in the GUI terminal (screenshotted): registry reports 13 keys /
11 values; `reg query \Registry\Machine\Software\SUB-OS` lists Version,
BuildArch (REG_SZ), GrowableHeap/ObjectManager (REG_DWORD) and InstallCookie
(REG_QWORD) with correct types. Clean build on x86_64/aarch64/armv8i; 0 panics.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:41:45 +05:30
subhobhai943andClaude Opus 4.8 cb7506f027 feat(kernel): NT-inspired Object Manager, dispatcher and IRQL
Take inspiration from the Windows NT Executive and add its most iconic
subsystem to SUB-OS: a typed, reference-counted object namespace.

- kernel/nt/ob.c + include/kernel/nt/ob.h (new):
  * Object Manager (Ob): typed objects (Directory/Event/Semaphore/Mutant) in a
    hierarchical named namespace rooted at "\", with NT-style deletion once
    handle_count and ref_count both reach zero (unless permanent).
  * Handle table (Io/Ob): NT-shaped non-zero 4-aligned HANDLEs into a 256-slot
    system table; open/reference/close with proper refcount transfer.
  * Dispatcher (Ke): set/reset/test events (auto- and manual-reset) and
    release/wait semaphores, poll-style.
  * Software IRQL: PASSIVE/APC/DISPATCH/HIGH with raise/lower semantics.
  * NTSTATUS result codes and NT_SUCCESS().
  * ob_init() seeds the standard namespace (\BaseNamedObjects, \Device,
    \ObjectTypes, \KernelObjects) plus a boot event and an I/O semaphore.
- Wired ob_init() into the [12/14] boot step; added kernel/nt/ob.c to CORE_SRCS.
- LazyBox: `objdir [\path]` walks the object namespace tree (WinObj-style);
  `ntobj` prints Ob statistics and runs a live dispatcher/handle/IRQL demo.

Verified live in the GUI terminal (screenshotted): 7 seeded objects
(5 directories, 1 event, 1 semaphore), auto-reset event signalling, handle
close reclaiming the object, and PASSIVE->DISPATCH->PASSIVE IRQL transitions.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:23:07 +05:30
subhobhai943andClaude Opus 4.8 c414c20b44 feat(rust,gui): RLE compression codec + Rust Lab desktop app
Rust kernel expansion:
- rust/src/compress/mod.rs (new): memory-safe PackBits-style run-length
  encoding codec (rle_compress / rle_decompress) with C-ABI shims, registered
  in lib.rs. C fallbacks added to rust_fallback.c so aarch64/armv8i match.
- include/kernel/rust.h: FFI declarations for the codec.
- LazyBox: new `rle <text>` applet that compresses, decompresses, prints the
  ratio and verifies the round-trip is lossless.

GUI expansion:
- gui/gui_apps.c: "Rust Lab" app -- a live dashboard for the Rust subsystem.
  Shows CRC-32C/Adler-32 of a sample, measured ChaCha20/SHA3-256/AES-128
  throughput as scaled bars plus a composite score (benchmark run once at
  launch and cached), and a live ChaCha20 CSPRNG byte stream refreshed
  periodically. Per-window state freed on close.
- Registered "Rust Lab" (info icon) in the desktop app table and on the
  desktop; desktop icon indices updated for the new entry.

Verified: clean build on x86_64/aarch64/armv8i; boots through the SUB login to
the desktop with the new Rust Lab icon rendering (screenshotted); checksum
self-test prints, 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:06:13 +05:30
subhobhai943andClaude Opus 4.8 e93b994469 feat(rust,gui): Rust checksum engine + Game of Life desktop app
Rust kernel expansion:
- rust/src/checksum/mod.rs (new): memory-safe CRC-32C (Castagnoli), Adler-32,
  and FNV-1a-64 with thin C-ABI shims. Registered in lib.rs with a boot-time
  self-test (crc32c("SUB-OS")=0x61e96ce9, verified against a reference impl).
- include/kernel/rust.h: FFI declarations; rust/rust_fallback.c: matching C
  fallbacks so aarch64/armv8i (no native rustc target) get identical results.
- LazyBox: new `cksum <text>` applet printing all three via the Rust engine.

GUI expansion:
- gui/gui_apps.c: Conway's Game of Life -- an interactive B3/S23 cellular
  automaton with Play/Pause/Step/Random/Clear toolbar, click-to-toggle cells,
  a live generation/population readout, and a board that scales with the
  window. The Random button seeds from the Rust ChaCha20 CSPRNG. Toolbar and
  cell input are gated on the MOUSE_DOWN edge so they do not re-fire while held.
- Registered "Life" (flask icon) in the desktop app table and on the desktop.

Verified: clean build on x86_64/aarch64/armv8i; boots through the SUB login to
the desktop (screenshotted) with the checksum self-test and 0 panics/0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 19:55:02 +05:30
subhobhai943andClaude Opus 4.8 c982d6b538 feat(auth): Boot-time login prompt; fix(configure): arch selection via dialog
Login:
- Add console_login() (security/auth.c): an interactive boot-time username /
  password gate rendered on the framebuffer console before any session starts.
  It blocks until valid credentials are entered, masks the password, scrubs it
  from the stack, and sets the current user. Default account is SUB / SUB, shown
  as a hint. Wired into the boot handoff in kernel/main.c ahead of both the GUI
  desktop and the emergency TTY shell.

Configure:
- scripts/kconfig/menuconfig.py: fix the architecture (and optimization) picker.
  The lxdialog front-end used --radiolist, where <Enter> confirms the ORIGINAL
  choice and only <Space> moves the dot -- so highlighting a new architecture
  and pressing <Enter> appeared to do nothing. Single-choice groups now use
  --menu (with --default-item), where <Enter> selects the highlighted row
  directly. Also stop the unconditional save-on-exit from overriding an explicit
  "No" at the exit confirmation.

Verified: SUB/SUB logs in (injected via QEMU monitor) -> "Login successful" ->
desktop starts; the desktop stays blocked until login. dialog --menu selection
of aarch64 persists to .config/autoconf.h and retargets `make`. Clean build on
x86_64/aarch64/armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 17:57:47 +05:30
subhobhai943andClaude Opus 4.8 71ad6aca3d feat: Growable heap, GUI-by-default run, live sysmon, SUB/SUB default login
Kernel / memory:
- mm/kmalloc.c: fix "Heap: Out of memory" under the GUI. The desktop allocates
  three full-screen 32-bit buffers (~11MB) which overflowed the fixed 16MB pool.
  The heap now starts at 32MB and grows on demand from the PMM (16MB regions),
  with region-safe coalescing (only merges physically adjacent blocks) so growth
  never corrupts across separate PMM allocations. Adds heap_get_grow_count().

Build / run:
- Makefile: merge `run-gui` into `run` so `make run` boots into the graphical
  desktop; add `run-headless` (serial-only via QEMU_CMD), bump guest RAM to
  256M, update .PHONY and help.

Configure:
- scripts/kconfig/menuconfig.py: gate the curses UI behind isatty() on stdin and
  stdout so non-interactive `make configure` stops emitting "nocbreak() returned
  ERR" and falls cleanly through to the non-interactive writer.

GUI / userland:
- gui/gui_apps.c: rebuild the System Monitor from hardcoded values into live
  telemetry -- real heap used/total/free with a colour-shifting bar, a scrolling
  heap-utilisation graph, the grow counter, and real PMM RAM figures.
- lazybox `free`: report how many times the heap has grown.

Auth:
- Default login is now SUB / SUB (Kali-style username==password), seeded first
  as the primary uid-0 account and set as the logged-in user; root kept for
  compatibility. Hardcoded "root" defaults (whoami, su, passwd, ssh, sshd
  session) updated to SUB.

Verified: clean build on x86_64/aarch64/armv8i; GUI boots with 0 OOM/0 panic;
auth engine initialises cleanly; `make configure` fallback is clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 17:34:50 +05:30
83 changed files with 11876 additions and 636 deletions

No files matched your search

+4
View File
@@ -9,3 +9,7 @@ build/
.DS_Store .DS_Store
.system_generated/ .system_generated/
.config .config
# Python bytecode
__pycache__/
*.pyc
+36 -16
View File
@@ -4,6 +4,14 @@
# Features: Linux Kconfig TUI (make configure / make menuconfig), dynamic Kbuild # 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 -include .config
# Architecture Selection (Default: x86_64) # 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/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/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/signal.c kernel/workqueue.c kernel/tsc.c kernel/vt_art.c \
kernel/sub/sub_runtime.c kernel/sub/sub_vm.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/wait.c kernel/futex.c kernel/rcu.c kernel/ktest.c kernel/time/ktime.c \
kernel/elf.c kernel/exec.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/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 \ 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 \ 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 # If no .config exists, default all core options to y
ifeq ($(wildcard .config),) ifeq ($(wildcard .config),)
@@ -227,7 +237,7 @@ endif
# Network Subsystem # Network Subsystem
ifeq ($(CONFIG_NET_STACK), y) 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 endif
ifeq ($(CONFIG_NET_FILTER), y) ifeq ($(CONFIG_NET_FILTER), y)
CONFIG_SRCS-y += net/filter.c CONFIG_SRCS-y += net/filter.c
@@ -277,7 +287,8 @@ endif
# Common Input/Display/Power/GPU/Networking Drivers # 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 \ 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/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 # Userland & GUI Desktop Subsystem
ifeq ($(CONFIG_USERLAND_LAZYBOX), y) ifeq ($(CONFIG_USERLAND_LAZYBOX), y)
@@ -285,7 +296,8 @@ ifeq ($(CONFIG_USERLAND_LAZYBOX), y)
endif 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 \ 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 # Deduplicate
CONFIG_SRCS-y := $(sort $(CONFIG_SRCS-y)) 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 .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) all: $(TARGET)
@@ -448,24 +460,29 @@ $(IMAGE): $(BUILD_DIR)/boot/boot.bin $(BUILD_DIR)/boot/stage2.bin $(BUILD_DIR)/k
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Execution & Emulation Targets # Execution & Emulation Targets
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
run qemu: $(TARGET)
$(QEMU_CMD)
# -vga std gives the Bochs VBE adapter the kernel drives. vgamem_mb is raised # -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. # 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) 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 \ -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 -device e1000,netdev=net0 $(QEMU_VGA) -display gtk,zoom-to-fit=on -serial stdio
else ifneq ($(filter $(ARCH), aarch64 arm64),) 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 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 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 # Fullscreen without distortion: zoom-to-fit scales the guest while preserving
# its aspect ratio, letterboxing rather than stretching on a wider panel. # its aspect ratio, letterboxing rather than stretching on a wider panel.
run-fullscreen: $(TARGET) run-fullscreen: $(TARGET)
@@ -521,7 +538,10 @@ help:
@echo " make aarch64_defconfig- Load AArch64 default configuration" @echo " make aarch64_defconfig- Load AArch64 default configuration"
@echo " make armv8i_defconfig - Load ARMv8i (32-bit ARM) 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 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 clean - Remove all compiled objects and disk images"
@echo " make info - Display build configuration and source metrics" @echo " make info - Display build configuration and source metrics"
+28 -11
View File
@@ -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 # SUB-OS: Modular Monolithic Linux-Compatible Operating System
```text </div>
_____ _ _ ____ ____ _____
/ ____| | | | _ \ / __ \ / ____|
| (___ | | | | |_) |______| | | | (___
\___ \| | | | _ <|______| | | |\___ \
____) | |__| | |_) | | |__| |____) |
|_____/ \____/|____/ \____/|_____/
```
[![Architectures](https://img.shields.io/badge/arch-x86__64%20%7C%20aarch64%20%7C%20armv8i-blue.svg?style=flat-square)]() [![Architectures](https://img.shields.io/badge/arch-x86__64%20%7C%20aarch64%20%7C%20armv8i-blue.svg?style=flat-square)]()
[![Kernel](https://img.shields.io/badge/kernel-Modular%20Monolithic-red.svg?style=flat-square)]() [![Kernel](https://img.shields.io/badge/kernel-Modular%20Monolithic-red.svg?style=flat-square)]()
@@ -15,8 +12,8 @@
[![Rust Layer](https://img.shields.io/badge/rust-Rust--for--SUB--OS%20no__std-orange.svg?style=flat-square)]() [![Rust Layer](https://img.shields.io/badge/rust-Rust--for--SUB--OS%20no__std-orange.svg?style=flat-square)]()
[![SUB Language](https://img.shields.io/badge/sub--lang-Native%20%2B%20In--Kernel%20VM-blueviolet.svg?style=flat-square)]() [![SUB Language](https://img.shields.io/badge/sub--lang-Native%20%2B%20In--Kernel%20VM-blueviolet.svg?style=flat-square)]()
[![Configuration](https://img.shields.io/badge/config-Linux%20Kconfig%20lxdialog%20TUI-cyan.svg?style=flat-square)]() [![Configuration](https://img.shields.io/badge/config-Linux%20Kconfig%20lxdialog%20TUI-cyan.svg?style=flat-square)]()
[![Version](https://img.shields.io/badge/version-v0.2.0--lts-purple.svg?style=flat-square)]() [![Version](https://img.shields.io/badge/version-v1.0.0--stable-purple.svg?style=flat-square)]()
[![Release](https://img.shields.io/badge/release-v0.0.1--beta-brightgreen.svg?style=flat-square)](https://github.com/subhobhai943/SUB-OS/releases/tag/v0.0.1-beta) [![Release](https://img.shields.io/badge/release-v1.0.0%20stable-brightgreen.svg?style=flat-square)](https://github.com/subhobhai943/SUB-OS/releases/tag/v1.0.0)
[![Userland](https://img.shields.io/badge/userland-LazyBox%20%2B%20Nano%20%2B%20Sh-orange.svg?style=flat-square)]() [![Userland](https://img.shields.io/badge/userland-LazyBox%20%2B%20Nano%20%2B%20Sh-orange.svg?style=flat-square)]()
[![Networking](https://img.shields.io/badge/network-TCP%2FIP%20%7C%20SSHD%20%7C%20HTTPD%20%7C%20NetFilter-green.svg?style=flat-square)]() [![Networking](https://img.shields.io/badge/network-TCP%2FIP%20%7C%20SSHD%20%7C%20HTTPD%20%7C%20NetFilter-green.svg?style=flat-square)]()
[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat-square)](LICENSE) [![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat-square)](LICENSE)
@@ -272,7 +269,27 @@ make run-vnc # Headless: VNC server on localhost:5900
## 📦 Official Release ## 📦 Official Release
Download precompiled kernel binaries and bootable disk images from GitHub: 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

+285
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+183
View File
@@ -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. 🐉
+87
View File
@@ -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.
+95
View File
@@ -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. 🐉
+80
View File
@@ -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
+64
View File
@@ -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
+91
View File
@@ -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
+404
View File
@@ -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;
}
+850
View File
@@ -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;
}
+717
View File
@@ -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
View File
@@ -8,8 +8,13 @@
#include <mm/kmalloc.h> #include <mm/kmalloc.h>
#include <mm/pmm.h> #include <mm/pmm.h>
#include <kernel/printk.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/string.h>
#include <lib/printf.h> #include <lib/printf.h>
#include <kernel/cpp_analytics.h>
#include <kernel/ktime.h>
static int simple_atoi(const char* s) { static int simple_atoi(const char* s) {
if (!s) return 0; if (!s) return 0;
@@ -26,19 +31,41 @@ static int simple_atoi(const char* s) {
// ================================================================= // =================================================================
// 1. System Performance Monitor App (High-Res 320x240) // 1. System Performance Monitor App (High-Res 320x240)
// ================================================================= // =================================================================
static uint8_t g_cpu_history[60] = { /* Rolling trace of live heap utilisation (percent), newest sample on the right.
12, 15, 18, 22, 30, 25, 20, 35, 45, 50, 42, 38, 55, 60, 48, 52, 35, 30, 25, 20, * Seeded flat and advanced one sample per repaint from real allocator stats. */
28, 32, 45, 60, 55, 40, 30, 25, 20, 18, 22, 25, 15, 18, 22, 30, 35, 40, 38, 42, static uint8_t g_heap_history[60];
30, 25, 20, 18, 15, 22, 28, 35, 40, 48, 52, 45, 38, 30, 25, 20, 18, 15, 20, 24 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) { static void sysmon_paint(gui_window_t* win) {
int cx = win->x + 12; int cx = win->x + 12;
int cy = win->y + GUI_TITLEBAR_HEIGHT + 10; 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_x = cx;
int graph_y = cy + 20; int graph_y = cy + 20;
int graph_w = win->width - 24; 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); 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++) { 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 y1 = graph_y + graph_h - 3 - (g_heap_history[i] * (graph_h - 6)) / 100;
int y2 = graph_y + graph_h - 3 - (g_cpu_history[i + 1] * (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); 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; 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_y = mem_y + 20;
int bar_w = win->width - 24; 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, 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); gui_gfx_draw_rect(cx, bar_y, bar_w, 12, GUI_THEME_BORDER);
// System Telemetry Metrics // System Telemetry Metrics (live)
char buf[64];
cpufreq_stats_t stats = cpufreq_get_stats(); cpufreq_stats_t stats = cpufreq_get_stats();
snprintf(buf, sizeof(buf), "Clock: %u.%03u GHz | Governor: ondemand", snprintf(buf, sizeof(buf), "Clock: %u.%03u GHz | Heap free: %llu KB | Grows: %llu",
stats.current_khz / 1000000, (stats.current_khz % 1000000) / 1000); 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); 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); 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; 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
View File
@@ -10,6 +10,7 @@
#include <gui/gui_dialog.h> #include <gui/gui_dialog.h>
#include <gui/gui_gfx.h> #include <gui/gui_gfx.h>
#include <gui/gui_theme.h> #include <gui/gui_theme.h>
#include <gui/gui_desktop.h>
#include <kernel/ktest.h> #include <kernel/ktest.h>
#include <kernel/printk.h> #include <kernel/printk.h>
#include <kernel/task.h> #include <kernel/task.h>
@@ -54,6 +55,23 @@ typedef struct {
static const char* const settings_tabs[] = { "Appearance", "Input", "Power" }; 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) { static void settings_paint(gui_window_t* win) {
settings_data_t* sd = (settings_data_t*)win->user_data; settings_data_t* sd = (settings_data_t*)win->user_data;
if (!sd) return; if (!sd) return;
@@ -69,7 +87,9 @@ static void settings_paint(gui_window_t* win) {
if (sd->active_tab == 0) { if (sd->active_tab == 0) {
gui_label_bold(10, y, "Desktop Appearance", GUI_THEME_PRIMARY); gui_label_bold(10, y, "Desktop Appearance", GUI_THEME_PRIMARY);
y += 24; 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; y += 22;
gui_checkbox(11, 10, y, "Drop shadows under windows", &sd->window_shadows); gui_checkbox(11, 10, y, "Drop shadows under windows", &sd->window_shadows);
y += 26; 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); gui_checkbox(21, 10, y, "Enable UI sound effects", &sd->sound_enabled);
y += 22; 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; y += 30;
const mouse_state_t* ms = mouse_get_state(); 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); gui_label(10, y, "CPUFreq governor", GUI_THEME_TEXT_MUTED);
y += 16; y += 16;
gui_radio(30, 14, y, "powersave", &sd->governor_choice, 0); y += 18; bool gov_changed = false;
gui_radio(31, 14, y, "ondemand", &sd->governor_choice, 1); y += 18; gov_changed |= gui_radio(30, 14, y, "powersave", &sd->governor_choice, 0); y += 18;
gui_radio(32, 14, y, "performance", &sd->governor_choice, 2); y += 26; 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(); cpufreq_stats_t cf = cpufreq_get_stats();
snprintf(buf, sizeof(buf), "Current clock: %u.%03u GHz", 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; int btn_y = gui_widget_client_height() - 32;
if (gui_button(90, 10, btn_y, 96, 24, "Apply")) { 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", gui_dialog_show(GUI_DIALOG_INFO, "Settings",
"Preferences applied to the current desktop session."); "Preferences applied to the current desktop session.");
} }
@@ -140,11 +170,23 @@ static void settings_paint(gui_window_t* win) {
sd->cursor_speed = 5; sd->cursor_speed = 5;
sd->brightness = 100; sd->brightness = 100;
sd->governor_choice = 1; 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(); 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) { void gui_app_settings_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Settings", x, y, gui_window_t* win = gui_wm_create_window("Settings", x, y,
(w > 0) ? w : 380, (h > 0) ? h : 300); (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)); settings_data_t* sd = (settings_data_t*)kzalloc(sizeof(settings_data_t));
if (!sd) return; 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->window_shadows = true;
sd->sound_enabled = true; sd->sound_enabled = true;
sd->show_clock_seconds = true; sd->show_clock_seconds = gui_desktop_get_clock_seconds();
sd->cursor_speed = 5; sd->cursor_speed = 5;
sd->brightness = 100; sd->brightness = 100;
sd->governor_choice = 1; sd->governor_choice = settings_choice_from_gov(cpufreq_get_stats().governor);
win->user_data = sd; win->user_data = sd;
win->paint = settings_paint; win->paint = settings_paint;
win->handle_event = app_forward_event; win->handle_event = settings_event;
} }
// =========================================================================== // ===========================================================================
+48
View File
@@ -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
View File
@@ -24,6 +24,8 @@
#include <drivers/keyboard.h> #include <drivers/keyboard.h>
#include <drivers/rtc.h> #include <drivers/rtc.h>
#include <drivers/cpufreq.h> #include <drivers/cpufreq.h>
#include <kernel/ktime.h>
#include <kernel/sched.h>
#include <mm/pmm.h> #include <mm/pmm.h>
#include <mm/kmalloc.h> #include <mm/kmalloc.h>
#include <arch/arch.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 }, { "Editor", GUI_ICON_FILE, gui_app_editor_launch, 120, 50, 500, 330 },
{ "Calculator", GUI_ICON_CALC, gui_app_calc_launch, 260, 100, 220, 240 }, { "Calculator", GUI_ICON_CALC, gui_app_calc_launch, 260, 100, 220, 240 },
{ "Paint", GUI_ICON_PAINT, gui_app_paint_launch, 200, 60, 360, 260 }, { "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 }, { "Clock", GUI_ICON_CLOCK, gui_app_clock_launch, 460, 120, 280, 260 },
{ "Settings", GUI_ICON_SETTINGS, gui_app_settings_launch, 160, 60, 400, 320 }, { "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 }, { "About", GUI_ICON_INFO, gui_app_about_launch, 240, 140, 330, 170 },
}; };
#define APP_COUNT ((int)(sizeof(g_apps) / sizeof(g_apps[0]))) #define APP_COUNT ((int)(sizeof(g_apps) / sizeof(g_apps[0])))
// Which of the above appear on the desktop itself. // 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]))) #define DESKTOP_ICON_COUNT ((int)(sizeof(g_desktop_icons) / sizeof(g_desktop_icons[0])))
static bool g_start_menu_open = false; 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_desktop_running = false;
static bool g_prev_left = false, g_prev_right = false; static bool g_prev_left = false, g_prev_right = false;
static bool g_show_grid = true; static bool g_show_grid = true;
static bool g_clock_seconds = true;
static int g_selected_icon = -1; static int g_selected_icon = -1;
static int g_hover_menu_item = -1; static int g_hover_menu_item = -1;
static uint64_t g_frames = 0; 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. * Cached wallpaper.
@@ -171,28 +197,50 @@ void gui_desktop_init(void) {
// Wallpaper and desktop icons // 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) { 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) { if (g_show_grid) {
// Blend rather than stamp a fixed colour: the gradient's lower half is // A finer, fainter grid than before reads as texture rather than lines.
// lighter than any single grid tone, so a constant colour disappears for (int y = 0; y < wh; y += 48) {
// there and only shows near the top of the screen. gui_gfx_fill_rect_blend(0, y, sw, 1, 0xFF3B4E80, 22);
for (int y = 0; y < wh; y += 40) {
gui_gfx_fill_rect_blend(0, y, sw, 1, GUI_COLOR_BLACK, 40);
} }
for (int x = 0; x < sw; x += 40) { for (int x = 0; x < sw; x += 48) {
gui_gfx_fill_rect_blend(x, 0, 1, wh, GUI_COLOR_BLACK, 40); gui_gfx_fill_rect_blend(x, 0, 1, wh, 0xFF3B4E80, 22);
} }
} }
// Branding, offset to the right so it clears the icon column. // Soft vignette: concentric 1px frames darkening toward the edges. The
int bx = sw / 2 + 40; // alpha falls off with depth, so the corners recede smoothly.
int by = wh / 2 - 30; int depth = (wh < sw ? wh : sw) / 6;
gui_gfx_draw_string_16_shadow(bx - 60, by, "SUB-OS DESKTOP", 0xFF38BDF8, 0xFF0284C7); for (int i = 0; i < depth; i++) {
gui_gfx_draw_string(bx - 76, by + 22, "Modular Monolithic Kernel v0.2.0-LTS", 0xFF64748B); uint8_t a = (uint8_t)(((depth - i) * 42) / depth);
gui_gfx_draw_string(bx - 76, by + 36, "F5 cycle F6 cascade F7 tile F8 max", 0xFF475569); 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) { void gui_desktop_invalidate_background(void) {
@@ -307,7 +355,7 @@ void gui_desktop_render_taskbar(void) {
// One button per window, in z-order // One button per window, in z-order
int tab_x = 106; 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++) { 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); gui_window_t* win = gui_wm_get_window_at_index(i);
if (!win || !win->visible) continue; 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); gui_gfx_draw_string(sw - 140, ty + 12, buf, GUI_COLOR_BLACK);
(void)total; (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_time_t now;
rtc_get_time(&now); rtc_get_time(&now);
if (g_clock_seconds) {
snprintf(buf, sizeof(buf), "%02u:%02u:%02u", now.hour, now.minute, now.second); 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); 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 // Start menu
// =========================================================================== // ===========================================================================
@@ -477,6 +542,10 @@ void gui_desktop_close_menus(void) {
g_context_menu_open = false; 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; } void gui_desktop_request_exit(void) { g_desktop_running = false; }
static void exit_confirm_cb(gui_dialog_result_t result, void* ctx) { static void exit_confirm_cb(gui_dialog_result_t result, void* ctx) {
@@ -663,9 +732,12 @@ int gui_desktop_run(void) {
fbcon_enable(false); fbcon_enable(false);
gui_desktop_init(); gui_desktop_init();
// Open a default session so the desktop is not empty on first boot. // Open a default session so the desktop is not empty on first boot. Placed
launch_app(2); // System Monitor // and sized for the HD work area rather than the old 800x600 defaults.
launch_app(0); // Terminal 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 // Discard anything the console left in the keyboard queue; a stale key
// arriving on frame 0 would otherwise act as a desktop shortcut. // 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"); printk(KERN_INFO "GUI: Desktop session started (ESC returns to the kernel TTY)\n");
while (g_desktop_running) { while (g_desktop_running) {
uint64_t frame_start_us = ktime_us();
// --- 1. Sample input ----------------------------------------------- // --- 1. Sample input -----------------------------------------------
const mouse_state_t* ms = mouse_get_state(); const mouse_state_t* ms = mouse_get_state();
int mx = ms ? ms->x : gui_gfx_get_width() / 2; 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 ((g_frames & 0x1F) == 0) sample_cpu_history();
if (scene_dirty) {
gui_desktop_render_background(); gui_desktop_render_background();
gui_desktop_render_icons(); gui_desktop_render_icons();
gui_wm_render(); gui_wm_render();
@@ -742,11 +838,40 @@ int gui_desktop_run(void) {
gui_desktop_render_start_menu(); gui_desktop_render_start_menu();
gui_desktop_render_context_menu(); gui_desktop_render_context_menu();
gui_dialog_render(); 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(); gui_gfx_present();
g_frames++; g_frames++;
g_pushed_pixels += gui_gfx_last_present_pixels(); 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) { if ((g_frames & 0xFF) == 0) {
uint64_t full = (uint64_t)gui_gfx_get_width() * (uint64_t)gui_gfx_get_height(); uint64_t full = (uint64_t)gui_gfx_get_width() * (uint64_t)gui_gfx_get_height();
+59 -1
View File
@@ -295,11 +295,69 @@ static const char* g_icons[GUI_ICON_COUNT][GUI_ICON_SIZE] = {
"8811118811118800", "8811118811118800",
"8888888888888888", "8888888888888888",
"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] = { static const char* g_icon_names[GUI_ICON_COUNT] = {
"Terminal", "Monitor", "Folder", "Calculator", "Paint", "Info", "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) { static uint8_t hex_index(char c) {
+26 -8
View File
@@ -164,7 +164,20 @@ void gui_badge(int x, int y, const char* text, uint32_t bg, uint32_t fg) {
// Interactive controls // 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 hovered = hit_test(x, y, w, h);
bool pressed = hovered && g_input.mouse_down; bool pressed = hovered && g_input.mouse_down;
bool clicked = false; 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 gui_gfx_fill_rect(SX(x), SY(y), w, h,
: hovered ? GUI_THEME_BG_ELEVATED pressed ? face_press : hovered ? face_hover : face);
: 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_draw_rect(SX(x), SY(y), w, h, border); gui_gfx_draw_rect(SX(x), SY(y), w, h, border);
// A pressed button shifts its label a pixel to sell the depression. // A pressed button shifts its label a pixel to sell the depression.
int tx = x + (w - text_width(label)) / 2 + (pressed ? 1 : 0); int tx = x + (w - text_width(label)) / 2 + (pressed ? 1 : 0);
int ty = y + (h - GUI_FONT_H) / 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; 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) { 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); return gui_button_colored(id, x, y, w, h, label, GUI_THEME_PRIMARY);
} }
+69 -3
View File
@@ -10,6 +10,9 @@
#include <drivers/keyboard.h> #include <drivers/keyboard.h>
#include <lib/string.h> #include <lib/string.h>
#include <mm/kmalloc.h> #include <mm/kmalloc.h>
#include <kernel/ktime.h>
#define WM_OPEN_ANIM_MS 160
#define BTN_SIZE 14 #define BTN_SIZE 14
#define BTN_GAP 4 #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; g_window_order[g_num_windows++] = win->id;
gui_wm_focus_window(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; 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) { static int order_index_of(int win_id) {
for (int i = 0; i < g_num_windows; i++) { for (int i = 0; i < g_num_windows; i++) {
if (g_window_order[i] == win_id) return 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]); gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (!win || !win->visible || win->minimized) continue; 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; uint32_t border = win->active ? GUI_THEME_PRIMARY : GUI_THEME_BORDER;
gui_gfx_draw_rect(win->x, win->y, win->width, win->height, border); gui_gfx_draw_rect(win->x, win->y, win->width, win->height, border);
uint32_t top_bg = win->active ? 0xFF1E293B : 0xFF0F172A; // Brighter, more saturated titlebar when focused; muted when not.
uint32_t bot_bg = win->active ? 0xFF0F172A : 0xFF0A0E1A; 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_gfx_draw_gradient_v(win->x + 1, win->y + 1, win->width - 2,
GUI_TITLEBAR_HEIGHT - 1, top_bg, bot_bg); 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, gui_gfx_draw_line(win->x, win->y + GUI_TITLEBAR_HEIGHT,
win->x + win->width - 1, win->y + GUI_TITLEBAR_HEIGHT, border); win->x + win->width - 1, win->y + GUI_TITLEBAR_HEIGHT, border);
+30
View File
@@ -46,6 +46,36 @@ static inline void sha256(const void* data, size_t len, uint8_t hash[32]) {
sha256_final(hash, &ctx); 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) // PRNG (Cryptographic Pseudo-Random Number Generator)
void prng_seed(uint64_t seed1, uint64_t seed2); void prng_seed(uint64_t seed1, uint64_t seed2);
uint64_t prng_rand64(void); uint64_t prng_rand64(void);
+21
View File
@@ -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
+17
View File
@@ -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
+20
View File
@@ -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
+8
View File
@@ -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_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_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_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 #endif // _GUI_APPS_H
+6
View File
@@ -10,4 +10,10 @@ int gui_cursor_get_x(void);
int gui_cursor_get_y(void); int gui_cursor_get_y(void);
void gui_cursor_draw(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 #endif // _GUI_CURSOR_H
+15
View File
@@ -32,6 +32,21 @@ bool gui_desktop_handle_click(int mx, int my);
// Height of the area windows may occupy (screen minus taskbar). // Height of the area windows may occupy (screen minus taskbar).
int gui_desktop_workarea_height(void); 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); void gui_desktop_request_exit(void);
int gui_desktop_run(void); int gui_desktop_run(void);
+3
View File
@@ -25,6 +25,9 @@ typedef enum {
GUI_ICON_FILE, GUI_ICON_FILE,
GUI_ICON_POWER, GUI_ICON_POWER,
GUI_ICON_WARNING, GUI_ICON_WARNING,
GUI_ICON_HEART,
GUI_ICON_NETWORK,
GUI_ICON_GLOBE,
GUI_ICON_COUNT GUI_ICON_COUNT
} gui_icon_id_t; } gui_icon_id_t;
+12
View File
@@ -75,6 +75,18 @@ void gui_badge(int x, int y, const char* text, uint32_t bg, uint32_t fg);
// within one window. // within one window.
bool gui_button(int id, int x, int y, int w, int h, const char* label); 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); 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_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_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); bool gui_slider(int id, int x, int y, int w, int* value, int min_val, int max_val);
+12
View File
@@ -70,6 +70,14 @@ typedef struct gui_window {
void* user_data; 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 (*paint)(struct gui_window* win);
void (*handle_event)(struct gui_window* win, const gui_event_t* ev); void (*handle_event)(struct gui_window* win, const gui_event_t* ev);
} gui_window_t; } gui_window_t;
@@ -80,6 +88,10 @@ void gui_wm_init(void);
void gui_wm_set_workarea(int width, int height); 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); 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_destroy_window(int win_id);
void gui_wm_focus_window(int win_id); void gui_wm_focus_window(int win_id);
gui_window_t* gui_wm_get_window(int win_id); gui_window_t* gui_wm_get_window(int win_id);
+6 -6
View File
@@ -1,25 +1,25 @@
#ifndef _INIT_VERSION_H #ifndef _INIT_VERSION_H
#define _INIT_VERSION_H #define _INIT_VERSION_H
#define SUBOS_VERSION_MAJOR 0 #define SUBOS_VERSION_MAJOR 1
#define SUBOS_VERSION_MINOR 2 #define SUBOS_VERSION_MINOR 0
#define SUBOS_VERSION_PATCH 0 #define SUBOS_VERSION_PATCH 0
#define SUBOS_VERSION_CODENAME "Titan" #define SUBOS_VERSION_CODENAME "Titan"
#if defined(__x86_64__) #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_BUILD_TARGET "x86_64-elf-baremetal"
#define SUBOS_COMPILER "x86_64-elf-gcc" #define SUBOS_COMPILER "x86_64-elf-gcc"
#elif defined(__aarch64__) #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_BUILD_TARGET "aarch64-linux-gnu-baremetal"
#define SUBOS_COMPILER "aarch64-linux-gnu-gcc" #define SUBOS_COMPILER "aarch64-linux-gnu-gcc"
#elif defined(__arm__) || defined(__armv8i__) #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_BUILD_TARGET "arm-linux-gnueabihf-baremetal"
#define SUBOS_COMPILER "arm-linux-gnueabihf-gcc" #define SUBOS_COMPILER "arm-linux-gnueabihf-gcc"
#else #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_BUILD_TARGET "generic-elf-baremetal"
#define SUBOS_COMPILER "gcc" #define SUBOS_COMPILER "gcc"
#endif #endif
+52
View File
@@ -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
+21
View File
@@ -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
+87
View File
@@ -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
+105
View File
@@ -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
+77
View File
@@ -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
+9
View File
@@ -71,6 +71,15 @@ typedef struct {
int rust_kernel_init(void); int rust_kernel_init(void);
const char* rust_kernel_status(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 // Cryptography
int rust_chacha20_crypt(const uint8_t* key, const uint8_t* nonce, uint32_t counter, uint8_t* data, size_t len); 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); int rust_csprng_get_random(uint8_t* buffer, size_t len);
+25
View File
@@ -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
+14
View File
@@ -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
+11
View File
@@ -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
+1
View File
@@ -14,6 +14,7 @@ void kfree(void* ptr);
size_t heap_get_used_bytes(void); size_t heap_get_used_bytes(void);
size_t heap_get_free_bytes(void); size_t heap_get_free_bytes(void);
size_t heap_get_total_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_used(void) { return heap_get_used_bytes(); }
static inline size_t kmalloc_get_free(void) { return heap_get_free_bytes(); } static inline size_t kmalloc_get_free(void) { return heap_get_free_bytes(); }
+17
View File
@@ -5,6 +5,23 @@
#include <stdbool.h> #include <stdbool.h>
void dhcp_init(void); 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); 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 #endif // _NET_DHCP_H
+67
View File
@@ -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
+18
View File
@@ -102,6 +102,24 @@ arp_entry_t* net_get_arp_table(int* count_out);
uint32_t ip_parse(const char* ip_str); uint32_t ip_parse(const char* ip_str);
void ip_to_str(uint32_t ip, char* buf); 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); void mac_to_str(const uint8_t* mac, char* buf);
#endif // _NET_NET_H #endif // _NET_NET_H
+23
View File
@@ -23,6 +23,11 @@ typedef struct sockaddr_in {
char sin_zero[8]; char sin_zero[8];
} sockaddr_in_t; } 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 { typedef struct socket {
int domain; int domain;
int type; int type;
@@ -39,8 +44,26 @@ void socket_subsystem_init(void);
int sys_socket(int domain, int type, int protocol); int sys_socket(int domain, int type, int protocol);
int sys_bind(int sockfd, const struct sockaddr_in* addr, size_t addrlen); 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); 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_send(int sockfd, const void* buf, size_t len, int flags);
ssize_t sys_recv(int sockfd, 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); 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 #endif // _NET_SOCKET_H
+96 -2
View File
@@ -34,15 +34,42 @@ typedef struct {
uint16_t urgent_pointer; uint16_t urgent_pointer;
} __attribute__((packed)) tcp_header_t; } __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 { typedef struct tcp_conn {
uint32_t local_ip; uint32_t local_ip;
uint32_t remote_ip; uint32_t remote_ip;
uint16_t local_port; uint16_t local_port;
uint16_t remote_port; uint16_t remote_port;
uint8_t state; uint8_t state;
uint32_t seq_num; uint32_t seq_num; // next sequence number we will send
uint32_t ack_num; uint32_t ack_num; // next sequence number we expect to receive
bool in_use; 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; } tcp_conn_t;
void tcp_init(void); 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, uint32_t seq, uint32_t ack, uint8_t flags,
const void* payload, uint16_t payload_len); 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); size_t tcp_get_connections_count(void);
const tcp_conn_t* tcp_get_connection(size_t index); 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 #endif // _NET_TCP_H
+41
View File
@@ -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
View File
@@ -3,8 +3,37 @@
#include <net/net.h> #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); 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_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 #endif // _NET_UDP_H
+5
View File
@@ -31,4 +31,9 @@ int auth_set_current_user(const char* username);
size_t auth_get_user_count(void); size_t auth_get_user_count(void);
const user_account_t* auth_get_user_by_index(size_t index); 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 #endif // _SECURITY_AUTH_H
+255 -255
View File
@@ -1,5 +1,5 @@
// Generated by scripts/mklogo.py -- do not edit by hand. // 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> #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. // 16-entry ARGB palette; index 0 is the wallpaper background.
const uint32_t boot_logo_palette[16] = { const uint32_t boot_logo_palette[16] = {
0xFFFEFDFD, 0xFFFDFDFD,
0xFF77D4F6, 0xFF5ECDF7,
0xFF1C94C6, 0xFF1D8DBB,
0xFF232D32, 0xFF222B2F,
0xFF211F1E, 0xFF211F1E,
0xFF201F1F, 0xFF201F1F,
0xFF201E1F, 0xFF201E1F,
@@ -27,7 +27,7 @@ const uint32_t boot_logo_palette[16] = {
0xFF0B132C, 0xFF0B132C,
0xFF0B132B, 0xFF0B132B,
0xFF0A132C, 0xFF0A132C,
0xFF141016, 0xFF141015,
}; };
// 160x160 pixels, two 4-bit indices per byte, row major. // 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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,
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, 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, 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, 0xDD,
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEE, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x22, 0x22, 0x22, 0x23, 0x9F, 0x83, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3F, 0x22, 0x22, 0x22,
0x22, 0x3F, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 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, 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, 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, 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, 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, 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, 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, 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, 0x22, 0xA3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAA, 0x82, 0x22, 0x22, 0x22, 0x12, 0xA3, 0x22, 0x22,
0x22, 0x22, 0x22, 0x22, 0x3A, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 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, 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, 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, 0x77, 0x77, 0x75, 0x5A, 0xA8, 0x32, 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, 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, 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, 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, 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, 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, 0x21, 0x2F, 0x99, 0xF7, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0xA3, 0x32, 0x22, 0x23, 0xA3, 0x3A, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x11, 0x22, 0x3A, 0xA3, 0x32,
0x22, 0x22, 0x3F, 0x32, 0x22, 0x22, 0x22, 0x2A, 0x57, 0x75, 0xA9, 0x22, 0xAA, 0x97, 0x77, 0x77, 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, 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, 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, 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, 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, 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, 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, 0x5A, 0xA5, 0x95, 0x59, 0x9A, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x5A, 0xA3, 0x22, 0x21, 0x29,
0x22, 0x22, 0x22, 0x52, 0x22, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x22, 0x23, 0x3A, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x52, 0x12, 0x22, 0x23, 0xAA, 0x32, 0x22, 0x12, 0x23, 0x3A, 0x31, 0x22, 0x22,
0x22, 0x22, 0x23, 0x72, 0x23, 0x22, 0x22, 0x22, 0xA9, 0x77, 0x75, 0x5A, 0x32, 0x3A, 0x55, 0x77, 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, 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, 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, 0x75, 0x57, 0x75, 0x9A, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x23, 0xAF, 0x33, 0x92, 0x12, 0x22, 0x32,
0x22, 0x22, 0x2A, 0x32, 0x22, 0x23, 0x33, 0x89, 0xAA, 0x93, 0x32, 0x22, 0x3A, 0x32, 0x22, 0x22, 0x12, 0x22, 0x2A, 0x22, 0x22, 0x23, 0x33, 0x89, 0xAA, 0x93, 0x22, 0x22, 0x3A, 0x32, 0x22, 0x22,
0x22, 0x22, 0x22, 0x39, 0x39, 0xA2, 0x22, 0x23, 0xA9, 0x77, 0x77, 0x79, 0xF3, 0x22, 0xA7, 0x57, 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, 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, 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, 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, 0x32, 0x22, 0x22, 0xAF, 0x32, 0x22, 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, 0x32, 0x2A, 0x85, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x22, 0x22, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xAA, 0xA3, 0x32, 0x22, 0x22, 0x22, 0x23, 0xF2, 0x12, 0x22,
0x22, 0x22, 0x3A, 0xAA, 0xA3, 0x32, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA5, 0x77, 0x5A, 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, 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, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x55, 0xA3, 0x22,
0x22, 0x22, 0x22, 0x22, 0x23, 0xA9, 0x22, 0x22, 0x32, 0x22, 0x3A, 0x68, 0x55, 0x77, 0x77, 0x77, 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, 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, 0x21, 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, 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, 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, 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, 0x22, 0x2A, 0x95, 0xA3, 0x12, 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, 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, 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, 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, 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, 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, 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, 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, 0x22, 0x22, 0x22, 0x22, 0x22, 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, 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, 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, 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, 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, 0x39, 0xAA, 0xAA, 0x93, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x23, 0x33, 0xAA, 0xAA, 0x33,
0x22, 0x22, 0x22, 0xA3, 0x36, 0x55, 0xAF, 0x32, 0x23, 0x33, 0x22, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x22, 0x22, 0x22, 0xA3, 0x36, 0x55, 0xAF, 0x31, 0x23, 0x33, 0x22, 0x22, 0xA5, 0x77, 0x77, 0x77,
0x9A, 0x22, 0x29, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 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, 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, 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, 0xA3, 0x22, 0x22, 0x32, 0x3A, 0x57, 0x57, 0x33, 0xF2, 0x13, 0x31, 0x2A, 0x67, 0x77, 0x77, 0x77,
0x75, 0xA2, 0x22, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x56, 0xA2, 0x12, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x5A, 0x32, 0x22, 0x22, 0x21, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x77, 0x77, 0x77, 0x77, 0x77,
0x77, 0x77, 0x83, 0x33, 0x5A, 0xA5, 0x57, 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, 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, 0xDD, 0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0x22, 0xA8, 0x21, 0x23,
0x22, 0xA9, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 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, 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, 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, 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, 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, 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, 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, 0x57, 0x77, 0x77, 0x75, 0xF3, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x11, 0x11, 0x11, 0x15, 0x87, 0x45, 0xF3, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x87, 0x45, 0xF3, 0x2F, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77,
0x77, 0x75, 0xA8, 0x10, 0x0A, 0x45, 0xA1, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x77, 0x75, 0xA8, 0x10, 0x0A, 0x45, 0xA2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x11, 0x5A, 0x47, 0x76, 0x89, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x74, 0x9E, 0xDD, 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, 0x32, 0x33, 0x22, 0xA5, 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, 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, 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, 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, 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, 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, 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, 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, 0xDE, 0xB4, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0xA6, 0x22, 0x22, 0xF3, 0x22, 0x23, 0x3A, 0x57,
0x77, 0x74, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x74, 0x5F, 0x20, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0xF2, 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, 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, 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, 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, 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, 0x10, 0x00, 0x00, 0x00, 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, 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, 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, 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, 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, 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, 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, 0x30, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0x10, 0x00, 0x00, 0x2F,
0x54, 0x77, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x58, 0x47, 0x77, 0x77, 0x77, 0x5F, 0x20, 0x00, 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, 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, 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, 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, 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, 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, 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, 0xB4, 0x77, 0x77, 0x77, 0x77, 0x75, 0x83, 0x22, 0x22, 0x2F, 0x22, 0x22, 0x2A, 0x97, 0x77, 0x5F,
0x10, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x00, 0x00, 0x3F, 0x57, 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, 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, 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, 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, 0x94, 0x77, 0x77, 0x77, 0x77, 0x79, 0xA2, 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, 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, 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, 0x01, 0xF5, 0x77, 0x77, 0x75, 0x8F, 0x20, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x5F, 0x20,
0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x79, 0xA2, 0x22, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x49, 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, 0x9A, 0x92, 0x22, 0x3A, 0x57, 0x74, 0xA3, 0x94, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x22, 0x22, 0x3A, 0x92, 0x22, 0x3A, 0x57, 0x74, 0xA3,
0x00, 0x00, 0x00, 0x00, 0x01, 0xA5, 0x77, 0x77, 0x77, 0x77, 0x75, 0xF1, 0x01, 0x8A, 0x57, 0x77, 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, 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, 0x03, 0xF5, 0x77, 0x77, 0x5A, 0xA1, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x5F, 0x00,
0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x79, 0xA3, 0x22, 0x3A, 0x97, 0x77, 0x77, 0x77, 0x77, 0x49, 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, 0x22, 0x3A, 0x57, 0x75, 0xF3, 0x98, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x22, 0x22, 0xA3, 0x32, 0x12, 0x3A, 0x57, 0x75, 0xF3,
0x00, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x77, 0xA0, 0x1F, 0x85, 0x77, 0x77, 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, 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, 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, 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, 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, 0x1F, 0x57, 0x77, 0x77, 0x75, 0xF3, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x57, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x23, 0xA2, 0x22, 0x2F, 0x97, 0x77, 0x5F, 0x30,
0x00, 0x00, 0x00, 0x00, 0x1F, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x77, 0x77, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x2F, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x01, 0xF5, 0x5F, 0x20, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x00, 0x3F, 0x57, 0x77, 0x77, 0x5A, 0x22, 0x5A, 0x21, 0x28, 0x67, 0x77, 0x77, 0x77, 0x77, 0x74,
0x57, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x29, 0x22, 0x2A, 0x32, 0x82, 0x29, 0x85, 0x77, 0x75, 0xF3, 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, 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, 0x75, 0xF0, 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, 0x00, 0x0F, 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, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x21, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x75,
0x57, 0x77, 0x77, 0x77, 0x5A, 0x32, 0x33, 0x22, 0x2A, 0x32, 0x27, 0x23, 0x86, 0x77, 0x77, 0x5F, 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, 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, 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, 0x00, 0x2F, 0x55, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x44, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x22, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x75, 0x0A, 0x44, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x3F, 0x21, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x75,
0x67, 0x77, 0x77, 0x77, 0x5A, 0x32, 0xA3, 0x22, 0x2A, 0x32, 0x22, 0xA9, 0x87, 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, 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, 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, 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, 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, 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, 0x10, 0x00, 0x00, 0x00, 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, 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, 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, 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, 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, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x68, 0x21, 0x2F, 0x31, 0x3A, 0x57, 0x77, 0x77, 0x77, 0x77, 0x89,
0x98, 0x77, 0x77, 0x77, 0x5A, 0x33, 0xF3, 0x22, 0x28, 0x32, 0x22, 0x23, 0x85, 0x77, 0x77, 0x77, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF5, 0x77, 0x77, 0x77, 0x77, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF5, 0x5F, 0x30, 0x00, 0x00, 0x00, 0x01, 0xF5, 0x77, 0x77, 0x77, 0x74, 0xA3, 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, 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, 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, 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, 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, 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, 0x22, 0x22, 0x28, 0x67, 0x77, 0x75, 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, 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, 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, 0xF5, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF5, 0x77, 0x77, 0x77, 0x75, 0xF2, 0x00, 0x00, 0x00,
0x00, 0x1F, 0x57, 0x77, 0x5A, 0x22, 0x23, 0xA9, 0xA3, 0x38, 0x67, 0x77, 0x77, 0x77, 0x74, 0xBD, 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, 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, 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, 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, 0xF1, 0x00, 0x00, 0x00, 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, 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, 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, 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, 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, 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, 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, 0x50, 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, 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, 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, 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, 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, 0x3F, 0x5F, 0x30, 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, 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, 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, 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, 0xDD, 0xC9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x5A, 0x22, 0x22, 0x39, 0x21, 0x3F, 0x5F, 0x30, 0x00,
0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x5F, 0x20, 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, 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, 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, 0x03, 0xF9, 0xA2, 0x3F, 0x32, 0x22, 0x9A, 0x31, 0x2A, 0x57, 0x77, 0x77, 0x77, 0x78, 0x9C, 0xDD,
0xDD, 0xE9, 0x47, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x22, 0x3F, 0x22, 0x35, 0xF3, 0x00, 0x00, 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, 0x58, 0x5F, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 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, 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, 0x01, 0xF5, 0x85, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xA4, 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, 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, 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, 0x03, 0xA5, 0x85, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x55, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x77, 0x77, 0x5A, 0x32, 0x11, 0x11, 0x22, 0x2A, 0x56, 0x45, 0x8A, 0x22, 0x11, 0x11, 0x22, 0x3A,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x77, 0x5A, 0x23, 0x8A, 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, 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, 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, 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, 0x79, 0xA2, 0x22, 0x22, 0x22, 0x22, 0x23, 0xA5, 0x75, 0xA2, 0x12, 0x22, 0x22, 0x22, 0x2A,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x28, 0xA3, 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, 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, 0xDD, 0xDD, 0xDD, 0xDD, 0xE9, 0x87, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA3, 0x12, 0x21, 0x2A, 0x32,
0x22, 0x22, 0x28, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x12, 0x21, 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, 0x5A, 0x32, 0x22, 0xAA, 0xAF, 0x32, 0x22, 0xA5, 0x5A, 0x32, 0x22, 0xFA, 0xAF, 0x21, 0x29,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x2A, 0xA2, 0x86, 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, 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, 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, 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, 0x76, 0x89, 0x22, 0x2A, 0x89, 0x99, 0xA2, 0x22, 0x98, 0x9A, 0x32, 0x23, 0xF8, 0x58, 0x32, 0x38,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x3F, 0x32, 0x47, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x31, 0x3F, 0x22,
0x22, 0x3A, 0x22, 0x2A, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x74, 0xBD, 0xDD, 0xDD, 0xDD, 0xDD, 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, 0x3A, 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, 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, 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, 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, 0xA8, 0x93, 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, 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, 0x22, 0xA2, 0x22, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0xA2, 0x12, 0xA2, 0x22,
0x3F, 0x32, 0x28, 0x85, 0x77, 0x77, 0x77, 0x77, 0x77, 0x78, 0x5B, 0xED, 0xDD, 0xDD, 0xDD, 0xDD, 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, 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, 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, 0x68, 0x92, 0x12, 0xA5, 0x77, 0x74, 0x72, 0x22, 0xA5, 0x79, 0x83, 0x22, 0x22, 0x12, 0x12, 0x98,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x9A, 0x32, 0x29, 0x22, 0x22, 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, 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, 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, 0x28, 0xA2, 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, 0x5A, 0x32, 0x12, 0xA9, 0x77, 0x5A, 0x32, 0x22, 0xA9, 0x5A, 0xAA, 0xAA, 0x93, 0x22, 0x22, 0x3A,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x67, 0x77, 0x75, 0xA3, 0x22, 0x22, 0x22, 0x2F, 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, 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, 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, 0x22, 0xAF, 0x32, 0x22, 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, 0x58, 0x32, 0x22, 0xA9, 0x55, 0x9F, 0x22, 0x23, 0x85, 0x83, 0x33, 0x99, 0x88, 0xF2, 0x22, 0x3A,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x75, 0x87, 0x77, 0x57, 0xA2, 0x22, 0x22, 0x22, 0xA9, 0x57, 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, 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, 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, 0x22, 0x28, 0xA3, 0x22, 0x22, 0x21, 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, 0x74, 0x72, 0x22, 0x3F, 0xAA, 0xF3, 0x22, 0x2A, 0x94, 0xA2, 0x12, 0xAA, 0xAA, 0xF2, 0x22, 0x38,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x76, 0x5A, 0x35, 0x77, 0x9A, 0x22, 0x22, 0x22, 0x28, 0xA3, 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, 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, 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, 0x22, 0x22, 0x7A, 0x32, 0x22, 0x22, 0x23, 0xFA, 0x65, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 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, 0xA3, 0x3A, 0x59, 0xA3, 0x22, 0x22, 0x22, 0x9A, 0x98, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x57, 0xA2, 0x3A, 0x59, 0xA3, 0x22, 0x22, 0x22, 0x9A, 0x98,
0xA9, 0xA3, 0x29, 0x85, 0x77, 0x77, 0x77, 0x77, 0x89, 0xCD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 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, 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, 0x22, 0x22, 0x23, 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, 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, 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, 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, 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, 0x22, 0x22, 0x22, 0x3A, 0xA2, 0x12, 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, 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, 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, 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, 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, 0x32, 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, 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, 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, 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, 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, 0xA9, 0x22, 0x22, 0x22, 0x2A, 0xF3, 0x22, 0x22, 0x22, 0x12, 0x25, 0x87, 0x55, 0x77, 0x77, 0x77,
0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 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, 0x22, 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, 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, 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, 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, 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, 0x22, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x23, 0x3A, 0xA5, 0x57, 0x77, 0x77, 0x77, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x22, 0x33, 0x33, 0x39, 0xAA, 0xAA, 0x89, 0x33, 0x33, 0x22, 0x22, 0x22, 0x12, 0x22,
0x22, 0x22, 0x33, 0x9A, 0xAA, 0xA6, 0x55, 0x57, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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,
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 NULL
}; };
+323
View File
@@ -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"
+83
View File
@@ -42,6 +42,89 @@ constexpr T&& forward(typename RemoveReference<T>::type&& arg) noexcept {
return static_cast<T&&>(arg); 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) // UniquePtr (Freestanding Smart Pointer RAII)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
+121
View File
@@ -1,7 +1,10 @@
// C++ Kernel Subsystem C-Fallback for Non-x86 Cross Targets // C++ Kernel Subsystem C-Fallback for Non-x86 Cross Targets
#include <kernel/cpp_kernel.h> #include <kernel/cpp_kernel.h>
#include <kernel/cpp_analytics.h>
#include <kernel/printk.h> #include <kernel/printk.h>
#include <kernel/metrics.h>
#include <mm/pmm.h> #include <mm/pmm.h>
#include <mm/kmalloc.h>
int cpp_kernel_init(void) { int cpp_kernel_init(void) {
printk(KERN_INFO "CXX: C++ Kernel Subsystem Bridge online (Cross-Architecture Mode)\n"); 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); printk(" UniquePtr RAII : " ANSI_BRIGHT_GREEN "PASSED\n\n" ANSI_RESET);
return 0; 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
View File
@@ -7,6 +7,9 @@
#include <kernel/timer.h> #include <kernel/timer.h>
#include <kernel/workqueue.h> #include <kernel/workqueue.h>
#include <kernel/module.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/trace.h>
#include <kernel/namespace.h> #include <kernel/namespace.h>
#include <kernel/syslog.h> #include <kernel/syslog.h>
@@ -63,6 +66,7 @@
#include <net/tcp.h> #include <net/tcp.h>
#include <net/dhcp.h> #include <net/dhcp.h>
#include <net/dns.h> #include <net/dns.h>
#include <net/http_client.h>
#include <net/dns_cache.h> #include <net/dns_cache.h>
#include <ipc/shm_posix.h> #include <ipc/shm_posix.h>
#include <sound/beep.h> #include <sound/beep.h>
@@ -70,12 +74,17 @@
#include <drivers/e1000e.h> #include <drivers/e1000e.h>
#include <drivers/virtio_gpu.h> #include <drivers/virtio_gpu.h>
#include <drivers/cpufreq.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 <drivers/virtio_input.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <init/init.h> #include <init/init.h>
#include <kernel/rust.h> #include <kernel/rust.h>
#include <kernel/sub_lang.h> #include <kernel/sub_lang.h>
#include <kernel/cpp_kernel.h> #include <kernel/cpp_kernel.h>
#include <kernel/cpp_analytics.h>
#include <net/filter.h> #include <net/filter.h>
#include <net/http.h> #include <net/http.h>
#include <net/ssh.h> #include <net/ssh.h>
@@ -125,7 +134,11 @@ static void subos_modular_core_boot(void) {
xhci_init(); xhci_init();
hwmon_init(); hwmon_init();
cpufreq_init(); cpufreq_init();
hpet_init();
nvram_init();
lpt_init();
#endif #endif
ktime_init();
canvas_init(); canvas_init();
pty_init(); pty_init();
virtio_rng_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"); boot_logo_splash_progress(55, "Starting Rust, C++ and SUB-Lang engines");
rust_kernel_init(); rust_kernel_init();
cpp_kernel_init(); cpp_kernel_init();
cpp_analytics_init();
sub_kernel_init(); sub_kernel_init();
prng_seed(0, 0); prng_seed(0, 0);
certs_init(); certs_init();
@@ -239,6 +253,9 @@ static void subos_modular_core_boot(void) {
syslog_init(); syslog_init();
metrics_init(); metrics_init();
cron_init(); cron_init();
ob_init();
reg_init();
io_init();
service_manager_init(); service_manager_init();
printk(ANSI_BRIGHT_GREEN "OK\n" ANSI_RESET); 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 * 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. */ * afterwards, so CPU-bound kernel threads are time-sliced from here on. */
weave_preempt_selftest(); 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 #endif
// Boot directly into the GUI Desktop unless emergency text mode is requested. // Boot directly into the GUI Desktop unless emergency text mode is requested.
bool emergency_text = init_has_param("nogui") || init_has_param("text") || bool emergency_text = init_has_param("nogui") || init_has_param("text") ||
init_has_param("emergency") || init_has_param("single"); init_has_param("emergency") || init_has_param("single");
if (!emergency_text) { // End the splash and return the screen to a text console so the login
printk(KERN_INFO "INIT: Booting directly into SUB-OS Graphical Desktop Environment...\n"); // prompt is visible before any session (graphical or TTY) begins.
boot_logo_splash_end(); 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(); 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 { } else {
printk(KERN_INFO "INIT: Emergency text/kernel TTY mode requested via boot parameters.\n"); 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 // 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. // in ring 3 with its own page tables and talks back only through INT 0x80.
exec_run_builtin_init(); exec_run_builtin_init();
+242
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+58
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -5,7 +5,8 @@
#include <kernel/printk.h> #include <kernel/printk.h>
#define HEAP_MAGIC 0x53554248 // "SUBH" #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 { typedef struct heap_block {
uint32_t magic; uint32_t magic;
@@ -18,11 +19,21 @@ typedef struct heap_block {
#define BLOCK_HEADER_SIZE sizeof(heap_block_t) #define BLOCK_HEADER_SIZE sizeof(heap_block_t)
static heap_block_t* heap_start = NULL; 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_used_bytes = 0;
static size_t heap_free_bytes = 0; static size_t heap_free_bytes = 0;
static size_t heap_total_bytes = 0; static size_t heap_total_bytes = 0;
static size_t heap_grow_count = 0;
static spinlock_t heap_lock = SPINLOCK_INIT; 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 heap_init(void) {
void* initial_mem = pmm_alloc_pages(HEAP_INITIAL_PAGES); void* initial_mem = pmm_alloc_pages(HEAP_INITIAL_PAGES);
if (!initial_mem) { if (!initial_mem) {
@@ -31,7 +42,7 @@ void heap_init(void) {
} }
heap_start = (heap_block_t*)initial_mem; 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->magic = HEAP_MAGIC;
heap_start->is_free = true; heap_start->is_free = true;
@@ -39,28 +50,63 @@ void heap_init(void) {
heap_start->next = NULL; heap_start->next = NULL;
heap_start->prev = NULL; heap_start->prev = NULL;
heap_tail = heap_start;
heap_used_bytes = 0; heap_used_bytes = 0;
heap_free_bytes = heap_start->size; heap_free_bytes = heap_start->size;
} }
void* kmalloc(size_t size) { /* Append a fresh region from the PMM to the tail of the free list. Must be
if (size == 0) return NULL; * 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 void* mem = pmm_alloc_pages(grow_pages);
size = (size + 15) & ~15; if (!mem && grow_pages != need_pages) {
/* Could not get the generous chunk; fall back to exactly what is needed. */
spin_lock(&heap_lock); grow_pages = need_pages;
mem = pmm_alloc_pages(grow_pages);
heap_block_t* curr = heap_start; }
while (curr) { if (!mem) {
if (curr->magic != HEAP_MAGIC) {
printk(KERN_ERR "Heap Corruption detected at %p!\n", curr);
spin_unlock(&heap_lock);
return NULL; return NULL;
} }
if (curr->is_free && curr->size >= size) { heap_block_t* region = (heap_block_t*)mem;
// Check if block can be split 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) { if (curr->size >= size + BLOCK_HEADER_SIZE + 16) {
heap_block_t* new_block = (heap_block_t*)((uint8_t*)curr + BLOCK_HEADER_SIZE + size); heap_block_t* new_block = (heap_block_t*)((uint8_t*)curr + BLOCK_HEADER_SIZE + size);
new_block->magic = HEAP_MAGIC; new_block->magic = HEAP_MAGIC;
@@ -71,6 +117,8 @@ void* kmalloc(size_t size) {
if (curr->next) { if (curr->next) {
curr->next->prev = new_block; curr->next->prev = new_block;
} else {
heap_tail = new_block;
} }
curr->next = new_block; curr->next = new_block;
curr->size = size; curr->size = size;
@@ -80,15 +128,44 @@ void* kmalloc(size_t size) {
heap_used_bytes += curr->size; heap_used_bytes += curr->size;
heap_free_bytes -= curr->size; heap_free_bytes -= curr->size;
spin_unlock(&heap_lock);
return (void*)((uint8_t*)curr + BLOCK_HEADER_SIZE); 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; 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); 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; return NULL;
} }
@@ -116,21 +193,27 @@ void kfree(void* ptr) {
heap_used_bytes -= block->size; heap_used_bytes -= block->size;
heap_free_bytes += block->size; heap_free_bytes += block->size;
// Coalesce with next block if free // Coalesce with next block if free AND physically adjacent (same region).
if (block->next && block->next->is_free) { if (block->next && block->next->is_free && blocks_adjacent(block, block->next)) {
block->size += BLOCK_HEADER_SIZE + block->next->size; heap_block_t* nxt = block->next;
block->next = block->next->next; block->size += BLOCK_HEADER_SIZE + nxt->size;
block->next = nxt->next;
if (block->next) { if (block->next) {
block->next->prev = block; block->next->prev = block;
} else {
heap_tail = block;
} }
} }
// Coalesce with prev block if free // Coalesce with prev block if free AND physically adjacent (same region).
if (block->prev && block->prev->is_free) { if (block->prev && block->prev->is_free && blocks_adjacent(block->prev, block)) {
block->prev->size += BLOCK_HEADER_SIZE + block->size; heap_block_t* prev = block->prev;
block->prev->next = block->next; prev->size += BLOCK_HEADER_SIZE + block->size;
prev->next = block->next;
if (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; return ptr;
} }
size_t old_size = block->size;
void* new_ptr = kmalloc(new_size); void* new_ptr = kmalloc(new_size);
if (new_ptr) { if (new_ptr) {
memcpy(new_ptr, ptr, block->size); memcpy(new_ptr, ptr, old_size);
kfree(ptr); kfree(ptr);
} }
return new_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_used_bytes(void) { return heap_used_bytes; }
size_t heap_get_free_bytes(void) { return heap_free_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_total_bytes(void) { return heap_total_bytes; }
size_t heap_get_grow_count(void) { return heap_grow_count; }
+55 -12
View File
@@ -1,11 +1,13 @@
#include <net/net.h> #include <net/net.h>
#include <net/tcp.h> #include <net/tcp.h>
#include <net/udp.h>
#include <net/filter.h> #include <net/filter.h>
#include <drivers/e1000.h> #include <drivers/e1000.h>
#include <lib/string.h> #include <lib/string.h>
#include <lib/printf.h> #include <lib/printf.h>
#include <mm/kmalloc.h> #include <mm/kmalloc.h>
#include <arch/arch.h> #include <arch/arch.h>
#include <kernel/sched.h>
#include <kernel/printk.h> #include <kernel/printk.h>
#define MAX_ARP_ENTRIES 16 #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 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; * One's-complement checksum, accumulated a byte at a time.
uint32_t sum = 0; *
* 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) { while (len > 1) {
sum += *ptr++; sum += ((uint32_t)p[0] << 8) | (uint32_t)p[1];
p += 2;
len -= 2; len -= 2;
} }
if (len > 0) { if (len) sum += (uint32_t)p[0] << 8; // odd tail byte is the high half
sum += *(const uint8_t*)ptr;
} return sum;
while (sum >> 16) { }
sum = (sum & 0xFFFF) + (sum >> 16);
} uint16_t net_csum_finish(uint32_t sum) {
return (uint16_t)(~sum); while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
return htons((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) { 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)) { if (payload_len >= sizeof(ip_header_t)) {
const ip_header_t* ip = (const ip_header_t*)payload; const ip_header_t* ip = (const ip_header_t*)payload;
uint8_t ihl = (ip->ihl_version & 0x0F) * 4; 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) { if (payload_len >= ihl) {
// Dynamically learn sender's MAC address in ARP cache // Dynamically learn sender's MAC address in ARP cache
arp_cache_insert(ip->src_ip, eth->src_mac); 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); net_handle_icmp(ip->src_ip, payload + ihl, payload_len - ihl);
} else if (ip->protocol == IP_PROTO_TCP) { } else if (ip->protocol == IP_PROTO_TCP) {
tcp_receive(payload + ihl, payload_len - ihl, ip->src_ip); 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; if (wait_ticks == 0) wait_ticks = 100;
while (!ping_received && (pit_get_ticks() - t_start < wait_ticks)) { while (!ping_received && (pit_get_ticks() - t_start < wait_ticks)) {
arch_halt(); net_wait();
} }
if (ping_received && ping_id == p_id && ping_seq == seq) { 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; 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) { void net_init(void) {
strcpy(primary_if.name, "eth0"); strcpy(primary_if.name, "eth0");
e1000_get_mac(primary_if.mac); e1000_get_mac(primary_if.mac);
+258 -16
View File
@@ -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/socket.h>
#include <net/udp.h>
#include <net/tcp.h>
#include <net/net.h>
#include <lib/string.h> #include <lib/string.h>
#include <kernel/printk.h> #include <kernel/printk.h>
#define MAX_SOCKETS 32 #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 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) { void socket_subsystem_init(void) {
memset(socket_table, 0, sizeof(socket_table)); 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"); 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) { int sys_socket(int domain, int type, int protocol) {
if (domain != AF_INET) return -1; 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++) { for (int i = 0; i < MAX_SOCKETS; i++) {
if (!socket_table[i].in_use) { 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].in_use = true;
socket_table[i].domain = domain; socket_table[i].domain = domain;
socket_table[i].type = type; 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; socket_table[i].state = 0;
return i; return i;
} }
@@ -26,37 +102,203 @@ int sys_socket(int domain, int type, int protocol) {
return -1; 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) { int sys_bind(int sockfd, const struct sockaddr_in* addr, size_t addrlen) {
(void)addrlen; (void)addrlen;
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !addr) return -1; if (!valid_fd(sockfd) || !addr) return -1;
socket_table[sockfd].local_ip = addr->sin_addr; socket_t* s = &socket_table[sockfd];
socket_table[sockfd].local_port = addr->sin_port; 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; return 0;
} }
int sys_connect(int sockfd, const struct sockaddr_in* addr, size_t addrlen) { int sys_connect(int sockfd, const struct sockaddr_in* addr, size_t addrlen) {
(void)addrlen; (void)addrlen;
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !addr) return -1; if (!valid_fd(sockfd) || !addr) return -1;
socket_table[sockfd].remote_ip = addr->sin_addr; socket_t* s = &socket_table[sockfd];
socket_table[sockfd].remote_port = addr->sin_port; sock_priv_t* pv = &socket_priv[sockfd];
socket_table[sockfd].state = 1; // Connected 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;
}
s->state = SOCK_STATE_CONNECTED; // address memorised for send/recv
if (s->type == SOCK_DGRAM) return dgram_ensure_bound(sockfd);
return 0; return 0;
} }
ssize_t sys_send(int sockfd, const void* buf, size_t len, int flags) { int sys_listen(int sockfd, int backlog) {
(void)flags; if (!valid_fd(sockfd)) return -1;
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !buf) 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; 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; (void)flags;
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !buf) return -1; if (!valid_fd(sockfd) || !buf) return -1;
return 0; 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) { int sys_close_socket(int sockfd) {
if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use) return -1; if (!valid_fd(sockfd)) return -1;
memset(&socket_table[sockfd], 0, sizeof(socket_t)); 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; 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;
}
+373
View File
@@ -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
View File
@@ -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/dhcp.h>
#include <net/udp.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 <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) { 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"); 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) { 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: 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
View File
@@ -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/dns.h>
#include <net/net.h> #include <net/net.h>
#include <net/udp.h>
#include <net/dns_cache.h>
#include <mm/kmalloc.h>
#include <lib/string.h> #include <lib/string.h>
#include <lib/printf.h>
#include <kernel/printk.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) { void dns_init(void) {
dns_reply_ready = false;
printk(KERN_INFO "DNS: Domain Name System Resolver client ready (Nameserver: 10.0.2.3)\n"); 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) { uint32_t dns_resolve(const char* hostname) {
if (!hostname) return 0; if (!hostname || !*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"); // Dotted-quad literal: no lookup needed.
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"); bool numeric = true;
return ip_parse("10.0.2.15"); 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
View File
@@ -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/dns_cache.h>
#include <net/net.h>
#include <lib/string.h> #include <lib/string.h>
#include <lib/printf.h> #include <lib/printf.h>
#include <kernel/printk.h> #include <kernel/printk.h>
@@ -10,12 +16,12 @@ static uint64_t total_hits = 0;
void dns_cache_init(void) { void dns_cache_init(void) {
memset(dns_table, 0, sizeof(dns_table)); memset(dns_table, 0, sizeof(dns_table));
// Seed common local and well-known hosts // Seed common local and well-known hosts (network byte order via ip_parse).
dns_cache_insert("localhost", 0x7F000001, 86400); // 127.0.0.1 dns_cache_insert("localhost", ip_parse("127.0.0.1"), 86400);
dns_cache_insert("gateway.local", 0x0A000202, 3600); // 10.0.2.2 dns_cache_insert("gateway.local", ip_parse("10.0.2.2"), 3600);
dns_cache_insert("dns.google", 0x08080808, 7200); // 8.8.8.8 dns_cache_insert("dns.google", ip_parse("8.8.8.8"), 7200);
dns_cache_insert("one.one.one.one", 0x01010101, 7200); // 1.1.1.1 dns_cache_insert("one.one.one.one", ip_parse("1.1.1.1"), 7200);
dns_cache_insert("github.com", 0x8C527964, 300); // 140.82.121.4 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); 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) { void dns_cache_flush(void) {
memset(dns_table, 0, sizeof(dns_table)); 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); 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++) { for (int i = 0; i < DNS_CACHE_MAX_ENTRIES; i++) {
if (dns_table[i].active) { if (dns_table[i].active) {
uint32_t ip = dns_table[i].ip;
char ip_str[20]; char ip_str[20];
snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u", ip_to_str(dns_table[i].ip, ip_str);
(ip >> 24) & 0xFF, (ip >> 16) & 0xFF,
(ip >> 8) & 0xFF, ip & 0xFF);
printk("%-28s " ANSI_BRIGHT_YELLOW "%-16s" ANSI_RESET " %8u %8llu\n", printk("%-28s " ANSI_BRIGHT_YELLOW "%-16s" ANSI_RESET " %8u %8llu\n",
dns_table[i].hostname, ip_str, dns_table[i].hostname, ip_str,
+764 -139
View File
@@ -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/tcp.h>
#include <net/net.h> #include <net/net.h>
#include <net/ssh.h> #include <net/ssh.h>
@@ -6,66 +32,99 @@
#include <lib/string.h> #include <lib/string.h>
#include <lib/printf.h> #include <lib/printf.h>
#include <kernel/printk.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 { // The timer tick is 100 Hz, so one tick is 10 ms.
uint32_t src_ip; #define TCP_RTO_TICKS 50 // 500 ms retransmission timeout
uint32_t dst_ip; #define TCP_MAX_RETRIES 5
uint8_t zero; #define TCP_CLOSE_TICKS 50 // how long tcp_close waits for the shutdown
uint8_t protocol; #define TCP_TIME_WAIT_TICKS 200 // 2 s; a deliberately shortened 2*MSL
uint16_t tcp_len;
} __attribute__((packed)) tcp_pseudo_header_t; // 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 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) { // A claimed port, plus the connections whose handshakes have completed on it
uint16_t tcp_total_len = sizeof(tcp_header_t) + payload_len; // 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; static tcp_listener_t tcp_listeners[TCP_MAX_LISTENERS];
ph.src_ip = src_ip;
ph.dst_ip = dst_ip; // Cumulative connections that have reached ESTABLISHED, and the high-water mark
ph.zero = 0; // of simultaneously-live table entries. Diagnostic only.
ph.protocol = IP_PROTO_TCP; static uint64_t tcp_total_opened = 0;
ph.tcp_len = htons(tcp_total_len); 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; uint32_t sum = 0;
const uint16_t* ptr = (const uint16_t*)&ph; sum = net_csum_add(sum, &src_ip, sizeof(src_ip));
for (size_t i = 0; i < sizeof(ph) / 2; i++) { sum = net_csum_add(sum, &dst_ip, sizeof(dst_ip));
sum += *ptr++; 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; return net_csum_finish(sum);
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);
} }
void tcp_init(void) { void tcp_init(void) {
memset(tcp_connections, 0, sizeof(tcp_connections)); 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, 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(); net_if_t* netif = net_get_primary_if();
uint32_t src_ip = netif ? netif->ip : 0x0A00020F; // 10.0.2.15 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->ack_num = htonl(ack);
tcp->data_offset_reserved = (sizeof(tcp_header_t) / 4) << 4; tcp->data_offset_reserved = (sizeof(tcp_header_t) / 4) << 4;
tcp->flags = flags; tcp->flags = flags;
tcp->window_size = htons(65535); tcp->window_size = htons(window);
tcp->urgent_pointer = 0; tcp->urgent_pointer = 0;
if (payload && payload_len > 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); 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++) { for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
if (tcp_connections[i].in_use && tcp_conn_t* c = &tcp_connections[i];
tcp_connections[i].remote_ip == r_ip && if (!c->in_use || c->state != TCP_STATE_TIME_WAIT) continue;
tcp_connections[i].remote_port == r_port &&
tcp_connections[i].local_port == l_port) { uint64_t linger = (c->role == TCP_ROLE_SERVICE)
return &tcp_connections[i]; ? 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++) { for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
if (!tcp_connections[i].in_use) { if (!tcp_connections[i].in_use) {
memset(&tcp_connections[i], 0, sizeof(tcp_conn_t));
tcp_connections[i].in_use = true; tcp_connections[i].in_use = true;
tcp_connections[i].remote_ip = r_ip; tcp_note_peak();
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;
return &tcp_connections[i]; return &tcp_connections[i];
} }
} }
return NULL; 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) { void tcp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) {
if (!packet || length < sizeof(tcp_header_t)) return; 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 seq_num = ntohl(tcp->seq_num);
uint32_t ack_num = ntohl(tcp->ack_num); uint32_t ack_num = ntohl(tcp->ack_num);
uint8_t flags = tcp->flags; uint8_t flags = tcp->flags;
(void)ack_num;
uint8_t header_len = (tcp->data_offset_reserved >> 4) * 4; uint8_t header_len = (tcp->data_offset_reserved >> 4) * 4;
const uint8_t* payload = packet + header_len; if (header_len < sizeof(tcp_header_t) || header_len > length) return;
uint16_t payload_len = length > header_len ? length - header_len : 0;
// 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_ssh_port = (dst_port == sshd_get_port() && sshd_is_running());
bool is_http_port = (dst_port == 80 && httpd_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) { // 3. Nothing is listening and nothing matches: refuse the segment.
// Port not open: send TCP RST
if (!(flags & TCP_FLAG_RST)) { 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);
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;
}
// 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;
}
// Handle TCP RST
if (flags & TCP_FLAG_RST) {
conn->in_use = false;
conn->state = TCP_STATE_CLOSED;
return;
}
// 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;
}
}
conn->state = TCP_STATE_ESTABLISHED;
if (payload_len > 0) {
conn->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, 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);
}
} 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;
}
}
} }
} }
// ===========================================================================
// 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;
}
conn_free(c);
}
// ===========================================================================
// 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;
}
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();
}
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;
}
void tcp_unlisten(uint16_t port) {
tcp_listener_t* l = find_listener(port);
if (!l) return;
// 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);
}
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) { 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; size_t count = 0;
for (size_t i = 0; i < MAX_TCP_CONNS; i++) { for (size_t i = 0; i < MAX_TCP_CONNS; i++) {
if (tcp_connections[i].in_use) count++; 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; if (index >= MAX_TCP_CONNS || !tcp_connections[index].in_use) return NULL;
return &tcp_connections[index]; 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];
}
+197 -10
View File
@@ -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 <net/udp.h>
#include <mm/kmalloc.h>
#include <lib/string.h> #include <lib/string.h>
#include <lib/printf.h>
#include <kernel/printk.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) { 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"); 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) { int udp_bind(uint16_t port, udp_handler_t handler, void* ctx) {
(void)packet; (void)length; (void)src_ip; 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) { void udp_unbind(uint16_t port) {
uint8_t buffer[1500]; for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
udp_header_t* udp = (udp_header_t*)buffer; 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->src_port = htons(src_port);
udp->dst_port = htons(dst_port); udp->dst_port = htons(dst_port);
udp->length = htons((uint16_t)(sizeof(udp_header_t) + len)); udp->length = htons(total);
udp->checksum = 0; udp->checksum = 0;
if (data && len > 0) { if (data && len > 0) memcpy(buf + sizeof(udp_header_t), data, len);
memcpy(buffer + sizeof(udp_header_t), data, len);
}
net_send_ip(dst_ip, IP_PROTO_UDP, buffer, (uint16_t)(sizeof(udp_header_t) + 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);
}
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;
} }
+1 -1
View File
@@ -30,7 +30,7 @@ int sshd_start(uint16_t port) {
ssh_sessions[0].in_use = true; ssh_sessions[0].in_use = true;
ssh_sessions[0].state = SSH_STATE_SHELL_ACTIVE; ssh_sessions[0].state = SSH_STATE_SHELL_ACTIVE;
ssh_sessions[0].authenticated = true; 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_ip = 0x0A000202; // 10.0.2.2 (QEMU Host Gateway)
ssh_sessions[0].client_port = 52341; ssh_sessions[0].client_port = 52341;
ssh_sessions[0].connect_time = pit_get_ticks() / 100; ssh_sessions[0].connect_time = pit_get_ticks() / 100;
+572
View File
@@ -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, &copy);
}
// ===========================================================================
// 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);
}
+66
View File
@@ -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); if (cipher && plain_out && cipher_len > 0) memcpy(plain_out, cipher, cipher_len);
return 0; 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;
}
+76
View File
@@ -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)
}
+93
View File
@@ -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
}
}
+9
View File
@@ -8,6 +8,8 @@
pub mod printk; pub mod printk;
pub mod drivers; pub mod drivers;
pub mod crypto; pub mod crypto;
pub mod checksum;
pub mod compress;
pub mod fs; pub mod fs;
pub mod kernel; pub mod kernel;
pub mod storage; pub mod storage;
@@ -42,6 +44,13 @@ pub extern "C" fn rust_kernel_init() -> i32 {
// Ping Rust watchdog // Ping Rust watchdog
kernel::watchdog::rust_watchdog_ping(1); 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 0
} }
+57 -34
View File
@@ -407,56 +407,68 @@ def run_dialog_gui():
continue continue
items = submenu["items"] 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: for item in items:
itype = item.get("type")
if itype == "bool":
status = "ON" if cfg.get(item["id"], False) else "OFF" status = "ON" if cfg.get(item["id"], False) else "OFF"
item_opts.extend([item["id"], item["label"], status]) 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) cmd = ["dialog", "--clear", "--backtitle", backtitle,
dialog_type = "--radiolist" if is_radiolist else "--checklist" "--title", submenu["title"], "--help-button", "--help-status",
"--checklist",
cmd = [
"dialog",
"--clear",
"--backtitle", backtitle,
"--title", submenu["title"],
"--help-button",
"--help-status",
dialog_type,
"Use <Space> to toggle features [*], <Enter> to confirm, <Help> for info.", "Use <Space> to toggle features [*], <Enter> to confirm, <Help> for info.",
"20", "78", "10" "20", "78", "10"] + item_opts
] + item_opts
res = subprocess.run(cmd, stderr=subprocess.PIPE, text=True) res = subprocess.run(cmd, stderr=subprocess.PIPE, text=True)
ret = res.returncode ret = res.returncode
out = res.stderr.strip() out = res.stderr.strip()
if ret == 0: # OK / Confirm 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" current_menu_id = "main"
elif ret == 1 or ret == 255: # Cancel or Esc elif ret == 1 or ret == 255: # Cancel or Esc
current_menu_id = "main" current_menu_id = "main"
elif ret == 2: # Help button elif ret == 2: # Help button
# With --help-status the payload is "HELP <focused-tag> <on-tags...>". if is_radio:
# Re-apply the on-tags first so the toggles made before pressing # --menu help payload is just "HELP <focused-tag>".
# <Help> survive the round trip, then show the focused item's help. 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) tokens = _help_tokens(out)
focus = tokens[0] if tokens else None 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) focused = next((i for i in items if i["id"] == focus), None)
if focused: if focused:
help_title = f"{focused['label']} - Help" help_title = f"{focused['label']} - Help"
@@ -467,8 +479,10 @@ def run_dialog_gui():
subprocess.run(["dialog", "--backtitle", backtitle, "--title", help_title, subprocess.run(["dialog", "--backtitle", backtitle, "--title", help_title,
"--msgbox", help_text, "16", "72"]) "--msgbox", help_text, "16", "72"])
save_config(cfg) # Saving is handled inside the loop: the <Save> button writes immediately,
print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***") # 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) # Pure Python Curses GUI Renderer (Pixel-Perfect Linux lxdialog Replica)
@@ -829,7 +843,12 @@ def main():
print(f"menuconfig: lxdialog front-end failed ({exc}); " print(f"menuconfig: lxdialog front-end failed ({exc}); "
f"falling back to the curses UI.", file=sys.stderr) 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: try:
import curses import curses
curses.wrapper(run_curses_gui) curses.wrapper(run_curses_gui)
@@ -838,8 +857,12 @@ def main():
except Exception as exc: except Exception as exc:
print(f"menuconfig: curses UI unavailable ({exc}); " print(f"menuconfig: curses UI unavailable ({exc}); "
f"writing the current configuration non-interactively.", file=sys.stderr) 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() cfg = load_config()
save_config(cfg) save_config(cfg)
print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***") print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
+1 -1
View File
@@ -4,7 +4,7 @@
Emits init/logo_data.c: a 4-bit palette-indexed bitmap plus the ARGB palette 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. 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 Re-run this whenever the source artwork changes; init/logo_data.c is generated
and should not be hand-edited. and should not be hand-edited.
+82 -2
View File
@@ -2,6 +2,7 @@
#include <crypto/crypto.h> #include <crypto/crypto.h>
#include <lib/string.h> #include <lib/string.h>
#include <kernel/printk.h> #include <kernel/printk.h>
#include <drivers/keyboard.h>
static user_account_t user_table[MAX_USERS]; static user_account_t user_table[MAX_USERS];
static size_t user_count = 0; static size_t user_count = 0;
@@ -23,11 +24,17 @@ void auth_init(void) {
user_count = 0; user_count = 0;
current_uid = 0; current_uid = 0;
// Seed default users // Seed default users. SUB/SUB is the primary superuser account -- the way
auth_add_user("root", "root", 0, 0, "/root", "/bin/sh"); // 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("admin", "admin123", 1000, 1000, "/home/admin", "/bin/sh");
auth_add_user("guest", "guest", 1001, 1001, "/home/guest", "/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"); 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; if (index >= MAX_USERS || !user_table[index].in_use) return NULL;
return &user_table[index]; 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
View File
@@ -5,6 +5,11 @@
#include <net/net.h> #include <net/net.h>
#include <net/dns.h> #include <net/dns.h>
#include <net/dhcp.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/e1000.h>
#include <drivers/ata.h> #include <drivers/ata.h>
#include <drivers/tty.h> #include <drivers/tty.h>
@@ -29,8 +34,16 @@
#include <mm/pmm.h> #include <mm/pmm.h>
#include <mm/kmalloc.h> #include <mm/kmalloc.h>
#include <kernel/rust.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/sub_lang.h>
#include <kernel/cpp_kernel.h> #include <kernel/cpp_kernel.h>
#include <kernel/cpp_analytics.h>
#include <userland/snake.h> #include <userland/snake.h>
#include <ipc/shm_posix.h> #include <ipc/shm_posix.h>
#include <net/dns_cache.h> #include <net/dns_cache.h>
@@ -463,6 +476,301 @@ static int applet_crc32(int argc, char** argv) {
return 0; 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) { static int applet_rand(int argc, char** argv) {
int count = (argc >= 2) ? atoi(argv[1]) : 4; int count = (argc >= 2) ? atoi(argv[1]) : 4;
printk("Random Integers: "); printk("Random Integers: ");
@@ -580,16 +888,35 @@ static int applet_rmmod(int argc, char** argv) {
static int applet_ifconfig(int argc, char** argv) { static int applet_ifconfig(int argc, char** argv) {
(void)argc; (void)argv; (void)argc; (void)argv;
net_if_t* nif = net_get_primary_if();
uint8_t mac[6]; uint8_t mac[6];
e1000_get_mac(mac); e1000_get_mac(mac);
printk("eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n"); char ip[16], mask[16], gw[16], dns[16];
printk(" inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.2\n"); 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", printk(" ether %02x:%02x:%02x:%02x:%02x:%02x txqueuelen 1000 (Ethernet)\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
printk(" RX packets 0 bytes 0\n"); printk(" RX packets %llu bytes %llu\n",
printk(" TX packets 0 bytes 0\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(" 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; return 0;
} }
@@ -622,11 +949,20 @@ static int applet_dhclient(int argc, char** argv) {
static int applet_nslookup(int argc, char** argv) { static int applet_nslookup(int argc, char** argv) {
const char* host = (argc >= 2) ? argv[1] : "google.com"; 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); 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]; char ip_buf[16];
ip_to_str(ip, ip_buf); 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", printk("Server:\t\t%s\nAddress:\t%s#53\n\nNon-authoritative answer:\nName:\t%s\nAddress: %s\n",
host, ip_buf); srv, srv, host, ip_buf);
return 0; return 0;
} }
@@ -709,6 +1045,8 @@ static int applet_free(int argc, char** argv) {
total_kb, used_kb, free_kb, free_kb); total_kb, used_kb, free_kb, free_kb);
printk("Heap: %8llu %8llu %8llu\n", printk("Heap: %8llu %8llu %8llu\n",
kmalloc_get_total() / 1024, kmalloc_get_used() / 1024, kmalloc_get_free() / 1024); 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; return 0;
} }
@@ -993,6 +1331,15 @@ static int applet_cppbench(int argc, char** argv) {
return cpp_run_benchmarks(); 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) { static int applet_shm(int argc, char** argv) {
(void)argc; (void)argv; (void)argc; (void)argv;
posix_shm_dump(); posix_shm_dump();
@@ -1137,7 +1484,7 @@ static int applet_aead(int argc, char** argv) {
static int applet_whoami(int argc, char** argv) { static int applet_whoami(int argc, char** argv) {
(void)argc; (void)argv; (void)argc; (void)argv;
const user_account_t* u = auth_get_current_user(); 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; return 0;
} }
@@ -1652,7 +1999,7 @@ static int applet_ssh(int argc, char** argv) {
return 1; return 1;
} }
char username[32] = "root"; char username[32] = "SUB";
char host[64] = "localhost"; char host[64] = "localhost";
uint16_t port = 22; uint16_t port = 22;
@@ -1774,7 +2121,7 @@ static int applet_logread(int argc, char** argv) {
} }
static int applet_su(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) { if (auth_set_current_user(target) == 0) {
printk(KERN_INFO "Switched to user '%s'\n", target); printk(KERN_INFO "Switched to user '%s'\n", target);
return 0; return 0;
@@ -1784,7 +2131,7 @@ static int applet_su(int argc, char** argv) {
} }
static int applet_passwd(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"; const char* new_pass = (argc >= 3) ? argv[2] : "password";
if (auth_change_password(user, new_pass) == 0) { if (auth_change_password(user, new_pass) == 0) {
printk(KERN_INFO "passwd: password updated successfully for '%s'\n", user); 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()) { if (httpd_is_running()) {
printk("tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN (httpd)\n"); 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++) { for (size_t i = 0; i < 8; i++) {
const ssh_session_t* s = sshd_get_session(i); const ssh_session_t* s = sshd_get_session(i);
if (s && s->in_use) { 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); 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; return 0;
} }
@@ -2168,6 +2914,16 @@ static const lazybox_applet_t applets[] = {
{"md5sum", applet_md5sum, "md5sum <file|text>", "Compute MD5 hash", "Crypto"}, {"md5sum", applet_md5sum, "md5sum <file|text>", "Compute MD5 hash", "Crypto"},
{"sha256sum", applet_sha256sum, "sha256sum <file|text>", "Compute SHA-256 hash", "Crypto"}, {"sha256sum", applet_sha256sum, "sha256sum <file|text>", "Compute SHA-256 hash", "Crypto"},
{"crc32", applet_crc32, "crc32 <text>", "Calculate CRC32 checksum", "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"}, {"rand", applet_rand, "rand [count]", "Generate random numbers", "Crypto"},
{"certcheck", applet_certcheck, "certcheck", "View X.509 kernel keyring", "Security"}, {"certcheck", applet_certcheck, "certcheck", "View X.509 kernel keyring", "Security"},
{"capsh", applet_capsh, "capsh", "View process capabilities", "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"}, {"nslookup", applet_nslookup, "nslookup <host>", "Query DNS name servers", "Network"},
{"iptables", applet_iptables, "iptables", "Firewall packet filter rules","Network"}, {"iptables", applet_iptables, "iptables", "Firewall packet filter rules","Network"},
{"netstat", applet_netstat, "netstat", "Network port connections", "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 // Storage & Devices
{"lsblk", applet_lsblk, "lsblk", "List block storage devices", "Storage"}, {"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"}, {"cpptest", applet_cpptest, "cpptest", "C++ polymorphism & new/del", "System"},
{"cppdevs", applet_cppdevs, "cppdevs", "C++ OOP device tree", "System"}, {"cppdevs", applet_cppdevs, "cppdevs", "C++ OOP device tree", "System"},
{"cppbench", applet_cppbench, "cppbench", "C++ OOP benchmark suite", "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"}, {"shm", applet_shm, "shm", "POSIX /dev/shm shared memory", "System"},
{"dnscache", applet_dnscache, "dnscache [-f]", "DNS resolver cache & metrics", "Network"}, {"dnscache", applet_dnscache, "dnscache [-f]", "DNS resolver cache & metrics", "Network"},
{"beep", applet_beep, "beep [freq/jingle] [ms]", "PC speaker & HDA tone synth", "Sound"}, {"beep", applet_beep, "beep [freq/jingle] [ms]", "PC speaker & HDA tone synth", "Sound"},