<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Blog ALDO TECH</title>
    <link>https://aldo.tech/cs/blog/</link>
    <atom:link href="https://aldo.tech/cs/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Poznámky z praxe: veřejná doprava, odbavení cestujících, hardware a software, které musí fungovat na cestě.</description>
    <language>cs</language>
    <item>
      <title>urMQTT po VPS: SQLite v uživatelském režimu, srovnání s Mosquittem, konfigurace jako data a builder v prohlížeči</title>
      <link>https://aldo.tech/cs/blog/urmqtt-config-builder-plaintext-mosquitto/</link>
      <guid isPermaLink="true">https://aldo.tech/cs/blog/urmqtt-config-builder-plaintext-mosquitto/</guid>
      <pubDate>Sun, 01 Nov 2026 08:00:00 +0000</pubDate>
      <description>Co se stalo po druhém textu o urMQTT: SQLite izolovaný mimo jádro, měření proti Mosquittu na stejném VPS (včetně toho, co nevyšlo v náš prospěch), nalezené příčiny pomalého jediného toku, konfigurace jako data s webovým builderem ISO a emulátorem v prohlížeči, nepovinný prostý MQTT a co ještě není dokázáno.</description>
      <content:encoded><![CDATA[<p>Druhý text, <a href="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/">urMQTT na skutečném VPS</a>, skončil seznamem „Co následuje": H6 (SQLite mimo jádro), H7 (bezpečnost času), X11 (srovnání s Mosquittem na stejném VPS) a první vydání. Tento text ten seznam sleduje ve stejném pořadí a pak přidává dvě věci, které na seznamu nebyly, protože vznikly cestou: konfiguraci jako data s webovým builderem ISO a nepovinný prostý MQTT bez TLS. Jako dříve: co jsme plánovali, co jsme postavili, co nás překvapilo a co zůstává otevřené. V tomto textu je i několik výsledků, které pro urMQTT nevycházejí dobře; nechali jsme je tu, protože jsou součástí pravdy.</p>
<p>Poznámka předem zůstává stejná: projekt je pre-alfa, první vydání je jen pro VPS s KVM a čísla měří kód, testy a konkrétní pokusy, ne zralost v produkci.</p>
<h2 id="jak-to-vypadalo-v-case">Jak to vypadalo v čase</h2>
<p>Od minulého textu (31. 10. ve 23:00) do dneška (1. 11. ve 20:41) přibylo v repozitáři 59 nových commitů, celkem 178. Všechny vznikly během jednoho kalendářního dne, takže platí stejné upozornění jako dřív: projekt vzniká s Claude Code (commity nesou <code>Co-Authored-By: Claude</code>), plán, kód, testy a ADR se píšou v krátkých uzavřených smyčkách a čísla níže vypovídají o množství kódu a ověření, ne o tom, kolik lidské práce v nich je.</p>
<figure><img loading="lazy" alt="Růst od minulého textu: řádků Rustu od 89 790 do 112 408, funkcí #[test] od 739 do 926, ADR od 104 do 126 a commitů od 119 do 178. Každý řádek má vlastní měřítko." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/growth-since.svg" /><figcaption>Růst od minulého textu: řádků Rustu od 89 790 do 112 408, funkcí #[test] od 739 do 926, ADR od 104 do 126 a commitů od 119 do 178. Každý řádek má vlastní měřítko.</figcaption></figure>
<p>Čísla jsou spočítána stejnou metodou jako v minulém textu: všechny soubory <code>.rs</code> mimo <code>vendor/</code> a <code>fuzz/</code>, funkce <code>#[test]</code>, nadpisy <code>## ADR-</code> v <code>docs/DECISIONS.md</code>.</p>
<table>
<thead>
<tr>
<th>Okamžik</th>
<th>Commit</th>
<th>Čas</th>
<th style="text-align: right;">Řádků Rustu</th>
<th style="text-align: right;">Testů</th>
<th style="text-align: right;">ADR</th>
</tr>
</thead>
<tbody>
<tr>
<td>minulý text</td>
<td>794f607</td>
<td>31.10. 23:00</td>
<td style="text-align: right;">89 790</td>
<td style="text-align: right;">739</td>
<td style="text-align: right;">104</td>
</tr>
<tr>
<td>H6 hotovo</td>
<td>a0423cc</td>
<td>1.11. 03:59</td>
<td style="text-align: right;">95 078</td>
<td style="text-align: right;">754</td>
<td style="text-align: right;">107</td>
</tr>
<tr>
<td>X11</td>
<td>a29f49b</td>
<td>1.11. 07:56</td>
<td style="text-align: right;">95 579</td>
<td style="text-align: right;">754</td>
<td style="text-align: right;">108</td>
</tr>
<tr>
<td>webový builder a zrcadlo vydání</td>
<td>582783f</td>
<td>1.11. 16:49</td>
<td style="text-align: right;">105 645</td>
<td style="text-align: right;">848</td>
<td style="text-align: right;">122</td>
</tr>
<tr>
<td>prostý MQTT, dokumentace</td>
<td>8f959f1</td>
<td>1.11. 19:05</td>
<td style="text-align: right;">112 408</td>
<td style="text-align: right;">926</td>
<td style="text-align: right;">125</td>
</tr>
<tr>
<td>dnes</td>
<td>d21e3ec</td>
<td>1.11. 20:41</td>
<td style="text-align: right;">112 408</td>
<td style="text-align: right;">926</td>
<td style="text-align: right;">126</td>
</tr>
</tbody>
</table>
<p>Pracovalo se paralelně na více větvích, takže pořadí commitů není přesně pořadím práce. ADR jsou číslována do 232, ale číslování má mezery (část byla při slučování větví přejmenována); počítáme nadpisy, ne nejvyšší číslo. Vydání: v0.2.0 (1. 11., 18:45 UTC) a v0.2.1 (1. 11., 20:22 UTC), obě označená jako pre-release.</p>
<h2 id="h6-sqlite-v-uzivatelskem-rezimu">H6: SQLite v uživatelském režimu</h2>
<p><strong>Co jsme plánovali.</strong> SQLite je 254 268 řádků C, které parsují databázi, WAL a žurnál z disku, a disk může být vyroben nebo upraven. Dosud běžel v jádře, s právy jádra. <strong>Co jsme postavili</strong> (ADR-210 až ADR-212): SQLite, jeho části libc a halda byly přesunuty do samostatného programu, <code>dbworker</code>, který běží na EL0 (aarch64), respektive v ring 3 (x86_64), ve vlastním adresním prostoru. Jádro už SQLite nelinkuje. Worker s jádrem komunikuje přes sdílené okno paměti a dvanáct systémových volání a každá vrácená délka a offset se před použitím kontroluje. Chyba ve workeru (nepovolený přístup, nedefinovaná instrukce, překročený časový rozpočet 30 s) ukončí volání, worker se znovu spustí a databáze se otevře z WAL jako při startu; po 5 restartech za minutu se úložiště prohlásí za vadné a nespouští se donekonečna.</p>
<p>Cena, změřená na aarch64 pod HVF, stejná VM, jádro se vestavěným SQLite oproti jádru s workerem, šest střídavých spuštění od každého:</p>
<table>
<thead>
<tr>
<th>Měření</th>
<th>Před (v jádře)</th>
<th>Po (worker)</th>
<th>Rozdíl</th>
</tr>
</thead>
<tbody>
<tr>
<td>vstup do uživatelského režimu a návrat</td>
<td>-</td>
<td>1,1 us na kolo</td>
<td></td>
</tr>
<tr>
<td><code>db-bench</code>, 5000 vložení v 50 transakcích, nejlepší / medián</td>
<td>61,9k / 58,4k op/s</td>
<td>54,7k / 53,3k op/s</td>
<td>-11,6% / -8,7%</td>
</tr>
<tr>
<td>jedno vložení na transakci, nejlepší / medián</td>
<td>2319 / 2148 op/s</td>
<td>2307 / 2126 op/s</td>
<td>-0,5% / -1%</td>
</tr>
<tr>
<td>celá záloha jedním voláním (23 MiB)</td>
<td>514 ms</td>
<td>635 ms</td>
<td>+23%</td>
</tr>
</tbody>
</table>
<p>Zápisy na disk (1572) a flushe (319) jsou identické, jak má být. Izolováno není tohle: <strong>jádro stále věří tomu, co worker vrátí</strong>, po kontrole tvaru. Kompromitovaný worker může lhát (například vrátit prázdný seznam odvolaných certifikátů a tím je „odvolat z odvolání"), ale nemůže číst paměť jádra, klíče TLS ani klíče ticketů. KPTI není, takže úniky typu Spectre/Meltdown nejsou zabráněny a nic o nich netvrdíme; klíč PAC sdílejí EL0 a EL1. Na x86_64 jsme cenu přepínání na KVM neměřili (pod TCG by číslo bylo nesmyslné) a samotné fuzzování workeru a protokolu není hotové. Sada testů úložiště (79 scénářů, každý točí SQLite ve workeru) prochází na aarch64 HVF a x86_64 TCG, na aarch64 TCG 76 (jeden se přeskakuje, protože TCG nemá MSI-X).</p>
<p><strong>H7 je uzavřen, ale ne tak, jak jsme oznámili.</strong> Dolní mez času (ADR-050) a SNTP s konsenzem (T1) pokrývají rozsah H7; NTS (RFC 8915) zůstává mimo rozsah a je zapsán jako zbývající riziko, ne jako něco, co funguje. Nepovažujeme bezpečnost času za „vyřešenou", ale za hotovou v té části, kterou lze dokázat.</p>
<p><strong>Nová síťová volba.</strong> Contabo nedává DHCP vlastnímu ISO, takže VPS dosud potřeboval jiný obraz než laboratorní VM. Nyní <code>network.mode = "auto"</code> (ADR-214) nejprve zeptá DHCP, a pokud pronájem nepřijde v zadané lhůtě (<code>dhcp_timeout_s</code>, výchozí 15 s) nebo ho ztratí, použije zadanou statickou adresu. Vyzkoušeno ručně na QEMU (s DHCP použije pronájem, bez sítě se po 5 s vrátí na statickou adresu); smoke scénář pro to ještě není.</p>
<h2 id="x11-srovnani-s-mosquittem-na-stejnem-vps">X11: srovnání s Mosquittem na stejném VPS</h2>
<p>Stejný druh stroje jako v minulém textu (Contabo, 4 vCPU AMD EPYC, 8 GiB), zátěž z linuxového hostitele vzdáleného 15 ms, oboustranný TLS 1.3 se stejnými certifikáty na obou stranách, Mosquitto 2.0.21 na Debianu 13 bez trvalého ukládání. Metoda a rozhodnutí jsou v ADR-213, surová data v <code>docs/perf-results/</code>. Medián ze tří spuštění.</p>
<figure><img loading="lazy" alt="Úplné handshaky za sekundu (TLS 1.3 + klientský certifikát + CONNECT, bez obnovení relace) při 32, 64 a 128 klientech: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. Jde o dolní meze: generátor zátěže byl blízko vlastního limitu." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/mosquitto.svg" /><figcaption>Úplné handshaky za sekundu (TLS 1.3 + klientský certifikát + CONNECT, bez obnovení relace) při 32, 64 a 128 klientech: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. Jde o dolní meze: generátor zátěže byl blízko vlastního limitu.</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>urMQTT</th>
<th>Mosquitto 2.0.21</th>
</tr>
</thead>
<tbody>
<tr>
<td>handshaky/s, 32 / 64 / 128 klientů (profil bench)</td>
<td>169 / 278 / 413</td>
<td>82 / 140 / 184</td>
</tr>
<tr>
<td>totéž, s limity, jaké se dodávají</td>
<td>185 / 262 / 333 (při 128: 202 až 286 chyb, resety handshaku)</td>
<td>-</td>
</tr>
<tr>
<td>paměť na nečinné spojení (20 000 spojení)</td>
<td>0,50 KB haldy jádra (0,66 KB při 5000)</td>
<td>26,8 KB růstu procesu</td>
</tr>
<tr>
<td>latence p50, QoS 0 / QoS 1, 800 zpráv/s, 64 B</td>
<td>14,0 / 14,8 ms</td>
<td>21,4 / 39,5 ms</td>
</tr>
<tr>
<td>QoS 1, 4800 zpráv/s, doručeno/s (p50)</td>
<td>4209 (77 ms)</td>
<td>4235 (197 ms)</td>
</tr>
<tr>
<td>100 odběratelů, 50 zpráv/s: p50</td>
<td>16,6 ms</td>
<td>99 ms</td>
</tr>
<tr>
<td>jeden tok, QoS 0, 64 B, publikováno/s</td>
<td>2962 (0 až 7733)</td>
<td>22 749</td>
</tr>
</tbody>
</table>
<p>Co to znamená a co ne. Handshaky jsou <strong>dolní meze pro oba servery</strong>: generátor zátěže je sdílená VM s 60 až 70% ukradeného procesorového času a při 128 vláknech byl blízko vlastního limitu, zatímco pracovní jádra urMQTT byla vytížena jen z 12 až 13%. Poměr při 128 klientech (asi 2,2násobek) je nejvýznamnější; při 32 a 64 většinou měří čekání klientů. Čísla paměti nepočítají totéž (halda jádra oproti RSS procesu), takže „asi 50krát méně" je řádová hodnota, ne přesný poměr. Latence 14 ms je jedno kolo sítě (asi 15 ms), tedy „žádný měřitelný čas brokeru"; ocasy (p99 asi 100 až 200 ms) jsou u obou brokerů ovládány sdílenými hostiteli. Rozdíly pod asi 20% netvrdíme.</p>
<p><strong>Co nevyšlo v náš prospěch</strong> a zůstalo v tabulkách: jediný tok TCP přes urMQTT nesl 2 až 8krát méně než přes Mosquitto, s výkyvy od nuly do 7,7 tisíce zpráv za sekundu mezi spuštěními, a 20ms watchdog handleru <strong>přerušoval živá spojení</strong>, když hypervizor zastavil vCPU (v produkčním nastavení <code>action = "drop"</code>, 6 až 8 spojení na spuštění při zahlcení QoS 0). Obojí je popsáno v další části, protože to mělo příčiny v našem kódu.</p>
<h2 id="proc-byl-jediny-tok-pomaly-priciny-ne-vymluvy">Proč byl jediný tok pomalý: příčiny, ne výmluvy</h2>
<p>Nejsnazší by bylo říct „sdílený VPS". Neřekli jsme to, ale s tímtéž VPS a stejnou metodou jsme hledali příčiny. Nalezlo se jich pět, čtyři v datové cestě a jedna ve watchdogu (ADR-221 až ADR-224):</p>
<table>
<thead>
<tr>
<th>Příznak v X11</th>
<th>Příčina v urMQTT</th>
<th>Změna</th>
</tr>
</thead>
<tbody>
<tr>
<td>jeden tok 1,9k zpráv/s, často <strong>0 doručeno</strong></td>
<td><strong>Ztracené probuzení:</strong> smyčka výstupu vracela seznam připravených spojení přes právě vypsaný; odběratel, jehož výstupní buffer se jednou zaplnil, už nikdy nebyl obsloužen, dokud ho nevzbudil PINGRESP</td>
<td>opraveno (ADR-223)</td>
</tr>
<tr>
<td>několik procent ztrát mění 48k na 14k zpráv/s</td>
<td><strong>každý segment po ztraceném byl zahozen</strong> (příjem jen v pořadí), takže obnova stála kolo sítě na segment</td>
<td>fronta segmentů mimo pořadí, SACK (ADR-222)</td>
</tr>
<tr>
<td>strop 4,4 MB/s při 15 ms</td>
<td>16bitové okno, odesílací fronta 64 KiB a pomalý start, který počítal ACK, ne bajty</td>
<td>škálování okna, časová razítka, počítání bajtů (ADR-222)</td>
</tr>
<tr>
<td>každé kolo toku, který se vejde, čeká 0 až 10 ms</td>
<td><strong>dispečer spal do dalšího taktu po 10 ms</strong> (virtio-net nemá přerušení pro příjem)</td>
<td>dotazování NIC každých 200 us, dokud existují spojení (ADR-224)</td>
</tr>
<tr>
<td>1 až 8 přerušených spojení na spuštění</td>
<td><strong>watchdog započítával handleru uplynulý čas</strong>, a VPS zastavuje vCPU často a dlouho</td>
<td>rozpočet je čas, kdy jádro skutečně běželo (ADR-221)</td>
</tr>
</tbody>
</table>
<p>Zastavení jsou skutečná: na tomto VPS měla jádra 0 až 2 medián 36 až 80 zpožděných taktů za 5 sekund a nejhorší zastavení v okně mělo medián asi 70 ms a až 0,9 s (jádro 3, databáze a žurnál: až 1,55 s), zatímco hostitel neukazuje ukradený čas (<code>host steal 0 us</code>). Proto watchdog potřeboval jiné hodiny, ne větší rozpočet.</p>
<figure><img loading="lazy" alt="Před a po, na stejném VPS se stejnou metodou, medián tří až šesti spuštění: jeden tok QoS 0, 64 B: publikováno 1,9k, 23,7k (Mosquitto 29,3k), doručeno 0 (medián), 12,6k (29,2k). 16 párů, zahlcení, 64 B: publikováno 24,7k, 43,2k (31,4k), doručeno 20,0k, 37,4k (30,7k)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/before-after.svg" /><figcaption>Před a po, na stejném VPS se stejnou metodou, medián tří až šesti spuštění: jeden tok QoS 0, 64 B: publikováno 1,9k, 23,7k (Mosquitto 29,3k), doručeno 0 (medián), 12,6k (29,2k). 16 párů, zahlcení, 64 B: publikováno 24,7k, 43,2k (31,4k), doručeno 20,0k, 37,4k (30,7k).</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>kód X11 (7f9517e)</th>
<th>nyní (86a71ff)</th>
<th>Mosquitto</th>
</tr>
</thead>
<tbody>
<tr>
<td>jeden tok, QoS 0, 64 B, publikováno / doručeno za s</td>
<td>1,9k / 0</td>
<td>23,7k / 12,6k</td>
<td>29,3k / 29,2k</td>
</tr>
<tr>
<td>jeden tok, QoS 0, 1 KiB</td>
<td>0 / 0</td>
<td>3,9k / 2,3k</td>
<td>5,4k / 5,4k</td>
</tr>
<tr>
<td>16 párů, zahlcení, QoS 0, 64 B</td>
<td>24,7k / 20,0k</td>
<td>43,2k / 37,4k</td>
<td>31,4k / 30,7k</td>
</tr>
<tr>
<td>16 párů, zahlcení, QoS 0, 1 KiB</td>
<td>3,7k / 2,9k</td>
<td>5,0k / 4,5k</td>
<td>5,9k / 5,7k</td>
</tr>
<tr>
<td>ztracená spojení na spuštění (16 párů, 64 B)</td>
<td>13 (7 až 16)</td>
<td>0 (0 až 0)</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>Watchdog: 148 přerušených spojení ve 30 zatížených spuštěních před, 0 ve 24 spuštěních po; na lokálním zařízení, které zpožďuje a ztrácí rámce, má při ztrátě 0,1% jeden tok 64 B s 14,3k zpráv/s (jádro X11) nyní 41,0k a při 0,5% z 5,0k na 17,7k.</p>
<p><strong>Co tím neříkáme.</strong> urMQTT <strong>stále není rychlejší než Mosquitto na jediném toku</strong>: při 64 B publikuje asi 80% jeho rychlosti a doručuje asi 40%, protože zahazuje QoS 0, když je tok odběratele pomalejší než vydavatelův (Mosquitto bez limitu řadí do fronty). Při 1 KiB v 16párovém zahlcení je Mosquitto vpředu asi o 15%, což je v rámci šumu. Generátor byl znovu sdílená VM s 58 až 77% ukradeného času. Druhé kolo (zamykání haldy mimo datovou cestu, SACK na straně odesílání a další) je na neintegrované větvi <code>perf-single-flow-wip</code>; jeho čísla zatím nejsou tvrzení a neuvádíme je zde.</p>
<h2 id="slib-ktery-jsme-nedali-ale-splnili-konfigurace-je-data">Slib, který jsme nedali, ale splnili: konfigurace je data</h2>
<p>Dosud byla každá konfigurace novým sestavením: klíče a limity byly zabudované v obrazu. C1 to obrací (ADR-215 až ADR-219): jádro se staví jednou a konfigurace jsou data.</p>
<ul>
<li><strong>Blob <code>urmqtt-cfg</code></strong> (C1a): verzovaný, kanonický zápis (pole podle rostoucích značek, otisk SHA-256, nepovinný oddělený podpis Ed25519), 70 polí s výchozími hodnotami a mezemi, dekodér, který odmítá vše neplatné.</li>
<li><strong>Jádro ho čte jako modul Limine</strong> (C1b), před čímkoli jiným, na aarch64 i x86_64. Existuje <strong>jedno vydané jádro pro každé <code>ENV</code> kromě <code>dev</code></strong> (ELF aarch64 pro <code>release</code>, <code>prod</code> a <code>bench</code> mají stejné SHA-256; ověřeno jen na aarch64). Vydané jádro odmítne vývojovou konfiguraci a bez konfigurace napíše „refusing to serve" a nic neobsluhuje.</li>
<li><strong>Nainstalovaný disk nese konfiguraci</strong> a aktualizace ji zachovává (C1c).</li>
<li><strong><code>urmqtt-config</code> a šablona ISO</strong> (C1d): <code>pack</code>, <code>show</code>, <code>verify</code>, a <code>pack --iso</code> nahradí obě kopie místa pro konfiguraci a 24 součtů modulů v hotovém ISO. Výsledek je bajt po bajtu stejný jako ISO, které sestaví <code>mkiso.sh --config</code>. Šablona vydání v0.2.0 má 63 111 168 bajtů.</li>
<li><strong>Prohlášení o důvěře</strong> (ADR-219): konfigurovaný obraz při startu vypíše otisk konfigurace (<code>config: fingerprint ...</code>), podpis je nepovinný a <strong>ve výchozím stavu vypnutý</strong>. Upravené ISO nemá oficiální součet; otisk je způsob, jak vidět, co v něm je.</li>
</ul>
<h2 id="builder-na-aldotech">Builder na aldo.tech</h2>
<p>Totéž, co dělá <code>urmqtt-config pack --iso</code>, dělá i webová stránka, týmž kódem: knihovna nástroje přeložená do WebAssembly (1,06 MB, bez importů a bez bloku <code>unsafe</code>). Stránka je na <a href="https://aldo.tech/cs/produkty/urmqtt-builder/">aldo.tech/cs/produkty/urmqtt-builder/</a> a existuje ve všech 15 jazycích webu (například <a href="https://aldo.tech/proizvodi/urmqtt-builder/">hr</a>).</p>
<ul>
<li><strong>Nic neopouští prohlížeč.</strong> Certifikáty, soukromé klíče a hotové ISO zůstávají v počítači; stránka stahuje jen soubory vydání z tohoto webu, žádné sledování ani skripty třetích stran. V živém testu žádný požadavek nenesl klíčový materiál.</li>
<li><strong>Zrcadlo vydání</strong> (ADR-227): hodinu po hodině (časovač systemd, nízká priorita) kopíruje vydání z GitHubu, každé před zveřejněním ověří a stránka v prohlížeči ověří podpis <code>SHA256SUMS</code> klíčem zabudovaným ve stránce, a teprve pak zapíše konfiguraci do šablony. Neúspěšné ověření stahování zablokuje.</li>
<li><strong>Jednoduše ve výchozím stavu, pokročile podle potřeby</strong> (ADR-232): první obrazovka má tři kroky (kde poběží, vaše certifikáty, název obrazu). Jedno pole pro přetažení souborů roztřídí certifikát, klíč a CA podle obsahu. Pokročilé zobrazení otevře každé z 70 polí, vyhledávání, import a export TOML; obě zobrazení upravují stejný stav. Šablona (63 MB) se stahuje, zatímco uživatel vyplňuje formulář. Při živé zkoušce, s zkušebním zrcadlem a zkušebním klíčem, trvalo sestavení ISO v prohlížeči 5 až 18 s.</li>
</ul>
<figure><img loading="lazy" alt="Jednoduché zobrazení builderu: tři kroky, „Kde poběží?&quot;, „Vaše certifikáty&quot; a „Pojmenujte svůj obraz&quot;, s jednou další akcí v postranním panelu." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-simple.png" /><figcaption>Jednoduché zobrazení builderu: tři kroky, „Kde poběží?&quot;, „Vaše certifikáty&quot; a „Pojmenujte svůj obraz&quot;, s jednou další akcí v postranním panelu.</figcaption></figure>
<figure><img loading="lazy" alt="Pokročilé zobrazení, zachycené na zkušebním zrcadle se zkušebním klíčem (datum vydání a podpisový klíč nejsou skutečné): vydání s ověřeným podpisem, šablony, každé pole konfigurace." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-advanced.png" /><figcaption>Pokročilé zobrazení, zachycené na zkušebním zrcadle se zkušebním klíčem (datum vydání a podpisový klíč nejsou skutečné): vydání s ověřeným podpisem, šablony, každé pole konfigurace.</figcaption></figure>
<p><strong>Emulátor, nepovinně.</strong> Vedle „Stáhnout ISO" stojí „Vyzkoušet v emulátoru": QEMU přeložené do WebAssembly spustí právě sestavený obraz v kartě prohlížeče. Nic z něj se před kliknutím nestahuje (asi 3 MB poprvé). Emulátor je jen BIOS, <strong>bez sítě</strong> (aby provoz uživatele neprošel přes nějakého prostředníka a nenarušil „nic neopouští prohlížeč"), potřebuje asi 1,5 GB paměti a počítačový prohlížeč (testováno v Chrome; Firefox a Safari ne). V něm urMQTT dosáhne <code>URMQTT READY</code> za asi 8 s (s přeskočeným odpočtem) a vypíše stejný otisk konfigurace jako stránka, což je důkaz, že se spustila právě ta konfigurace. Start je asi 2krát pomalejší než původní QEMU na stejném Macu (0,82 s oproti 1,9 s) a kryptografie asi 58krát pomalejší, protože hardwarové instrukce jsou emulované. Proto <strong>z emulátoru nevyvozujeme žádná tvrzení o rychlosti</strong> a stránka to říká před spuštěním. Při sestavení bylo třeba záplatovat čtyři bajty v <code>.wasm</code> kvůli chybě v překladu instrukce POPCNT v původním emulátoru (zatím jsme ji nenahlásili); samotný emulátor je pod GPL, s nabídkou zdrojového kódu.</p>
<figure><img loading="lazy" alt="Diagnostická položka v emulátoru naživo na aldo.tech: jádro vypsalo stejný otisk konfigurace jako stránka a obrazovka ukazuje diagnostiku (v emulátoru končí asi za 20 s; její čísla popisují emulátor, ne urMQTT)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/emulator-diagnostic.png" /><figcaption>Diagnostická položka v emulátoru naživo na aldo.tech: jádro vypsalo stejný otisk konfigurace jako stránka a obrazovka ukazuje diagnostiku (v emulátoru končí asi za 20 s; její čísla popisují emulátor, ne urMQTT).</figcaption></figure>
<h2 id="prosty-mqtt-jen-pro-site-kterym-verite">Prostý MQTT, jen pro sítě, kterým věříte</h2>
<p>Prostý MQTT (TCP 1883, bez TLS) je v IoT velmi běžný: je výchozí v Mosquittu, ve většině firmwarů pro Arduino a ESP, v Tasmotě, Zigbee2MQTT a Home Assistantu a používá se i uvnitř segmentovaných průmyslových sítí, za VPN a na soukromých LAN, i na zařízeních bez hodin (certifikáty vyžadují datum), bez dost flashe nebo procesoru pro TLS. Přes nedůvěryhodnou síť není přijatelný: kdo je na cestě, čte a mění provoz. Proto je P1 (ADR-229 až ADR-231) výslovná, omezená volba, <strong>ve výchozím stavu vypnutá</strong>, a port TLS zůstává; TLS se tím nevypíná, prostý port přichází vedle něj.</p>
<ul>
<li><strong>Seznam povolených sítí je povinný</strong> (<code>allow_from</code>, CIDR) a kontroluje se při SYN: mimo seznam není SYN-ACK ani stav. Celý internet (<code>0.0.0.0/0</code>) vyžaduje samostatnou výslovnou volbu.</li>
<li><strong>Tři způsoby přihlášení:</strong> heslo (výchozí, když je zapnuto; PBKDF2-HMAC-SHA-256, sůl, 10 000 iterací, porovnání v konstantním čase, zamykání podle adresy), anonymně (jedna nakonfigurovaná identita s vlastním ACL) a podle adresy (<code>ip</code>). Builder heslo hashuje v prohlížeči a v paměti drží jen součet.</li>
<li><strong>Méně práv než mTLS:</strong> prostý klient nikdy nemá servisní, operátorská ani řídicí práva, nemá <code>$SYS</code> ani konzoli, má vlastní relace (<code>$plain/&lt;identita&gt;/&lt;id klienta&gt;</code>, jen čisté), vlastní limity a registr povolených ho dál zavazuje.</li>
<li><strong>Co se ztrácí, je napsáno v <code>SECURITY.md</code>:</strong> důvěrnost, integrita, autentičnost brokera a opakování zachyceného přihlášení. Konfigurací se to opravit nedá; tak to je, když není TLS.</li>
</ul>
<p>Změřeno na notebooku (Apple silicon, HVF, uživatelská síť QEMU), 16 párů, 64 B, tři spuštění po 10 s: při 8000 zprávách za sekundu doručují cesta TLS i prostá všechny zprávy se stejnou latencí (p50 0,17 ms oproti 0,13 až 0,18 ms), nová spojení za sekundu: TLS asi 2600 až 2750, anonymní prostá asi 4900 až 7800, a <strong>přihlášení heslem je drahá část</strong>: jeden PBKDF2 stojí asi 2,5 ms, takže počet nových přihlášení je omezen na asi 200 na datové jádro za sekundu (změřeno 308 až 342 na dvou jádrech). Zahlcení na tomto stroji není stabilní nástroj (20k až 440k doručených za sekundu mezi spuštěními) a <strong>nevyvozujeme z něj závěr</strong>. Srovnání prosté cesty s Mosquittem na VPS ještě nebylo provedeno. Testy: skupina smoke <code>plaintext</code> 7 ze 7 na aarch64 HVF i na x86_64 TCG, nový fuzz cíl <code>plain_input</code> (2,3 milionu spuštění bez nálezů).</p>
<h2 id="chyby-ktere-nasel-provoz-nazivo">Chyby, které našel provoz naživo</h2>
<p>Dvě věci neodhalily testy, ale první zapnutí na skutečném webu:</p>
<ul>
<li>Stránka hledala soubory vydání <strong>o jeden adresář výš</strong>, takže každé ověření podpisu odpovědělo 404 (oprava 13af301). Test používal zkušební zrcadlo na jiné cestě.</li>
<li><strong>Klávesnice emulátoru nikdy nedorazila k hostu</strong>: příčinou nebyl špatný překlad kláves, ale to, že dialog klávesy vůbec neposlouchal (oprava v ADR-232, s položkou pro start bez kláves).</li>
</ul>
<h2 id="pouceni">Poučení</h2>
<ul>
<li><strong>Hledejte příčinu, neviňte prostředí.</strong> Většina slabin jediného toku a všechna přerušená spojení byly naše; hypervizor je skutečný, ale bylo třeba ho měřit, ne předpokládat.</li>
<li><strong>Srovnání s jiným produktem musí zahrnovat i ztráty.</strong> Tabulka, v níž jsou jen naše výhry, by nebyla srovnáním.</li>
<li><strong>Měřicí přístroj má meze.</strong> Sdílený generátor s 60 až 70% ukradeného času mění, co čísla znamenají; proto píšeme „dolní mez" a netvrdíme rozdíly pod 20%.</li>
<li><strong>Izolace není důkaz.</strong> SQLite v EL0 zmenšuje, co může způsobit chyba v něm, ne to, co může lhát.</li>
<li><strong>Skutečná instalace najde to, co testy nehledají</strong>: cestu, která je špatně složena, a klávesy, které nikdo neposlouchá.</li>
</ul>
<h2 id="co-jeste-neni-dokazano">Co ještě není dokázáno</h2>
<ul>
<li>Podpisový klíč vydání je zatím zkušební klíč; v0.2.0 a v0.2.1 jsou pre-release a obrazy k nim jsou vývojové se zkušebními certifikáty (kromě šablony, která je bez konfigurace).</li>
<li>13 překladů stránky builderu (a tohoto textu) napsala AI a ověřily je testy a vzorky, ne rodilí mluvčí.</li>
<li>Srovnání s Mosquittem je jedna relace, jedna cesta a jeden sdílený generátor, na sdíleném VPS; rozdíly pod 20% nejsou tvrzení.</li>
<li>Prostá cesta nebyla měřena na VPS ani proti Mosquittu.</li>
<li>Lighthouse na novém jednoduchém zobrazení není potvrzen (staré zobrazení: přístupnost 95 nebo více).</li>
<li>Bare metal, cena workeru na x86_64 na KVM, Firefox a Safari pro emulátor: neměřeno.</li>
</ul>
<h2 id="co-nasleduje">Co následuje</h2>
<ul>
<li><strong>D1: veřejný demo broker.</strong> Emulátor z prohlížeče nemůžeme udělat veřejným: žije v kartě jednoho návštěvníka, je 20 až 100krát pomalejší než KVM, nemá příchozí síť a zmizí se zavřením karty; přesměrování internetu do cizího prohlížeče by bylo i zneužitím. Možný je skutečný demo broker: urMQTT na VPS za jménem na aldo.tech, s publikovanou zkušební CA, přísným ACL a malými limity. Zatím je to jen nápad, ne stavba, a vyžaduje výslovný souhlas s veřejným vystavením.</li>
<li><strong>Druhé kolo pro jediný tok</strong> (větev <code>perf-single-flow-wip</code>): teprve až bude změřeno na VPS a sloučeno.</li>
<li><strong>Prostá cesta proti Mosquittu na VPS</strong>, přihlášení a odvolání účtu pro prostý MQTT přes podepsaný řídicí kanál.</li>
<li><strong>Po vydání:</strong> bare metal (X12 až X16), IOMMU a Secure Boot, šifrování disku.</li>
</ul>
<p>Kód je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a> a <code>docs/PROGRESS.md</code> zůstává jediným zdrojem pravdy o tom, co skutečně funguje, <code>docs/PERFORMANCE.md</code> o tom, co bylo změřeno. Pre-alfa, jako dříve; hlášení chyb a bezpečnostní hlášení podle pokynů v <code>SECURITY.md</code> jsou vítána.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT na skutečném VPS: ISO, handshake za 0,24 ms, vlastní disk a zpevněné jádro</title>
      <link>https://aldo.tech/cs/blog/urmqtt-vps-handshake-disk-hardening/</link>
      <guid isPermaLink="true">https://aldo.tech/cs/blog/urmqtt-vps-handshake-disk-hardening/</guid>
      <pubDate>Sat, 31 Oct 2026 08:00:00 +0000</pubDate>
      <description>Co se stalo se třemi sliby z prvního textu o urMQTT (jedno ISO, surový disk bez souborového systému, prokazatelná bezpečnost) po prvních čtyřech dnech vývoje, s měřeními, tabulkami a seznamem toho, co ještě není hotové.</description>
      <content:encoded><![CDATA[<p>V <a href="https://aldo.tech/blog/urmqtt-unikernel-mqtt-broker/">prvním textu o urMQTT</a> jsme popsali unikernel, který <em>je</em> MQTT broker, až po milník M17, a skončili jsme třemi sliby pod nadpisem „Co následuje": jedno ISO, které nabootuje na více platformách, surový disk bez souborového systému a bezpečnost, kterou lze dokázat, a ne jen tvrdit. Nic z toho tehdy nebylo postavené. Tento text sleduje, co se stalo s každým slibem, ve stejném pořadí jako dříve: co jsme plánovali, co jsme postavili, co nás překvapilo a co zůstává otevřené. Cestou ukážeme i to, jak dlouho to trvalo, protože i to je součást příběhu.</p>
<p>Jedna poznámka předem: projekt je pořád pre-alfa. Čísla v textu měří kód a testy, ne zralost v produkci, a první vydání je záměrně omezené na VPS s KVM. Bare metal zůstává na později.</p>
<h2 id="jak-to-vypadalo-v-case">Jak to vypadalo v čase</h2>
<p>První commit vznikl 28. října 2026 ve 23:48 (M0). Do 31. října ve 23:00 má repozitář 119 commitů, rozložených jako 3, 39, 38 a 39 podle kalendářních dní. Hned řekněme, jak je to možné: projekt vzniká s Claude Code a commity nesou trailery <code>Co-Authored-By: Claude</code>. Čtyři kalendářní dny historie neznamenají, že jeden člověk napsal za čtyři dny 90 000 řádků; znamenají, že plán, implementace, testy a ADR se píšou v krátkých uzavřených smyčkách a každý krok končí stejným ověřovacím rituálem jako dřív. Čísla níže vypovídají o množství kódu a testů, ne o tom, kolik skutečné práce to všechno obnášelo.</p>
<figure><img loading="lazy" alt="Růst projektu během čtyř dnů s milníky od M0 po dnešek: tisíce řádků Rustu (od 1,6 do 89,8), počet funkcí #[test] (od 27 do 739) a počet ADR (od 13 do 104), spolu se sloupci commitů za den (39, 38 a 39 za 29., 30. a 31. října)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/growth.svg" /><figcaption>Růst projektu během čtyř dnů s milníky od M0 po dnešek: tisíce řádků Rustu (od 1,6 do 89,8), počet funkcí #[test] (od 27 do 739) a počet ADR (od 13 do 104), spolu se sloupci commitů za den (39, 38 a 39 za 29., 30. a 31. října).</figcaption></figure>
<p>Graf ukazuje tři křivky, které rostou téměř společně, což je záměr: testy a zapsaná rozhodnutí kód doprovázejí, a nezaostávají za ním. Sklon je nejstrmější 29. října, kdy vzniká celá cesta od jádra k brokeru. Dál se řádky přidávají pomaleji, ale testy rostou rychleji než kód (27 testů na 1,6 tisíce řádků na začátku, 739 na 89,8 tisíce dnes).</p>
<table>
<thead>
<tr>
<th>Milník</th>
<th>Commit</th>
<th>Čas</th>
<th style="text-align: right;">Řádků Rustu</th>
<th style="text-align: right;">Testů</th>
<th style="text-align: right;">ADR</th>
</tr>
</thead>
<tbody>
<tr>
<td>M0</td>
<td>9412a9c</td>
<td>28.10. 23:48</td>
<td style="text-align: right;">1 592</td>
<td style="text-align: right;">27</td>
<td style="text-align: right;">13</td>
</tr>
<tr>
<td>M5</td>
<td>a61083b</td>
<td>29.10. 01:32</td>
<td style="text-align: right;">9 314</td>
<td style="text-align: right;">88</td>
<td style="text-align: right;">24</td>
</tr>
<tr>
<td>M9</td>
<td>4be8cc9</td>
<td>29.10. 02:21</td>
<td style="text-align: right;">15 729</td>
<td style="text-align: right;">149</td>
<td style="text-align: right;">28</td>
</tr>
<tr>
<td>M11</td>
<td>4800904</td>
<td>29.10. 03:43</td>
<td style="text-align: right;">22 737</td>
<td style="text-align: right;">180</td>
<td style="text-align: right;">30</td>
</tr>
<tr>
<td>M13</td>
<td>a558d5e</td>
<td>29.10. 05:22</td>
<td style="text-align: right;">26 556</td>
<td style="text-align: right;">193</td>
<td style="text-align: right;">32</td>
</tr>
<tr>
<td>M17</td>
<td>7df4f98</td>
<td>29.10. 08:48</td>
<td style="text-align: right;">33 504</td>
<td style="text-align: right;">232</td>
<td style="text-align: right;">36</td>
</tr>
<tr>
<td>S1</td>
<td>ecf72ec</td>
<td>30.10. 00:46</td>
<td style="text-align: right;">41 910</td>
<td style="text-align: right;">299</td>
<td style="text-align: right;">45</td>
</tr>
<tr>
<td>X8</td>
<td>fb1504a</td>
<td>30.10. 19:08</td>
<td style="text-align: right;">56 341</td>
<td style="text-align: right;">456</td>
<td style="text-align: right;">59</td>
</tr>
<tr>
<td>PERF</td>
<td>76f7c36</td>
<td>31.10. 14:19</td>
<td style="text-align: right;">72 511</td>
<td style="text-align: right;">618</td>
<td style="text-align: right;">76</td>
</tr>
<tr>
<td>dnes</td>
<td>HEAD</td>
<td>31.10. 23:00</td>
<td style="text-align: right;">89 790</td>
<td style="text-align: right;">739</td>
<td style="text-align: right;">104</td>
</tr>
</tbody>
</table>
<p>Řádky jsou všechny soubory <code>.rs</code> mimo <code>vendor/</code> a <code>fuzz/</code>, testy jsou funkce <code>#[test]</code>. Co který úsek přidal, podle data z historie gitu:</p>
<table>
<thead>
<tr>
<th>Datum</th>
<th>Milníky</th>
<th>Co bylo přidáno</th>
</tr>
</thead>
<tbody>
<tr>
<td>29.10.</td>
<td>M0 až M17</td>
<td>jádro, síť, TLS, broker, SQLite, více jader, vlastní TCP, zpevnění, reprodukovatelné vydání</td>
</tr>
<tr>
<td>29.10. večer a 30.10.</td>
<td>X0 až X10, S1, H2, H3 (X0 začíná 29.10. ve 20:54)</td>
<td>port na x86_64, univerzální ISO, jeden disk a samoinstalační ISO, kontroly TLS, útočné klienty</td>
</tr>
<tr>
<td>31.10.</td>
<td>O1 až O4, T1, PERF, H4, H5, H9, S2 až S6</td>
<td>přehled a konzole přes MQTT, živý režim a diagnostika, SNTP, zrychlení, zpevnění, verifikace, dodavatelský řetězec, úložiště</td>
</tr>
</tbody>
</table>
<h2 id="slib-1-jedno-iso-pro-vice-platforem">Slib 1: jedno ISO pro více platforem</h2>
<p><strong>Co jsme plánovali.</strong> Jeden soubor, který nabootuje na aarch64 UEFI, x86_64 UEFI a x86_64 BIOS. <strong>Co jsme postavili.</strong> Port na x86_64 (X0 až X10) a univerzální ISO s bootloaderem Limine, jediné hybridní ISO, z něhož se zavádějí všechny tyto kombinace; kouřové testy ho spouštějí na čtyřech virtuálních strojích. Nabídka má tři položky: urMQTT (živý režim, běží z RAM, nic nezapisuje na disk), instalace na disk stroje, na kterém běží (instalace zapíše GPT a data a poté stroj vypne) a Diagnostika (detekce hardwaru a měření).</p>
<figure><img loading="lazy" alt="Zaváděcí nabídka ISO zachycená na VPS: položky „urMQTT&quot;, „Install urMQTT on this machine's disk&quot; a „Diagnostic: hardware detection and benchmarks&quot; s odpočtem do automatického spuštění. Snímek je z testovacího ISO s delším odpočtem; výchozí hodnota je 10 sekund." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/boot-menu.png" /><figcaption>Zaváděcí nabídka ISO zachycená na VPS: položky „urMQTT&quot;, „Install urMQTT on this machine's disk&quot; a „Diagnostic: hardware detection and benchmarks&quot; s odpočtem do automatického spuštění. Snímek je z testovacího ISO s delším odpočtem; výchozí hodnota je 10 sekund.</figcaption></figure>
<p>Prvním skutečným cílem byl VPS, ne fyzický počítač: rozhodnutí zní, že první vydání podporuje jen VPS (KVM) a bare metal přijde po něm. Testovali jsme na VPS u Contabo s procesorem AMD EPYC (rodina 23, Zen 1), 4 vCPU a 8 GiB RAM, se sítí virtio-net a diskem virtio-scsi. První pokus nás překvapil: ISO připojené jako virtuální CD spustilo výchozí položku, produkční obraz odmítl běžet z média jen pro čtení a resetoval se, takže vznikla nekonečná smyčka nabídka, jádro, reset bez čehokoli čitelného. Dnes fatální chyba v produkčním obrazu nechá zprávu na konzoli 60 sekund před resetem a z optického média vypíše, že bylo spuštěno z média jen pro čtení, a zastaví se. Živý režim v RAM (O3) později tento problém pro běžný případ obešel.</p>
<p>Diagnostická položka vznikla z této zkušenosti: stroj, který nevidíme, se musíme umět zeptat, co má. Vypisuje CPU, RAM, síť, disky a zařízení PCI, měří propustnost paměti, disk a kryptografii a stejná měření vypisuje jako JSON na sériový port. Zápis na disk je povolený jen v nepřiděleném prostoru a až po zadání <code>WIPE</code>; na snímku níže byl přeskočen.</p>
<figure><img loading="lazy" alt="Diagnostická obrazovka na VPS u Contabo (AMD EPYC, KVM, 4 vCPU): systém, paměť, disk, zařízení PCI, propustnost a latence RAM a kryptografická měření, včetně 244 us na mTLS handshake, X25519 48 us, podpis ECDSA 35,7 us a ověření 107 us." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/diag-vps.png" /><figcaption>Diagnostická obrazovka na VPS u Contabo (AMD EPYC, KVM, 4 vCPU): systém, paměť, disk, zařízení PCI, propustnost a latence RAM a kryptografická měření, včetně 244 us na mTLS handshake, X25519 48 us, podpis ECDSA 35,7 us a ověření 107 us.</figcaption></figure>
<p><strong>Co zůstává otevřené.</strong> Skutečný hardware. Po prvním vydání přijde bare metal (zavádění z USB, NVMe a AHCI, síťová karta skutečného počítače) spolu s IOMMU a Secure Bootem, protože bez IOMMU může zlovolné nebo vadné zařízení zapisovat do celé paměti. V této verzi o tom nic netvrdíme.</p>
<h2 id="handshake-z-12-ms-na-024-ms">Handshake: z 1,2 ms na 0,24 ms</h2>
<p>První měření diagnostiky na VPS dalo nepříjemné číslo: mTLS handshake, jen kryptografie na straně serveru, trval 1,2 ms. Na aarch64 jsme už používali hardwarové instrukce AES a SHA, ale x86_64 nemělo nic podobného pro veřejný klíč: RustCrypto je přenositelné a constant-time, a tedy i pomalé. Srazily to tři změny.</p>
<ul>
<li>Pro P-256 jsme převzali assembler <code>nistz256</code> z OpenSSL a ring (ADR-130), spolu s tabulkami s pevnou bází pro podpis a ověření podpisu CA (ADR-110, ADR-111).</li>
<li>X25519 bylo přepsáno na čtyři 64bitové končetiny s instrukcemi MULX/ADX (ADR-131).</li>
<li>Poté jsme zprofilovali i zbytek (ADR-132, ADR-200).</li>
</ul>
<figure><img loading="lazy" alt="Handshake na VPS: 1200 us před, 237 us po assembleru a tabulkách; A/B srovnání memcpy při stejném spuštění, 278,8 us se starým, 229,4 us s novým; a před a po u X25519 (146 na 48 us), podpisu ECDSA (673 na 34 us) a ověření (309 na 90 us)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/handshake.svg" /><figcaption>Handshake na VPS: 1200 us před, 237 us po assembleru a tabulkách; A/B srovnání memcpy při stejném spuštění, 278,8 us se starým, 229,4 us s novým; a před a po u X25519 (146 na 48 us), podpisu ECDSA (673 na 34 us) a ověření (309 na 90 us).</figcaption></figure>
<table>
<thead>
<tr>
<th>Měření na VPS (server, mikrosekundy)</th>
<th style="text-align: right;">Před</th>
<th style="text-align: right;">Po</th>
</tr>
</thead>
<tbody>
<tr>
<td>mTLS handshake, celá kryptografie</td>
<td style="text-align: right;">1200</td>
<td style="text-align: right;">kolem 240 (237 v měření z grafu, 244 na snímku)</td>
</tr>
<tr>
<td>X25519</td>
<td style="text-align: right;">146</td>
<td style="text-align: right;">48</td>
</tr>
<tr>
<td>podpis ECDSA</td>
<td style="text-align: right;">673</td>
<td style="text-align: right;">34</td>
</tr>
<tr>
<td>ověření ECDSA</td>
<td style="text-align: right;">309</td>
<td style="text-align: right;">90 (107 na snímku)</td>
</tr>
<tr>
<td>handshake, starý memcpy, stejný boot</td>
<td style="text-align: right;">278,8</td>
<td style="text-align: right;">229,4 (-17,7%)</td>
</tr>
</tbody>
</table>
<p>Čísla se mezi spuštěními liší asi o 10 %, proto jsme zapsali rozsah místo jedné hodnoty. Celkově je to zhruba pětkrát méně CPU času na handshake.</p>
<p>Co jsme se naučili o memcpy. Když kryptografie klesla, profil ukázal něco, co jsme nečekali: funkce <code>mem*</code> (memcpy, memmove, memset) spotřebovávaly 7 % handshaku, 13 až 39 % cesty TLS záznamu (39 % pro 64 B, 13 % pro 4 KiB) a 17 až 34 % rozdělování PUBLISH. Nahradili jsme je assemblerem SSE2 pro x86_64 (ADR-200), s diferenciálními testy proti referenční implementaci pro všechny velikosti, zarovnání a překryvy. Na jedno volání je to 55 až 75 cyklů oproti 7 až 12 pro 1 až 64 bajtů.</p>
<figure><img loading="lazy" alt="Vlevo: podíl času serveru ve funkcích mem* před přepsáním (handshake 7 %, TLS záznam 64 B 39 %, 256 B 37 %, 1 KiB 24 %, rozdělování 17 až 34 %). Vpravo: o kolik je každá cesta rychlejší potom (handshake 17,7 %, 64 B 26,4 %, 256 B 19,6 %, 1 KiB 16,1 %, 4 KiB 6,6 %, rozdělování 7 až 34 %)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/hotpaths.svg" /><figcaption>Vlevo: podíl času serveru ve funkcích mem* před přepsáním (handshake 7 %, TLS záznam 64 B 39 %, 256 B 37 %, 1 KiB 24 %, rozdělování 17 až 34 %). Vpravo: o kolik je každá cesta rychlejší potom (handshake 17,7 %, 64 B 26,4 %, 256 B 19,6 %, 1 KiB 16,1 %, 4 KiB 6,6 %, rozdělování 7 až 34 %).</figcaption></figure>
<table>
<thead>
<tr>
<th>Cesta</th>
<th style="text-align: right;">Podíl mem* před</th>
<th style="text-align: right;">Rychlejší po</th>
</tr>
</thead>
<tbody>
<tr>
<td>handshake</td>
<td style="text-align: right;">7%</td>
<td style="text-align: right;">17,7%</td>
</tr>
<tr>
<td>TLS záznam 64 B</td>
<td style="text-align: right;">39%</td>
<td style="text-align: right;">26,4%</td>
</tr>
<tr>
<td>TLS záznam 256 B</td>
<td style="text-align: right;">37%</td>
<td style="text-align: right;">19,6%</td>
</tr>
<tr>
<td>TLS záznam 1 KiB</td>
<td style="text-align: right;">24%</td>
<td style="text-align: right;">16,1%</td>
</tr>
<tr>
<td>TLS záznam 4 KiB</td>
<td style="text-align: right;">13%</td>
<td style="text-align: right;">6,6%</td>
</tr>
<tr>
<td>rozdělování zpráv</td>
<td style="text-align: right;">17 až 34%</td>
<td style="text-align: right;">7 až 34%</td>
</tr>
</tbody>
</table>
<p>Jak číst graf: čím menší záznam, tím větší část času jde na kopírování, a tím větší je i zisk. U malých zpráv, které tvoří většinu provozu MQTT, to je to, co se počítá.</p>
<p>Co to neznamená. Na nativním x86_64 jsme OpenSSL 3.5.5 (ADR-130) měřili jen jako referenci pro assembler, ne jako srovnání produktů. Srovnání Linuxu s Mosquittem na stejném VPS (úkol X11) jsme ještě neudělali. Proto netvrdíme, že urMQTT je rychlejší než Linux: nebylo to změřeno. Čísla výše srovnávají urMQTT samo se sebou.</p>
<p>Přetížení bylo druhým výsledkem. Na VPS, z jednoho Macu (RTT 35 ms), dalo 128 klientů 618 handshaků za sekundu, 256 klientů 393 a 512 klientů 67, s tisíci odmítnutých spojení. Nebyla to plošina, ale zhroucení. Příčiny jsme našli v kódu (dvě jsou potvrzené v QEMU/HVF, na VPS se zopakovat nedaly), několik najednou: odmítnuté spojení vracelo klienta přes další segment, který nese platnou cookie SYN, odmítnutí pomocí RST říkalo klientovi, aby to hned zkusil znovu, a příjem byl O(fronta). Oprava (ADR-080 až ADR-082) zahazuje SYN, který by nebyl přijat, pamatuje si odmítnuté toky, drží příjem v O(1) a počítá handshaky na jádro. Je možné (hypotéza, neověřeno), že část příčiny byla i na straně klienta: nástroj pro zátěž zavíral spojení jako první, takže jeden Mac s 16 384 efemérními porty a TIME_WAIT 30 sekund byl omezen na zhruba 550 spojení za sekundu. Proto skutečný strop serveru stále nemáme změřený.</p>
<h2 id="slib-2-surovy-disk-ale-opatrny">Slib 2: surový disk, ale opatrný</h2>
<p><strong>Co jsme plánovali.</strong> Zachovat vlastní rozvržení bez souborového systému a doplnit vše, co souborový systém obvykle dává: zarovnání, růst, discard, zálohy a kontroly. <strong>Proč ne souborový systém.</strong> Diskutovali jsme o tom a rozhodnutí se potvrdilo: urMQTT zapisuje jednu databázi, jeden telemetrický žurnál a vlastní metadata, nepotřebuje adresáře, oprávnění ani názvy souborů, a každá další vrstva by byla další kód v důvěryhodné části a další způsob, jak může zápis tiše zmizet. Disk je GPT s oddílem ESP a jedním datovým oddílem urMQTT, na kterém je vlastní rozvržení.</p>
<p><strong>Co jsme postavili (S2 až S6).</strong></p>
<ul>
<li><strong>Rozvržení v2 (S2).</strong> Topologie disku, zarovnání na 1 MiB nebo optimální velikost I/O, celé fyzické bloky, A/B dvojice superbloků s generací (ADR-140). Rozvržení v1 jádro odmítne s jasnou zprávou a migruje ho offline nástroj <code>urmqtt-disk migrate</code> (ADR-141): výchozí je zkušební průchod, kopíruje se a teprve potom přepíná, s návratem a vkládáním chyb při každém zápisu.</li>
<li><strong>Růst (S3).</strong> Při startu může disk narůst (nikdy se nezmenšuje) a na virtio-blk i za běhu, přes přerušení změny konfigurace (ADR-142, ADR-143).</li>
<li><strong>Discard a write-zeroes (S4).</strong> Tence přidělené disky vracejí prostor hostiteli. V testu klesl soubor qcow2 na hostiteli z 62 na 16 MiB poté, co vypršely telemetrické segmenty, a obnova nikdy nedůvěřuje obsahu, který byl zahozen.</li>
<li><strong>Záloha databáze na disku (S5).</strong> Kopie A/B pomocí online backup API SQLite po krocích po 16 stránkách: nejdelší krok 4,6 ms oproti 445 ms při kopírování jedním voláním. Commity během kopie měly maximum 2,4 ms oproti 2,2 ms bez ní. Pokud <code>quick_check</code> při startu selže, databáze se obnoví z kopie.</li>
<li><strong>Čištění a omezený start (S6).</strong> Scrubber čte disk s omezeným rozpočtem I/O a hlásí poškozené záznamy a start čte jen nejnovější segment každého oddílu, takže doba startu neroste s velikostí telemetrické oblasti. Tím jsme uzavřeli problém, který jsme si předem spočítali: na velkém disku by úplná obnova trvala od minut do desítek minut.</li>
</ul>
<p>Celá skupina kouřových testů pro úložiště prochází na obou architekturách: 79 úspěšných, 0 neúspěšných, na aarch64 pod HVF a na x86_64 pod TCG. Měřili jsme také, že S4 databázi nezpomalil: 53,3 tisíce vložení za sekundu v dávkách před i po, bez měřitelného rozdílu.</p>
<p><strong>Co překvapilo.</strong> Kolik z „jednoduchého" rozvržení tvoří okrajové případy: virtio-scsi a virtio-blk hlásí topologii různými způsoby a neúplný zápis uprostřed migrace musí vždy ponechat alespoň jednu platnou verzi. Proto každý zápis na cestě migrace a růstu prošel vkládáním chyb.</p>
<p><strong>Co zůstává otevřené.</strong> Stránky SQLite nemají kontrolní součet, takže změněný bit uvnitř hodnoty v řádku databáze neodhalí; telemetrické záznamy a obsah kopií mají CRC a odhalí se. Šifrování disku (S7) není hotové.</p>
<h2 id="slib-3-prokazatelne-bezpecne-nakolik-to-dokazeme">Slib 3: prokazatelně bezpečné, nakolik to dokážeme</h2>
<p><strong>Zpevnění (H4).</strong> Alokátor maže uvolněnou paměť a kontroluje ji při opětovném použití, odhaluje dvojí uvolnění, váže ukazatele volného seznamu pomocí XOR a nuluje klíče při uvolnění (ADR-170). Ochrana zásobníku (<code>-Zstack-protector=strong</code>, 1315 funkcí, a SQLite s <code>-fstack-protector-strong</code>, ADR-171). Hlášení chyb v produkci neobsahují registry ani adresy (ADR-172). KASLR na obou architekturách: jádro je PIE a Limine ho posouvá asi o 19 bitů, a náhodně se umísťují halda, zásobníky a okno MMIO (ADR-173). Na aarch64 MTE označuje haldu (ADR-174), ale jen pod TCG: HVF a x86 ho nemají. kCFI jsme vyhodnotili a nepřijali, protože překladač na připnutém nightly padá s vnitřní chybou; IOMMU je odloženo spolu s bare metalem (ADR-175, ADR-176).</p>
<p><strong>Cena je změřená, ne předpokládaná.</strong> Na cestě handshaku (<code>dp-bench</code>, aarch64 HVF, plné handshaky TLS 1.3 + mTLS + CONNECT):</p>
<table>
<thead>
<tr>
<th>Krok</th>
<th style="text-align: right;">1 jádro</th>
<th style="text-align: right;">4 jádra</th>
<th>Změna</th>
</tr>
</thead>
<tbody>
<tr>
<td>bez zpevnění</td>
<td style="text-align: right;">1941</td>
<td style="text-align: right;">7620</td>
<td></td>
</tr>
<tr>
<td>+ alokátor</td>
<td style="text-align: right;">1898</td>
<td style="text-align: right;">7429</td>
<td>-2,2% až -2,5%</td>
</tr>
<tr>
<td>+ ochrana zásobníku</td>
<td style="text-align: right;">1845</td>
<td style="text-align: right;">7243</td>
<td>dohromady -4,9% až -5%</td>
</tr>
<tr>
<td>+ PIE a značky</td>
<td style="text-align: right;">kolem 1800</td>
<td style="text-align: right;">kolem 6750</td>
<td>na zatíženém počítači, zašuměné</td>
</tr>
</tbody>
</table>
<p>Celkově je tedy cena na cestě handshaku zhruba 5 % pro části měřené čistě a s PIE a značkami vychází asi 7 % na jednom a asi 11 % na čtyřech jádrech, ale toto měření je zašuměné. Považujeme ji za přijatelnou, ale neopakovali jsme ji na x86_64 VPS.</p>
<p>Stojí za to porovnat s číslem z prvního textu (862 handshaků za sekundu na jednom jádře). Stejný nástroj dnes, s ovladačem hardwarového šifrování na aarch64 (X0b) a řadou zrychlení, která od té doby přišla, dává před zpevněním 1941. Příspěvek každé změny jsme neizolovali, takže o příčině nic netvrdíme.</p>
<p><strong>Jak je to ověřeno, a co ne.</strong> H5 přidal Miri na podmnožinu hostitelských crate, důkazy Kani, sanitizery, model-fuzzer pro ACL a hlášení o pokrytí. Kani dokázal <code>record_len</code> v TLS, <code>parse</code> v TCP a dvě aritmetiky kola časovačů. <strong>Nedokázáno</strong> (vypršel čas): oba dekodéry MQTT, obě parsování konfigurace a chování kola časovačů; úloha CI pro Kani proto zatím neblokuje. Miri neumí spouštět assembler. Dodavatelský řetězec (H9): SBOM CycloneDX s 215 komponentami, <code>cargo-vet</code> prochází s 39 plně zrevidovanými a 140 výjimkami, a cargo-deny je nakonfigurováno tak, že upozornění na zranitelnosti bere jako selhání CI (tyto úlohy CI byly zatím ověřeny jen lokálně, ne na GitHubu, a negativní test nebyl spuštěn), spolu s <code>VULNERABILITY-HANDLING.md</code>. Poctivý stav: ze 67 verzí cizích crate linkovaných do jádra je 53 výjimek, ne revizí. Vet dokazuje, že množinu závislostí nelze změnit bez viditelného rozdílu, ne že TCB byla zkontrolována. Vlastní revizi žádného crate jsme nenapsali.</p>
<p>SNTP (T1) je v tomto smyslu malý příběh o bezpečnosti: je volitelné, jen vůči soukromým serverům IPv4, s maximálním skokem a podmínkou shody zdrojů (medián, většina do jedné sekundy). Hodiny, které může kdokoli na cestě posunout, nejsou hodiny, kterým smíme věřit při platnosti certifikátů.</p>
<h2 id="na-macu-aarch64-nativne-a-x86_64-emulaci">Na Macu: aarch64 nativně a x86_64 emulací</h2>
<p>Vyvíjíme na Macu a ten má dva způsoby, jak spustit stejný kód, a jsou velmi odlišné.</p>
<table>
<thead>
<tr>
<th></th>
<th>aarch64 pod HVF</th>
<th>x86_64 pod TCG</th>
</tr>
</thead>
<tbody>
<tr>
<td>Co to je</td>
<td>nativní provádění, rychlost blízká skutečnému procesoru</td>
<td>QEMU emuluje instrukce</td>
</tr>
<tr>
<td>Handshake v hostu</td>
<td>asi 0,58 ms (poprvé měřeno kolem M13); <code>dp-bench</code> dnes 1941/s na jádro</td>
<td>asi 3,3 ms</td>
</tr>
<tr>
<td>Scénáře kouřových testů TLS</td>
<td>rychlé</td>
<td>asi 15 minut na scénář</td>
</tr>
<tr>
<td>MTE</td>
<td>ne (na HVF není)</td>
<td>x86 ho nemá; označování haldy funguje jen pro hosta aarch64 pod TCG</td>
</tr>
<tr>
<td>K čemu slouží</td>
<td>skutečná měření</td>
<td>správnost: ověřuje se cesta x86, čísla nejsou výkon</td>
</tr>
</tbody>
</table>
<p>Pravidlo je jednoduché: čísla pod TCG jsou emulace a necitujeme je jako výkon. Výkon x86 měříme jen na skutečném VPS. Proto kryptografická čísla výše pocházejí z VPS u Contabo, ne z Macu.</p>
<h2 id="lekce">Lekce</h2>
<ul>
<li><strong>Měřte na skutečném stroji, než začnete optimalizovat.</strong> První přístup byl optimalizovat to, co jsme považovali za pomalé; teprve diagnostika na VPS ukázala 1,2 ms a kam přesně čas odchází.</li>
<li><strong>Přetížení je samostatná vlastnost.</strong> Systém, který je rychlý pod zátěží, se může nad ní zhroutit; odmítnutí musí být levnější než přijetí.</li>
<li><strong>Profil překvapuje.</strong> Největší jednotlivý zisk pro malé zprávy nebyl v kryptografii, ale v kopírování paměti.</li>
<li><strong>Disk bez souborového systému vyžaduje víc práce, ne méně.</strong> To, co souborový systém obvykle dává (zarovnání, růst, discard, kopie, kontroly), musíme napsat a ověřit sami, každý krok s vkládáním chyb.</li>
<li><strong>Poctivost v číslech.</strong> Měření paměti na sdíleném VPS kolísají až 10krát mezi spuštěními a o kódu nic nevypovídají, proto je neuvádíme. Kryptografická čísla jsou stabilní do asi 10 %.</li>
<li><strong>Rychlost vývoje není zralost.</strong> Testy a ADR rostou s kódem, ale čtyři dny nejsou skutečný provoz.</li>
</ul>
<h2 id="co-nasleduje">Co následuje</h2>
<ul>
<li><strong>H6:</strong> SQLite izolované na úrovni EL0, aby chyba v databázi neotevřela jádro.</li>
<li><strong>H7:</strong> zbytek bezpečnosti času (NTS a zpracování skoků); SNTP je teprve první krok.</li>
<li><strong>X11:</strong> srovnání s Linuxem a Mosquittem na stejném VPS, teprve pak má smysl mluvit o relativní rychlosti.</li>
<li><strong>Vydání:</strong> první veřejné vydání pro VPS (KVM), s podepsanými artefakty a návody.</li>
<li><strong>Po vydání:</strong> bare metal (X12 až X16), IOMMU a Secure Boot a šifrování disku.</li>
</ul>
<p>Kód je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a> a <code>docs/PROGRESS.md</code> je pořád jediným zdrojem pravdy o tom, co skutečně funguje. Pre-alfa, jako dřív; hlášení chyb a bezpečnostní hlášení podle pokynů v <code>SECURITY.md</code> jsou vítána.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT: stavíme MQTT broker, který je zároveň operačním systémem</title>
      <link>https://aldo.tech/cs/blog/urmqtt-unikernel-mqtt-broker/</link>
      <guid isPermaLink="true">https://aldo.tech/cs/blog/urmqtt-unikernel-mqtt-broker/</guid>
      <pubDate>Thu, 29 Oct 2026 08:00:00 +0000</pubDate>
      <description>Vývojářské poznámky o urMQTT, unikernelu napsaném v Rustu, který sám o sobě je MQTT broker: jeden ELF s vlastním TCP/IP, TLS 1.3, brokerem a SQLite, spouštěný z jediného ISO.</description>
      <content:encoded><![CDATA[<p>Zařízení ve vozidlech, na parkovištích a ve skříních s elektronikou mají jednu společnou vlastnost: posílají malé zprávy, zřídka, ale je jich mnoho a jsou neustále připojená. Roky je připojujeme k MQTT brokerům a roky nás trápí stejná otázka. Proč potřebujeme pro něco tak jednoduchého celý Linux, OpenSSL, runtime nějakého jazyka, SSH, správce balíčků a teprve pak broker? Každá z těchto vrstev přináší vlastní zranitelnosti, spotřebovává vlastní paměť a přidává další věc, která se může porouchat, a flotila zařízení od serveru potřebuje jen tři věci: aby držel velké množství téměř nečinných spojení, aby spolehlivě autentizoval každé zařízení a aby doručil zprávy bez ztrát.</p>
<p>Tak vznikl <a href="https://github.com/zeljan-alduk/urMQTT">urMQTT</a>: unikernel, který <em>je</em> MQTT broker. Jádro, TCP/IP, TLS 1.3, MQTT a vestavěná databáze v jediném binárním programu v Rustu, který se spouští z jediného ISO. Žádné procesy, žádná systémová volání, žádný shell. První platforma, na které vzniká a je testován, je aarch64, a stejný kód míří na x86_64, přes UEFI i klasický BIOS, a na skutečný hardware: cílem je jedno ISO, které nabootuje na všech. Projekt je open source pod licencí MIT nebo Apache 2.0 a v tomto textu chceme zaznamenat, kde jsme, jak je uvnitř poskládaný a co jsme se cestou naučili.</p>
<h2 id="co-presne-znamena-unikernel">Co přesně znamená „unikernel"</h2>
<p>Běžný broker je program, který běží na operačním systému. urMQTT pod sebou žádný operační systém nemá: zavádí se sám přes firmware UEFI a bootloader Limine, nastavuje si vlastní stránkové tabulky, programuje řadič přerušení a časovač, nachází zařízení virtio přes PCIe, získá adresu pomocí DHCP a naslouchá na jediném portu. Všechno, co by jinak dělalo jádro, ovladače, síťový zásobník a aplikace, je tady jediný ELF běžící na úrovni EL1.</p>
<p>Srovnání s typickou instalací mluví samo za sebe:</p>
<table>
<thead>
<tr>
<th></th>
<th>Typický broker na Linuxu</th>
<th>urMQTT</th>
</tr>
</thead>
<tbody>
<tr>
<td>Vystavené služby</td>
<td>SSH, broker, často i port pro metriky nebo administraci</td>
<td><strong>pouze TCP 8883</strong> (MQTT přes TLS 1.3 s mTLS)</td>
</tr>
<tr>
<td>Kód v obrazu</td>
<td>jádro, userland, libc, OpenSSL, runtime</td>
<td><strong>jeden ELF v Rustu</strong> (plus vendorovaný SQLite)</td>
</tr>
<tr>
<td>Vzdálená správa</td>
<td>shell, konfigurační soubory, aktualizace balíčků</td>
<td><strong>nic.</strong> Nová verze je nové ISO</td>
</tr>
<tr>
<td>Paměť na nečinné spojení</td>
<td>kilobajty (vyrovnávací paměti socketu, objekty TLS, halda)</td>
<td><strong>cíl ≤ 512 B</strong></td>
</tr>
<tr>
<td>Vícejádrový model</td>
<td>vlákna a zámky nebo jedna smyčka událostí</td>
<td><strong>vlákno na jádro, bez sdíleného stavu, zpracování do konce</strong></td>
</tr>
<tr>
<td>Integrita kódu</td>
<td>možná jsou mapování, která jsou zároveň zapisovatelná i spustitelná</td>
<td><strong>W^X, zamčené stránkové tabulky, PAC/BTI</strong></td>
</tr>
</tbody>
</table>
<p>Priority jsou v tomto pořadí: bezpečnost, škálovatelnost, vícejádrový výkon, malá stopa a neměnná instalace. Kdykoli jsou dva cíle v rozporu, vítězí ten, který je na tomto seznamu výše.</p>
<h2 id="kde-jsme-ted">Kde jsme teď</h2>
<p>Projekt je v pre-alfa fázi, ale uzavřeli jsme sedmnáct vývojových milníků, od M0 do M17. Dnes urMQTT obsluhuje MQTT 3.1.1 přes TLS 1.3 s povinnými klientskými certifikáty a stav ukládá do SQLite na surovém disku. Stručně, co už funguje:</p>
<ul>
<li><strong>Jádro:</strong> vlastní vektory výjimek s dekódovanými hlášeními, vlastní stránkové tabulky s vynuceným W^X, ochranné stránky, PAC a BTI tam, kde je procesor má, GICv3 a GICv2, generický časovač, SMP až do 64 jader.</li>
<li><strong>Detekce hardwaru</strong> z DTB nebo ACPI do jednoho popisu platformy, s totožným výsledkem z obou zdrojů.</li>
<li><strong>Ovladače:</strong> PCIe ECAM, MSI-X, virtio-net, virtio-blk, virtio-scsi a virtio-rng, a také CSPRNG osazený hardwarovou entropií.</li>
<li><strong>Síť:</strong> vlastní DHCPv4, ARP, ICMP a přísné IPv4, dále vlastní TCP pouze pro stranu serveru: SYN cookies, hierarchické kolo časovačů, NewReno, slučování ACK, bez stavu TIME_WAIT.</li>
<li><strong>TLS:</strong> pouze TLS 1.3, povinné mTLS, identita zařízení z CN nebo SAN URI; handshake zajišťuje rustls, po něm následuje vlastní vrstva záznamů s AES-128-GCM a ChaCha20-Poly1305. Obnovení relace pomocí PSK ticketů, seznam odvolaných certifikátů, řízení příjmu už při handshaku.</li>
<li><strong>MQTT:</strong> kodek bez kopírování, fuzzovaný a testovaný se skutečnými klienty mosquitto; QoS 0 a 1, zástupné znaky, retained zprávy, závěť (LWT), trvalé relace, ACL s omezením na <code>devices/{id}/#</code>, limity rychlosti na zařízení.</li>
<li><strong>Úložiště:</strong> SQLite uvnitř jádra přes VFS nad surovým diskem, v režimu WAL, s kontrolou integrity při startu; registr zařízení, odvolání, retained zprávy, trvalé relace s frontami zpráv pro zařízení, která jsou offline; na každé jádro samostatný telemetrický žurnál s CRC u každého záznamu a obnovou po pádu.</li>
<li><strong>Provoz a dohled:</strong> watchdog časovač pro obslužné rutiny, kontrola zdraví, omezené logování, syslog a statsd přes UDP, řízení příjmu, které přežije bouře opakovaných připojení.</li>
<li><strong>Dodání:</strong> bitově shodné opakované sestavení ověřované v CI, ISO a surový obraz disku GPT, podpisy minisign, návody pro KVM, Proxmox a Hetzner.</li>
</ul>
<p>Každý krok je označen milníkem, který jej přinesl, a soubor <code>docs/PROGRESS.md</code> v repozitáři je jediným zdrojem pravdy o tom, co skutečně funguje.</p>
<h2 id="architektura-ve-trech-obrazcich">Architektura ve třech obrázcích</h2>
<p>Celý návrh je popsán v <code>docs/ARCHITECTURE.md</code> a důvody každého důležitějšího rozhodnutí jsou zapsány jako ADR v <code>docs/DECISIONS.md</code>. Zde je přehled.</p>
<h3 id="vrstvy">Vrstvy</h3>
<figure><img loading="lazy" alt="Vrstvy urMQTT: od brokeru přes kodek MQTT, TLS, TCP a síť až po ovladače virtio a architektonickou vrstvu, vše v jediném ELF na úrovni EL1." src="https://aldo.tech/blog/urmqtt/img/layers.svg" /><figcaption>Vrstvy urMQTT: od brokeru přes kodek MQTT, TLS, TCP a síť až po ovladače virtio a architektonickou vrstvu, vše v jediném ELF na úrovni EL1.</figcaption></figure>
<p>Odshora dolů: broker (relace, strom témat, QoS, retained zprávy, závěti, ACL, limity), kodek MQTT, TLS, TCP, síťová vrstva a pak ovladače. Vedle této hlavní cesty stojí úložiště (SQLite a telemetrický žurnál) a pod vším správa paměti, detekce platformy a konfigurace přeložená v době sestavení do konstant, spolu s vestavěným certifikátem CA, serverovým certifikátem a klíčem. Hypervizor se zařízeními virtio je jediné, co je mimo obraz.</p>
<h3 id="vlakno-na-jadro-bez-sdileneho-stavu">Vlákno na jádro, bez sdíleného stavu</h3>
<figure><img loading="lazy" alt="Model provádění: každé datové jádro má vlastní smyčku a vlastní část brokeru a nejvyšší jádro spouští pouze SQLite. Jádra spolu komunikují výhradně přes prstence SPSC." src="https://aldo.tech/blog/urmqtt/img/cores.svg" /><figcaption>Model provádění: každé datové jádro má vlastní smyčku a vlastní část brokeru a nejvyšší jádro spouští pouze SQLite. Jádra spolu komunikují výhradně přes prstence SPSC.</figcaption></figure>
<p>Každé datové jádro točí vlastní smyčku s vlastním zásobníkem TCP, stavy TLS, částí brokeru (relace a strom témat), cache haldy a oddílem telemetrie. Jádra nesdílejí žádný měnitelný stav, jen omezené prstence bez zámků, po jednom pro každou uspořádanou dvojici jader. Nejvyšší jádro spouští SQLite a nic jiného.</p>
<p>Síťová karta má dnes jeden pár front, který drží jádro 0. To každý tok MQTT podle Toeplitzova hashe jeho čtveřice směruje na jedno z pracovních jader a odesílá to, co pracovní jádra vrátí. Relace žije na svém domovském jádře, určeném hashem identifikátoru klienta, takže převzetí, závěti a trvanlivost nikdy nepřekračují hranice jader. Spojení, které přistane na jiném jádře, si tam ponechá svůj TCP a TLS, a běžný MQTT předává domovskému jádru s řízením toku založeným na kreditech. Každá směrovaná zpráva se rozesílá také ostatním částem brokeru, z nichž každá drží repliku retained zpráv.</p>
<p>Tento model drží pohromadě tři pravidla. <strong>Zpracování do konce:</strong> přijatý paket se zpracuje celý na jednom jádře, od TCP přes dešifrování TLS na místě, parsování MQTT a směrování až po šifrovanou odpověď, bez front úloh a bez kopírování do aplikačních vyrovnávacích pamětí. <strong>Adaptivní dotazování:</strong> pod zátěží se přijímací fronty aktivně dotazují, v klidu se jádro vrací k přerušením a <code>WFI</code>; obslužné rutiny přerušení pouze potvrdí přerušení a označí práci. <strong>Watchdog časovač:</strong> tik 10 ms na jádro zachytí obslužné rutiny, které překročí svůj rozpočet.</p>
<h3 id="zivot-jednoho-publish">Život jednoho PUBLISH</h3>
<figure><img loading="lazy" alt="Cesta jedné zprávy PUBLISH s QoS 1: od záznamu TLS na síťové kartě přes dešifrování, kontrolu ACL a směrování až po PUBACK sloučený s TCP ACK v jediném segmentu." src="https://aldo.tech/blog/urmqtt/img/publish.svg" /><figcaption>Cesta jedné zprávy PUBLISH s QoS 1: od záznamu TLS na síťové kartě přes dešifrování, kontrolu ACL a směrování až po PUBACK sloučený s TCP ACK v jediném segmentu.</figcaption></figure>
<p>Zařízení pošle záznam TLS. Síťová karta jej předá smyčce jádra deskriptorem, bez kopírování. Smyčka zkontroluje TCP, dešifruje TLS na místě a parsuje MQTT z vypůjčené vyrovnávací paměti, poté v paměti zkontroluje ACL, limit rychlosti a cestu stromem témat. Pokud má o toto téma zájem jiné jádro, dostane směrovací zprávu s odkazem na obsah a identifikátorem tématu; databáze dostane telemetrický záznam nebo aktualizaci retained zprávy, v dávce. Odpověď PUBACK se šifruje přímo do odchozí vyrovnávací paměti a sloučí se s TCP ACK, takže zařízení odchází jediný segment. Jiné jádro zpráva stojí jedno šifrování na odběratele v okamžiku odeslání.</p>
<h2 id="kolik-stoji-jedno-spojeni">Kolik stojí jedno spojení</h2>
<p>Cílem je <strong>nejvýše 512 bajtů na nečinné TLS spojení</strong>, s ambicí dosáhnout 256. Dokud spojení odpočívá, nic se pro něj nedrží ve vyrovnávací paměti: přijatá data se zpracovávají přímo ze síťového rámce a vyrovnávací paměti pro odesílání, záznamy a MQTT existují jen tehdy, když je něco nedokončeno nebo nepotvrzeno. Jádro to samo měří: přesné čítače haldy v požadovaných bajtech i v blocích, které zabírají, zapisované každých deset sekund jako halda nad základní úrovní na otevřené spojení.</p>
<table>
<thead>
<tr>
<th>Část</th>
<th style="text-align: right;">Velikost</th>
<th>Jak</th>
</tr>
</thead>
<tbody>
<tr>
<td>Řídicí blok TCP</td>
<td style="text-align: right;">96 B</td>
<td>zabalené příznaky, 16bitové RTT/RTO, slot v kole časovačů, žádné vyrovnávací paměti v klidu</td>
</tr>
<tr>
<td>Slot v tabulce TCP</td>
<td style="text-align: right;">4–8 B</td>
<td>pouze index spojení, tabulka nejvýše z poloviny plná</td>
</tr>
<tr>
<td>Spojení a stav záznamů TLS 1.3</td>
<td style="text-align: right;">152 B</td>
<td>tajemství a sekvence na směr (40 B); klíč a IV se odvozují pro každý záznam</td>
</tr>
<tr>
<td>Spojení MQTT</td>
<td style="text-align: right;">48 B</td>
<td>identita a závěť na haldě jen po dobu, kdy existují</td>
</tr>
<tr>
<td>Relace MQTT, horká podoba</td>
<td style="text-align: right;">64 B</td>
<td>jedno předplatné v řádku, internované identifikátory uzlů</td>
</tr>
<tr>
<td>Předplatné <code>devices/&lt;id&gt;/cmd/#</code></td>
<td style="text-align: right;">0 uzlů stromu</td>
<td>uloženo relativně k identifikátoru relace, společné všem zařízením</td>
</tr>
</tbody>
</table>
<p>Naměřeno pod HVF se čtyřmi virtuálními jádry, z toho třemi pracovními, přičemž každé zařízení drží spojení mTLS a jedno předplatné s QoS 1, včetně zaokrouhlování alokátoru:</p>
<table>
<thead>
<tr>
<th style="text-align: right;">Spojení</th>
<th style="text-align: right;">Bajtů na spojení</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;">960</td>
<td style="text-align: right;">531 B</td>
</tr>
<tr>
<td style="text-align: right;">9 922</td>
<td style="text-align: right;">494 B</td>
</tr>
<tr>
<td style="text-align: right;">10 197</td>
<td style="text-align: right;">474 B</td>
</tr>
</tbody>
</table>
<p>Fond síťových rámců je pevný, zhruba 2 MiB, což je přibližně 200 B na spojení při deseti tisících spojení a asi 2 B při milionu. Více jsme změřit nedokázali: uživatelská síť QEMU na macOS přestala přijímat spojení na 10 197, zhruba na hranici 10 240 deskriptorů procesu QEMU. Číslo klesá, jak se pevné části rozkládají na více spojení, takže neroste s jejich počtem.</p>
<h2 id="bezpecnostni-model">Bezpečnostní model</h2>
<p>Bezpečnost je první priorita a vyhrává všechny rozpory s ostatními cíli. Model má několik vrstev:</p>
<ul>
<li><strong>Jeden port.</strong> TCP 8883, pouze TLS 1.3, povinné klientské certifikáty podepsané CA vestavěnou do obrazu. Identifikátor klienta MQTT musí být shodný s identifikátorem zařízení z certifikátu.</li>
<li><strong>Žádný vstup.</strong> Žádný shell, konzolový vstup, SSH, administrační API, dynamický kód, SQL dostupný po síti ani souborový systém.</li>
<li><strong>Paměťová bezpečnost.</strong> Rust, s <code>forbid(unsafe_code)</code> v protokolových balíčcích; každý blok <code>unsafe</code> má komentář <code>// SAFETY:</code>, který kontroluje lint, a na vnější vstup nedochází k panice: clippy v balíčcích na datové cestě zakazuje <code>unwrap</code>, <code>expect</code>, indexování a <code>panic!</code>.</li>
<li><strong>Zmírnění zneužití.</strong> W^X se vynucuje kódováním deskriptorů a bitem <code>SCTLR_EL1.WXN</code> a revidují se při každém startu. Stránkové tabulky jsou po startu pouze pro čtení, bez zapisovatelného aliasu. Dolní polovina adresního prostoru je vyloučena. Každý zásobník má ochranné stránky a výjimky běží na samostatném zásobníku. PAC a BTI se zapínají, když je procesor podporuje, a ověřují to kouřové testy.</li>
<li><strong>Odolnost proti zneužití.</strong> SYN cookies; řízení příjmu odmítá nová spojení, ještě před jakoukoli kryptografií, když je halda nízká, když databáze zaostává, když je příliš mnoho probíhajících handshaků na jádro nebo na zdrojovou adresu nebo když je rychlost handshaků příliš vysoká. Limity rychlosti na zařízení, omezené fronty, TCP okno vázané na protitlak brokeru, omezené logování, watchdog časovač a kontrola zdraví.</li>
<li><strong>Neměnnost.</strong> Konfigurace, CA a serverový klíč jsou vestavěny do obrazu. Změna čehokoli znamená nový obraz.</li>
</ul>
<p>Model hrozeb je zapsán v <code>docs/SECURITY.md</code>.</p>
<h2 id="uloziste-bez-souboroveho-systemu">Úložiště bez souborového systému</h2>
<p>Data žijí na samostatném disku, virtio-blk nebo virtio-scsi, na kterém není souborový systém. Rozvržení je minimální a vlastní:</p>
<pre><code>LBA 0      [ superblok: magické číslo · verze rozvržení · uuid · tabulka oblastí · CRC-32C ]
LBA 8, 16  [ tabulka velikostí souborů, sloty A/B (vítězí nejnovější platný) ]
LBA 64     [ hlavní databáze SQLite ½ ][ SQLite WAL ⅛ ][ žurnál 1 MiB ][ telemetrický žurnál: 8 oddílů … ]
poslední 4 KiB [ prostor pro sebetest blokového zařízení při startu ]
</code></pre>
<p>SQLite (vendorovaná verze 3.54.1, s kontrolou otisku při sestavení) je přeložen do jádra clangem, bez standardní knihovny, a používá pevnou haldu <code>memsys5</code>. Jeho VFS mapuje tři pevné soubory na oblasti disku. Běží v režimu WAL s <code>locking_mode=EXCLUSIVE</code>, takže nepotřebuje index ve sdílené paměti, a se <code>synchronous=FULL</code>; pokud <code>quick_check</code> při startu najde chybu, systém se bezpečně zastaví. V databázi jsou registr zařízení, odvolání, retained zprávy a trvalé relace s frontami zpráv QoS 1 pro zařízení, která nejsou připojena.</p>
<p>Jádro pro databázi, to s nejvyšším číslem, spouští SQLite a nic jiného. Datová rovina mu posílá změny stavu přes prstenec SPSC bez zámků; ono je shromažďuje do jedné transakce každých 5 ms nebo každých 512 operací a potvrzení vrací druhým prstencem. Prázdný disk se naformátuje při prvním startu; nic jiného se nikdy nepřeformátovává.</p>
<p>Telemetrie jde jinou cestou. Každý přijatý PUBLISH na tématech z <code>storage.telemetry_topics</code> odchází do oddílu telemetrického žurnálu svého jádra: prstenec segmentů s CRC u každého záznamu, osolený sekvencí segmentu, takže se staré průchody prstencem nikdy znovu neobjeví. Jádra předávají záznamy databázi vlastním prstencem, bez kopírování, a ta je zapisuje každých 5 ms nebo každých 256 KiB. Při startu obnova zachová každý zapsaný záznam, odřízne nedokončený konec a pokračuje v novém segmentu. Uchovávání je podle velikosti, přes prstenec, i podle stáří, přes <code>retention_days</code>. Ověřili jsme to s milionem záznamů a s <code>kill -9</code> uprostřed zápisu.</p>
<h2 id="cisla">Čísla</h2>
<p>Nástroj pro zátěž v <code>tools/loadgen</code> dělá tři věci: bouři handshaků TLS 1.3 s mTLS a CONNECT, dlouhodobá spojení s PINGREQ, která každou sekundu publikují telemetrii, a bouře opakovaných připojení. <code>make bench</code> na Apple M4 Pro pod HVF dává:</p>
<table>
<thead>
<tr>
<th>Pracovní jádra</th>
<th>handshaků/s v hostu</th>
<th>škálování</th>
<th>přes uživatelskou síť QEMU (spojení/s)</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>862</td>
<td>1,00×</td>
<td>789</td>
</tr>
<tr>
<td>2</td>
<td>1 724</td>
<td>2,00×</td>
<td>636</td>
</tr>
<tr>
<td>4</td>
<td>3 438</td>
<td>3,99×</td>
<td>536</td>
</tr>
</tbody>
</table>
<p>Číslo v hostu, kde jsou klient i server uvnitř téhož virtuálního stroje a každé pracovní jádro běží samo za sebe, ukazuje, že datová rovina škáluje lineárně. Síťové číslo je omezeno jednovláknovou uživatelskou sítí QEMU, ne hostem; i to je zapsáno jako ADR-032.</p>
<p>Bouře opakovaných připojení testujeme tak, že se všechna zařízení připojí najednou, odmítnuté pokusy opakují se zpožděním a pak všechna spadnou bez DISCONNECT. Parametr <code>heap_limit_mb=</code> ve vývojovém režimu přiměje řízení příjmu považovat haldu za menší, než je, takže bouři větší než halda lze vyzkoušet s malým počtem socketů. V jednom takovém kole s 3 000 zařízeními se připojilo 1 368 za 36 sekund a 5 619 pokusů bylo řádně odmítnuto, při limitu 8 MiB; paměť se po bouři vrátila.</p>
<h2 id="jak-pracujeme">Jak pracujeme</h2>
<p>Projekt řídí psaná specifikace, <code>PROMPT.md</code>, s plánem milníků, a každý milník končí stejným rituálem: <code>make check</code> a <code>make smoke</code> zelené, jeden commit se štítkem <code>M&lt;n&gt;</code>, zápis v <code>docs/PROGRESS.md</code> a ADR pro každé rozhodnutí, které není zřejmé. Pravidla jsou krátká a nevyjednáváme o nich: žádná panika na vnější vstup, <code>unsafe</code> jen v hardwarových balíčcích a balíčcích FFI a vždy s komentářem, žádné alokace na horké cestě, komentář u každého pořadí atomických operací, každý limit pochází z konfigurace a záměrné provizorní části selhávají nahlas.</p>
<p>CI dělá víc, než bychom stihli ručně: fmt, clippy pro host i pro <code>aarch64-unknown-none</code>, typos, actionlint a shellcheck; testy na Linuxu a macOS na arm64; sestavení jádra pro vývojovou i produkční konfiguraci s velikostí ELF v souhrnu; start pod QEMU se všemi scénáři kouřového testu a ukládáním sériových logů; cargo-deny pro bezpečnostní upozornění, licence a zdroje; minutu fuzzingu na každý z jedenácti cílů a v noci deset minut na cíl s korpusem v cache. K tomu CodeQL, OpenSSF Scorecard, Dependabot a vydání s původem SLSA. Každá cizí akce je připnutá na úplný SHA commitu.</p>
<p>Reprodukovatelnost chápeme doslova: <code>make repro</code> sestaví HEAD dvakrát v čerstvých pracovních stromech a porovná ELF, ISO i surový obraz bajt po bajtu. Vydání jsou podepsána minisign a artefakty lze ověřit pomocí <code>gh attestation verify</code>. Produkční obraz obsahuje soukromý klíč serveru, takže samotné ISO je tajemství; obrazy u vydání na GitHubu jsou vývojové, s dočasnými klíči, a slouží jen k vyzkoušení.</p>
<h2 id="co-jsme-se-naucili">Co jsme se naučili</h2>
<p>Několik lekcí, které změnily plán:</p>
<ul>
<li><strong>Vlastní TCP se přece jen vyplatilo.</strong> Prototyp běžel na smoltcp a dobře nám sloužil do M13. Server, který drží stovky tisíc spojení, ale nepotřebuje klientské TCP, potřebuje SYN cookies, kolo časovačů a stav o 96 bajtech. V M14 jsme smoltcp nahradili vlastním zásobníkem pouze pro stranu serveru, s testy simulované ztráty a fuzzingem segmentů.</li>
<li><strong>Paměť měřte zevnitř.</strong> Odhady na spojení byly k ničemu, dokud jádro samo nezačalo počítat haldu v blocích alokátoru, a ne v požadovaných bajtech. Teprve pak jsme viděli, kam 512 bajtů skutečně odchází, a sestoupili jsme na 494.</li>
<li><strong>Nástroj pro testování je součást produktu.</strong> Největším omezením nebylo jádro, ale uživatelská síť QEMU a počet efemérních portů na vývojovém počítači. Pro sto tisíc a více spojení je potřeba <code>vmnet-shared</code> na macOS nebo Linux s KVM, rozhraním tap a více klientskými adresami.</li>
<li><strong>SQLite bez operačního systému je možné</strong>, ale jen za přísných podmínek: pevná halda, exkluzivní zamykání bez sdílené paměti, VFS, které o souborech neví, a řízení příjmu, které nepustí databázi do zpoždění.</li>
<li><strong>Bouře opakovaných připojení jsou běžný stav</strong>, ne výjimka. Když spadne mobilní síť a vrátí se, tisíce zařízení klepou současně. Proto řízení příjmu odmítá spojení před jakoukoli kryptografií a každé odmítnuté spojení stojí téměř nic.</li>
</ul>
<h2 id="co-nasleduje">Co následuje</h2>
<p>Práce po M17 je naplánována ve třech směrech a pořadí mezi nimi je zapsáno v <code>docs/PLAN.md</code>. Nic z toho zatím není postaveno.</p>
<ul>
<li><strong>Jedno ISO pro více platforem.</strong> Stejný soubor <code>urmqtt-&lt;env&gt;-&lt;verze&gt;.iso</code> a jeden surový obraz GPT, které se zavádějí na aarch64 UEFI, x86_64 UEFI a x86_64 BIOS, ve virtuálních strojích i na skutečném hardwaru, počínaje jedním Intel PC. Stále zařízení se vším uvnitř, reprodukovatelné a podepsané.</li>
<li><strong>Surový disk, bez souborového systému.</strong> Specifikace formátu na úrovni bajtů, hostitelský nástroj <code>urmqtt-disk</code>, který sdílí parsery s jádrem, zarovnání oblastí na 1 MiB, růst disku s daty, discard pro tence alokované disky, snímky databáze A/B, kontrola bitů a volitelně šifrování AES-256-XTS s hardwarovým AES.</li>
<li><strong>Prokazatelně bezpečné.</strong> Odvolání a seznam povolených zařízení za běhu přes podepsaný řídicí kanál, bez nového obrazu; diferenciální a konformační testování vlastního TLS a TCP; KASLR, ochrana zásobníku, kCFI, MTE a zpevněný alokátor; SQLite izolovaný na úrovni EL0; Secure Boot; SBOM a cargo-vet; a nakonec balíček pro externí audit.</li>
</ul>
<p>Platformy, které nabízejí jen zařízení jiná než virtio, jako AWS ENA nebo Azure MANA, zatím nejsou podporovány. Raspberry Pi 4 a 5 jako hostitel KVM, Hetzner CAX a Oracle Ampere A1 jsou zdokumentovány a podporovány kódem, ale zatím nebyly vyzkoušeny na skutečné službě.</p>
<h2 id="vyzkousejte">Vyzkoušejte</h2>
<p>Repozitář je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a>. S Rustem s připnutým nightly toolchainem a QEMU stačí <code>make certs</code>, <code>make run</code> a jeden klient mosquitto k prvnímu PUBLISH přes mTLS na vlastním počítači. Upozornění ze začátku platí i na konci: je to pre-alfa a vše, co funguje, je napsáno v <code>docs/PROGRESS.md</code>. Hlášení chyb, otázky k návrhu a bezpečnostní hlášení podle pokynů v <code>SECURITY.md</code> jsou vítány. O každém dalším milníku se zde ozveme.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>Spouštíme blog: poznámky z praxe</title>
      <link>https://aldo.tech/cs/blog/spoustime-blog/</link>
      <guid isPermaLink="true">https://aldo.tech/cs/blog/spoustime-blog/</guid>
      <pubDate>Sat, 17 Oct 2026 08:00:00 +0000</pubDate>
      <description>Proč firma, která staví terminály, aplikace a elektroniku pro veřejnou dopravu, začíná psát, a o čem tu budete číst.</description>
      <content:encoded><![CDATA[<p>Většinu toho, co děláme, nikdo nevidí. Software v terminálu u dveří autobusu, firmware v zařízení, které nikdy nedostane displej, server, který v noci mění denní transakce v reporty. Když všechno funguje, funguje potichu. Když ne, všimnou si toho všichni.</p>
<p>Na tomto blogu si zapisujeme, co jsme se cestou naučili. Ne marketingové texty, ale konkrétní poznámky: co se pokazilo, jak jsme to vyřešili a co bychom dnes udělali jinak.</p>
<h2 id="o-cem-budeme-psat">O čem budeme psát</h2>
<ul>
<li><strong>Odbavení cestujících ve veřejné dopravě.</strong> Bezkontaktní karty, moduly SAM, práce bez sítě a všechno, co musí terminál vydržet ve vozidle, které se dvanáct hodin denně třese.</li>
<li><strong>Mobilní aplikace v terénu.</strong> Android a Flutter na zařízeních, která nejsou obyčejné telefony: se čtečkami karet, tiskárnami a bateriemi, které musí vydržet celou směnu.</li>
<li><strong>Backend a dohled nad flotilou.</strong> Jak poznat, že zařízení ve vozidle má problém, dřív než ho nahlásí řidič.</li>
<li><strong>Hardware.</strong> Od prototypu na stole po desku, která jde do sériové výroby, a všechny chyby mezi tím.</li>
<li><strong>Lokální modely AI.</strong> Kde dávají smysl modely na vlastním hardwaru a kde jsou cloud a API prostě lepší volba.</li>
</ul>
<h2 id="jak-budeme-psat">Jak budeme psát</h2>
<p>Krátce a konkrétně, s kódem a čísly tam, kde je máme. Publikovat budeme, když budeme mít co říct, ne podle rozvrhu. Příspěvky píšeme chorvatsky a překládáme je do všech jazyků tohoto webu; sledovat nás můžete i přes <a href="https://aldo.tech/cs/blog/feed.xml">RSS</a>.</p>
<p>Pokud vás zajímá nějaké téma nebo řešíte podobný problém, napište nám na <a href="mailto:info@aldo.tech">info@aldo.tech</a>.</p>]]></content:encoded>
      <category>uvod</category>
    </item>
  </channel>
</rss>
