<?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>ALDO TECH blog</title>
    <link>https://aldo.tech/blog/</link>
    <atom:link href="https://aldo.tech/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Bilješke iz prakse: javni prijevoz, naplata, hardver i softver koji mora raditi na terenu.</description>
    <language>hr</language>
    <item>
      <title>urMQTT nakon VPS-a: SQLite u korisničkom načinu, usporedba s Mosquittom, konfiguracija kao podatak i graditelj u pregledniku</title>
      <link>https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/</link>
      <guid isPermaLink="true">https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/</guid>
      <pubDate>Sun, 01 Nov 2026 08:00:00 +0000</pubDate>
      <description>Što se dogodilo nakon drugog teksta o urMQTT-u: SQLite izoliran izvan jezgre, mjerenja protiv Mosquitta na istom VPS-u (i ono što nije išlo u našu korist), pronađeni uzroci sporog jednog toka, konfiguracija kao podatak uz web graditelj ISO-a i emulator u pregledniku, neobavezni običan MQTT i što još nije dokazano.</description>
      <content:encoded><![CDATA[<p>Drugi tekst, <a href="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/">urMQTT na pravom VPS-u</a>, završio je popisom "Što slijedi": H6 (SQLite izvan jezgre), H7 (sigurnost vremena), X11 (usporedba s Mosquittom na istom VPS-u) i prvo izdanje. Ovaj tekst prati taj popis istim redom i onda dodaje dvije stvari kojih na popisu nije bilo, jer su se pojavile usput: konfiguracija kao podatak, s web graditeljem ISO-a, i neobavezni običan MQTT bez TLS-a. Kao i prije: što smo planirali, što smo izgradili, što nas je iznenadilo i što je otvoreno. U ovom tekstu ima i nekoliko rezultata koji ne idu u korist urMQTT-a; ostavili smo ih jer su dio istine.</p>
<p>Napomena unaprijed ostaje ista: projekt je pred-alfa, prvo izdanje je samo za VPS-ove s KVM-om, a brojke mjere kod, testove i određene pokuse, ne zrelost u produkciji.</p>
<h2 id="kako-je-to-izgledalo-u-vremenu">Kako je to izgledalo u vremenu</h2>
<p>Od prošlog teksta (31.10. u 23:00) do danas (1.11. u 20:41) u repozitoriju je 59 novih commitova, ukupno 178. Svi su nastali u jednom kalendarskom danu, pa vrijedi isto upozorenje kao prije: projekt se gradi uz Claude Code (commitovi nose <code>Co-Authored-By: Claude</code>), plan, kod, testovi i ADR-ovi nastaju u kratkim zatvorenim petljama, a brojke ispod govore o količini koda i provjera, ne o tome koliko je ljudskog rada u njima.</p>
<figure><img loading="lazy" alt="Rast od prošlog teksta: Rust redaka od 89 790 do 112 408, #[test] funkcija od 739 do 926, ADR-ova od 104 do 126 i commitova od 119 do 178. Svaki red ima vlastitu ljestvicu." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/growth-since.svg" /><figcaption>Rast od prošlog teksta: Rust redaka od 89 790 do 112 408, #[test] funkcija od 739 do 926, ADR-ova od 104 do 126 i commitova od 119 do 178. Svaki red ima vlastitu ljestvicu.</figcaption></figure>
<p>Brojke su izračunate istom metodom kao u prošlom tekstu: svi <code>.rs</code> fajlovi izvan <code>vendor/</code> i <code>fuzz/</code>, <code>#[test]</code> funkcije, naslovi <code>## ADR-</code> u <code>docs/DECISIONS.md</code>.</p>
<table>
<thead>
<tr>
<th>Trenutak</th>
<th>Commit</th>
<th>Vrijeme</th>
<th style="text-align: right;">Rust redaka</th>
<th style="text-align: right;">Testova</th>
<th style="text-align: right;">ADR-ova</th>
</tr>
</thead>
<tbody>
<tr>
<td>prošli tekst</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 gotov</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>web graditelj i zrcalo izdanja</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>običan MQTT, dokumentacija</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>danas</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>Radilo se paralelno na više grana, pa redoslijed commitova nije strogo redoslijed rada. ADR-ovi su numerirani do 232, ali numeracija ima praznina (dio je preimenovan pri spajanju grana); brojimo naslove, ne najveći broj. Izdanja: v0.2.0 (1.11., 18:45 UTC) i v0.2.1 (1.11., 20:22 UTC), oba označena kao pre-release.</p>
<h2 id="h6-sqlite-u-korisnickom-nacinu-rada">H6: SQLite u korisničkom načinu rada</h2>
<p><strong>Što smo planirali.</strong> SQLite je 254 268 redaka C-a koji parsira bazu, WAL i dnevnik s diska, a disk može biti izrađen ili izmijenjen. Dotad je radio u jezgri, s pravima jezgre. <strong>Što smo izgradili</strong> (ADR-210 do ADR-212): SQLite, njegovi libc dijelovi i heap premješteni su u zaseban program, <code>dbworker</code>, koji radi na EL0 (aarch64) odnosno ring 3 (x86_64), u vlastitom adresnom prostoru. Jezgra više ne linka SQLite. Radnik s jezgrom razgovara kroz zajednički prozor memorije i dvanaest sistemskih poziva, a svaka duljina i pomak koji se vrate provjeravaju se prije upotrebe. Greška u radniku (nedopušten pristup, nedefinirana instrukcija, prekoračen vremenski proračun od 30 s) završava poziv, radnik se ponovno pokreće i baza se otvara iz WAL-a kao pri podizanju; nakon 5 ponovnih pokretanja u minuti pohrana se proglašava neispravnom, a ne pokreće se u beskraj.</p>
<p>Cijena, izmjerena na aarch64 pod HVF-om, ista VM, jezgra s ugrađenim SQLite-om prema jezgri s radnikom, šest naizmjeničnih pokretanja svake:</p>
<table>
<thead>
<tr>
<th>Mjerenje</th>
<th>Prije (u jezgri)</th>
<th>Poslije (radnik)</th>
<th>Razlika</th>
</tr>
</thead>
<tbody>
<tr>
<td>ulazak i izlaz iz korisničkog načina</td>
<td>-</td>
<td>1,1 us po krugu</td>
<td></td>
</tr>
<tr>
<td><code>db-bench</code>, 5 000 umetanja u 50 transakcija, najbolji / medijan</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 umetanje po transakciji, najbolji / medijan</td>
<td>2 319 / 2 148 op/s</td>
<td>2 307 / 2 126 op/s</td>
<td>-0,5 % / -1 %</td>
</tr>
<tr>
<td>cijela sigurnosna kopija jednim pozivom (23 MiB)</td>
<td>514 ms</td>
<td>635 ms</td>
<td>+23 %</td>
</tr>
</tbody>
</table>
<p>Zapisi na disk (1 572) i flushevi (319) su identični, kako i mora biti. Nije izolirano ovo: <strong>jezgra i dalje vjeruje onome što radnik vrati</strong>, nakon provjere oblika. Kompromitirani radnik može lagati (na primjer vratiti praznu listu opozvanih certifikata, a time ih "odopozvati"), ali ne može čitati memoriju jezgre, TLS ključeve ni ključeve tiketa. Nema KPTI-ja, pa curenja tipa Spectre/Meltdown nisu spriječena i ne tvrdimo ništa o njima; PAC ključ dijele EL0 i EL1. Na x86_64 cijenu prebacivanja nismo izmjerili na KVM-u (pod TCG-om bi broj bio besmislen), a samo fuzziranje radnika i protokola nije gotovo. Skup testova za pohranu (79 scenarija, svaki vrti SQLite u radniku) prolazi na aarch64 HVF-u i x86_64 TCG-u, na aarch64 TCG-u 76 (jedan se preskače jer TCG nema MSI-X).</p>
<p><strong>H7 je zatvoren, ali ne onako kako smo najavili.</strong> Donja granica vremena (ADR-050) i SNTP s konsenzusom (T1) pokrivaju opseg H7; NTS (RFC 8915) ostaje izvan opsega i zapisan je kao preostali rizik, a ne kao nešto što radi. Ne smatramo da je sigurnost vremena "riješena", nego da je njezin dokaziv dio gotov.</p>
<p><strong>Nova mrežna opcija.</strong> Contabo ne daje DHCP prilagođenom ISO-u, pa je VPS dotad tražio drugu sliku od laboratorijske VM. Sada <code>network.mode = "auto"</code> (ADR-214) prvo pita DHCP, a ako zakupa nema u zadanom roku (<code>dhcp_timeout_s</code>, zadano 15 s) ili ga izgubi, primjenjuje zadanu statičku adresu. Isprobano ručno na QEMU-u (s DHCP-om koristi zakup, bez mreže se nakon 5 s vraća na statičku adresu); smoke scenarija za to još nema.</p>
<h2 id="x11-usporedba-s-mosquittom-na-istom-vps-u">X11: usporedba s Mosquittom na istom VPS-u</h2>
<p>Ista vrsta stroja kao u prošlom tekstu (Contabo, 4 vCPU AMD EPYC, 8 GiB), opterećenje s Linux domaćina 15 ms dalje, uzajamni TLS 1.3 s istim certifikatima na obje strane, Mosquitto 2.0.21 na Debianu 13 bez trajnosti. Metoda i odluke su u ADR-213, sirovi podaci u <code>docs/perf-results/</code>. Medijan od tri pokretanja.</p>
<figure><img loading="lazy" alt="Potpuna rukovanja po sekundi (TLS 1.3 + klijentski certifikat + CONNECT, bez nastavka sesije) pri 32, 64 i 128 klijenata: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. To su donje granice: generator opterećenja je bio blizu vlastitog ograničenja." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/mosquitto.svg" /><figcaption>Potpuna rukovanja po sekundi (TLS 1.3 + klijentski certifikat + CONNECT, bez nastavka sesije) pri 32, 64 i 128 klijenata: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. To su donje granice: generator opterećenja je bio blizu vlastitog ograničenja.</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>urMQTT</th>
<th>Mosquitto 2.0.21</th>
</tr>
</thead>
<tbody>
<tr>
<td>rukovanja/s, 32 / 64 / 128 klijenata (bench profil)</td>
<td>169 / 278 / 413</td>
<td>82 / 140 / 184</td>
</tr>
<tr>
<td>isto, s ograničenjima kakva se isporučuju</td>
<td>185 / 262 / 333 (pri 128: 202 do 286 grešaka, reseti rukovanja)</td>
<td>-</td>
</tr>
<tr>
<td>memorija po neaktivnoj vezi (20 000 veza)</td>
<td>0,50 KB hepa jezgre (0,66 KB pri 5 000)</td>
<td>26,8 KB rasta procesa</td>
</tr>
<tr>
<td>latencija p50, QoS 0 / QoS 1, 800 poruka/s, 64 B</td>
<td>14,0 / 14,8 ms</td>
<td>21,4 / 39,5 ms</td>
</tr>
<tr>
<td>QoS 1, 4 800 poruka/s, isporučeno/s (p50)</td>
<td>4 209 (77 ms)</td>
<td>4 235 (197 ms)</td>
</tr>
<tr>
<td>100 pretplatnika, 50 poruka/s: p50</td>
<td>16,6 ms</td>
<td>99 ms</td>
</tr>
<tr>
<td>jedan tok, QoS 0, 64 B, objavljeno/s</td>
<td>2 962 (0 do 7 733)</td>
<td>22 749</td>
</tr>
</tbody>
</table>
<p>Što to znači, a što ne. Rukovanja su <strong>donje granice za oba poslužitelja</strong>: generator opterećenja je dijeljena VM s 60 do 70 % ukradenog procesorskog vremena i pri 128 niti bio je blizu vlastitog ograničenja, a urMQTT-ove radne jezgre bile su zauzete samo 12 do 13 %. Omjer pri 128 klijenata (oko 2,2 puta) najsmisleniji je; pri 32 i 64 većinom mjeri čekanje klijenata. Brojke memorije ne broje isto (hep jezgre nasuprot RSS-u procesa), pa je "oko 50 puta manje" red veličine, ne precizan omjer. Latencija od 14 ms je jedan krug mreže (oko 15 ms), dakle "nema mjerljivog vremena brokera"; repovi (p99 oko 100 do 200 ms) su kod oba brokera dominirani dijeljenim domaćinima. Razlike ispod oko 20 % ne tvrdimo.</p>
<p><strong>Što nije išlo u našu korist</strong> i ostalo je u tablicama: jedan TCP tok kroz urMQTT nosio je 2 do 8 puta manje nego kroz Mosquitto, s promjenama od nule do 7,7 tisuća poruka u sekundi među pokretanjima, a 20 ms watchdog rukovatelja <strong>prekidao je žive veze</strong> kad je hipervizor zaustavio vCPU (u produkcijskoj postavci <code>action = "drop"</code>, 6 do 8 veza po pokretanju pri QoS 0 preplavljivanju). Obje stvari su opisane u sljedećem odjeljku jer su imale uzroke u našem kodu.</p>
<h2 id="zasto-je-jedan-tok-bio-spor-uzroci-a-ne-izgovori">Zašto je jedan tok bio spor: uzroci, a ne izgovori</h2>
<p>Najlakše bi bilo reći "dijeljeni VPS". Nismo, nego smo uz isti VPS i istu metodu tražili uzroke. Pronađeno je pet uzroka, četiri u putu podataka i jedan u watchdogu (ADR-221 do ADR-224):</p>
<table>
<thead>
<tr>
<th>Simptom u X11</th>
<th>Uzrok u urMQTT-u</th>
<th>Promjena</th>
</tr>
</thead>
<tbody>
<tr>
<td>jedan tok 1,9k poruka/s, često <strong>0 isporučeno</strong></td>
<td><strong>Izgubljeno buđenje:</strong> petlja izlaza vraćala je popis spremnih veza preko upravo ispisanog; pretplatnik čiji se izlazni međuspremnik jednom napunio više nikad nije bio posluživan dok ga PINGRESP nije probudio</td>
<td>popravljeno (ADR-223)</td>
</tr>
<tr>
<td>nekoliko postotaka gubitka pretvara 48k u 14k poruka/s</td>
<td><strong>svaki segment nakon izgubljenog bio je odbačen</strong> (prijem samo po redu), pa je oporavak koštao krug mreže po segmentu</td>
<td>red segmenata izvan redoslijeda, SACK (ADR-222)</td>
</tr>
<tr>
<td>strop 4,4 MB/s pri 15 ms</td>
<td>16-bitni prozor, red slanja od 64 KiB i spori start koji je brojao ACK-ove, a ne bajtove</td>
<td>skaliranje prozora, vremenske oznake, brojanje bajtova (ADR-222)</td>
</tr>
<tr>
<td>svaki krug toka koji stane čeka 0 do 10 ms</td>
<td><strong>dispečer je spavao do sljedećeg otkucaja od 10 ms</strong> (virtio-net nema prekid za prijem)</td>
<td>ispitivanje NIC-a svakih 200 us dok postoje veze (ADR-224)</td>
</tr>
<tr>
<td>1 do 8 prekinutih veza po pokretanju</td>
<td><strong>watchdog je rukovatelju naplaćivao proteklo vrijeme</strong>, a VPS zaustavlja vCPU-e često i dugo</td>
<td>proračun je vrijeme koje je jezgra stvarno radila (ADR-221)</td>
</tr>
</tbody>
</table>
<p>Zaustavljanja su stvarna: na ovom VPS-u jezgre 0 do 2 imale su medijan 36 do 80 zakašnjelih otkucaja po 5 sekundi, a najgore zaustavljanje u prozoru bilo je medijan oko 70 ms i do 0,9 s (jezgra 3, baza i dnevnik: do 1,55 s), dok domaćin ne javlja ukradeno vrijeme (<code>host steal 0 us</code>). Zato je watchdog trebao drugi sat, ne veći proračun.</p>
<figure><img loading="lazy" alt="Prije i poslije, na istom VPS-u s istom metodom, medijan tri do šest pokretanja: jedan tok QoS 0, 64 B: objavljeno 1,9k, 23,7k (Mosquitto 29,3k), isporučeno 0 (medijan), 12,6k (29,2k). 16 parova, preplavljivanje, 64 B: objavljeno 24,7k, 43,2k (31,4k), isporučeno 20,0k, 37,4k (30,7k)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/before-after.svg" /><figcaption>Prije i poslije, na istom VPS-u s istom metodom, medijan tri do šest pokretanja: jedan tok QoS 0, 64 B: objavljeno 1,9k, 23,7k (Mosquitto 29,3k), isporučeno 0 (medijan), 12,6k (29,2k). 16 parova, preplavljivanje, 64 B: objavljeno 24,7k, 43,2k (31,4k), isporučeno 20,0k, 37,4k (30,7k).</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>X11 kod (7f9517e)</th>
<th>sada (86a71ff)</th>
<th>Mosquitto</th>
</tr>
</thead>
<tbody>
<tr>
<td>jedan tok, QoS 0, 64 B, objavljeno / isporučeno u s</td>
<td>1,9k / 0</td>
<td>23,7k / 12,6k</td>
<td>29,3k / 29,2k</td>
</tr>
<tr>
<td>jedan 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 parova, preplavljivanje, 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 parova, preplavljivanje, 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>izgubljene veze po pokretanju (16 parova, 64 B)</td>
<td>13 (7 do 16)</td>
<td>0 (0 do 0)</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>Watchdog: 148 prekinutih veza u 30 opterećenih pokretanja prije, 0 u 24 pokretanja poslije; na lokalnoj napravi koja odgađa i gubi okvire, pri gubitku od 0,1 % jedan tok od 64 B s 14,3k poruka/s (jezgra X11) sada ima 41,0k, a pri 0,5 % s 5,0k na 17,7k.</p>
<p><strong>Što ovo ne kaže.</strong> urMQTT <strong>i dalje nije brži od Mosquitta na jednom toku</strong>: pri 64 B objavljuje oko 80 % njegove brzine, a isporučuje oko 40 %, jer odbacuje QoS 0 kad je tok pretplatnika sporiji od izdavačeva (Mosquitto bez granice stavlja u red). Pri 1 KiB u 16-parnom preplavljivanju Mosquitto je ispred za oko 15 %, što je unutar šuma. Generator je ponovno bio dijeljena VM s 58 do 77 % ukradenog vremena. Druga runda (zaključavanje heapa izvan puta podataka, SACK na strani slanja i drugo) je na neintegriranoj grani <code>perf-single-flow-wip</code>; njezine brojke još nisu tvrdnje i ovdje ih ne navodimo.</p>
<h2 id="obecanje-koje-nismo-dali-ali-smo-ga-ispunili-konfiguracija-je-podatak">Obećanje koje nismo dali, ali smo ga ispunili: konfiguracija je podatak</h2>
<p>Do sada je svaka konfiguracija bila nova gradnja: ključevi i ograničenja bili su ugrađeni u sliku. C1 to okreće (ADR-215 do ADR-219): jezgra se gradi jednom, a konfiguracija je podatak.</p>
<ul>
<li><strong>Blob <code>urmqtt-cfg</code></strong> (C1a): verzionirani, kanonski zapis (polja po rastućim oznakama, SHA-256 otisak, neobavezni odvojeni Ed25519 potpis), 70 polja s zadanim vrijednostima i granicama, dekoder koji odbija sve neispravno.</li>
<li><strong>Jezgra ga čita kao Limine modul</strong> (C1b), prije bilo čega drugog, na aarch64 i x86_64. Postoji <strong>jedna izdana jezgra za svaki <code>ENV</code> osim <code>dev</code></strong> (aarch64 ELF-ovi za <code>release</code>, <code>prod</code> i <code>bench</code> imaju isti SHA-256; provjereno samo na aarch64). Izdana jezgra odbija razvojnu konfiguraciju, a bez konfiguracije piše "refusing to serve" i ne poslužuje ništa.</li>
<li><strong>Instalirani disk nosi konfiguraciju</strong> i ažuriranje je zadržava (C1c).</li>
<li><strong><code>urmqtt-config</code> i predložak ISO</strong> (C1d): <code>pack</code>, <code>show</code>, <code>verify</code>, a <code>pack --iso</code> zamjenjuje oba primjerka mjesta za konfiguraciju i 24 sažetka modula u gotovom ISO-u. Rezultat je bajt za bajt isti kao ISO koji <code>mkiso.sh --config</code> gradi. Predložak izdanja v0.2.0 ima 63 111 168 bajtova.</li>
<li><strong>Izjava o povjerenju</strong> (ADR-219): konfigurirana slika pri podizanju ispisuje otisak konfiguracije (<code>config: fingerprint ...</code>), a potpis je neobavezan i <strong>isključen po zadanom</strong>. Izmijenjeni ISO nema službeni sažetak; otisak je način da se vidi što je u njemu.</li>
</ul>
<h2 id="graditelj-na-aldotech">Graditelj na aldo.tech</h2>
<p>Isto što radi <code>urmqtt-config pack --iso</code>, radi i web stranica, istim kodom: biblioteka alata prevedena u WebAssembly (1,06 MB, bez uvoza i bez <code>unsafe</code> bloka). Stranica je na <a href="https://aldo.tech/proizvodi/urmqtt-builder/">aldo.tech/proizvodi/urmqtt-builder/</a> i u svih 15 jezika stranice (na primjer <a href="https://aldo.tech/en/products/urmqtt-builder/">en</a>).</p>
<ul>
<li><strong>Ništa ne napušta preglednik.</strong> Certifikati, privatni ključevi i gotov ISO ostaju na računalu; stranica skida samo datoteke izdanja s ovog sjedišta, nema praćenja ni skripti trećih strana. U testu uživo nijedan zahtjev nije nosio ključni materijal.</li>
<li><strong>Zrcalo izdanja</strong> (ADR-227): sat po sat (systemd timer, nisko prioritetan) kopira izdanja s GitHuba, svako provjerava prije objave, a stranica u pregledniku provjerava potpis <code>SHA256SUMS</code> ključem ugrađenim u stranicu, pa tek onda upisuje konfiguraciju u predložak. Neuspjela provjera blokira preuzimanje.</li>
<li><strong>Jednostavno prema zadanom, napredno po potrebi</strong> (ADR-232): prvi zaslon su tri koraka (gdje radi, vaši certifikati, ime slike). Jedno polje za povlačenje datoteka razvrstava certifikat, ključ i CA po sadržaju. Napredni prikaz otvara svako od 70 polja, pretraživanje, uvoz i izvoz TOML-a; oba prikaza uređuju isto stanje. Predložak (63 MB) skida se dok korisnik ispunjava formu. U probi uživo, s probnim zrcalom i probnim ključem, izgradnja ISO-a u pregledniku trajala je 5 do 18 s.</li>
</ul>
<figure><img loading="lazy" alt="Jednostavni prikaz graditelja: tri koraka, &quot;Gdje će raditi?&quot;, &quot;Vaši certifikati&quot; i &quot;Ime slike&quot;, s jednom sljedećom radnjom u bočnoj traci." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-simple.png" /><figcaption>Jednostavni prikaz graditelja: tri koraka, &quot;Gdje će raditi?&quot;, &quot;Vaši certifikati&quot; i &quot;Ime slike&quot;, s jednom sljedećom radnjom u bočnoj traci.</figcaption></figure>
<figure><img loading="lazy" alt="Napredni prikaz, snimljen na probnom zrcalu s probnim ključem (datum izdanja i ključ potpisa nisu pravi): izdanje s provjerenim potpisom, predlošci, svako polje konfiguracije." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-advanced.png" /><figcaption>Napredni prikaz, snimljen na probnom zrcalu s probnim ključem (datum izdanja i ključ potpisa nisu pravi): izdanje s provjerenim potpisom, predlošci, svako polje konfiguracije.</figcaption></figure>
<p><strong>Emulator, neobavezno.</strong> Pored "Preuzmi ISO" stoji "Isprobaj u emulatoru": QEMU preveden u WebAssembly pokreće upravo izgrađenu sliku u kartici preglednika. Ništa od njega se ne skida prije klika (oko 3 MB prvi put). Emulator je BIOS-only, <strong>bez mreže</strong> (da promet korisnika ne bi prošao kroz neki posrednik i narušio "ništa ne napušta preglednik"), traži oko 1,5 GB memorije i računalni preglednik (testiran Chrome; Firefox i Safari nisu). U njemu urMQTT stiže do <code>URMQTT READY</code> za oko 8 s (s preskočenim odbrojavanjem) i ispisuje isti otisak konfiguracije kao stranica, što je dokaz da je podignuta upravo ta konfiguracija. Podizanje je oko 2 puta sporije od izvornog QEMU-a na istom Macu (0,82 s prema 1,9 s), a kriptografija oko 58 puta sporija jer su sklopovske instrukcije emulirane. Zato <strong>iz emulatora ne izvlačimo nikakve tvrdnje o brzini</strong>, a stranica to kaže prije pokretanja. Pri izgradnji je trebalo zakrpati četiri bajta u <code>.wasm</code>-u zbog greške u prijevodu instrukcije POPCNT u izvornom emulatoru (još je nismo prijavili); sam emulator je pod GPL-om, uz ponudu izvornog koda.</p>
<figure><img loading="lazy" alt="Dijagnostički unos u emulatoru uživo na aldo.tech: kernel je ispisao isti otisak konfiguracije kao stranica, a zaslon prikazuje dijagnostiku (u emulatoru završava za oko 20 s; njezine brojke opisuju emulator, ne urMQTT)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/emulator-diagnostic.png" /><figcaption>Dijagnostički unos u emulatoru uživo na aldo.tech: kernel je ispisao isti otisak konfiguracije kao stranica, a zaslon prikazuje dijagnostiku (u emulatoru završava za oko 20 s; njezine brojke opisuju emulator, ne urMQTT).</figcaption></figure>
<h2 id="obican-mqtt-samo-za-mreze-kojima-vjerujete">Običan MQTT, samo za mreže kojima vjerujete</h2>
<p>Običan MQTT (TCP 1883, bez TLS-a) vrlo je čest u IoT-u: zadano je u Mosquittu, u većini firmwarea za Arduino i ESP, u Tasmoti, Zigbee2MQTT-u i Home Assistantu, a koristi se i unutar segmentiranih industrijskih mreža, iza VPN-a i na privatnim LAN-ovima, pa i na uređajima bez sata (certifikati traže datum), bez dovoljno flasha ili procesora za TLS. Preko nepouzdane mreže nije prihvatljiv: tko je na putu, čita i mijenja promet. Zato je P1 (ADR-229 do ADR-231) izričit, ograničen izbor, <strong>isključen po zadanom</strong>, a TLS port ostaje; TLS se ovim ne isključuje, običan port dolazi uz njega.</p>
<ul>
<li><strong>Popis dopuštenih mreža je obavezan</strong> (<code>allow_from</code>, CIDR-ovi) i provjerava se pri SYN-u: izvan popisa nema SYN-ACK-a ni stanja. Cijeli internet (<code>0.0.0.0/0</code>) traži zasebnu izričitu opciju.</li>
<li><strong>Tri načina prijave:</strong> lozinka (zadano kad je uključeno; PBKDF2-HMAC-SHA-256, sol, 10 000 iteracija, usporedba u stalnom vremenu, zaključavanje po adresi), anonimno (jedan konfiguriran identitet s vlastitim ACL-om) i po adresi (<code>ip</code>). Graditelj lozinku raspršuje u pregledniku i u memoriji drži samo sažetak.</li>
<li><strong>Manje prava nego mTLS:</strong> običan klijent nikad nema servisna, operatorska ni upravljačka prava, nema <code>$SYS</code> ni konzole, ima vlastite sesije (<code>$plain/&lt;identitet&gt;/&lt;id klijenta&gt;</code>, samo čiste), vlastita ograničenja i registar dopuštenih ga i dalje obvezuje.</li>
<li><strong>Što se gubi, piše u <code>SECURITY.md</code>:</strong> povjerljivost, cjelovitost, autentičnost brokera i ponavljanje snimljene prijave. To se konfiguracijom ne može popraviti; tako je kad nema TLS-a.</li>
</ul>
<p>Izmjereno na laptopu (Apple silicon, HVF, QEMU korisnička mreža), 16 parova, 64 B, tri pokretanja od 10 s: pri 8 000 poruka u sekundi TLS i običan put isporučuju sve poruke s istom latencijom (p50 0,17 ms prema 0,13 do 0,18 ms), nove veze po sekundi: TLS oko 2 600 do 2 750, anonimni običan oko 4 900 do 7 800, a <strong>prijava lozinkom je skupi dio</strong>: jedan PBKDF2 stoji oko 2,5 ms, pa se broj novih prijava ograničuje na oko 200 po jezgri podataka u sekundi (izmjereno 308 do 342 na dvije jezgre). Preplavljivanje na tom stroju nije stabilan instrument (20k do 440k isporučenih u sekundi među pokretanjima) i <strong>iz njega ne izvlačimo zaključak</strong>. Usporedba običnog puta s Mosquittom na VPS-u još nije napravljena. Testovi: smoke skupina <code>plaintext</code> 7 od 7 na aarch64 HVF-u i na x86_64 TCG-u, novi fuzz cilj <code>plain_input</code> (2,3 milijuna pokretanja bez nalaza).</p>
<h2 id="greske-koje-je-pronasao-rad-uzivo">Greške koje je pronašao rad uživo</h2>
<p>Dvije stvari nisu ulovili testovi, nego prvo uključenje na pravom sjedištu:</p>
<ul>
<li>Stranica je datoteke izdanja tražila <strong>jedan direktorij previsoko</strong>, pa je svaka provjera potpisa odgovorila 404 (ispravak 13af301). Test je koristio probno zrcalo na drugoj putanji.</li>
<li><strong>Tipkovnica emulatora nikad nije stigla do gosta</strong>: uzrok nije bio loš prijevod tipki, nego to što dijalog uopće nije slušao tipke (ispravak u ADR-232, uz unos za podizanje bez tipki).</li>
</ul>
<h2 id="lekcije">Lekcije</h2>
<ul>
<li><strong>Tražite uzrok, ne okrivljavajte okolinu.</strong> Većina slabosti jednog toka i sve prekinute veze bile su naše; hipervizor je stvaran, ali ga je trebalo mjeriti, ne pretpostavljati.</li>
<li><strong>Usporedba s drugim proizvodom mora uključiti i gubitke.</strong> Tablica u kojoj su samo naše pobjede ne bi bila usporedba.</li>
<li><strong>Mjerni instrument ima granice.</strong> Dijeljeni generator s 60 do 70 % ukradenog vremena mijenja što brojke znače; zato pišemo "donja granica" i ne tvrdimo razlike ispod 20 %.</li>
<li><strong>Izolacija nije dokaz.</strong> SQLite u EL0 smanjuje što greška u njemu može, ne ono što može lagati.</li>
<li><strong>Stvarna instalacija nalazi ono što testovi ne traže</strong>: putanju koja je krivo složena i tipke koje nitko ne sluša.</li>
</ul>
<h2 id="sto-jos-nije-dokazano">Što još nije dokazano</h2>
<ul>
<li>Potpisni ključ izdanja za sada je probni ključ; v0.2.0 i v0.2.1 su pre-release, a slike uz njih razvojne uz probne certifikate (osim predloška, koji je bez konfiguracije).</li>
<li>13 prijevoda stranice graditelja (i ovog teksta) napisao je AI i provjerili su ih testovi i uzorci, ne izvorni govornici.</li>
<li>Usporedba s Mosquittom je jedna sjednica, jedan put i jedan dijeljeni generator, na dijeljenom VPS-u; razlike ispod 20 % nisu tvrdnje.</li>
<li>Običan put nije mjeren na VPS-u ni protiv Mosquitta.</li>
<li>Lighthouse na novom jednostavnom prikazu nije potvrđen (stari prikaz: pristupačnost 95 ili više).</li>
<li>Bare metal, x86_64 cijena radnika na KVM-u, Firefox i Safari za emulator: nije mjereno.</li>
</ul>
<h2 id="sto-slijedi">Što slijedi</h2>
<ul>
<li><strong>D1: javni demo broker.</strong> Emulator iz preglednika ne možemo učiniti javnim: živi u kartici jednog posjetitelja, 20 do 100 puta je sporiji od KVM-a, nema dolazne mreže i nestaje sa zatvaranjem kartice; preusmjeravanje interneta u tuđi preglednik bilo bi i zlouporaba. Moguć je pravi demo broker: urMQTT na VPS-u iza imena na aldo.tech, s objavljenim probnim CA-om, strogim ACL-om i malim ograničenjima. To je zasad samo ideja, ne gradnja, i treba izričit pristanak za javno izlaganje.</li>
<li><strong>Druga runda za jedan tok</strong> (grana <code>perf-single-flow-wip</code>): tek kad je izmjerena na VPS-u i spojena.</li>
<li><strong>Običan put protiv Mosquitta na VPS-u</strong>, prijava i opoziv računa za običan MQTT preko potpisanog upravljačkog kanala.</li>
<li><strong>Poslije izdanja:</strong> bare metal (X12 do X16), IOMMU i Secure Boot, šifriranje diska.</li>
</ul>
<p>Kod je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a>, a <code>docs/PROGRESS.md</code> i dalje je jedini izvor istine o tome što stvarno radi, <code>docs/PERFORMANCE.md</code> o tome što je izmjereno. Pred-alfa, kao i prije; prijave grešaka i sigurnosne prijave po uputama u <code>SECURITY.md</code> dobrodošli su.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT na pravom VPS-u: ISO, rukovanje od 0,24 ms, vlastiti disk i ojačana jezgra</title>
      <link>https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/</link>
      <guid isPermaLink="true">https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/</guid>
      <pubDate>Sat, 31 Oct 2026 08:00:00 +0000</pubDate>
      <description>Što se dogodilo s tri obećanja iz prvog teksta o urMQTT-u (jedan ISO, sirovi disk bez datotečnog sustava, dokazivo sigurno) nakon prva četiri dana razvoja, s mjerenjima, tablicama i popisom onoga što još nije gotovo.</description>
      <content:encoded><![CDATA[<p>U <a href="https://aldo.tech/blog/urmqtt-unikernel-mqtt-broker/">prvom tekstu o urMQTT-u</a> opisali smo unikernel koji jest MQTT broker, do prekretnice M17, i završili s tri obećanja pod naslovom "Što slijedi": jedan ISO koji se podiže na više platformi, sirovi disk bez datotečnog sustava i sigurnost koja se može dokazati, a ne samo tvrditi. Ništa od toga tada nije bilo izgrađeno. Ovaj tekst prati što se dogodilo sa svakim obećanjem, istim redom kao i prije: što smo planirali, što smo izgradili, što nas je iznenadilo i što je još otvoreno. Usput ćemo pokazati i koliko je to trajalo, jer je to dio priče.</p>
<p>Jedna napomena unaprijed: projekt je i dalje pred-alfa. Brojke u tekstu mjere kod i testove, ne zrelost u produkciji, a prva objava je namjerno ograničena na VPS-ove s KVM-om. Bare metal ostaje za kasnije.</p>
<h2 id="kako-je-to-izgledalo-u-vremenu">Kako je to izgledalo u vremenu</h2>
<p>Prvi commit nastao je 28. listopada 2026. u 23:48 (M0). Do 31. listopada u 23:00 u repozitoriju je 119 commitova, raspoređenih kao 3, 39, 38 i 39 po kalendarskim danima. Odmah recimo i kako je to moguće: projekt se gradi uz Claude Code, a commitovi nose <code>Co-Authored-By: Claude</code> trailere. Četiri kalendarska dana povijesti ne znače da je jedna osoba za četiri dana napisala 90 000 redaka; znače da se plan, implementacija, testovi i ADR-ovi pišu u kratkim, zatvorenim petljama, a svaki korak završava istim ritualom provjere kao i prije. Brojke ispod govore o količini koda i testova, ne o tome koliko je sve to provedeno u stvarnom radu.</p>
<figure><img loading="lazy" alt="Rast projekta tijekom četiri dana s prekretnicama od M0 do danas: tisuće redaka Rusta (od 1,6 do 89,8), broj #[test] funkcija (od 27 do 739) i broj ADR-ova (od 13 do 104), uz stupce commitova po danu (39, 38 i 39 za 29., 30. i 31. listopada)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/growth.svg" /><figcaption>Rast projekta tijekom četiri dana s prekretnicama od M0 do danas: tisuće redaka Rusta (od 1,6 do 89,8), broj #[test] funkcija (od 27 do 739) i broj ADR-ova (od 13 do 104), uz stupce commitova po danu (39, 38 i 39 za 29., 30. i 31. listopada).</figcaption></figure>
<p>Graf pokazuje tri krivulje koje rastu skoro zajedno, što i je namjera: testovi i zapisane odluke prate kod, a ne zaostaju za njim. Nagib je najstrmiji 29. listopada, kad nastaje cijeli put od jezgre do brokera. Dalje se redovi dodaju sporije, ali testovi rastu brže od koda (27 testova na 1,6 tisuća redaka na početku, 739 na 89,8 tisuća danas).</p>
<table>
<thead>
<tr>
<th>Prekretnica</th>
<th>Commit</th>
<th>Vrijeme</th>
<th style="text-align: right;">Rust redaka</th>
<th style="text-align: right;">Testova</th>
<th style="text-align: right;">ADR-ova</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>danas</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>Redovi su svi <code>.rs</code> fajlovi izvan <code>vendor/</code> i <code>fuzz/</code>, testovi su <code>#[test]</code> funkcije. Što je koji dio dodao, po datumu iz git povijesti:</p>
<table>
<thead>
<tr>
<th>Datum</th>
<th>Prekretnice</th>
<th>Što je dodano</th>
</tr>
</thead>
<tbody>
<tr>
<td>29.10.</td>
<td>M0 do M17</td>
<td>jezgra, mreža, TLS, broker, SQLite, više jezgri, vlastiti TCP, ojačanje, reproducibilno izdanje</td>
</tr>
<tr>
<td>29.10. navečer i 30.10.</td>
<td>X0 do X10, S1, H2, H3 (X0 počinje 29.10. u 20:54)</td>
<td>port na x86_64, univerzalni ISO, jedan disk i samoinstalirajući ISO, TLS provjere, napadački klijenti</td>
</tr>
<tr>
<td>31.10.</td>
<td>O1 do O4, T1, PERF, H4, H5, H9, S2 do S6</td>
<td>uvid i konzola preko MQTT-a, živi način rada i dijagnostika, SNTP, ubrzanja, ojačanje, verifikacija, lanac nabave, pohrana</td>
</tr>
</tbody>
</table>
<h2 id="obecanje-1-jedan-iso-za-vise-platformi">Obećanje 1: jedan ISO za više platformi</h2>
<p><strong>Što smo planirali.</strong> Jedna datoteka koja se podiže na aarch64 UEFI-ju, x86_64 UEFI-ju i x86_64 BIOS-u. <strong>Što smo izgradili.</strong> Port na x86_64 (X0 do X10) i univerzalni ISO s Limine bootloaderom, jedan hibridni ISO s kojeg se podižu sve te kombinacije; smoke testovi ga podižu na četiri virtualna stroja. Izbornik ima tri unosa: urMQTT (živi način rada, radi iz RAM-a, ništa ne piše na disk), instalacija na disk stroja na kojem je pokrenut (instalacija piše GPT i podatke, pa gasi stroj) i Dijagnostika (detekcija hardvera i mjerenja).</p>
<figure><img loading="lazy" alt="Boot izbornik ISO-a snimljen na VPS-u: unosi &quot;urMQTT&quot;, &quot;Install urMQTT on this machine's disk&quot; i &quot;Diagnostic: hardware detection and benchmarks&quot; s odbrojavanjem do automatskog podizanja. Snimka je s probne ISO slike s duljim odbrojavanjem; zadano je 10 sekundi." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/boot-menu.png" /><figcaption>Boot izbornik ISO-a snimljen na VPS-u: unosi &quot;urMQTT&quot;, &quot;Install urMQTT on this machine's disk&quot; i &quot;Diagnostic: hardware detection and benchmarks&quot; s odbrojavanjem do automatskog podizanja. Snimka je s probne ISO slike s duljim odbrojavanjem; zadano je 10 sekundi.</figcaption></figure>
<p>Prva stvarna meta bio je VPS, a ne fizičko računalo: odluka je da prvo izdanje podržava samo VPS (KVM), a bare metal dolazi nakon njega. Testirali smo na Contabo VPS-u s AMD EPYC procesorom (obitelj 23, Zen 1), 4 vCPU-a i 8 GiB RAM-a, s virtio-net mrežom i virtio-scsi diskom. Prvi pokušaj nas je iznenadio: ISO priključen kao virtualni CD podizao je zadani unos, produkcijska slika odbila je poslužiti s medija samo za čitanje i resetirala se, pa smo dobili beskonačnu petlju izbornik, jezgra, reset bez ičega čitljivog. Danas fatalna greška u produkcijskoj slici ostavlja poruku na konzoli 60 sekundi prije resetiranja, a s optičkog medija ispisuje da je podignuto s medija samo za čitanje i staje. Živi način rada u RAM-u (O3) kasnije je taj problem zaobišao za uobičajeni slučaj.</p>
<p>Dijagnostički unos nastao je iz tog iskustva: stroj koji ne vidimo moramo moći pitati što ima. Ispisuje CPU, RAM, mrežu, diskove i PCI uređaje, mjeri propusnost memorije, disk i kriptografiju, a ista mjerenja ispisuje kao JSON na serijski port. Zapisivanje na disk dopušteno je samo u neraspoređenom prostoru i tek nakon što se utipka <code>WIPE</code>; na snimci ispod to je preskočeno.</p>
<figure><img loading="lazy" alt="Dijagnostički zaslon na Contabo VPS-u (AMD EPYC, KVM, 4 vCPU): sustav, memorija, disk, PCI uređaji, propusnost i latencija RAM-a te kripto-mjerenja, uključujući 244 us po mTLS rukovanju, X25519 48 us, ECDSA potpis 35,7 us i provjera 107 us." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/diag-vps.png" /><figcaption>Dijagnostički zaslon na Contabo VPS-u (AMD EPYC, KVM, 4 vCPU): sustav, memorija, disk, PCI uređaji, propusnost i latencija RAM-a te kripto-mjerenja, uključujući 244 us po mTLS rukovanju, X25519 48 us, ECDSA potpis 35,7 us i provjera 107 us.</figcaption></figure>
<p><strong>Što je otvoreno.</strong> Pravi hardver. Poslije prvog izdanja slijedi bare metal (USB podizanje, NVMe i AHCI, mrežna kartica stvarnog računala), zajedno s IOMMU-om i Secure Bootom, jer bez IOMMU-a zlonamjerni ili neispravni uređaj može pisati po cijeloj memoriji. U ovoj verziji o tome ne tvrdimo ništa.</p>
<h2 id="rukovanje-od-12-ms-do-024-ms">Rukovanje: od 1,2 ms do 0,24 ms</h2>
<p>Prvo mjerenje dijagnostike na VPS-u dalo je nezgodnu brojku: mTLS rukovanje, samo kriptografija na strani poslužitelja, trajalo je 1,2 ms. Na aarch64 smo već koristili hardverske AES i SHA instrukcije, ali x86_64 nije imao ništa slično za javni ključ: RustCrypto je prenosiv i constant-time, pa i spor. Tri promjene su to spustile.</p>
<ul>
<li>Za P-256 smo preuzeli <code>nistz256</code> asembler iz OpenSSL-a i ring-a (ADR-130), uz tablice s fiksnom bazom za potpisivanje i provjeru potpisa CA (ADR-110, ADR-111).</li>
<li>X25519 je prepisan na četiri 64-bitna uda s MULX/ADX instrukcijama (ADR-131).</li>
<li>Nakon toga smo profilirali i ostatak (ADR-132, ADR-200).</li>
</ul>
<figure><img loading="lazy" alt="Rukovanje na VPS-u: 1200 us prije, 237 us nakon asemblera i tablica; A/B usporedba memcpy-a na istom pokretanju, 278,8 us sa starim, 229,4 us s novim; te prije i poslije za X25519 (146 na 48 us), ECDSA potpis (673 na 34 us) i provjeru (309 na 90 us)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/handshake.svg" /><figcaption>Rukovanje na VPS-u: 1200 us prije, 237 us nakon asemblera i tablica; A/B usporedba memcpy-a na istom pokretanju, 278,8 us sa starim, 229,4 us s novim; te prije i poslije za X25519 (146 na 48 us), ECDSA potpis (673 na 34 us) i provjeru (309 na 90 us).</figcaption></figure>
<table>
<thead>
<tr>
<th>Mjerenje na VPS-u (server, mikrosekunde)</th>
<th style="text-align: right;">Prije</th>
<th style="text-align: right;">Poslije</th>
</tr>
</thead>
<tbody>
<tr>
<td>mTLS rukovanje, cijela kriptografija</td>
<td style="text-align: right;">1200</td>
<td style="text-align: right;">oko 240 (237 u mjerenju s grafa, 244 na snimci)</td>
</tr>
<tr>
<td>X25519</td>
<td style="text-align: right;">146</td>
<td style="text-align: right;">48</td>
</tr>
<tr>
<td>ECDSA potpis</td>
<td style="text-align: right;">673</td>
<td style="text-align: right;">34</td>
</tr>
<tr>
<td>ECDSA provjera</td>
<td style="text-align: right;">309</td>
<td style="text-align: right;">90 (107 na snimci)</td>
</tr>
<tr>
<td>rukovanje, stari memcpy, isti boot</td>
<td style="text-align: right;">278,8</td>
<td style="text-align: right;">229,4 (-17,7%)</td>
</tr>
</tbody>
</table>
<p>Brojke se razlikuju po pokretanju oko 10%, pa smo zapisali raspon umjesto jedne vrijednosti. Ukupno je to otprilike pet puta manje CPU vremena za rukovanje.</p>
<p>Što smo naučili o memcpy-u. Kad je kriptografija pala, profil je pokazao nešto što nismo očekivali: funkcije <code>mem*</code> (memcpy, memmove, memset) trošile su 7% rukovanja, 13 do 39% puta TLS zapisa (39% za 64 B, 13% za 4 KiB) i 17 do 34% razdjeljivanja PUBLISH-a. Zamijenili smo ih SSE2 asemblerom za x86_64 (ADR-200), uz diferencijalne testove prema referentnoj implementaciji za sve veličine, poravnanja i preklapanja. Po pozivu je to 55 do 75 ciklusa prema 7 do 12 za 1 do 64 bajta.</p>
<figure><img loading="lazy" alt="Lijevo: udio vremena poslužitelja u mem* funkcijama prije prepisivanja (rukovanje 7%, TLS zapis 64 B 39%, 256 B 37%, 1 KiB 24%, razdjeljivanje 17 do 34%). Desno: koliko je svaki put brži poslije (rukovanje 17,7%, 64 B 26,4%, 256 B 19,6%, 1 KiB 16,1%, 4 KiB 6,6%, razdjeljivanje 7 do 34%)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/hotpaths.svg" /><figcaption>Lijevo: udio vremena poslužitelja u mem* funkcijama prije prepisivanja (rukovanje 7%, TLS zapis 64 B 39%, 256 B 37%, 1 KiB 24%, razdjeljivanje 17 do 34%). Desno: koliko je svaki put brži poslije (rukovanje 17,7%, 64 B 26,4%, 256 B 19,6%, 1 KiB 16,1%, 4 KiB 6,6%, razdjeljivanje 7 do 34%).</figcaption></figure>
<table>
<thead>
<tr>
<th>Put</th>
<th style="text-align: right;">Udio mem* prije</th>
<th style="text-align: right;">Brže poslije</th>
</tr>
</thead>
<tbody>
<tr>
<td>rukovanje</td>
<td style="text-align: right;">7%</td>
<td style="text-align: right;">17,7%</td>
</tr>
<tr>
<td>TLS zapis 64 B</td>
<td style="text-align: right;">39%</td>
<td style="text-align: right;">26,4%</td>
</tr>
<tr>
<td>TLS zapis 256 B</td>
<td style="text-align: right;">37%</td>
<td style="text-align: right;">19,6%</td>
</tr>
<tr>
<td>TLS zapis 1 KiB</td>
<td style="text-align: right;">24%</td>
<td style="text-align: right;">16,1%</td>
</tr>
<tr>
<td>TLS zapis 4 KiB</td>
<td style="text-align: right;">13%</td>
<td style="text-align: right;">6,6%</td>
</tr>
<tr>
<td>razdjeljivanje poruka</td>
<td style="text-align: right;">17 do 34%</td>
<td style="text-align: right;">7 do 34%</td>
</tr>
</tbody>
</table>
<p>Čitanje grafa: što je zapis manji, to veći dio vremena odlazi na kopiranje, pa je i dobitak veći. Za male poruke, koje su većina MQTT prometa, to je ono što znači.</p>
<p>Što ovo ne znači. Na nativnom x86_64 smo samo kao referencu za asembler mjerili OpenSSL 3.5.5 (ADR-130), a ne kao usporedbu proizvoda. Usporedbu Linux plus Mosquitto na istom VPS-u (zadatak X11) još nismo napravili. Zato ne tvrdimo da je urMQTT brži od Linuxa: nije izmjereno. Brojke iznad uspoređuju urMQTT sa samim sobom.</p>
<p>Preopterećenje je bio drugi rezultat. Na VPS-u, s jednog Maca (RTT 35 ms), 128 klijenata dalo je 618 rukovanja u sekundi, 256 klijenata 393, a 512 klijenata 67, uz tisuće odbijenih veza. Nije to bio plato nego urušavanje. Uzroke smo pronašli u kodu (dva su potvrđena u QEMU-u/HVF-u, na VPS-u se nisu mogli ponoviti), više njih odjednom: odbijena veza vraćala je klijenta preko sljedećeg segmenta koji nosi valjani SYN kolačić, odbijanje je RST-om govorilo klijentu da odmah proba ponovno, a prijem je bio O(red). Ispravak (ADR-080 do ADR-082) ispušta SYN koji ne bi bio prihvaćen, pamti odbijene tokove, drži prijem u O(1) i broji rukovanja po jezgri. Moguće je (hipoteza, nije provjereno) da je dio uzroka bio i na klijentu: alat za opterećenje je zatvarao prvi, pa je jedan Mac s 16 384 efemernih portova i TIME_WAIT-om od 30 sekundi ograničen na oko 550 veza u sekundi. Zato pravi strop poslužitelja još nismo izmjerili.</p>
<h2 id="obecanje-2-sirovi-disk-ali-pazljiv">Obećanje 2: sirovi disk, ali pažljiv</h2>
<p><strong>Što smo planirali.</strong> Zadržati vlastiti raspored bez datotečnog sustava i dodati sve ono što datotečni sustav inače daje: poravnanje, rast, discard, sigurnosne kopije i provjere. <strong>Zašto ne datotečni sustav.</strong> O tome smo raspravljali i odluka je potvrđena: urMQTT piše jednu bazu, jedan telemetrijski dnevnik i vlastite metapodatke, ne treba imenike, dozvole, ni imena datoteka, a svaki dodatni sloj bio bi još kod u povjerljivom dijelu i još jedan način da zapis tiho nestane. Disk je GPT s ESP particijom i jednom urMQTT particijom podataka, na kojoj je vlastiti raspored.</p>
<p><strong>Što smo izgradili (S2 do S6).</strong></p>
<ul>
<li><strong>Raspored v2 (S2).</strong> Topologija diska, poravnanje na 1 MiB ili optimalnu veličinu I/O-a, cijeli fizički blokovi, A/B par superblokova s generacijom (ADR-140). Raspored v1 kernel odbija uz jasnu poruku, a migrira ga izvanmrežni alat <code>urmqtt-disk migrate</code> (ADR-141): zadano je probni prolaz, kopira se pa tek onda prebacuje, uz povrat i ubrizgavanje greške na svakom zapisu.</li>
<li><strong>Rast (S3).</strong> Pri podizanju disk može narasti (nikad se ne smanjuje), a na virtio-blk i za vrijeme rada, preko prekida promjene konfiguracije (ADR-142, ADR-143).</li>
<li><strong>Discard i write-zeroes (S4).</strong> Tanko dodijeljeni diskovi vraćaju prostor domaćinu. U testu je qcow2 datoteka na domaćinu pala sa 62 na 16 MiB nakon što su istekli telemetrijski segmenti, a oporavak nikad ne vjeruje sadržaju koji je odbačen.</li>
<li><strong>Sigurnosna kopija baze na disku (S5).</strong> A/B kopija pomoću SQLite online backup API-ja u koracima po 16 stranica: najduži korak 4,6 ms naspram 445 ms kad se kopira u jednom pozivu. Komitovi tijekom kopije imali su maksimum 2,4 ms prema 2,2 ms bez nje. Ako <code>quick_check</code> pri podizanju ne uspije, baza se vraća iz kopije.</li>
<li><strong>Čišćenje i ograničeno podizanje (S6).</strong> Scrubber čita disk uz ograničen I/O proračun i prijavljuje oštećene zapise, a podizanje čita samo najnoviji segment svake particije, tako da vrijeme podizanja ne raste s veličinom telemetrijske regije. Time smo zatvorili problem koji smo izračunali unaprijed: na velikom disku bi potpuni oporavak trajao od minuta do desetaka minuta.</li>
</ul>
<p>Cijela grupa smoke testova za pohranu prolazi na obje arhitekture: 79 uspješnih, 0 neuspjelih, na aarch64 pod HVF-om i na x86_64 pod TCG-om. Mjerili smo i da S4 nije usporio bazu: 53,3 tisuće umetanja u sekundi u paketima prije i poslije, bez izmjerive razlike.</p>
<p><strong>Što je iznenadilo.</strong> Koliko od "jednostavnog" rasporeda otpada na rubne slučajeve: virtio-scsi i virtio-blk prijavljuju topologiju na različite načine, a nepotpuni zapis u sredini migracije mora uvijek ostaviti bar jednu valjanu verziju. Zato je svaki zapis na putu migracije i rasta prošao ubrizgavanje greške.</p>
<p><strong>Što je otvoreno.</strong> SQLite stranice nemaju kontrolni zbroj, pa promijenjeni bit unutar vrijednosti u retku baza neće otkriti; telemetrijski zapisi i sadržaj kopija imaju CRC i otkrivaju se. Šifriranje diska (S7) nije gotovo.</p>
<h2 id="obecanje-3-dokazivo-sigurno-koliko-god-to-uspijemo">Obećanje 3: dokazivo sigurno, koliko god to uspijemo</h2>
<p><strong>Ojačanje (H4).</strong> Alokator briše oslobođenu memoriju i provjerava je pri ponovnoj uporabi, otkriva dvostruko oslobađanje, veže pokazivače slobodne liste XOR-om i nulira ključeve pri oslobađanju (ADR-170). Zaštita stoga (<code>-Zstack-protector=strong</code>, 1315 funkcija, a SQLite s <code>-fstack-protector-strong</code>, ADR-171). Izvješća o greškama u produkciji ne sadrže registre ni adrese (ADR-172). KASLR na obje arhitekture: kernel je PIE i Limine ga pomiče za oko 19 bitova, a nasumično se postavljaju heap, stogovi i MMIO prozor (ADR-173). Na aarch64 MTE označava heap (ADR-174), ali samo pod TCG-om: HVF i x86 ga nemaju. kCFI smo procijenili i nismo ga prihvatili jer prevoditelj na prikvačenom nightlyju pada s unutarnjom greškom; IOMMU je odgođen s bare metalom (ADR-175, ADR-176).</p>
<p><strong>Cijena je izmjerena, ne pretpostavljena.</strong> Na putu rukovanja (<code>dp-bench</code>, aarch64 HVF, puna TLS 1.3 + mTLS + CONNECT rukovanja):</p>
<table>
<thead>
<tr>
<th>Korak</th>
<th style="text-align: right;">1 jezgra</th>
<th style="text-align: right;">4 jezgre</th>
<th>Promjena</th>
</tr>
</thead>
<tbody>
<tr>
<td>bez ojačanja</td>
<td style="text-align: right;">1941</td>
<td style="text-align: right;">7620</td>
<td></td>
</tr>
<tr>
<td>+ alokator</td>
<td style="text-align: right;">1898</td>
<td style="text-align: right;">7429</td>
<td>-2,2% do -2,5%</td>
</tr>
<tr>
<td>+ zaštita stoga</td>
<td style="text-align: right;">1845</td>
<td style="text-align: right;">7243</td>
<td>zajedno -4,9% do -5%</td>
</tr>
<tr>
<td>+ PIE i oznake</td>
<td style="text-align: right;">oko 1800</td>
<td style="text-align: right;">oko 6750</td>
<td>na opterećenom računalu, šumovito</td>
</tr>
</tbody>
</table>
<p>Sveukupno je dakle cijena na putu rukovanja otprilike 5% za dijelove mjerene čisto, a s PIE-jem i oznakama izlazi oko 7% na jednoj i oko 11% na četiri jezgre, no to je mjerenje šumovito. Smatramo je prihvatljivom, ali nismo je ponovili na x86_64 VPS-u.</p>
<p>Vrijedi usporediti s brojkom iz prvog teksta (862 rukovanja u sekundi na jednoj jezgri). Isti alat danas, s pogonom hardverskog kriptiranja na aarch64 (X0b) i nizom ubrzanja koja su stigla otada, daje 1941 prije ojačanja. Nismo izolirali doprinos svake promjene, pa o uzroku ne tvrdimo ništa.</p>
<p><strong>Kako je provjereno, i što nije.</strong> H5 je dodao Miri na podskup host crateova, Kani dokaze, sanitizere, model-fuzzer za ACL i izvješća o pokrivenosti. Kani je dokazao <code>record_len</code> u TLS-u, <code>parse</code> u TCP-u i dvije aritmetike kotača tajmera. <strong>Nije dokazano</strong> (isteklo vrijeme): oba MQTT dekodera, oba parsiranja konfiguracije i ponašanje kotača tajmera; CI posao za Kani zato još ne blokira. Miri ne može izvoditi asembler. Lanac nabave (H9): CycloneDX SBOM s 215 komponenti, <code>cargo-vet</code> prolazi s 39 potpuno revidiranih i 140 izuzetaka, a cargo-deny je konfiguriran da savjete o ranjivostima tretira kao pad CI-ja (ti CI poslovi do sada su provjereni samo lokalno, ne na GitHubu, a negativni test nije pokrenut), uz <code>VULNERABILITY-HANDLING.md</code>. Iskreno stanje: od 67 kernel-linkanih verzija trećih crateova, 53 su izuzeci, a ne revizije. Vet dokazuje da se skup ovisnosti ne može promijeniti bez vidljive razlike, ne da je TCB pregledan. Vlastitu reviziju ni jednog cratea nismo napisali.</p>
<p>SNTP (T1) je u tom smislu mala priča o sigurnosti: opcionalan je, samo prema privatnim IPv4 poslužiteljima, s maksimalnim skokom i uvjetom da se izvori slažu (medijan, većina unutar jedne sekunde). Sat koji bilo tko na putu može pomaknuti nije sat kojem smijemo vjerovati kod valjanosti certifikata.</p>
<h2 id="na-macu-aarch64-izvorno-i-x86_64-emulacijom">Na Macu: aarch64 izvorno i x86_64 emulacijom</h2>
<p>Razvijamo na Macu, a taj ima dva načina za pokretanje istog koda i oni su vrlo različiti.</p>
<table>
<thead>
<tr>
<th></th>
<th>aarch64 pod HVF-om</th>
<th>x86_64 pod TCG-om</th>
</tr>
</thead>
<tbody>
<tr>
<td>Što je to</td>
<td>izvorno izvođenje, brzina blizu stvarnog procesora</td>
<td>QEMU emulira instrukcije</td>
</tr>
<tr>
<td>Rukovanje u gostu</td>
<td>oko 0,58 ms (prvi put mjereno oko M13); <code>dp-bench</code> danas 1941/s po jezgri</td>
<td>oko 3,3 ms</td>
</tr>
<tr>
<td>TLS smoke scenariji</td>
<td>brzi</td>
<td>oko 15 minuta po scenariju</td>
</tr>
<tr>
<td>MTE</td>
<td>ne (nema ga na HVF-u)</td>
<td>x86 ga nema; označavanje heapa radi samo za aarch64 gosta pod TCG-om</td>
</tr>
<tr>
<td>Čemu služi</td>
<td>stvarna mjerenja</td>
<td>ispravnost: x86 put se provjerava, brojke nisu performanse</td>
</tr>
</tbody>
</table>
<p>Pravilo je jednostavno: brojke pod TCG-om su emulacija i ne citiramo ih kao performanse. x86 performanse mjerimo samo na pravom VPS-u. Zato kriptografske brojke iznad dolaze s Contabo VPS-a, a ne s Maca.</p>
<h2 id="lekcije">Lekcije</h2>
<ul>
<li><strong>Mjerite na stvarnom stroju prije optimiranja.</strong> Prvi pristup bio je optimizirati ono što smo mislili da je sporo; tek je dijagnostika na VPS-u pokazala 1,2 ms i gdje točno odlazi vrijeme.</li>
<li><strong>Preopterećenje je zasebna značajka.</strong> Sustav koji je brz pod opterećenjem može se urušiti iznad njega; odbijanje mora biti jeftinije od prihvaćanja.</li>
<li><strong>Profil iznenađuje.</strong> Najveći pojedinačni dobitak za male poruke nije bio u kriptografiji nego u kopiranju memorije.</li>
<li><strong>Disk bez datotečnog sustava traži više posla, ne manje.</strong> Ono što sustav datoteka inače daje (poravnanje, rast, discard, kopije, provjere) moramo napisati i provjeriti sami, svaki korak s ubrizgavanjem grešaka.</li>
<li><strong>Iskrenost u brojkama.</strong> Memorijska mjerenja na dijeljenom VPS-u variraju do 10 puta među pokretanjima i ne govore ništa o kodu, pa ih ne navodimo. Kripto brojke su stabilne do oko 10%.</li>
<li><strong>Brzina razvoja nije zrelost.</strong> Testovi i ADR-ovi rastu uz kod, ali četiri dana nisu pravi pogon.</li>
</ul>
<h2 id="sto-slijedi">Što slijedi</h2>
<ul>
<li><strong>H6:</strong> SQLite izoliran na razini EL0, tako da greška u bazi ne otvara jezgru.</li>
<li><strong>H7:</strong> ostatak sigurnosti vremena (NTS i obrada skokova); SNTP je tek prvi korak.</li>
<li><strong>X11:</strong> usporedba s Linuxom i Mosquittom na istom VPS-u, tek tada ima smisla govoriti o relativnoj brzini.</li>
<li><strong>Izdanje:</strong> prva javna objava za VPS (KVM), uz potpisane artefakte i upute.</li>
<li><strong>Poslije izdanja:</strong> bare metal (X12 do X16), IOMMU i Secure Boot, te šifriranje diska.</li>
</ul>
<p>Kod je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a>, a <code>docs/PROGRESS.md</code> i dalje je jedini izvor istine o tome što stvarno radi. Pred-alfa, kao i prije; prijave grešaka i sigurnosne prijave po uputama u <code>SECURITY.md</code> dobrodošli su.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT: gradimo MQTT broker koji je ujedno i operacijski sustav</title>
      <link>https://aldo.tech/blog/urmqtt-unikernel-mqtt-broker/</link>
      <guid isPermaLink="true">https://aldo.tech/blog/urmqtt-unikernel-mqtt-broker/</guid>
      <pubDate>Thu, 29 Oct 2026 08:00:00 +0000</pubDate>
      <description>Razvojne bilješke o urMQTT-u, unikernelu napisanom u Rustu koji je sam po sebi MQTT broker: jedan ELF s vlastitim TCP/IP-om, TLS-om 1.3, brokerom i SQLite-om, pokrenut s jednog ISO-a.</description>
      <content:encoded><![CDATA[<p>Uređaji u vozilima, na parkiralištima i u ormarima s elektronikom imaju jednu zajedničku osobinu: šalju male poruke, rijetko, ali ih je puno i stalno su spojeni. Godinama ih spajamo na MQTT brokere, i godinama nas muči isto pitanje. Zašto za nešto tako jednostavno trebamo cijeli Linux, OpenSSL, runtime nekog jezika, SSH, upravitelj paketa i tek onda broker? Svaki od tih slojeva donosi vlastite ranjivosti, troši vlastitu memoriju i dodaje još nešto što se može pokvariti, a flota uređaja od poslužitelja treba samo troje: da drži puno gotovo neaktivnih veza, da svaki uređaj pouzdano autentificira i da poruke prenese bez gubitka.</p>
<p>Tako je nastao <a href="https://github.com/zeljan-alduk/urMQTT">urMQTT</a>: unikernel koji <em>jest</em> MQTT broker. Jezgra, TCP/IP, TLS 1.3, MQTT i ugrađena baza podataka u jednom Rust binarnom programu koji se pokreće s jednog ISO-a. Nema procesa, nema sustavskih poziva, nema ljuske. Prva platforma na kojoj se gradi i testira je aarch64, a isti kod ide prema x86_64, po UEFI-ju i klasičnom BIOS-u, i prema pravom hardveru: cilj je jedan ISO koji se podiže na svemu tome. Projekt je otvorenog koda pod licencom MIT ili Apache 2.0, i u ovom tekstu želimo zapisati gdje smo stigli, kako je iznutra složen i što smo usput naučili.</p>
<h2 id="sto-tocno-znaci-unikernel">Što točno znači "unikernel"</h2>
<p>Uobičajeni broker je program koji radi na operacijskom sustavu. urMQTT nema operacijski sustav ispod sebe: sam se podiže kroz UEFI firmware i Limine bootloader, postavlja vlastite tablice stranica, programira kontroler prekida i tajmer, pronalazi virtio uređaje preko PCIe-a, dobiva adresu DHCP-om i sluša na jednom portu. Sve što bi inače radila jezgra, upravljački programi, mrežni stog i aplikacija, ovdje je jedan ELF koji radi na razini EL1.</p>
<p>Usporedba s tipičnom instalacijom govori sama za sebe:</p>
<table>
<thead>
<tr>
<th></th>
<th>Tipičan broker na Linuxu</th>
<th>urMQTT</th>
</tr>
</thead>
<tbody>
<tr>
<td>Izloženi servisi</td>
<td>SSH, broker, često i port za metrike ili administraciju</td>
<td><strong>samo TCP 8883</strong> (MQTT preko TLS-a 1.3 s mTLS-om)</td>
</tr>
<tr>
<td>Kod u slici</td>
<td>jezgra, userland, libc, OpenSSL, runtime</td>
<td><strong>jedan Rust ELF</strong> (plus vendorirani SQLite)</td>
</tr>
<tr>
<td>Udaljeno upravljanje</td>
<td>ljuska, konfiguracijske datoteke, ažuriranja paketa</td>
<td><strong>ništa.</strong> Nova verzija je novi ISO</td>
</tr>
<tr>
<td>Memorija po neaktivnoj vezi</td>
<td>kilobajti (međuspremnici socketa, TLS objekti, heap)</td>
<td><strong>cilj ≤ 512 B</strong></td>
</tr>
<tr>
<td>Višejezgreni model</td>
<td>niti i brave ili jedna petlja događaja</td>
<td><strong>nit po jezgri, bez dijeljenog stanja, obrada do kraja</strong></td>
</tr>
<tr>
<td>Integritet koda</td>
<td>moguća su preslikavanja koja su i zapisiva i izvršna</td>
<td><strong>W^X, zaključane tablice stranica, PAC/BTI</strong></td>
</tr>
</tbody>
</table>
<p>Prioriteti su, tim redom: sigurnost, skalabilnost, višejezgrene performanse, mali otisak i nepromjenjiva instalacija. Kad god su dva cilja u sukobu, pobjeđuje onaj koji je viši na toj listi.</p>
<h2 id="gdje-smo-sada">Gdje smo sada</h2>
<p>Projekt je u pred-alfa fazi, ali je zatvoreno sedamnaest razvojnih prekretnica, od M0 do M17. Danas urMQTT poslužuje MQTT 3.1.1 preko TLS-a 1.3 s obaveznim klijentskim certifikatima, a stanje čuva u SQLite-u na sirovom disku. Ukratko, što već radi:</p>
<ul>
<li><strong>Jezgra:</strong> vlastiti vektori iznimaka s dekodiranim izvještajima, vlastite tablice stranica s provedenim W^X-om, zaštitne stranice, PAC i BTI gdje ih procesor ima, GICv3 i GICv2, generički tajmer, SMP do 64 jezgre.</li>
<li><strong>Otkrivanje hardvera</strong> iz DTB-a ili ACPI-ja u jedan opis platforme, s identičnim rezultatom iz oba izvora.</li>
<li><strong>Upravljački programi:</strong> PCIe ECAM, MSI-X, virtio-net, virtio-blk, virtio-scsi i virtio-rng, te CSPRNG zasijan hardverskom entropijom.</li>
<li><strong>Mreža:</strong> vlastiti DHCPv4, ARP, ICMP i strogi IPv4, pa vlastiti TCP samo za poslužiteljsku stranu: SYN kolačići, hijerarhijski kotač tajmera, NewReno, spajanje ACK-ova, bez TIME_WAIT stanja.</li>
<li><strong>TLS:</strong> samo TLS 1.3, obavezni mTLS, identitet uređaja iz CN-a ili SAN URI-ja; rukovanje radi rustls, a nakon njega vlastiti sloj zapisa s AES-128-GCM-om i ChaCha20-Poly1305. Nastavak sjednice PSK ulaznicama, lista opoziva, kontrola prijema već kod rukovanja.</li>
<li><strong>MQTT:</strong> kodek bez kopiranja, fuzziran i testiran s pravim mosquitto klijentima; QoS 0 i 1, džokeri, zadržane poruke, oporuka (LWT), trajne sjednice, ACL s ograničenjem na <code>devices/{id}/#</code>, ograničenja brzine po uređaju.</li>
<li><strong>Pohrana:</strong> SQLite unutar jezgre preko VFS-a nad sirovim diskom, u WAL načinu, s provjerom integriteta pri podizanju; registar uređaja, opozivi, zadržane poruke, trajne sjednice s redovima poruka za uređaje koji su offline; po jezgri zaseban telemetrijski dnevnik s CRC-om po zapisu i oporavkom nakon pada.</li>
<li><strong>Pogon i nadzor:</strong> nadzorni tajmer za obrađivače, zdravstvena provjera, ograničeno logiranje, syslog i statsd preko UDP-a, prijem koji preživljava oluje ponovnih spajanja.</li>
<li><strong>Isporuka:</strong> bitovno identične ponovne izgradnje provjerene u CI-ju, ISO i sirova GPT slika diska, minisign potpisi, upute za KVM, Proxmox i Hetzner.</li>
</ul>
<p>Svaki je korak označen prekretnicom koja ga je donijela, a datoteka <code>docs/PROGRESS.md</code> u repozitoriju jedini je izvor istine o tome što stvarno radi.</p>
<h2 id="arhitektura-u-tri-slike">Arhitektura u tri slike</h2>
<p>Cijeli dizajn opisan je u <code>docs/ARCHITECTURE.md</code>, a razlozi za svaku važniju odluku zapisani su kao ADR-ovi u <code>docs/DECISIONS.md</code>. Ovdje je pregled.</p>
<h3 id="slojevi">Slojevi</h3>
<figure><img loading="lazy" alt="Slojevi urMQTT-a: od brokera preko MQTT kodeka, TLS-a, TCP-a i mreže do virtio upravljačkih programa i arhitekturnog sloja, sve u jednom ELF-u na razini EL1." src="https://aldo.tech/blog/urmqtt/img/layers.svg" /><figcaption>Slojevi urMQTT-a: od brokera preko MQTT kodeka, TLS-a, TCP-a i mreže do virtio upravljačkih programa i arhitekturnog sloja, sve u jednom ELF-u na razini EL1.</figcaption></figure>
<p>Od vrha prema dnu: broker (sjednice, stablo tema, QoS, zadržane poruke, oporuke, ACL, ograničenja), MQTT kodek, TLS, TCP, mrežni sloj, pa upravljački programi. Pokraj tog glavnog puta stoji pohrana (SQLite i telemetrijski dnevnik), a ispod svega upravljanje memorijom, otkrivanje platforme i konfiguracija koja je u vrijeme izgradnje prevedena u konstante, zajedno s ugrađenim CA certifikatom, poslužiteljskim certifikatom i ključem. Hipervizor s virtio uređajima jedino je što je izvan slike.</p>
<h3 id="nit-po-jezgri-bez-dijeljenog-stanja">Nit po jezgri, bez dijeljenog stanja</h3>
<figure><img loading="lazy" alt="Model izvođenja: svaka podatkovna jezgra ima svoju petlju i svoj dio brokera, a najviša jezgra pokreće samo SQLite. Jezgre komuniciraju isključivo SPSC prstenovima." src="https://aldo.tech/blog/urmqtt/img/cores.svg" /><figcaption>Model izvođenja: svaka podatkovna jezgra ima svoju petlju i svoj dio brokera, a najviša jezgra pokreće samo SQLite. Jezgre komuniciraju isključivo SPSC prstenovima.</figcaption></figure>
<p>Svaka podatkovna jezgra vrti vlastitu petlju s vlastitim TCP stogom, TLS stanjima, dijelom brokera (sjednice i stablo tema), predmemorijom heapa i particijom telemetrije. Jezgre ne dijele nikakvo promjenjivo stanje, samo ograničene prstenove bez brava, po jedan za svaki uređeni par jezgri. Najviša jezgra pokreće SQLite i ništa drugo.</p>
<p>Mrežna kartica danas ima jedan par redova koji drži jezgra 0. Ona svaki MQTT tok prema Toeplitz hashu njegove četvorke usmjerava na jednu od radnih jezgri i šalje ono što radne jezgre vrate. Sjednica živi na svojoj domaćoj jezgri, određenoj hashom identifikatora klijenta, pa preuzimanje, oporuke i trajnost nikad ne prelaze jezgre. Veza koja sleti na drugu jezgru zadržava tamo svoj TCP i TLS, a običan MQTT prosljeđuje domaćoj jezgri uz kontrolu toka temeljenu na kreditima. Svaka usmjerena poruka razdjeljuje se i na ostale dijelove brokera, od kojih svaki drži repliku zadržanih poruka.</p>
<p>Tri pravila drže taj model na okupu. <strong>Obrada do kraja:</strong> primljeni paket obrađuje se u cijelosti na jednoj jezgri, od TCP-a preko dešifriranja TLS-a na mjestu, parsiranja MQTT-a i usmjeravanja do šifriranog odgovora, bez redova zadataka i bez kopiranja u aplikacijske međuspremnike. <strong>Prilagodljivo ispitivanje:</strong> pod opterećenjem se redovi za primanje aktivno ispituju, a u mirovanju se jezgra vraća na prekide i <code>WFI</code>; obrađivači prekida samo potvrde prekid i označe posao. <strong>Nadzorni tajmer:</strong> otkucaj od 10 ms po jezgri hvata obrađivače koji premaše svoj proračun.</p>
<h3 id="zivot-jednog-publish-a">Život jednog PUBLISH-a</h3>
<figure><img loading="lazy" alt="Put jedne poruke PUBLISH s QoS 1: od TLS zapisa na mrežnoj kartici preko dešifriranja, provjere ACL-a i usmjeravanja do PUBACK-a spojenog s TCP ACK-om u jednom segmentu." src="https://aldo.tech/blog/urmqtt/img/publish.svg" /><figcaption>Put jedne poruke PUBLISH s QoS 1: od TLS zapisa na mrežnoj kartici preko dešifriranja, provjere ACL-a i usmjeravanja do PUBACK-a spojenog s TCP ACK-om u jednom segmentu.</figcaption></figure>
<p>Uređaj pošalje TLS zapis. Mrežna kartica ga preda petlji jezgre deskriptorom, bez kopiranja. Petlja provjeri TCP, dešifrira TLS na mjestu i parsira MQTT iz posuđenog međuspremnika, zatim u memoriji provjeri ACL, ograničenje brzine i put kroz stablo tema. Ako drugu jezgru zanima ta tema, dobije poruku za usmjeravanje s referencom na sadržaj i identifikatorom teme; baza dobije telemetrijski zapis ili ažuriranje zadržane poruke, u paketu. Odgovor PUBACK šifrira se izravno u odlazni međuspremnik i spoji s TCP ACK-om, pa uređaju odlazi jedan segment. Drugu jezgru poruka košta jedno šifriranje po pretplatniku u trenutku slanja.</p>
<h2 id="koliko-kosta-jedna-veza">Koliko košta jedna veza</h2>
<p>Cilj je <strong>najviše 512 bajtova po neaktivnoj TLS vezi</strong>, s težnjom prema 256. Dok veza miruje, ništa se za nju ne drži u međuspremniku: primljeni podaci obrađuju se izravno iz mrežnog okvira, a međuspremnici za slanje, zapise i MQTT postoje samo dok je nešto nedovršeno ili nepotvrđeno. Jezgra to i sama mjeri: točni brojači heapa u traženim bajtovima i u blokovima koje zauzimaju, zapisani svakih deset sekundi kao heap iznad osnovne razine po otvorenoj vezi.</p>
<table>
<thead>
<tr>
<th>Dio</th>
<th style="text-align: right;">Veličina</th>
<th>Kako</th>
</tr>
</thead>
<tbody>
<tr>
<td>TCP kontrolni blok</td>
<td style="text-align: right;">96 B</td>
<td>zapakirane zastavice, 16-bitni RTT/RTO, utor u kotaču tajmera, bez međuspremnika u mirovanju</td>
</tr>
<tr>
<td>Utor u TCP tablici</td>
<td style="text-align: right;">4–8 B</td>
<td>samo indeks veze, tablica najviše napola puna</td>
</tr>
<tr>
<td>Veza i stanje TLS 1.3 zapisa</td>
<td style="text-align: right;">152 B</td>
<td>tajna i sekvenca po smjeru (40 B); ključ i IV izvode se po zapisu</td>
</tr>
<tr>
<td>MQTT veza</td>
<td style="text-align: right;">48 B</td>
<td>identitet i oporuka na heapu samo dok postoje</td>
</tr>
<tr>
<td>MQTT sjednica, vrući oblik</td>
<td style="text-align: right;">64 B</td>
<td>jedna pretplata u retku, internirani identifikatori čvorova</td>
</tr>
<tr>
<td>Pretplata <code>devices/&lt;id&gt;/cmd/#</code></td>
<td style="text-align: right;">0 čvorova stabla</td>
<td>pohranjena relativno prema identifikatoru sjednice, zajednička svim uređajima</td>
</tr>
</tbody>
</table>
<p>Izmjereno pod HVF-om s četiri virtualne jezgre, od toga tri radne, pri čemu svaki uređaj drži mTLS vezu i jednu pretplatu s QoS 1, uključujući zaokruživanje alokatora:</p>
<table>
<thead>
<tr>
<th style="text-align: right;">Veze</th>
<th style="text-align: right;">Bajtova po vezi</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>Bazen mrežnih okvira fiksnih je oko 2 MiB, što je otprilike 200 B po vezi pri deset tisuća veza i oko 2 B pri milijun. Više od toga nismo mogli izmjeriti: QEMU-ovo korisničko umrežavanje na macOS-u prestalo je prihvaćati veze na 10 197, otprilike na granici od 10 240 deskriptora QEMU procesa. Brojka pada kako se fiksni dijelovi raspodjeljuju na više veza, pa ne raste s njihovim brojem.</p>
<h2 id="sigurnosni-model">Sigurnosni model</h2>
<p>Sigurnost je prvi prioritet i dobiva sve sukobe s ostalim ciljevima. Model ima nekoliko slojeva:</p>
<ul>
<li><strong>Jedan port.</strong> TCP 8883, samo TLS 1.3, obavezni klijentski certifikati potpisani CA-om ugrađenim u sliku. Identifikator MQTT klijenta mora biti jednak identifikatoru uređaja iz certifikata.</li>
<li><strong>Nema ulaza.</strong> Nema ljuske, konzolnog unosa, SSH-a, administracijskog API-ja, dinamičkog koda, SQL-a dostupnog mrežom ni datotečnog sustava.</li>
<li><strong>Memorijska sigurnost.</strong> Rust, s <code>forbid(unsafe_code)</code> u protokolnim paketima; svaki <code>unsafe</code> blok ima komentar <code>// SAFETY:</code> koji provjerava lint, a na vanjski unos nema panike: clippy u paketima na podatkovnom putu zabranjuje <code>unwrap</code>, <code>expect</code>, indeksiranje i <code>panic!</code>.</li>
<li><strong>Ublažavanje iskorištavanja.</strong> W^X provodi se kodiranjem deskriptora i bitom <code>SCTLR_EL1.WXN</code> i revidira pri svakom podizanju. Tablice stranica nakon podizanja su samo za čitanje, bez zapisivog aliasa. Donja polovica adresnog prostora je isključena. Svaki stog ima zaštitne stranice, a iznimke rade na zasebnom stogu. PAC i BTI uključuju se kad ih procesor podržava, i to provjeravaju dimni testovi.</li>
<li><strong>Otpornost na zloporabu.</strong> SYN kolačići; prijem odbija nove veze, prije ikakve kriptografije, kad je heap nizak, kad baza zaostaje, kad je previše rukovanja u tijeku po jezgri ili po izvornoj adresi ili kad je brzina rukovanja previsoka. Ograničenja brzine po uređaju, ograničeni redovi, TCP prozor vezan uz protutlak brokera, ograničeno logiranje, nadzorni tajmer i zdravstvena provjera.</li>
<li><strong>Nepromjenjivost.</strong> Konfiguracija, CA i poslužiteljski ključ ugrađeni su u sliku. Promjena bilo čega znači novu sliku.</li>
</ul>
<p>Model prijetnji zapisan je u <code>docs/SECURITY.md</code>.</p>
<h2 id="pohrana-bez-datotecnog-sustava">Pohrana bez datotečnog sustava</h2>
<p>Podaci žive na zasebnom disku, virtio-blk ili virtio-scsi, na kojem nema datotečnog sustava. Raspored je minimalan i vlastiti:</p>
<pre><code>LBA 0      [ superblok: magija · verzija rasporeda · uuid · tablica regija · CRC-32C ]
LBA 8, 16  [ tablica veličina datoteka, utori A/B (najnoviji valjani pobjeđuje) ]
LBA 64     [ SQLite glavna baza ½ ][ SQLite WAL ⅛ ][ dnevnik 1 MiB ][ telemetrijski dnevnik: 8 particija … ]
zadnja 4 KiB [ prostor za samoprovjeru blok-uređaja pri podizanju ]
</code></pre>
<p>SQLite (vendorirani 3.54.1, s provjerom sažetka pri izgradnji) preveden je u jezgru clangom, bez standardne biblioteke, i koristi fiksni <code>memsys5</code> heap. Njegov VFS preslikava tri fiksne datoteke na regije diska. Radi u WAL načinu s <code>locking_mode=EXCLUSIVE</code>, pa mu ne treba indeks u dijeljenoj memoriji, i sa <code>synchronous=FULL</code>; ako <code>quick_check</code> pri podizanju nađe grešku, sustav se sigurno zaustavlja. U bazi su registar uređaja, opozivi, zadržane poruke i trajne sjednice s redovima QoS 1 poruka za uređaje koji nisu spojeni.</p>
<p>Jezgra za bazu, ona s najvišim brojem, pokreće SQLite i ništa drugo. Podatkovna ravnina šalje joj promjene stanja preko SPSC prstena bez brava; ona ih skuplja u jednu transakciju svakih 5 ms ili svakih 512 operacija i potvrde vraća drugim prstenom. Prazan disk formatira se pri prvom podizanju; sve drugo se nikad ne formatira preko.</p>
<p>Telemetrija ide drugim putem. Svaki prihvaćeni PUBLISH na temama iz <code>storage.telemetry_topics</code> odlazi u particiju telemetrijskog dnevnika svoje jezgre: prsten segmenata s CRC-om po zapisu, posoljenim sekvencom segmenta, tako da se stari prolazi prstena nikad ne pojave ponovno. Jezgre predaju zapise bazi vlastitim prstenom, bez kopiranja, a ona ih ispisuje svakih 5 ms ili svakih 256 KiB. Pri podizanju oporavak zadržava svaki ispisani zapis, odsiječe nedovršeni rep i nastavlja u novom segmentu. Zadržavanje je po veličini, kroz prsten, i po starosti, kroz <code>retention_days</code>. Provjerili smo to s milijun zapisa i s <code>kill -9</code> usred pisanja.</p>
<h2 id="brojke">Brojke</h2>
<p>Alat za opterećenje u <code>tools/loadgen</code> radi tri stvari: oluju TLS 1.3 rukovanja s mTLS-om i CONNECT-om, dugotrajne veze s PINGREQ-om koje svake sekunde objavljuju telemetriju, i oluje ponovnog spajanja. <code>make bench</code> na Apple M4 Pro pod HVF-om daje:</p>
<table>
<thead>
<tr>
<th>Radne jezgre</th>
<th>rukovanja/s u gostu</th>
<th>skaliranje</th>
<th>kroz QEMU korisničku mrežu (veza/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>Brojka u gostu, gdje su klijent i poslužitelj unutar istog virtualnog stroja i svaka radna jezgra radi sama za sebe, pokazuje da podatkovna ravnina skalira linearno. Mrežna brojka ograničena je jednonitnim korisničkim umrežavanjem QEMU-a, ne gostom; i to je zapisano kao ADR-032.</p>
<p>Oluje ponovnog spajanja testiramo tako da se svi uređaji spoje odjednom, ponavljaju odbijene pokušaje s odgodom i onda svi padnu bez DISCONNECT-a. Parametar <code>heap_limit_mb=</code> u razvojnom načinu natjera prijem da heap smatra manjim nego što jest, pa se oluja veća od heapa može isprobati s malo socketa. U jednom takvom krugu od 3 000 uređaja spojilo se 1 368 u 36 sekundi, a 5 619 pokušaja bilo je uredno odbijeno, uz ograničenje od 8 MiB; memorija se nakon oluje vratila.</p>
<h2 id="kako-radimo">Kako radimo</h2>
<p>Projekt vodi pisana specifikacija, <code>PROMPT.md</code>, s planom prekretnica, i svaka prekretnica završava istim ritualom: <code>make check</code> i <code>make smoke</code> zeleni, jedan commit s oznakom <code>M&lt;n&gt;</code>, zapis u <code>docs/PROGRESS.md</code> i ADR za svaku odluku koja nije očita. Pravila su kratka i ne pregovaramo o njima: nema panike na vanjski unos, <code>unsafe</code> samo u hardverskim i FFI paketima i uvijek s komentarom, nema alokacija na vrućem putu, komentar uz svaki redoslijed atomskih operacija, svako ograničenje dolazi iz konfiguracije, a namjerni privremeni dijelovi padaju glasno.</p>
<p>CI radi više nego što bismo ručno stigli: fmt, clippy za host i za <code>aarch64-unknown-none</code>, typos, actionlint i shellcheck; testove na Linuxu i macOS-u na arm64; izgradnju jezgre za razvojnu i produkcijsku konfiguraciju s veličinom ELF-a u sažetku; podizanje pod QEMU-om sa svim scenarijima dimnog testa i spremanje serijskih zapisa; cargo-deny za savjete, licence i izvore; minutu fuzzinga po svakoj od jedanaest meta, a noću deset minuta po meti s predmemoriranim korpusom. Uz to CodeQL, OpenSSF Scorecard, Dependabot i objava s SLSA porijeklom. Svaka tuđa akcija zakvačena je na puni commit SHA.</p>
<p>Reproducibilnost shvaćamo doslovno: <code>make repro</code> izgradi HEAD dvaput u svježim radnim stablima i usporedi ELF, ISO i sirovu sliku bajt po bajt. Objave su potpisane minisignom, a artefakti se mogu provjeriti s <code>gh attestation verify</code>. Produkcijska slika sadrži privatni ključ poslužitelja, pa je sam ISO tajna; slike uz objave na GitHubu razvojne su, s privremenim ključevima, i služe samo za isprobavanje.</p>
<h2 id="sto-smo-naucili">Što smo naučili</h2>
<p>Nekoliko lekcija koje su promijenile plan:</p>
<ul>
<li><strong>Vlastiti TCP ipak se isplatio.</strong> Prototip je radio na smoltcp-u i dobro nam je poslužio do M13. Ali poslužitelj koji drži stotine tisuća veza ne treba klijentski TCP, treba SYN kolačiće, kotač tajmera i stanje od 96 bajtova. U M14 smo smoltcp zamijenili vlastitim stogom samo za poslužiteljsku stranu, s testovima simuliranog gubitka i fuzzingom segmenata.</li>
<li><strong>Mjerite memoriju iznutra.</strong> Procjene po vezi bile su beskorisne dok jezgra nije sama počela brojati heap u blokovima alokatora, a ne u traženim bajtovima. Tek tada smo vidjeli gdje 512 bajtova stvarno odlazi i spustili se na 494.</li>
<li><strong>Alat za testiranje je dio proizvoda.</strong> Najveće ograničenje nije bila jezgra, nego QEMU-ovo korisničko umrežavanje i broj efemernih portova na razvojnom računalu. Za sto tisuća i više veza treba <code>vmnet-shared</code> na macOS-u ili Linux s KVM-om, tap sučeljem i više klijentskih adresa.</li>
<li><strong>SQLite bez operacijskog sustava je moguć</strong>, ali samo uz stroge uvjete: fiksni heap, ekskluzivno zaključavanje bez dijeljene memorije, VFS koji ne zna za datoteke, i prijem koji baze ne pušta da zaostane.</li>
<li><strong>Oluje ponovnog spajanja su normalno stanje</strong>, ne iznimka. Kad padne mobilna mreža i vrati se, tisuće uređaja kucaju istodobno. Zato prijem odbija vezu prije ikakve kriptografije, a svaka odbijena veza košta gotovo ništa.</li>
</ul>
<h2 id="sto-slijedi">Što slijedi</h2>
<p>Rad nakon M17 planiran je u tri smjera, a redoslijed među njima zapisan je u <code>docs/PLAN.md</code>. Ništa od ovoga još nije izgrađeno.</p>
<ul>
<li><strong>Jedan ISO za više platformi.</strong> Ista datoteka <code>urmqtt-&lt;env&gt;-&lt;verzija&gt;.iso</code> i jedna sirova GPT slika koje se podižu na aarch64 UEFI-ju, x86_64 UEFI-ju i x86_64 BIOS-u, u virtualnim strojevima i na pravom hardveru, počevši od jednog Intel PC-a. I dalje uređaj sa svime unutra, reproducibilan i potpisan.</li>
<li><strong>Sirovi disk, bez datotečnog sustava.</strong> Specifikacija formata na razini bajta, host alat <code>urmqtt-disk</code> koji dijeli parsere s jezgrom, poravnanje regija na 1 MiB, rast diska s podacima, discard za tanko dodijeljene diskove, A/B snimke baze, provjera bitova i, po izboru, šifriranje AES-256-XTS s hardverskim AES-om.</li>
<li><strong>Dokazivo sigurno.</strong> Opoziv i lista dopuštenih uređaja za vrijeme rada preko potpisanog kontrolnog kanala, bez nove slike; diferencijalno i konformacijsko testiranje vlastitog TLS-a i TCP-a; KASLR, zaštita stoga, kCFI, MTE i ojačani alokator; SQLite izoliran na razini EL0; Secure Boot; SBOM i cargo-vet; i na kraju paket za vanjsku reviziju.</li>
</ul>
<p>Platforme koje nude samo ne-virtio uređaje, poput AWS ENA-e ili Azure MANA-e, zasad nisu podržane. Raspberry Pi 4 i 5 kao KVM domaćin, Hetzner CAX i Oracle Ampere A1 dokumentirani su i podržani kodom, ali još nisu isprobani na pravoj usluzi.</p>
<h2 id="isprobajte">Isprobajte</h2>
<p>Repozitorij je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a>. Uz Rust s prikvačenim nightly lancem alata i QEMU, <code>make certs</code>, <code>make run</code> i jedan mosquitto klijent dovoljni su za prvi PUBLISH preko mTLS-a na vlastitom računalu. Upozorenje s početka vrijedi i na kraju: ovo je pred-alfa, i sve što radi piše u <code>docs/PROGRESS.md</code>. Prijave grešaka, pitanja o dizajnu i sigurnosne prijave po uputama u <code>SECURITY.md</code> su dobrodošli. O svakoj sljedećoj prekretnici javit ćemo se ovdje.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>Pokrećemo blog: bilješke s terena</title>
      <link>https://aldo.tech/blog/pokrecemo-blog/</link>
      <guid isPermaLink="true">https://aldo.tech/blog/pokrecemo-blog/</guid>
      <pubDate>Sat, 17 Oct 2026 08:00:00 +0000</pubDate>
      <description>Zašto tvrtka koja gradi terminale, aplikacije i elektroniku za javni prijevoz počinje pisati, i o čemu ćete ovdje čitati.</description>
      <content:encoded><![CDATA[<p>Većinu onoga što radimo nitko ne vidi. Softver u terminalu na ulazu u autobus, firmware u uređaju koji nikad ne dobije ekran, poslužitelj koji noću pretvara dnevne transakcije u izvješća. Kad sve radi, radi tiho. Kad ne radi, primijete svi.</p>
<p>Na ovom blogu želimo zapisati ono što smo na tom putu naučili. Ne marketinške tekstove, nego konkretne bilješke: što je pošlo po zlu, kako smo to riješili i što bismo danas napravili drukčije.</p>
<h2 id="o-cemu-cemo-pisati">O čemu ćemo pisati</h2>
<ul>
<li><strong>Naplata u javnom prijevozu.</strong> Beskontaktne kartice, SAM moduli, rad bez mreže i sve što terminal mora podnijeti u vozilu koje se trese dvanaest sati dnevno.</li>
<li><strong>Mobilne aplikacije na terenu.</strong> Android i Flutter na uređajima koji nisu obični telefoni: s čitačima kartica, pisačima i baterijama koje moraju izdržati cijelu smjenu.</li>
<li><strong>Backend i nadzor flote.</strong> Kako znati da uređaj u vozilu ima problem prije nego što ga prijavi vozač.</li>
<li><strong>Hardver.</strong> Od prototipa na stolu do pločice koja ide u serijsku proizvodnju, i sve pogreške između.</li>
<li><strong>Lokalni AI modeli.</strong> Gdje modeli na vlastitom hardveru imaju smisla, a gdje su oblak i API jednostavno bolji izbor.</li>
</ul>
<h2 id="kako-cemo-pisati">Kako ćemo pisati</h2>
<p>Kratko i konkretno, s kodom i brojkama gdje ih ima. Objavljivat ćemo kad imamo što reći, a ne po rasporedu. Tekstove pišemo na hrvatskom i prevodimo ih na sve jezike stranice, a novosti možete pratiti i preko <a href="https://aldo.tech/blog/feed.xml">RSS-a</a>.</p>
<p>Ako vas zanima neka tema ili radite na sličnom problemu, javite nam se na <a href="mailto:info@aldo.tech">info@aldo.tech</a>.</p>]]></content:encoded>
      <category>uvod</category>
    </item>
  </channel>
</rss>
